Skip to content
Open
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
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions mk/tests.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
9 changes: 5 additions & 4 deletions src/core/stack.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.,
Expand Down Expand Up @@ -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.
*/
Expand Down
22 changes: 12 additions & 10 deletions src/runtime/procemu.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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());
}

Expand Down
43 changes: 37 additions & 6 deletions src/syscall/proc-identity.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include <stdatomic.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>

#include "syscall/abi.h"
Expand All @@ -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;
Expand Down
98 changes: 98 additions & 0 deletions tests/test-identity-override-host.c
Original file line number Diff line number Diff line change
@@ -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 <stdio.h>
#include <stdlib.h>
#include <assert.h>

#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;
}
Loading