Skip to content

Intercept fstatfs and synthesize procfs magic for all /proc files#207

Merged
jserv merged 1 commit into
sysprog21:mainfrom
open-sources-port:proc_path
Jul 16, 2026
Merged

Intercept fstatfs and synthesize procfs magic for all /proc files#207
jserv merged 1 commit into
sysprog21:mainfrom
open-sources-port:proc_path

Conversation

@doanbaotrung

@doanbaotrung doanbaotrung commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

When fstatfs is called on open file descriptors pointing to /proc files (e.g., /proc/self/maps, /proc/uptime, /proc/loadavg), it returned the host filesystem magic (e.g. APFS) instead of LINUX_PROC_SUPER_MAGIC (0x9fa0).

Fix this by:

  • Modifying resolve_virtual_path in fs.c to resolve and normalize all paths under /proc (not just virtual directories/stateful files) and record them in the fd table's proc_path field.
  • Introducing a fill_proc_statfs helper in fs-stat.c that populates a static linux_statfs_t layout with PROC_SUPER_MAGIC (0x9fa0) and zero blocks/free space.
  • Intercepting both sys_statfs and sys_fstatfs when the target path or fd's proc_path points to /proc, and directly writing the synthesized procfs metadata back to the guest.

Fix #142


Summary by cubic

Return the correct procfs magic for /proc files by intercepting statfs/fstatfs and normalizing /proc paths, while skipping procfs symlinks so only real proc entries report procfs. Fixes #142.

  • Bug Fixes
    • Normalize and record /proc paths in resolve_virtual_path, mapping /proc/<pid> (for the current pid) to /proc/self, and exclude procfs symlinks (/proc/self/{exe,cwd,root}, /proc/self/fd/<n>, and task equivalents) so they report the target filesystem.
    • Add fill_proc_statfs to synthesize a linux_statfs_t with PROC_SUPER_MAGIC (0x9fa0), 4K block size, and zeroed counts.
    • Intercept sys_statfs and sys_fstatfs for /proc targets and return the synthesized procfs stats.

Written for commit 9718690. Summary will update on new commits.

Review in cubic

cubic-dev-ai[bot]

This comment was marked as resolved.

@jserv
jserv requested a review from Max042004 July 15, 2026 08:09
@jserv jserv changed the title fs: intercept fstatfs and synthesize procfs magic for all /proc files Intercept fstatfs and synthesize procfs magic for all /proc files Jul 15, 2026
Comment thread src/syscall/fs-stat.c
* 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)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

statfs on a nonexistent /proc path now returns success instead of ENOENT. statfs_path_is_proc() matches any /proc-prefixed path, so this block fills PROC_SUPER_MAGIC and returns 0 without confirming the entry exists. The previous code fell through proc_intercept_statfs -> proc_intercept_open -> statfs(host_path), so a miss propagated ENOENT.

Trigger: statfs("/proc/bogus", &buf) or statfs("/proc/self/not_a_file", &buf) -- real Linux and pre-PR return -ENOENT; this returns 0 with f_type=0x9fa0. Programs that use statfs as an existence/feature probe are misled.

Fix: gate synthesis on a positive existence check (only synthesize when proc_intercept_stat(tx.intercept_path, ...) recognizes the path, otherwise fall through to the host statfs that yields ENOENT). Only f_type needed to change; existence semantics should be preserved.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix: I modified sys_statfs to execute a positive existence check first:

  1. Check via proc_intercept_stat if it is a synthetic /proc node.
  2. If proc_intercept_stat returns PROC_NOT_INTERCEPTED, check if the path exists on the host's sysroot mapping via stat(tx.host_path).
  3. Only synthesize PROC_SUPER_MAGIC statfs if either check confirms the file/directory exists; otherwise, fall through to let host statfs yield ENOENT.

Comment thread src/syscall/fs.c
return true;
}

/* If it has a valid /proc prefix, normalize it and record it. */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stamping proc_path for every non-symlink /proc/* entry -- including regular files like /proc/cpuinfo and /proc/self/maps -- leaks dirfd semantics into consumers that key off proc_path. dirfd_guest_base_path (path.c:883) accepts any fd with a non-empty proc_path as a resolution base and returns before the snap.type != FD_DIR -> ENOTDIR guard at path.c:909 -- unlike resolve_proc_dirfd_path (path.c:522), which requires FD_DIR.

Trigger: open /proc/cpuinfo as fd 5, then openat(5, "x", ...). Real Linux returns ENOTDIR; this builds /proc/cpuinfo/x and resolves it (wrong errno / wrong target). It also reroutes plain fstat on such fds through proc_intercept_stat.

Fix: only stamp proc_path for /proc entries that are directories (the cases where the fd is a legitimate dirfd), or record a separate proc-mount flag used solely by the statfs path rather than overloading proc_path, which carries dirfd-resolution meaning.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix: I updated dirfd_guest_base_path to assert that the file descriptor is of type FD_DIR. If a regular emulated file descriptor under /proc (e.g. /proc/cpuinfo) is passed as a dirfd, it now correctly throws ENOTDIR and returns -1 (matching real Linux). This resolves the leaking dirfd semantics while allowing fstat and emulated read/write slow paths to continue referencing proc_path for regular files.

Comment thread src/syscall/fs-stat.c
return !strncmp(path, "/proc", 5) && (path[5] == '\0' || path[5] == '/');
}

static void fill_proc_statfs(linux_statfs_t *lin)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linux_statfs_t layout here is complete and correct (all fields covered by memset(0); f_type/f_bsize/f_namelen/f_frsize consistent with a real procfs). No regression test asserts the magic, though -- the existing tests only compare statfs vs fstatfs, not the value 0x9fa0. Worth adding one assertion for f_type == 0x9fa0 on both statfs("/proc/...") and fstatfs(open("/proc/...")).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix: Added test assertions verifying that st.f_type == 0x9fa0 (representing PROC_SUPER_MAGIC) for /proc, /proc/ (trailing slash), and /proc/self/cmdline under both statfs and fstatfs.

@Max042004

Copy link
Copy Markdown
Collaborator

The new proc_path_is_symlink() exclusion only feeds resolve_virtual_path()
in fs.c, which is what stamps a file descriptor's proc_path at open
time. sys_statfs() (the path-based syscall, fs-stat.c:384) never goes
through that function — it gates purely on
statfs_path_is_proc(tx.intercept_path) (fs-stat.c:363), a plain /proc
prefix check with no symlink exclusion

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;
}

This replaced the previous fallback chain that called proc_intercept_open()

  • real fstatfs() on paths proc_intercept_statfs() didn't own — which is
    exactly what used to make /proc/self/exe report the real backing
    filesystem, since statfs(2) (unlike lstat) always follows the symlink on
    real Linux.

@Max042004

Copy link
Copy Markdown
Collaborator

sys_statfs() no longer calls it (see #1), and nothing else in the tree does
either — it only calls itself recursively via the /proc/<pid> self-alias
branch, which is now unreachable. It can be deleted

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 sysprog21#142
@doanbaotrung

doanbaotrung commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

Cleanup of proc_intercept_statfs (procemu.c / procemu.h)

  • Fix: Since sys_statfs now performs direct existence resolution and no longer calls proc_intercept_statfs, and nothing else in the tree calls it, I completely deleted the declaration and definition of proc_intercept_statfs.

@jserv
jserv merged commit 7550202 into sysprog21:main Jul 16, 2026
9 checks passed
@jserv

jserv commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Thank @doanbaotrung for contributing!

@doanbaotrung
doanbaotrung deleted the proc_path branch July 16, 2026 19:22
@doanbaotrung
doanbaotrung restored the proc_path branch July 16, 2026 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing proc_path for Dynamic/Non-Stateful procfs Files in sys_fstatfs

3 participants