Skip to content

setuid-on-exec grants the host uid, which is outside the guest's uid namespace #264

Description

@doanbaotrung

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.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions