From da2339d0763508f4283ab9112b7b7e5d8187fdc5 Mon Sep 17 00:00:00 2001 From: Trung Date: Tue, 23 Jun 2026 14:51:23 +0700 Subject: [PATCH] Support dynamic guest identity (UID/GID) configuration via environment variables Allow users to dynamically configure the simulated guest UID and GID at runtime via environment variables, for example: ELFUSE_GUEST_UID and ELFUSE_GUEST_GID. If these variables are present in the host process environment, elfuse should parse them and override the default guest UID/GID. --- Makefile | 8 +++ mk/tests.mk | 7 ++- src/core/stack.c | 9 +-- src/runtime/procemu.c | 22 ++++--- src/syscall/proc-identity.c | 43 +++++++++++-- tests/test-identity-override-host.c | 98 +++++++++++++++++++++++++++++ 6 files changed, 165 insertions(+), 22 deletions(-) create mode 100644 tests/test-identity-override-host.c diff --git a/Makefile b/Makefile index c3bbb66..35bf1d7 100644 --- a/Makefile +++ b/Makefile @@ -151,6 +151,14 @@ $(BUILD_DIR)/test-fork-ipc-protocol-host: \ @echo " LD $@" $(Q)$(CC) $(CFLAGS) -o $@ $^ +## Build the identity override host test (native macOS binary) +$(BUILD_DIR)/test-identity-override-host: \ + $(BUILD_DIR)/test-identity-override-host.o \ + $(BUILD_DIR)/syscall/proc-identity.o | $(BUILD_DIR) + @echo " LD $@" + $(Q)$(CC) $(CFLAGS) -o $@ $^ + + ## Build the proctitle argv-tail regression test (native macOS binary) # Links against the project-built proctitle.o so the exact in-tree code is # exercised; no HVF entitlement is needed because the test only manipulates diff --git a/mk/tests.mk b/mk/tests.mk index 6462ff2..b4fa1c4 100644 --- a/mk/tests.mk +++ b/mk/tests.mk @@ -10,7 +10,7 @@ test-matrix test-matrix-elfuse-aarch64 test-matrix-qemu-aarch64 \ test-full test-multi-vcpu test-rwx test-sysroot-rename \ test-case-collision test-case-collision-fallback test-getdents64-overlong \ - test-sysroot-create-paths test-fork-ipc-protocol-host \ + test-sysroot-create-paths test-fork-ipc-protocol-host test-identity-override-host \ test-proctitle-host test-proctitle-low-stack \ test-sysroot-procfs-exec test-timeout-disable test-fuse-alpine \ test-sysroot-nofollow test-sysroot-chdir perf @@ -38,12 +38,15 @@ endef ## Run the unit test suite plus busybox applet validation check: $(ELFUSE_BIN) $(TEST_DEPS) check-syscall-coverage \ $(BUILD_DIR)/test-tlbi-encoder-host \ - $(BUILD_DIR)/test-fork-ipc-protocol-host + $(BUILD_DIR)/test-fork-ipc-protocol-host \ + $(BUILD_DIR)/test-identity-override-host @bash tests/driver.sh -e $(ELFUSE_BIN) -d $(TEST_DIR) -v @printf "\n$(BLUE)━━━ TLBI RVAE1IS encoder unit test ━━━$(RESET)\n" @$(BUILD_DIR)/test-tlbi-encoder-host @printf "\n$(BLUE)━━━ fork IPC protocol identity unit test ━━━$(RESET)\n" @$(BUILD_DIR)/test-fork-ipc-protocol-host + @printf "\n$(BLUE)━━━ identity override unit test ━━━$(RESET)\n" + @$(BUILD_DIR)/test-identity-override-host @printf "\n$(BLUE)━━━ proctitle argv-tail regression ━━━$(RESET)\n" @$(MAKE) --no-print-directory test-proctitle-host @printf "\n$(BLUE)━━━ proctitle low-stack regression ━━━$(RESET)\n" diff --git a/src/core/stack.c b/src/core/stack.c index e0a8d19..d8e74d8 100644 --- a/src/core/stack.c +++ b/src/core/stack.c @@ -17,6 +17,7 @@ #include "core/stack.h" #include "syscall/abi.h" /* GUEST_UID, GUEST_GID */ +#include "syscall/proc.h" /* Linux aarch64 HWCAP bits (from asm/hwcap.h). Only the bits the VZ-sanitized * ID registers actually advertise are listed here; HWCAP bits left out (e.g., @@ -284,10 +285,10 @@ uint64_t build_linux_stack(guest_t *g, AUX(AT_PHENT, elf_info->phentsize); AUX(AT_PHNUM, elf_info->phnum); AUX(AT_ENTRY, elf_info->entry + elf_load_base); - AUX(AT_UID, GUEST_UID); - AUX(AT_EUID, GUEST_UID); - AUX(AT_GID, GUEST_GID); - AUX(AT_EGID, GUEST_GID); + AUX(AT_UID, proc_get_uid()); + AUX(AT_EUID, proc_get_euid()); + AUX(AT_GID, proc_get_gid()); + AUX(AT_EGID, proc_get_egid()); /* Bionic's __libc_init_AT_SECURE aborts when AT_SECURE is absent. elfuse * never elevates privileges, so AT_SECURE is always 0. */ diff --git a/src/runtime/procemu.c b/src/runtime/procemu.c index 7d28599..cd1716c 100644 --- a/src/runtime/procemu.c +++ b/src/runtime/procemu.c @@ -2547,17 +2547,18 @@ int proc_intercept_open(const guest_t *g, "Tgid:\t%lld\n" "Pid:\t%lld\n" "PPid:\t%lld\n" - "Uid:\t%d\t%d\t%d\t%d\n" - "Gid:\t%d\t%d\t%d\t%d\n" + "Uid:\t%u\t%u\t%u\t%u\n" + "Gid:\t%u\t%u\t%u\t%u\n" "VmPeak:\t%llu kB\n" "VmSize:\t%llu kB\n" "VmRSS:\t%llu kB\n" "Threads:\t%d\n", name, (long long) proc_get_pid(), (long long) proc_get_pid(), - (long long) proc_get_ppid(), GUEST_UID, GUEST_UID, GUEST_UID, - GUEST_UID, GUEST_GID, GUEST_GID, GUEST_GID, GUEST_GID, - (unsigned long long) vm_size_kb, (unsigned long long) vm_size_kb, - (unsigned long long) vm_rss_kb, threads); + (long long) proc_get_ppid(), proc_get_uid(), proc_get_euid(), + proc_get_suid(), proc_get_euid(), proc_get_gid(), proc_get_egid(), + proc_get_sgid(), proc_get_egid(), (unsigned long long) vm_size_kb, + (unsigned long long) vm_size_kb, (unsigned long long) vm_rss_kb, + threads); } /* /proc/self/limits -> resource limits from prlimit64 cache */ @@ -2656,12 +2657,13 @@ int proc_intercept_open(const guest_t *g, "Tgid:\t%lld\n" "Pid:\t%ld\n" "PPid:\t%lld\n" - "Uid:\t%d\t%d\t%d\t%d\n" - "Gid:\t%d\t%d\t%d\t%d\n" + "Uid:\t%u\t%u\t%u\t%u\n" + "Gid:\t%u\t%u\t%u\t%u\n" "Threads:\t%d\n", proc_comm_name(), (long long) proc_get_pid(), tid, - (long long) proc_get_ppid(), GUEST_UID, GUEST_UID, GUEST_UID, - GUEST_UID, GUEST_GID, GUEST_GID, GUEST_GID, GUEST_GID, + (long long) proc_get_ppid(), proc_get_uid(), proc_get_euid(), + proc_get_suid(), proc_get_euid(), proc_get_gid(), + proc_get_egid(), proc_get_sgid(), proc_get_egid(), thread_active_count()); } diff --git a/src/syscall/proc-identity.c b/src/syscall/proc-identity.c index 5a157cd..4d4c151 100644 --- a/src/syscall/proc-identity.c +++ b/src/syscall/proc-identity.c @@ -6,6 +6,8 @@ #include #include +#include +#include #include #include "syscall/abi.h" @@ -24,16 +26,45 @@ static _Atomic int64_t guest_sid = 1, guest_pgid = 1; static _Atomic int64_t guest_fg_pgrp = 1; static _Atomic int32_t guest_has_ctty = 1; +static bool parse_env_identity(const char *env_name, uint32_t *out_val) +{ + const char *env_str = getenv(env_name); + if (!env_str || *env_str == '\0') { + return false; + } + char *endptr = NULL; + errno = 0; + unsigned long val = strtoul(env_str, &endptr, 10); + const char *p = env_str; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\v' || *p == '\f' || + *p == '\r') { + p++; + } + if (p != endptr && errno != ERANGE && *endptr == '\0' && + val <= UINT32_MAX && *p >= '0' && *p <= '9') { + *out_val = (uint32_t) val; + return true; + } + return false; +} + void proc_identity_init(void) { guest_pid = 1; parent_pid = 0; - emu_uid = GUEST_UID; - emu_euid = GUEST_UID; - emu_suid = GUEST_UID; - emu_gid = GUEST_GID; - emu_egid = GUEST_GID; - emu_sgid = GUEST_GID; + + uint32_t uid = GUEST_UID; + parse_env_identity("ELFUSE_GUEST_UID", &uid); + + uint32_t gid = GUEST_GID; + parse_env_identity("ELFUSE_GUEST_GID", &gid); + + emu_uid = uid; + emu_euid = uid; + emu_suid = uid; + emu_gid = gid; + emu_egid = gid; + emu_sgid = gid; emu_nice = 0; guest_sid = 1; guest_pgid = 1; diff --git a/tests/test-identity-override-host.c b/tests/test-identity-override-host.c new file mode 100644 index 0000000..a9be4d3 --- /dev/null +++ b/tests/test-identity-override-host.c @@ -0,0 +1,98 @@ +/* Host-side unit test for ELFUSE_GUEST_UID / ELFUSE_GUEST_GID environment + * overrides. + * + * Copyright 2026 elfuse contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "syscall/proc.h" +#include "syscall/proc-identity.h" +#include "syscall/abi.h" + +/* Mock shim_globals_publish_pgsid to avoid linking the entire guest shim + * subsystem */ +void shim_globals_publish_pgsid(guest_t *g, int64_t pgid, int64_t sid); +void shim_globals_publish_pgsid(guest_t *g, int64_t pgid, int64_t sid) +{ + (void) g; + (void) pgid; + (void) sid; +} + +int main(void) +{ + /* Test 1: Fallback case (no env vars) */ + unsetenv("ELFUSE_GUEST_UID"); + unsetenv("ELFUSE_GUEST_GID"); + proc_identity_init(); + + assert(proc_get_uid() == GUEST_UID); + assert(proc_get_euid() == GUEST_UID); + assert(proc_get_suid() == GUEST_UID); + assert(proc_get_gid() == GUEST_GID); + assert(proc_get_egid() == GUEST_GID); + assert(proc_get_sgid() == GUEST_GID); + + /* Test 2: Override case */ + setenv("ELFUSE_GUEST_UID", "2000", 1); + setenv("ELFUSE_GUEST_GID", "3000", 1); + proc_identity_init(); + + assert(proc_get_uid() == 2000); + assert(proc_get_euid() == 2000); + assert(proc_get_suid() == 2000); + assert(proc_get_gid() == 3000); + assert(proc_get_egid() == 3000); + assert(proc_get_sgid() == 3000); + + /* Test 3: Override only UID */ + setenv("ELFUSE_GUEST_UID", "4000", 1); + unsetenv("ELFUSE_GUEST_GID"); + proc_identity_init(); + + assert(proc_get_uid() == 4000); + assert(proc_get_euid() == 4000); + assert(proc_get_suid() == 4000); + assert(proc_get_gid() == GUEST_GID); + assert(proc_get_egid() == GUEST_GID); + assert(proc_get_sgid() == GUEST_GID); + + /* Test 4: Override only GID */ + unsetenv("ELFUSE_GUEST_UID"); + setenv("ELFUSE_GUEST_GID", "5000", 1); + proc_identity_init(); + + assert(proc_get_uid() == GUEST_UID); + assert(proc_get_euid() == GUEST_UID); + assert(proc_get_suid() == GUEST_UID); + assert(proc_get_gid() == 5000); + assert(proc_get_egid() == 5000); + assert(proc_get_sgid() == 5000); + + /* Test 5: Invalid values should be ignored (fall back to default) */ + setenv("ELFUSE_GUEST_UID", "-10", 1); + setenv("ELFUSE_GUEST_GID", "abc", 1); + proc_identity_init(); + assert(proc_get_uid() == GUEST_UID); + assert(proc_get_gid() == GUEST_GID); + + setenv("ELFUSE_GUEST_UID", "5000000000", 1); /* overflows uint32_t */ + setenv("ELFUSE_GUEST_GID", "", 1); + proc_identity_init(); + assert(proc_get_uid() == GUEST_UID); + assert(proc_get_gid() == GUEST_GID); + + /* Test 6: Dynamic values above INT_MAX but <= UINT32_MAX */ + setenv("ELFUSE_GUEST_UID", "4294967294", 1); /* valid uint32 > INT_MAX */ + setenv("ELFUSE_GUEST_GID", "4294967295", 1); /* valid uint32 (UINT32_MAX) */ + proc_identity_init(); + assert(proc_get_uid() == 4294967294); + assert(proc_get_gid() == 4294967295); + + printf("test-identity-override-host: PASS\n"); + return 0; +}