Skip to content

Track virtual file ownership through chown EPERM#93

Merged
jserv merged 1 commit into
mainfrom
chown
Jun 17, 2026
Merged

Track virtual file ownership through chown EPERM#93
jserv merged 1 commit into
mainfrom
chown

Conversation

@jserv

@jserv jserv commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

A rootless macOS host cannot chown a file to an arbitrary uid/gid, so the existing fallback turns the host EPERM into no-op success. Follow-up stat still returns the host's real owner instead of the value the guest just chowned to, which trips programs that diff metadata after extraction (tar --same-owner, cp -p, rsync -a, install -o user, dpkg, Python shutil.chown + os.stat).

Add a small (dev, ino) -> (uid, gid) overlay keyed by host stat. sys_fchownat and sys_fchown record the intent on EPERM and on host success alike, with the partial-chown -1 sentinel preserving the prior field. The stat-family wrappers in src/syscall/fs-stat.c consult the overlay once before translating struct stat into linux_stat[x]_t, so fstat / newfstatat / statx all pick up the override automatically. The set helper auto-prunes entries whose final (uid, gid) matches host's current values, so a guest that resets to the host owner walks away with a clean table.

Host inode reuse is defended by hooks in sys_unlinkat, sys_renameat2, and sys_close: stat the identity before the operation, and clear the overlay only when no other path or open fd still references the same (dev, ino). Forked children inherit the overlay through a new chown_overlay_send / chown_overlay_recv pair on the existing fork IPC socket. The recv helper reads records into a transient heap buffer before taking the lock so blocking IO does not serialize sibling stats; it also rejects out-of-band record counts.

stat is hot. The lock is a pthread_rwlock_t with apply / send taking the read lock and set / clear / recv taking the write lock; an atomic entry counter lets apply skip the lock entirely when no overrides exist.

The fork IPC header drops the standalone version field. The first header word is the protocol identity: bumping FORK_IPC_PROTOCOL_MAGIC (0x454C464C, "ELFL") replaces what bumping IPC_VERSION used to do, with one fewer field on the wire and one fewer concept in the code.


Summary by cubic

Adds a virtual ownership overlay so chown on rootless macOS records intended uid/gid even when the host returns EPERM, and the stat family returns those values. Also moves fork IPC to a protocol magic, carries the overlay across fork, and adds tests.

  • New Features

    • Record owner/group on chown success or EPERM; honor -1 partial updates; auto-prune when matching the host.
    • Apply overrides in stat/fstat/newfstatat/statx, keyed by (st_dev, st_ino) so it survives renames and covers hard links.
    • Clear entries on unlink, rename-overwrite, and when the last open fd to an unlinked inode closes.
    • Fork: send/recv the overlay over IPC; recv pre-reads outside the lock and caps record count; readers use an rwlock with an empty fast path.
    • Errors: non-EPERM chown errors propagate; fchownat on EPERM returns EAGAIN if the path changed during the call; return ENOMEM if an overlay entry cannot be allocated. Tests: add test-chown-overlay (chown/fchown/fchownat, -1, symlink nofollow, unlink/last-fd, fork), wired into make check.
  • Refactors

    • Fork IPC header now uses FORK_IPC_PROTOCOL_MAGIC ("ELFL") as the protocol identity; removed IPC_VERSION; has_shm and is_rosetta are bool.
    • Added native test-fork-ipc-protocol-host and build/test targets; run as part of make check.

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

Review in cubic

@jserv jserv requested a review from Max042004 June 10, 2026 06:50
cubic-dev-ai[bot]

This comment was marked as resolved.

A rootless macOS host cannot chown a file to an arbitrary uid/gid, so
the existing fallback turns the host EPERM into no-op success. Follow-up
stat still returns the host's real owner instead of the value the guest
just chowned to, which trips programs that diff metadata after extraction
(tar --same-owner, cp -p, rsync -a, install -o user, dpkg, Python
shutil.chown + os.stat).

Add a small (dev, ino) -> (uid, gid) overlay keyed by host stat.
sys_fchown{at} record the intent on EPERM and on host success alike,
with the partial-chown -1 sentinel preserving the prior field. The
stat-family wrappers in src/syscall/fs-stat.c consult the overlay once
before translating struct stat into linux_stat[x]_t, so fstat /
newfstatat / statx all pick up the override automatically. The set helper
auto-prunes entries whose final (uid, gid) matches host's current values,
so a guest that resets to the host owner walks away with a clean table.

Host inode reuse is defended by hooks in sys_unlinkat, sys_renameat2,
and sys_close: stat the identity before the operation, and clear the
overlay only when no other path or open fd still references the same
(dev, ino). Forked children inherit the overlay through a new
chown_overlay_{send,recv} on the existing fork IPC socket. The recv
helper reads records into a transient heap buffer before taking the lock
so blocking IO does not serialize sibling stats; it also rejects
out-of-band record counts.

stat is hot. The lock is a pthread_rwlock_t with apply / send taking the
read lock and set / clear / recv taking the write lock; an atomic entry
counter lets apply skip the lock entirely when no overrides exist.

The fork IPC header drops the standalone version field. The first header
word is the protocol identity: bumping FORK_IPC_PROTOCOL_MAGIC
(0x454C464C, "ELFL") replaces what bumping IPC_VERSION used to do, with
one fewer field on the wire and one fewer concept in the code.

Close #59
@jserv jserv merged commit 1e392bb into main Jun 17, 2026
4 checks passed
@jserv jserv deleted the chown branch June 17, 2026 16:21
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.

1 participant