From f9d0e05ac31561c60273003eaea6be98b4bdd38b Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 11 May 2026 17:13:27 +0800 Subject: [PATCH] Emit AT_SECURE in the auxiliary vector Bionic's __libc_init_AT_SECURE walks the auxv on startup and calls __early_abort when AT_SECURE is missing, so guest binaries linked against Bionic abort immediately under elfuse. glibc and musl tolerate a missing entry, which is why no existing test caught this. Set the value to zero: elfuse never elevates guest privileges (no setuid/setgid or file-capability emulation), so there is no scenario in which secure-exec mode should be reported as on. Place the entry between AT_EGID and AT_HWCAP2 to match the kernel ordering. Bump LINUX_STACK_AUXV_WORDS_MAX from 40 to 48: the previous bound sat exactly at the post-fix maximum of 20 entries / 40 words, with zero headroom for future additions. Add a /proc/self/auxv probe to test-tier-a covering AT_SECURE presence and value. getauxval(3) returns 0 both for absent entries and for AT_SECURE=0, so the test must walk the raw auxv buffer with a partial-read-safe loop to distinguish the two cases. Reported by Doan Bao Trung Close #26 --- src/core/stack.c | 4 +++ src/core/stack.h | 3 +- tests/test-tier-a.c | 71 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/core/stack.c b/src/core/stack.c index fb75916..3339559 100644 --- a/src/core/stack.c +++ b/src/core/stack.c @@ -286,6 +286,10 @@ uint64_t build_linux_stack(guest_t *g, AUX(AT_EUID, GUEST_UID); AUX(AT_GID, GUEST_GID); AUX(AT_EGID, GUEST_GID); + /* Bionic's __libc_init_AT_SECURE aborts when AT_SECURE is absent. + * elfuse never elevates privileges, so AT_SECURE is always 0. + */ + AUX(AT_SECURE, 0); AUX(AT_HWCAP2, query_hwcap2()); AUX(AT_HWCAP, query_hwcap()); AUX(AT_CLKTCK, 100); diff --git a/src/core/stack.h b/src/core/stack.h index c9cf4fe..8fed8d7 100644 --- a/src/core/stack.h +++ b/src/core/stack.h @@ -30,6 +30,7 @@ #define AT_EGID 14 #define AT_HWCAP 16 #define AT_CLKTCK 17 +#define AT_SECURE 23 #define AT_RANDOM 25 #define AT_HWCAP2 26 #define AT_SYSINFO_EHDR 33 @@ -39,7 +40,7 @@ /* Maximum number of uint64_t words needed to serialize the auxv emitted by * build_linux_stack(), including the AT_NULL terminator pair. */ -#define LINUX_STACK_AUXV_WORDS_MAX 40 +#define LINUX_STACK_AUXV_WORDS_MAX 48 typedef struct { uint64_t words[LINUX_STACK_AUXV_WORDS_MAX]; diff --git a/tests/test-tier-a.c b/tests/test-tier-a.c index 5895494..c0f7128 100644 --- a/tests/test-tier-a.c +++ b/tests/test-tier-a.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -448,6 +449,73 @@ static void test_pac_hwcap(void) FAIL("PACA/PACG not set in HWCAP"); } +/* AT_SECURE auxv probe. Bionic aborts when AT_SECURE is missing from the + * auxiliary vector. elfuse never elevates privileges, so the value is 0. + * getauxval() also returns 0 when an entry is not present, so distinguish + * the two by walking /proc/self/auxv and looking for the AT_SECURE key. + * + * Read /proc/self/auxv with a loop: procfs is not required to return the + * whole file in one read, and EINTR must be retried. AT_NULL must appear + * in the accumulated buffer or the test cannot conclude. + */ +static void test_at_secure_present(void) +{ + TEST("AT_SECURE present and zero"); + + int fd = open("/proc/self/auxv", O_RDONLY); + if (fd < 0) { + FAIL("cannot open /proc/self/auxv"); + return; + } + uint64_t buf[128]; + size_t total = 0; + for (;;) { + ssize_t n = read(fd, (char *) buf + total, sizeof(buf) - total); + if (n < 0) { + if (errno == EINTR) + continue; + close(fd); + FAIL("read /proc/self/auxv failed"); + return; + } + if (n == 0) + break; + total += (size_t) n; + if (total >= sizeof(buf)) + break; + } + close(fd); + if (total == 0 || (total & 7) != 0) { + FAIL("auxv read returned no bytes or misaligned length"); + return; + } + + int found = 0, terminated = 0; + uint64_t value = 0; + for (size_t i = 0; i + 1 < total / 8; i += 2) { + if (buf[i] == 0 /* AT_NULL */) { + terminated = 1; + break; + } + if (buf[i] == 23 /* AT_SECURE */) { + found = 1; + value = buf[i + 1]; + break; + } + } + + if (found) { + if (value != 0) + FAIL("AT_SECURE present but non-zero"); + else + PASS(); + } else if (!terminated) { + FAIL("AT_NULL not reached in auxv buffer"); + } else { + FAIL("AT_SECURE missing from /proc/self/auxv"); + } +} + int main(void) { printf("test-tier-a: Tier A compatibility tests\n\n"); @@ -483,6 +551,9 @@ int main(void) /* PAC feature */ test_pac_hwcap(); + /* AT_SECURE auxv entry */ + test_at_secure_present(); + SUMMARY("test-tier-a"); return fails > 0 ? 1 : 0; }