diff --git a/src/runtime/procemu.c b/src/runtime/procemu.c index 19b166df..7a4eb229 100644 --- a/src/runtime/procemu.c +++ b/src/runtime/procemu.c @@ -3436,38 +3436,6 @@ int proc_intercept_open(const guest_t *g, return PROC_NOT_INTERCEPTED; } -int proc_intercept_statfs(const char *path, struct statfs *out) -{ - /* /proc/[/...] -> /proc/self[...], same alias as - * proc_intercept_open/proc_intercept_stat. - */ - char alias[LINUX_PATH_MAX]; - int aliased = proc_alias_self(path, alias, sizeof(alias)); - if (aliased < 0) - return -1; - if (aliased > 0) - return proc_intercept_statfs(alias, out); - - /* /proc/self/fd and /proc/self/fdinfo are the only /proc nodes whose open - * path allocates per-call scratch state: proc_open_fd_scratch mkdtemp's a - * fresh directory and creates one placeholder file per live guest fd on - * every single open, purely so getdents has something to enumerate. A - * plain statfs() never enumerates the directory, so paying for that - * allocation just to fstatfs() the result and immediately discard it is - * wasted work -- and once PROC_SCRATCH_DIRS_MAX untracked opens - * accumulate, the atexit cleanup no longer knows about further dirs and - * they leak in /tmp permanently. - * - * Every scratch dir (like every other synthetic /proc node) is created - * under /tmp, so statfs("/tmp") reports identical filesystem info without - * allocating anything. - */ - if (strcmp(path, "/proc/self/fd") && strcmp(path, "/proc/self/fd/") && - strcmp(path, "/proc/self/fdinfo") && strcmp(path, "/proc/self/fdinfo/")) - return PROC_NOT_INTERCEPTED; - - return statfs("/tmp", out) < 0 ? -1 : 0; -} int proc_intercept_stat(const char *path, struct stat *st) { diff --git a/src/runtime/procemu.h b/src/runtime/procemu.h index 3ab092c4..32d8767a 100644 --- a/src/runtime/procemu.h +++ b/src/runtime/procemu.h @@ -45,16 +45,6 @@ int proc_intercept_readlink(const char *path, char *buf, size_t bufsiz); */ int proc_intercept_stat(const char *path, struct stat *mac_st); -/* Intercept statfs for /proc paths that would otherwise route through - * proc_intercept_open purely to obtain filesystem-level info, forcing - * allocation of scratch state (e.g. /proc/self/fd's per-open directory of - * placeholder files, one per live guest fd) that statfs() has no use for. - * Returns 0 if the filesystem info was synthesized (mac_st filled), or -2 - * (PROC_NOT_INTERCEPTED) if the path is not intercepted (fall through to the - * normal open/fstatfs/close path). - */ -int proc_intercept_statfs(const char *path, struct statfs *mac_st); - /* Intercept writes to synthetic proc files that need stateful behavior. * Returns 1 if handled (with *written_out set), 0 if not intercepted, or -1 on * error with errno set. diff --git a/src/syscall/fs-stat.c b/src/syscall/fs-stat.c index 6a7dd2e9..9e10c477 100644 --- a/src/syscall/fs-stat.c +++ b/src/syscall/fs-stat.c @@ -381,11 +381,13 @@ static void fill_proc_statfs(linux_statfs_t *lin) lin->f_frsize = 4096; } -int64_t sys_statfs(guest_t *g, uint64_t path_gva, uint64_t buf_gva) +static int64_t sys_statfs_impl(guest_t *g, + const char *path, + uint64_t buf_gva, + int depth) { - char path[LINUX_PATH_MAX]; - if (guest_read_str_small(g, path_gva, path, sizeof(path)) < 0) - return -LINUX_EFAULT; + if (depth > 40) + return -LINUX_ELOOP; path_translation_t tx; if (path_translate_at(LINUX_AT_FDCWD, path, PATH_TR_NONE, &tx) < 0) @@ -394,11 +396,37 @@ int64_t sys_statfs(guest_t *g, uint64_t path_gva, uint64_t buf_gva) return -LINUX_ENOSYS; if (statfs_path_is_proc(tx.intercept_path)) { - linux_statfs_t lin_st; - fill_proc_statfs(&lin_st); - if (guest_write_small(g, buf_gva, &lin_st, sizeof(lin_st)) < 0) - return -LINUX_EFAULT; - return 0; + if (proc_path_is_symlink(tx.intercept_path)) { + char link[LINUX_PATH_MAX]; + int len = proc_intercept_readlink(tx.intercept_path, link, + sizeof(link) - 1); + if (len < 0) + return linux_errno(); + link[len] = '\0'; + return sys_statfs_impl(g, link, buf_gva, depth + 1); + } + + struct stat mac_st; + int intercepted = proc_intercept_stat(tx.intercept_path, &mac_st); + if (intercepted == 0) { + linux_statfs_t lin_st; + fill_proc_statfs(&lin_st); + if (guest_write_small(g, buf_gva, &lin_st, sizeof(lin_st)) < 0) + return -LINUX_EFAULT; + return 0; + } + if (intercepted == -1) + return linux_errno(); + + /* It might be /proc itself or a host-backed file/directory under /proc + */ + if (stat(tx.host_path, &mac_st) == 0) { + linux_statfs_t lin_st; + fill_proc_statfs(&lin_st); + if (guest_write_small(g, buf_gva, &lin_st, sizeof(lin_st)) < 0) + return -LINUX_EFAULT; + return 0; + } } struct statfs mac_st; @@ -413,6 +441,15 @@ int64_t sys_statfs(guest_t *g, uint64_t path_gva, uint64_t buf_gva) return 0; } +int64_t sys_statfs(guest_t *g, uint64_t path_gva, uint64_t buf_gva) +{ + char path[LINUX_PATH_MAX]; + if (guest_read_str_small(g, path_gva, path, sizeof(path)) < 0) + return -LINUX_EFAULT; + + return sys_statfs_impl(g, path, buf_gva, 0); +} + int64_t sys_fstatfs(guest_t *g, int fd, uint64_t buf_gva) { fd_entry_t snap; diff --git a/src/syscall/fs.c b/src/syscall/fs.c index 20a3e2f1..0a512c92 100644 --- a/src/syscall/fs.c +++ b/src/syscall/fs.c @@ -127,7 +127,7 @@ static const char *proc_stateful_file_path(const char *path) return NULL; } -static bool proc_path_is_symlink(const char *path) +bool proc_path_is_symlink(const char *path) { if (!path) return false; diff --git a/src/syscall/internal.h b/src/syscall/internal.h index d5943f10..d2c79a69 100644 --- a/src/syscall/internal.h +++ b/src/syscall/internal.h @@ -461,8 +461,8 @@ int64_t host_iov_prepare_msg(guest_t *g, int required_perms, host_iov_buf_t *buf); -/* Release any heap spillover backing a host_iov_buf_t. Idempotent. */ void host_iov_free(host_iov_buf_t *buf); +bool proc_path_is_symlink(const char *path); /* Read a guest path string with small-buffer optimization. * diff --git a/src/syscall/path.c b/src/syscall/path.c index 0a66bbbb..2888a023 100644 --- a/src/syscall/path.c +++ b/src/syscall/path.c @@ -879,6 +879,10 @@ static int dirfd_guest_base_path(guest_fd_t dirfd, char *out, size_t outsz) return -1; } if (snap.proc_path[0] != '\0') { + if (snap.type != FD_DIR) { + errno = ENOTDIR; + return -1; + } size_t len = str_copy_trunc(out, snap.proc_path, outsz); if (len >= outsz) { errno = ENAMETOOLONG; diff --git a/tests/test-proc.c b/tests/test-proc.c index b3b26931..a0d839d6 100644 --- a/tests/test-proc.c +++ b/tests/test-proc.c @@ -208,28 +208,35 @@ int main(void) TEST("statfs /proc"); { struct statfs st; - if (statfs("/proc", &st) < 0) + if (statfs("/proc", &st) < 0) { FAIL("statfs failed"); - else - PASS(); + } else { + EXPECT_TRUE(st.f_type == 0x9fa0, + "statfs /proc f_type not PROC_SUPER_MAGIC"); + } } TEST("statfs /proc/ (trailing slash)"); { struct statfs st; - if (statfs("/proc/", &st) < 0) + if (statfs("/proc/", &st) < 0) { FAIL("statfs failed"); - else - PASS(); + } else { + EXPECT_TRUE(st.f_type == 0x9fa0, + "statfs /proc/ f_type not PROC_SUPER_MAGIC"); + } } TEST("statfs /proc/self/cmdline"); { struct statfs st; - if (statfs("/proc/self/cmdline", &st) < 0) + if (statfs("/proc/self/cmdline", &st) < 0) { FAIL("statfs failed"); - else - PASS(); + } else { + EXPECT_TRUE( + st.f_type == 0x9fa0, + "statfs /proc/self/cmdline f_type not PROC_SUPER_MAGIC"); + } } TEST("statfs matches fstatfs for /proc/self/cmdline"); @@ -245,6 +252,36 @@ int main(void) } else { EXPECT_TRUE(path_st.f_type == fd_st.f_type, "statfs/fstatfs f_type mismatch"); + EXPECT_TRUE(path_st.f_type == 0x9fa0, + "statfs f_type not PROC_SUPER_MAGIC"); + } + if (fd >= 0) + close(fd); + } + + TEST("statfs follows /proc symlinks"); + { + struct statfs exe_st, fd_st; + int fd = open("/proc/self/exe", O_RDONLY); + if (fd < 0) { + FAIL("open /proc/self/exe failed"); + } else if (statfs("/proc/self/exe", &exe_st) < 0) { + FAIL("statfs /proc/self/exe failed"); + } else { + /* /proc/self/exe points to the host binary file, which resides on a + * real filesystem, not procfs. */ + EXPECT_TRUE(exe_st.f_type != 0x9fa0, + "statfs /proc/self/exe returned PROC_SUPER_MAGIC"); + + /* Build /proc/self/fd/N path */ + char fd_path[64]; + snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", fd); + if (statfs(fd_path, &fd_st) < 0) { + FAIL("statfs /proc/self/fd/N failed"); + } else { + EXPECT_TRUE(fd_st.f_type != 0x9fa0, + "statfs /proc/self/fd/N returned PROC_SUPER_MAGIC"); + } } if (fd >= 0) close(fd);