setuid-on-exec grants the host uid, which is outside the guest's uid namespace
Summary
When a guest process execves a setuid binary inside a --sysroot, elfuse sets the
new euid to the host file owner. On macOS the sysroot's files are owned by the
invoking macOS user (e.g. uid 502), so the guest ends up with an euid that does not
exist in its own uid namespace — neither root (0) nor the guest user (1000).
Reproducer
/* suidtest.c — aarch64-linux-gnu-gcc -static -o suidtest suidtest.c */
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char **argv) {
struct stat st;
if (argc > 1 && stat(argv[1], &st) == 0)
printf("guest stat(%s): st_uid=%u mode=%04o\n", argv[1],
(unsigned) st.st_uid, st.st_mode & 07777);
printf("uid=%u euid=%u\n", (unsigned) getuid(), (unsigned) geteuid());
return 0;
}
cp suidtest "$SYSROOT/tmp/suidtest"
chmod u+s "$SYSROOT/tmp/suidtest" # owner is the macOS user, e.g. uid 502
# must be a *guest* execve -- the initial program load does not take this path
./elfuse --sysroot "$SYSROOT" /bin/sh -c "/tmp/suidtest /tmp/suidtest"
Actual
guest stat(/tmp/suidtest): st_uid=502 mode=4755
uid=1000 euid=502
Expected
Something representable in the guest. On Linux a setuid binary owned by root yields
euid=0; here the owner is a host uid with no meaning inside the guest, so the
result is neither root nor the guest user.
Cause
src/syscall/exec.c computes the new euid straight from a raw host fstat:
bool have_exec_st = (fstat(exec_fd, &exec_st) == 0);
...
if (have_exec_st && !exec_is_script && S_ISREG(exec_st.st_mode)) {
if (exec_st.st_mode & S_ISUID) {
new_euid = (uint32_t) exec_st.st_uid; /* host uid, unmapped */
}
Host file ownership is never translated into the guest's uid namespace, so whatever
uid happens to own the file on the host becomes the guest's effective uid.
Impact
- Any setuid binary in a sysroot yields a meaningless euid. Privilege checks against
euid == 0 fail, and ownership checks against guest uids fail too.
- The granted uid is whatever host uid owns the file — on a shared or restored
sysroot that need not be the invoking user.
- Correctly, elfuse already ignores setuid on
#! scripts (matching
fs/exec.c:bprm_fill_uid); this is only about regular files.
Suggested fix
Either map host ownership into the guest namespace before computing setuid, or
ignore setuid entirely when the owner is not representable in the guest. Note
src/syscall/fs-stat.c applies chown_overlay_apply() to guest-facing stat results
while exec.c does not consult it at all, so any mapping should be applied
consistently in both places.
setuid-on-exec grants the host uid, which is outside the guest's uid namespace
Summary
When a guest process
execves a setuid binary inside a--sysroot, elfuse sets thenew euid to the host file owner. On macOS the sysroot's files are owned by the
invoking macOS user (e.g. uid 502), so the guest ends up with an euid that does not
exist in its own uid namespace — neither root (0) nor the guest user (1000).
Reproducer
Actual
Expected
Something representable in the guest. On Linux a setuid binary owned by root yields
euid=0; here the owner is a host uid with no meaning inside the guest, so theresult is neither root nor the guest user.
Cause
src/syscall/exec.ccomputes the new euid straight from a raw hostfstat:Host file ownership is never translated into the guest's uid namespace, so whatever
uid happens to own the file on the host becomes the guest's effective uid.
Impact
euid == 0fail, and ownership checks against guest uids fail too.sysroot that need not be the invoking user.
#!scripts (matchingfs/exec.c:bprm_fill_uid); this is only about regular files.Suggested fix
Either map host ownership into the guest namespace before computing setuid, or
ignore setuid entirely when the owner is not representable in the guest. Note
src/syscall/fs-stat.capplieschown_overlay_apply()to guest-facing stat resultswhile
exec.cdoes not consult it at all, so any mapping should be appliedconsistently in both places.