From 9718690712aee86dd27b529885f8f4470e9f2821 Mon Sep 17 00:00:00 2001 From: Trung Date: Wed, 15 Jul 2026 10:13:19 +0700 Subject: [PATCH] fs: skip procfs symlinks in virtual proc_path resolution To fix fstatfs returning the incorrect magic on open descriptors of procfs magic links and symlinks, exclude paths like /proc/self/exe, /proc/self/cwd, /proc/self/root, /proc/self/fd/N, and task-specific equivalents from resolving to a virtual proc_path stamp. This prevents the guest fd table from recording these entries as part of the emulated procfs, letting fstatfs correctly query the filesystem information of their resolved host file targets. Fix #142 --- src/syscall/fs-stat.c | 62 +++++++++++++++++++++------------------- src/syscall/fs.c | 66 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 95 insertions(+), 33 deletions(-) diff --git a/src/syscall/fs-stat.c b/src/syscall/fs-stat.c index ddcbe9e..6a7dd2e 100644 --- a/src/syscall/fs-stat.c +++ b/src/syscall/fs-stat.c @@ -367,6 +367,20 @@ static bool statfs_path_is_proc(const char *path) return !strncmp(path, "/proc", 5) && (path[5] == '\0' || path[5] == '/'); } +static void fill_proc_statfs(linux_statfs_t *lin) +{ + memset(lin, 0, sizeof(*lin)); + lin->f_type = 0x9fa0; /* PROC_SUPER_MAGIC */ + lin->f_bsize = 4096; + lin->f_blocks = 0; + lin->f_bfree = 0; + lin->f_bavail = 0; + lin->f_files = 0; + lin->f_ffree = 0; + lin->f_namelen = 255; + lin->f_frsize = 4096; +} + int64_t sys_statfs(guest_t *g, uint64_t path_gva, uint64_t buf_gva) { char path[LINUX_PATH_MAX]; @@ -379,38 +393,18 @@ int64_t sys_statfs(guest_t *g, uint64_t path_gva, uint64_t buf_gva) if (tx.fuse_path) return -LINUX_ENOSYS; - struct statfs mac_st; - - /* /proc has no host-filesystem counterpart under the sysroot: every entry - * is synthesized on open (see proc_intercept_open), so a raw statfs() on - * the guest path always misses with ENOENT even for "/proc" itself. Try - * the lightweight statfs-specific resolver first (it shortcuts nodes whose - * open path would otherwise allocate scratch state statfs never uses), - * then fall back to the same intercept open/fstatfs/close that - * sys_newfstatat uses via proc_intercept_stat. - */ if (statfs_path_is_proc(tx.intercept_path)) { - int intercepted = proc_intercept_statfs(tx.intercept_path, &mac_st); - if (intercepted == PROC_NOT_INTERCEPTED) { - int host_fd = proc_intercept_open(g, tx.intercept_path, 0, 0); - if (host_fd == PROC_NOT_INTERCEPTED) { - if (statfs(tx.host_path, &mac_st) < 0) - return linux_errno(); - } else if (host_fd < 0) { - return linux_errno(); - } else { - int rc = fstatfs(host_fd, &mac_st); - close_keep_errno(host_fd); - if (rc < 0) - return linux_errno(); - } - } else if (intercepted < 0) { - return linux_errno(); - } - } else if (statfs(tx.host_path, &mac_st) < 0) { - return linux_errno(); + 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; + if (statfs(tx.host_path, &mac_st) < 0) + return linux_errno(); + linux_statfs_t lin_st; translate_statfs(&mac_st, &lin_st); if (guest_write_small(g, buf_gva, &lin_st, sizeof(lin_st)) < 0) @@ -421,6 +415,16 @@ int64_t sys_statfs(guest_t *g, uint64_t path_gva, uint64_t buf_gva) int64_t sys_fstatfs(guest_t *g, int fd, uint64_t buf_gva) { + fd_entry_t snap; + memset(&snap, 0, sizeof(snap)); + if (fd_snapshot(fd, &snap) && statfs_path_is_proc(snap.proc_path)) { + linux_statfs_t proc_st; + fill_proc_statfs(&proc_st); + if (guest_write_small(g, buf_gva, &proc_st, sizeof(proc_st)) < 0) + return -LINUX_EFAULT; + return 0; + } + host_fd_ref_t host_ref; if (host_fd_ref_open(fd, &host_ref) < 0) return -LINUX_EBADF; diff --git a/src/syscall/fs.c b/src/syscall/fs.c index 6213d1f..20a3e2f 100644 --- a/src/syscall/fs.c +++ b/src/syscall/fs.c @@ -127,6 +127,43 @@ static const char *proc_stateful_file_path(const char *path) return NULL; } +static bool proc_path_is_symlink(const char *path) +{ + if (!path) + return false; + + if (!strcmp(path, "/proc/self/exe") || !strcmp(path, "/proc/self/cwd") || + !strcmp(path, "/proc/self/root")) { + return true; + } + + if (!strncmp(path, "/proc/self/fd/", 14)) { + char *endp; + long n = strtol(path + 14, &endp, 10); + if (endp != path + 14 && *endp == '\0' && n >= 0) + return true; + } + + if (!strncmp(path, "/proc/self/task/", 16)) { + char *endp; + strtol(path + 16, &endp, 10); + if (endp != path + 16 && *endp == '/') { + const char *sub = endp + 1; + if (!strcmp(sub, "exe") || !strcmp(sub, "cwd") || + !strcmp(sub, "root")) { + return true; + } + if (!strncmp(sub, "fd/", 3)) { + long n = strtol(sub + 3, &endp, 10); + if (endp != sub + 3 && *endp == '\0' && n >= 0) + return true; + } + } + } + + return false; +} + /* Resolve the proc_path the fd table should record for an intercepted path. * Returns true and fills *out when a mapping exists; false otherwise so the * caller can skip the install entirely. Pure string work; safe to call before @@ -149,11 +186,32 @@ static bool resolve_virtual_path(const char *path, char *out, size_t out_size) const char *virt = proc_virtual_dir_path(path, virt_buf, sizeof(virt_buf)); if (!virt) virt = proc_stateful_file_path(path); - if (!virt) - return false; - str_copy_trunc(out, virt, out_size); - return true; + if (virt) { + str_copy_trunc(out, virt, out_size); + return true; + } + + /* If it has a valid /proc prefix, normalize it and record it. */ + if (path[5] == '\0' || path[5] == '/') { + if (strncmp(path, "/proc/", 6) == 0) { + char *endp; + long pid = strtol(path + 6, &endp, 10); + if (endp != path + 6 && pid == (long) proc_get_pid() && + (*endp == '\0' || *endp == '/')) { + snprintf(out, out_size, "/proc/self%s", endp); + if (proc_path_is_symlink(out)) + return false; + return true; + } + } + if (proc_path_is_symlink(path)) + return false; + str_copy_trunc(out, path, out_size); + return true; + } + + return false; } static const char *proc_virtual_dir_path(const char *path,