Skip to content

Commit

Permalink
check: verify ino and dev of overlayfs files in /proc/pid/maps
Browse files Browse the repository at this point in the history
Check that the file device and inode shown in /proc/pid/maps match
values returned by stat(2).

Signed-off-by: Andrei Vagin <[email protected]>
  • Loading branch information
avagin committed Dec 30, 2023
1 parent 61224f2 commit 8e4d6db
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 2 deletions.
184 changes: 183 additions & 1 deletion criu/cr-check.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
#include "restorer.h"
#include "uffd.h"
#include "linux/aio_abi.h"
#include "syscall.h"
#include "mount-v2.h"

#include "images/inventory.pb-c.h"

Expand Down Expand Up @@ -721,7 +723,7 @@ static int check_special_mapping_mremap(void)
return -1;
}
/* Probably, we're interrupted with a signal - cleanup */
pr_err("Failed to wait for a child %d\n", errno);
pr_perror("Failed to wait for a child");
kill(child, SIGKILL);
waitpid(child, NULL, 0);
return -1;
Expand Down Expand Up @@ -1382,6 +1384,184 @@ static int check_ipv6_freebind(void)
return 0;
}

static long get_file_dev_and_inode(void *addr, struct statx *stx)
{
char buf[4096];
FILE *mapf;

mapf = fopen("/proc/self/maps", "r");
if (mapf == NULL) {
pr_perror("fopen(/proc/self/maps)");
return -1;
}

while (fgets(buf, sizeof(buf), mapf)) {
unsigned long start, end;
uint32_t maj, min;
__u64 ino;

if (sscanf(buf, "%lx-%lx %*s %*s %x:%x %llu",
&start, &end, &maj, &min, &ino) != 5) {

Check warning on line 1404 in criu/cr-check.c

View workflow job for this annotation

GitHub Actions / build

pr_perror("unable to parse: %s", buf);
return -1;
}
if (start == (unsigned long)addr) {
stx->stx_dev_major = maj;
stx->stx_dev_minor = min;
stx->stx_ino = ino;
return 0;
}
}

pr_err("unable to find the mapping\n");
return -1;
}

static int ovl_mount(void)
{
int tmpfs, fsfd, ovl;

fsfd = sys_fsopen("tmpfs", 0);
if (fsfd == -1) {
pr_perror("Unable to fsopen tmpfs");
return -1;
}

if (sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) == -1) {
pr_perror("Unable to create tmpfs mount");
return -1;
}

tmpfs = sys_fsmount(fsfd, 0, 0);
if (tmpfs == -1) {
pr_perror("Unable to mount tmpfs");
return -1;
}

close(fsfd);

/* overlayfs can't be constructed on top of a detached mount. */
if (sys_move_mount(tmpfs, "", AT_FDCWD, "/tmp", MOVE_MOUNT_F_EMPTY_PATH)) {
pr_perror("Unable to attach tmpfs mount");
return -1;
}
close(tmpfs);

if (chdir("/tmp")) {
pr_perror("Unable to change working directory");
return -1;
}

if (mkdir("/tmp/w", 0755) == -1 ||
mkdir("/tmp/u", 0755) == -1 ||
mkdir("/tmp/l", 0755) == -1) {
pr_perror("mkdir");
return -1;
}

fsfd = sys_fsopen("overlay", 0);
if (fsfd == -1) {
pr_perror("Unable to fsopen overlayfs");
return -1;
}
if (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "test", 0) == -1 ||
sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "lowerdir", "/tmp/l", 0) == -1 ||
sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "upperdir", "/tmp/u", 0) == -1 ||
sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "workdir", "/tmp/w", 0) == -1) {
pr_perror("Unable to configure overlayfs");
return -1;
}
if (sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) == -1) {
pr_perror("Unable to create overlayfs");
return -1;
}
ovl = sys_fsmount(fsfd, 0, 0);
if (ovl == -1) {
pr_perror("Unable to mount overlayfs");
return -1;
}

return ovl;
}

/*
* Check that the file device and inode shown in /proc/pid/maps match values
* returned by stat(2).
*/
static int do_check_overlayfs_maps(void)
{
struct statx stx, mstx;
int ovl, fd;
void *addr;

/* Create a new mount namespace to not care about cleaning test mounts. */
if (unshare(CLONE_NEWNS) == -1) {
pr_warn("Unable to create a new mount namespace\n");
return 0;
}

if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) == -1) {
pr_perror("Unable to remount / with MS_SLAVE");
return -1;
}

ovl = ovl_mount();
if (ovl == -1)
return -1;

fd = openat(ovl, "test", O_RDWR | O_CREAT, 0644);
if (fd == -1) {
pr_perror("Unable to open a test file");
return -1;
}

addr = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
pr_perror("Unable to map the test file");
return -1;
}

if (get_file_dev_and_inode(addr, &mstx))
return -1;
if (statx(fd, "", AT_EMPTY_PATH | AT_STATX_SYNC_AS_STAT, STATX_INO, &stx)) {
pr_perror("statx");
return -1;
}

if (stx.stx_dev_major != mstx.stx_dev_major ||
stx.stx_dev_minor != mstx.stx_dev_minor ||
stx.stx_ino != mstx.stx_ino) {
pr_err("unmatched dev:ino %x:%x:%llx (expected %x:%x:%llx)\n",
mstx.stx_dev_major, mstx.stx_dev_minor, mstx.stx_ino,

Check warning on line 1535 in criu/cr-check.c

View workflow job for this annotation

GitHub Actions / build

stx.stx_dev_major, stx.stx_dev_minor, stx.stx_ino);
return -1;
}

return 0;
}

static int check_overlayfs_maps(void)
{
pid_t pid;
int status;

pid = fork();
if (pid == -1) {
pr_perror("Unable to fork a child");
return -1;
}
if (pid == 0) {
if (do_check_overlayfs_maps())
exit(1);
exit(0);
}
if (waitpid(pid, &status, 0) == -1) {
pr_perror("waitpid");
return -1;
}
return status == 0 ? 0 : -1;
}

static int (*chk_feature)(void);

/*
Expand Down Expand Up @@ -1502,6 +1682,7 @@ int cr_check(void)
ret |= check_openat2();
ret |= check_ptrace_get_rseq_conf();
ret |= check_ipv6_freebind();
ret |= check_overlayfs_maps();

if (kdat.lsm == LSMTYPE__APPARMOR)
ret |= check_apparmor_stacking();
Expand Down Expand Up @@ -1623,6 +1804,7 @@ static struct feature_list feature_list[] = {
{ "openat2", check_openat2 },
{ "get_rseq_conf", check_ptrace_get_rseq_conf },
{ "ipv6_freebind", check_ipv6_freebind },
{ "overlayfs_maps", check_overlayfs_maps },
{ NULL, NULL },
};

Expand Down
9 changes: 8 additions & 1 deletion scripts/ci/run-ci-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,14 @@ ip net add test
# This is a temporary workaround until fixed in the kernel.
# The kernel currently does not show correct device and inode numbers in /proc/pid/maps
# for stackable file systems.
if capsh --supports=cap_checkpoint_restore && unshare -c /bin/true && [ ! -e /run/.containerenv ]; then
skip=0
findmnt -no FSTYPE / | grep overlay && {
./criu/criu check --feature overlayfs_maps || skip=1
}
unshare -c /bin/true || skip=1
capsh --supports=cap_checkpoint_restore || skip=1

if [ "$skip" == 0 ]; then
make -C test/zdtm/ cleanout
rm -rf test/dump
setcap cap_checkpoint_restore,cap_sys_ptrace+eip criu/criu
Expand Down

0 comments on commit 8e4d6db

Please sign in to comment.