Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/syscall/chown-overlay.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ int chown_overlay_set(uint64_t dev,
}
e->dev = dev;
e->ino = ino;
/* Initialise with the sentinel (uint32_t)-1, meaning "no override
* recorded for this field yet". The apply path skips sentinel
* fields, so a partial chown that only touches one of uid/gid does
* not corrupt the other field with a stale physical host value.
*/
e->uid = (uint32_t) -1;
e->gid = (uint32_t) -1;
unsigned int idx = bucket_index(dev, ino);
Expand All @@ -131,6 +136,10 @@ int chown_overlay_set(uint64_t dev,
if (new_group != (uint32_t) -1)
e->gid = new_group;

/* A sentinel field means "keep whatever cur reports". Resolve before
* comparing so the no-op check is evaluated in the guest-virtual ID
* space on both sides.
*/
uint32_t effective_uid = e->uid == (uint32_t) -1 ? cur_uid : e->uid;
uint32_t effective_gid = e->gid == (uint32_t) -1 ? cur_gid : e->gid;
if (effective_uid == cur_uid && effective_gid == cur_gid)
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Expand Down Expand Up @@ -166,6 +175,9 @@ void chown_overlay_apply(struct stat *st)
overlay_entry_t *e =
find_locked((uint64_t) st->st_dev, (uint64_t) st->st_ino);
if (e) {
/* Skip sentinel fields: (uint32_t)-1 means "no override recorded"
* for that field, so leave the host value untouched.
*/
if (e->uid != (uint32_t) -1)
st->st_uid = e->uid;
if (e->gid != (uint32_t) -1)
Expand Down
32 changes: 24 additions & 8 deletions src/syscall/fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -2458,15 +2458,31 @@ static int64_t chown_result(int host_rc,
{
if (host_rc < 0 && errno != EPERM)
return linux_errno();
if (host_st && chown_overlay_set((uint64_t) host_st->st_dev,
(uint64_t) host_st->st_ino, owner, group,
host_st->st_uid, host_st->st_gid) < 0) {
/* Override allocation failed; reporting success would lie about the
* post-call stat round-trip. Linux's chown(2) lists ENOMEM among its
* possible errors for related allocation paths, so surface that to the
* guest instead.
if (host_st) {
/* cur_uid/cur_gid must be the owner the guest *currently sees*, not
* the raw host stat values. The two differ on macOS where the host
* user UID (e.g. 501) does not match GUEST_UID (1000), and whenever
* the file already has an overlay entry that virtualises its owner.
* chown_overlay_set uses cur_uid/cur_gid for both the early-exit
* no-op guard and the stale-entry removal guard; passing the physical
* UID evaluates those guards in the wrong ID namespace.
*
* Apply the existing overlay to a temporary copy of the host stat to
* obtain the guest-visible owner before forwarding to
* chown_overlay_set.
*/
return -LINUX_ENOMEM;
struct stat guest_st = *host_st;
chown_overlay_apply(&guest_st);
if (chown_overlay_set((uint64_t) host_st->st_dev,
(uint64_t) host_st->st_ino, owner, group,
guest_st.st_uid, guest_st.st_gid) < 0) {
/* Override allocation failed; reporting success would lie about
* the post-call stat round-trip. Linux's chown(2) lists ENOMEM
* among its possible errors for related allocation paths, so
* surface that to the guest instead.
*/
return -LINUX_ENOMEM;
}
}
return 0;
}
Expand Down
Loading