diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/CMakeLists.txt new file mode 100644 index 0000000000..0f135ddfd5 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.20) +project(test-uid-gid-re-setters C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +# Group C: setreuid / setregid 两参数 setter (real + effective) +add_executable(test-uid-gid-re-setters + src/main.c + src/setreuid.c + src/setregid.c + src/saved_id_semantics.c + src/boundary.c + src/matrix.c + src/procfs_visibility.c + src/nptl_sync.c + src/core_dump_inhibit.c + src/errno_preservation.c) +target_link_libraries(test-uid-gid-re-setters PRIVATE pthread) + +target_include_directories(test-uid-gid-re-setters PRIVATE src) +target_compile_options(test-uid-gid-re-setters PRIVATE -Wall -Wextra -Werror) +install(TARGETS test-uid-gid-re-setters RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/boundary.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/boundary.c new file mode 100644 index 0000000000..4c8bdf9f9a --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/boundary.c @@ -0,0 +1,95 @@ +#define _GNU_SOURCE +#include "test_framework.h" + +#include +#include +#include +#include +#include +#include + +/* boundary — setreuid/setregid 边界 + 异常输入。 + * + * 边界值: + * - (-1, -1) sentinel — 已在 setreuid (a) 测;这里测 raw syscall 直传 u32::MAX + * - (uid, -1) / (-1, uid) — 单参数 NOCHG + * - root setreuid(u32::MAX-1, u32::MAX-1) — 极大值,root 接受 + * - 错误输入:raw syscall 传无效组合 + */ + +static int waitpid_safely(pid_t pid, int *status) +{ + pid_t r = waitpid(pid, status, 0); + return r == pid ? 0 : -1; +} + +/* raw syscall 直传 u32::MAX (NOCHG) — kernel 应识别为 NOCHG */ +static void boundary_raw_u32max_nochg(void) +{ + uid_t r0, e0, s0; + getresuid(&r0, &e0, &s0); + long rc = syscall(SYS_setreuid, (uid_t)-1, (uid_t)-1); + CHECK(rc == 0, "boundary (a) raw setreuid(u32::MAX, u32::MAX) -> 0"); + uid_t r1, e1, s1; + getresuid(&r1, &e1, &s1); + CHECK(r0 == r1 && e0 == e1 && s0 == s1, "boundary (a) cred unchanged after NOCHG sentinel"); +} + +/* root: 极大值 setreuid 应成功(CAP_SETUID 时无 EINVAL)*/ +static void boundary_root_extreme_values(void) +{ + if (getuid() != 0) { + printf(" boundary (b) skip: not root\n"); + return; + } + pid_t pid = fork(); + if (pid == 0) { + /* 0xFFFFFFFE = u32::MAX - 1,避免与 NOCHG 混淆 */ + if (setreuid(0xFFFFFFFE, 0xFFFFFFFE) != 0) _exit(1); + uid_t r, e, s; + if (getresuid(&r, &e, &s) != 0) _exit(2); + if (r == 0xFFFFFFFE && e == 0xFFFFFFFE) _exit(0); + _exit(3); + } + if (pid > 0) { + int status; + if (waitpid_safely(pid, &status) == 0) { + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "boundary (b) root setreuid(u32::MAX-1) accepted (no EINVAL)"); + } + } +} + +/* unpriv: setreuid(u32::MAX-1, -1) 应 EPERM (不在自身 ID 集) */ +static void boundary_unpriv_extreme_eperm(void) +{ + if (getuid() != 0) { + printf(" boundary (c) skip\n"); + return; + } + pid_t pid = fork(); + if (pid == 0) { + if (setresuid(1000, 1000, 1000) != 0) _exit(99); + errno = 0; + int rc = setreuid(0xFFFFFFFE, (uid_t)-1); + if (rc == -1 && errno == EPERM) _exit(0); + _exit(1); + } + if (pid > 0) { + int status; + if (waitpid_safely(pid, &status) == 0) { + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "boundary (c) unpriv setreuid(u32::MAX-1, -1) -> -1 EPERM"); + } + } +} + +int boundary_run(void) +{ + printf("\n----- boundary -----\n"); + boundary_raw_u32max_nochg(); + boundary_root_extreme_values(); + boundary_unpriv_extreme_eperm(); + printf(" ----- boundary: %d pass, %d fail -----\n", __pass, __fail); + return __fail; +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/core_dump_inhibit.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/core_dump_inhibit.c new file mode 100644 index 0000000000..bdfe6a9277 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/core_dump_inhibit.c @@ -0,0 +1,87 @@ +/* core_dump_inhibit.c — setreuid 改 euid → dumpable=0 (man N2). + * + * man 2 setreuid (引用自 setuid 共享 NOTES 隐含): + * "If euid is different from the old effective UID, the process will be + * forbidden from leaving core dumps." + * + * 3 case (a-c). + */ + +#define _GNU_SOURCE +#include "test_framework.h" + +#include +#include +#include +#include +#include +#include +#include + +static void cd_baseline(void) +{ + /* 测什么/怎么测/期望/为什么: dumpable 启动 1, prctl(PR_GET_DUMPABLE) 返 1. */ + int rc = prctl(PR_GET_DUMPABLE); + if (rc < 0 && errno == EINVAL) { + printf(" KNOWN-STARRY-LIMITATION | core_dump (a) PR_GET_DUMPABLE 不支持\n"); + return; + } + CHECK(rc == 1, "core_dump (a) baseline dumpable == 1"); +} + +/* (b) setreuid 不改 euid → dumpable 不变 */ +static void cd_setreuid_no_euid_change(void) +{ + /* 测什么: setreuid(-1,-1) NOCHG 不动 euid → dumpable 仍 1. + * 怎么测: fork → child setreuid(-1,-1) → 验 dumpable. + * 期望: 1. */ + pid_t pid = fork(); + if (pid == 0) { + if (setreuid((uid_t)-1, (uid_t)-1) != 0) _exit(99); + int rc = prctl(PR_GET_DUMPABLE); + if (rc < 0 && errno == EINVAL) _exit(20); + if (rc == 1) _exit(0); + _exit(1); + } + int status; + waitpid(pid, &status, 0); + int ec = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + if (ec == 0) CHECK(1, "core_dump (b) setreuid(-1,-1) NOCHG → dumpable 仍 1"); + else if (ec == 20) printf(" KNOWN-STARRY-LIMITATION | core_dump (b) PR_GET_DUMPABLE 不支持\n"); + else CHECK(0, "core_dump (b) failed"); +} + +/* (c) setreuid(1000, 2000) 改 euid → dumpable=0 */ +static void cd_setreuid_changes_euid(void) +{ + /* 测什么: setreuid 改 euid (0→2000) → dumpable=0. + * 怎么测: root fork → child setreuid(1000, 2000) → 验 dumpable. + * 期望: 0 (禁 core dump). */ + if (getuid() != 0) { printf(" core_dump (c) skip\n"); return; } + pid_t pid = fork(); + if (pid == 0) { + if (setreuid(1000, 2000) != 0) _exit(99); + int rc = prctl(PR_GET_DUMPABLE); + if (rc < 0 && errno == EINVAL) _exit(20); + if (rc == 0) _exit(0); + if (rc == 1) _exit(21); + _exit(1); + } + int status; + waitpid(pid, &status, 0); + int ec = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + if (ec == 0) CHECK(1, "core_dump (c) setreuid(1k,2k) 改 euid → dumpable=0"); + else if (ec == 20) printf(" KNOWN-STARRY-LIMITATION | core_dump (c) PR_GET_DUMPABLE\n"); + else if (ec == 21) printf(" KNOWN-STARRY-LIMITATION | core_dump (c) starry 未禁 dumpable\n"); + else CHECK(0, "core_dump (c) failed"); +} + +int core_dump_inhibit_run(void) +{ + printf("\n----- core_dump_inhibit (man N2) -----\n"); + cd_baseline(); + cd_setreuid_no_euid_change(); + cd_setreuid_changes_euid(); + printf(" ----- core_dump_inhibit: %d pass, %d fail -----\n", __pass, __fail); + return __fail; +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/errno_preservation.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/errno_preservation.c new file mode 100644 index 0000000000..7266ecd7bb --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/errno_preservation.c @@ -0,0 +1,88 @@ +/* errno_preservation.c — setreuid/setregid 成功路径 errno 不被误改 (Group C Round 8). + * + * man 2 setreuid §"RETURN VALUE": + * "On success, zero is returned. On error, -1 is returned, and errno is + * set to indicate the error." + * + * Linux 实测: 成功路径 errno 不动. 验 starry sys_setreuid/setregid 在 + * success path 不误改 user errno (常见 starry vm_write/cred update 后误设 + * errno 残值的 bug 类型). + */ + +#define _GNU_SOURCE +#include "test_framework.h" + +#include +#include +#include +#include +#include + +static void setreuid_nochg_errno_preserved(void) +{ + /* 测什么/怎么测/期望/为什么: setreuid(-1,-1) NOCHG 路径成功 + errno 不动. */ + errno = 4242; + int rc = setreuid((uid_t)-1, (uid_t)-1); + int err = errno; + CHECK(rc == 0, "errno_preserve (a) setreuid(-1,-1) -> 0"); + CHECK(err == 4242, "errno_preserve (a) setreuid(-1,-1) 不改 errno (still 4242)"); + errno = 0; +} + +static void setregid_nochg_errno_preserved(void) +{ + errno = 5555; + int rc = setregid((gid_t)-1, (gid_t)-1); + int err = errno; + CHECK(rc == 0, "errno_preserve (b) setregid(-1,-1) -> 0"); + CHECK(err == 5555, "errno_preserve (b) setregid(-1,-1) 不改 errno (still 5555)"); + errno = 0; +} + +static void setreuid_self_errno_preserved(void) +{ + /* 测什么: setreuid(self, self) 实际写 cred 路径 errno 不动. */ + uid_t u = getuid(); + uid_t e = geteuid(); + errno = 6666; + int rc = setreuid(u, e); + int err = errno; + CHECK(rc == 0, "errno_preserve (c) setreuid(self,self) -> 0"); + CHECK(err == 6666, "errno_preserve (c) setreuid(self,self) 不改 errno (still 6666)"); + errno = 0; +} + +static void setregid_self_errno_preserved(void) +{ + gid_t g = getgid(); + gid_t e = getegid(); + errno = 7777; + int rc = setregid(g, e); + int err = errno; + CHECK(rc == 0, "errno_preserve (d) setregid(self,self) -> 0"); + CHECK(err == 7777, "errno_preserve (d) setregid(self,self) 不改 errno (still 7777)"); + errno = 0; +} + +static void raw_setreuid_success_errno_preserved(void) +{ + /* 测什么: raw syscall 路径 errno 不动 (与 libc 路径分离测). */ + errno = 1111; + long rc = syscall(SYS_setreuid, getuid(), geteuid()); + int err = errno; + CHECK(rc == 0, "errno_preserve (e) raw setreuid(self,self) -> 0"); + CHECK(err == 1111, "errno_preserve (e) raw setreuid(self,self) 不改 errno (still 1111)"); + errno = 0; +} + +int errno_preservation_run(void) +{ + printf("\n----- errno_preservation (Round 8) -----\n"); + setreuid_nochg_errno_preserved(); + setregid_nochg_errno_preserved(); + setreuid_self_errno_preserved(); + setregid_self_errno_preserved(); + raw_setreuid_success_errno_preserved(); + printf(" ----- errno_preservation: %d pass, %d fail -----\n", __pass, __fail); + return __fail; +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/main.c new file mode 100644 index 0000000000..d286049089 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/main.c @@ -0,0 +1,81 @@ +#define _GNU_SOURCE +#include "test_framework.h" + +#include +#include + +/* + * test-uid-gid-re-setters —— setreuid / setregid 地毯式全覆盖 + * + * Group C of uid/gid 5 分组测例方案。 + * + * man 2 setreuid §"DESCRIPTION": + * "setreuid() sets real and effective user IDs of the calling process. + * Supplying a value of -1 for either the real or effective user ID + * forces the system to leave that ID unchanged." + * "Unprivileged processes may only set the effective user ID to the real + * user ID, the effective user ID, or the saved set-user-ID." + * "Unprivileged users may only set the real user ID to the real user ID + * or the effective user ID." + * + * man §"saved set-user-ID auto-update": + * "If the real user ID is set (i.e., ruid is not -1) or the effective user + * ID is set to a value not equal to the previous real user ID, the saved + * set-user-ID will be set to the new effective user ID." + * + * 测试自包含 — root 与 unprivileged 双路径;设计核心是 saved-set-uid 的 + * 自动更新规则(最容易被实现错过的语义)。 + */ + +int setreuid_run(void); +int setregid_run(void); +int saved_id_semantics_run(void); +int boundary_run(void); +int matrix_run(void); +int procfs_visibility_run(void); +int nptl_sync_run(void); +int core_dump_inhibit_run(void); +int errno_preservation_run(void); + +int main(void) +{ + TEST_START("uid/gid re-setters: setreuid + setregid 地毯式(9 模块: setreuid + setregid + saved_id_semantics + boundary + matrix + procfs_visibility + nptl_sync + core_dump_inhibit + errno_preservation)"); + + (void)__pass; + (void)__fail; + + int total_fail = 0; + int rc; + + #define COLLECT(label, call) do { \ + rc = (call); \ + if (rc < 0 || rc > 1000000) { \ + printf(" %-22s : \n", label, rc); \ + rc = 1; \ + } else { \ + printf(" %-22s : %d fail\n", label, rc); \ + } \ + total_fail += rc; \ + } while (0) + + printf("================================================\n"); + + COLLECT("setreuid", setreuid_run()); + COLLECT("setregid", setregid_run()); + COLLECT("saved_id_semantics", saved_id_semantics_run()); + COLLECT("boundary", boundary_run()); + COLLECT("matrix(man-first)", matrix_run()); + COLLECT("procfs_visibility", procfs_visibility_run()); + COLLECT("nptl_sync(V1)", nptl_sync_run()); + COLLECT("core_dump_inhibit(N2)", core_dump_inhibit_run()); + COLLECT("errno_preservation", errno_preservation_run()); + + #undef COLLECT + + printf(" -------------------------------------------\n"); + printf(" TOTAL: %d fail | RESULT: %s\n", + total_fail, total_fail == 0 ? "ALL PASS" : "HAS FAILURES"); + printf("================================================\n\n"); + + return total_fail > 0 ? 1 : 0; +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/matrix.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/matrix.c new file mode 100644 index 0000000000..5976965be1 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/matrix.c @@ -0,0 +1,286 @@ +/* matrix.c — 完备测试矩阵 setreuid/setregid (man-first design) + * + * 参 notes/21-re-setter-test-design.md + * + * man 2 setreuid 关键子句: + * [RE-D1] sets r and e + * [RE-D2] -1 = NOCHG + * [RE-D3] unpriv: euid in {r, e, s} + * [RE-D4] unpriv: ruid in {r, e} (not s!) + * [RE-D5] saved rule: if ruid set OR (euid set != prev r) → s = new e + * [RE-E3] EPERM if violation + * + * starry 实现 (sys.rs:201-281) 完全贴合 Linux semantics 含 RE-D5。 + * + * 矩阵: + * 8 ruid × 8 euid × 6 state × 2 mode × 2 syscall = 1536 case + * 每 case 4-5 assertion ≈ 6000+ PASS + */ + +#define _GNU_SOURCE +#include "test_framework.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NOCHG 0xFFFFFFFFu + +/* ── 维度 A: ruid/rgid 输入 (6 值) + * + * 减少 8→6 输入 (drop INT32_MAX + UINT32_MAX-1) 以避免 aarch64 starry qemu + * timeout (1536 case fork-heavy 在 emulated aarch64 上接近 5min 大概率超时). + * 边界覆盖仍含 NOCHG / root / small / user / 16-bit max — 关键 boundary 保留. + * + * 大边界值由 Group D matrix (5 values) 以及 bug-* 复现独立覆盖. + * ───────────────────────────────────────────────────────────── */ +static const uint32_t INPUT_R[] = { + NOCHG, /* I-R1 NOCHG (-1) */ + 0u, /* I-R2 root */ + 1u, /* I-R3 small */ + 1000u, /* I-R4 user */ + 2000u, /* I-R5 another */ + 65535u, /* I-R6 16-bit max */ +}; +#define N_R (sizeof(INPUT_R) / sizeof(INPUT_R[0])) + +/* ── 维度 B: euid/egid 输入 (同) ─────────────────────────────── */ +#define INPUT_E INPUT_R +#define N_E N_R + +/* ── 维度 C: cred 启动 state (6) ─────────────────────────────── */ +typedef enum { + S_ROOT_UNMOD = 0, + S_ROOT_AFTER_SETUID_1000, /* caps drop */ + S_ROOT_AFTER_SETRESUID_1K_2K_3K, + S_NONROOT_FULL_1000, + S_NONROOT_R0_E1000_S2000, /* 混杂 (uid: root+setresuid(0,1000,2000)+setuid?) */ + S_AFTER_SETREUID_1K_2K, /* 用 setreuid 设的初始态 */ + S_STATE_COUNT +} cred_state_t; + +static const char *state_name(cred_state_t s) +{ + switch (s) { + case S_ROOT_UNMOD: return "root_unmod"; + case S_ROOT_AFTER_SETUID_1000: return "setuid(1000)[caps_drop]"; + case S_ROOT_AFTER_SETRESUID_1K_2K_3K: return "setresuid(1k,2k,3k)"; + case S_NONROOT_FULL_1000: return "nonroot_full(1k)"; + case S_NONROOT_R0_E1000_S2000: return "mixed(setresuid 0,1k,2k)"; + case S_AFTER_SETREUID_1K_2K: return "setreuid(1k,2k)"; + default: return "?"; + } +} + +static int setup_state(cred_state_t s, bool for_gid) +{ + /* for_gid: 镜像应用到 gid 路径 (避免 gid 全零导致 derive 失效) */ + switch (s) { + case S_ROOT_UNMOD: + return (getuid() == 0) ? 0 : -100; + + case S_ROOT_AFTER_SETUID_1000: + if (getuid() != 0) return -100; + if (for_gid) { + if (setresgid(1000, 1000, 1000) != 0) return -101; + } + return setuid(1000) == 0 ? 0 : -102; + + case S_ROOT_AFTER_SETRESUID_1K_2K_3K: + if (getuid() != 0) return -100; + if (for_gid) { + return setresgid(1000, 2000, 3000) == 0 ? 0 : -103; + } + return setresuid(1000, 2000, 3000) == 0 ? 0 : -104; + + case S_NONROOT_FULL_1000: + if (getuid() == 0) { + if (setresgid(1000, 1000, 1000) != 0) return -105; + if (setresuid(1000, 1000, 1000) != 0) return -106; + } + return 0; + + case S_NONROOT_R0_E1000_S2000: + if (getuid() != 0) return -100; + /* 用 setresuid 设 r=0 e=1000 s=2000,但 e=1000 → caps drop + * 所以此 state 只能用 setresuid 在 root 时 set,setresuid 进入 + * 时 cap 仍在 */ + if (for_gid) { + return setresgid(0, 1000, 2000) == 0 ? 0 : -107; + } + return setresuid(0, 1000, 2000) == 0 ? 0 : -108; + + case S_AFTER_SETREUID_1K_2K: + if (getuid() != 0) return -100; + if (for_gid) { + return setregid(1000, 2000) == 0 ? 0 : -109; + } + return setreuid(1000, 2000) == 0 ? 0 : -110; + + default: + return -200; + } +} + +/* ── cred 读 ──────────────────────────────────────────────────── */ +typedef struct { uint32_t r, e, s; } cred_t; + +static int read_uid_cred(cred_t *c) { + uid_t r, e, s; + if (getresuid(&r, &e, &s) != 0) return -1; + c->r = r; c->e = e; c->s = s; return 0; +} +static int read_gid_cred(cred_t *c) { + gid_t r, e, s; + if (getresgid(&r, &e, &s) != 0) return -1; + c->r = r; c->e = e; c->s = s; return 0; +} + +/* ── 期望 ─────────────────────────────────────────────────────── */ +typedef struct { + int rc; + int err; + uint32_t new_r, new_e, new_s; +} expected_t; + +static expected_t derive(cred_t pre, uint32_t euid_caller, + uint32_t rin, uint32_t ein) +{ + expected_t exp = {0}; + uint32_t new_r = pre.r, new_e = pre.e, new_s = pre.s; + bool has_cap = (euid_caller == 0); + + if (has_cap) { + if (rin != NOCHG) new_r = rin; + if (ein != NOCHG) new_e = ein; + } else { + if (rin != NOCHG) { + if (rin != pre.r && rin != pre.e) { + exp.rc = -1; exp.err = EPERM; + return exp; + } + new_r = rin; + } + if (ein != NOCHG) { + if (ein != pre.r && ein != pre.e && ein != pre.s) { + exp.rc = -1; exp.err = EPERM; + return exp; + } + new_e = ein; + } + } + + /* RE-D5 saved rule */ + if (rin != NOCHG || (ein != NOCHG && new_e != pre.r)) { + new_s = new_e; + } + + exp.rc = 0; + exp.new_r = new_r; + exp.new_e = new_e; + exp.new_s = new_s; + return exp; +} + +/* ── 模式 / syscall ──────────────────────────────────────────── */ +typedef enum { M_LIBC = 0, M_RAW = 1 } call_mode_t; +typedef enum { SC_SETREUID = 0, SC_SETREGID = 1 } syscall_kind_t; + +static long do_call(syscall_kind_t sc, call_mode_t m, + uint32_t r, uint32_t e) +{ + if (m == M_LIBC) { + return (sc == SC_SETREUID) ? setreuid((uid_t)r, (uid_t)e) + : setregid((gid_t)r, (gid_t)e); + } else { + long sysn = (sc == SC_SETREUID) ? SYS_setreuid : SYS_setregid; + return syscall(sysn, r, e); + } +} + +/* ── 单 case ──────────────────────────────────────────────────── */ +static int waitpid_safely(pid_t pid, int *st) +{ + return waitpid(pid, st, 0) == pid ? 0 : -1; +} + +static void matrix_one(syscall_kind_t sc, cred_state_t s, + uint32_t rin, uint32_t ein, call_mode_t m) +{ + pid_t pid = fork(); + if (pid == 0) { + bool for_gid = (sc == SC_SETREGID); + if (setup_state(s, for_gid) != 0) _exit(99); + + cred_t pre; + if ((sc == SC_SETREUID ? read_uid_cred(&pre) : read_gid_cred(&pre)) != 0) _exit(98); + + cred_t uid_pre; + if (read_uid_cred(&uid_pre) != 0) _exit(97); + uint32_t euid_caller = uid_pre.e; + + expected_t exp = derive(pre, euid_caller, rin, ein); + + errno = 0; + long rc = do_call(sc, m, rin, ein); + int err = errno; + + if (rc != exp.rc) _exit(11); + if (exp.rc == -1 && err != exp.err) _exit(12); + + if (exp.rc == 0) { + cred_t post; + int prv = (sc == SC_SETREUID ? read_uid_cred(&post) + : read_gid_cred(&post)); + if (prv != 0) _exit(13); + if (post.r != exp.new_r) _exit(14); + if (post.e != exp.new_e) _exit(15); + if (post.s != exp.new_s) _exit(16); + } + _exit(0); + } + int status; + if (pid < 0 || waitpid_safely(pid, &status) != 0) { + CHECK(0, "matrix: fork/waitpid failed"); + return; + } + int ec = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + char msg[300]; + snprintf(msg, sizeof msg, + "matrix sc=%s s=%s r=0x%08x e=0x%08x m=%s", + sc == SC_SETREUID ? "setreuid" : "setregid", + state_name(s), rin, ein, m == M_RAW ? "raw" : "libc"); + if (ec == 0) CHECK(1, msg); + else if (ec == 99) printf(" SKIP (setup) | %s\n", msg); + else { + char buf[400]; snprintf(buf, sizeof buf, "FAIL ec=%d | %s", ec, msg); + CHECK(0, buf); + } +} + +int matrix_run(void) +{ + printf("\n----- matrix (man-first 完备) -----\n"); + if (getuid() != 0) { + printf(" matrix: needs root; non-root state subset only\n"); + } + int total = 0; + for (int sc = 0; sc < 2; sc++) + for (int s = 0; s < S_STATE_COUNT; s++) + for (size_t i = 0; i < N_R; i++) + for (size_t j = 0; j < N_E; j++) + for (int m = 0; m < 2; m++) { + matrix_one((syscall_kind_t)sc, (cred_state_t)s, + INPUT_R[i], INPUT_E[j], (call_mode_t)m); + total++; + } + printf(" ----- matrix: %d pass, %d fail (out of %d cases) -----\n", + __pass, __fail, total); + return __fail; +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/nptl_sync.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/nptl_sync.c new file mode 100644 index 0000000000..016a57d943 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/nptl_sync.c @@ -0,0 +1,142 @@ +/* nptl_sync.c — setreuid/setregid 跨 pthread cred 同步 (man V1). + * + * man 2 setreuid §"C library/kernel differences": + * "...NPTL...signal-based...when one thread changes credentials, all of the + * other threads in the process also change their credentials..." + * + * 3 case (a-c): main↔pthread setreuid/setregid 同步 + 并发收敛 + */ + +#define _GNU_SOURCE +#include "test_framework.h" + +#include +#include +#include +#include +#include +#include +#include + +static atomic_int main_done; +static atomic_int observed_uid = -1; +static atomic_int observed_gid = -1; + +static void *observer_uid(void *arg) +{ + (void)arg; + while (!atomic_load(&main_done)) usleep(1000); + atomic_store(&observed_uid, (int)getuid()); + return NULL; +} + +static void *pthread_setregid_caller(void *arg) +{ + (void)arg; + if (setregid(1234, 5678) == 0) atomic_store(&observed_gid, 1); + else atomic_store(&observed_gid, -2); + return NULL; +} + +/* (a) main setreuid → pthread sees new uid */ +static void nptl_setreuid_main_to_pthread(void) +{ + /* 测什么: man V1 — setreuid in main 跨 pthread 同步. + * 怎么测: root fork → child 起 pthread observer → main setreuid(2345, 2345) + * → pthread 看 getuid. + * 期望: Linux NPTL ✓ pthread 看 2345; starry 无 NPTL → 0 (KNOWN-LIMIT). */ + if (getuid() != 0) { printf(" nptl (a) skip\n"); return; } + pid_t pid = fork(); + if (pid == 0) { + atomic_store(&main_done, 0); + atomic_store(&observed_uid, -1); + pthread_t t; + if (pthread_create(&t, NULL, observer_uid, NULL) != 0) _exit(99); + if (setreuid(2345, 2345) != 0) _exit(98); + atomic_store(&main_done, 1); + pthread_join(t, NULL); + int o = atomic_load(&observed_uid); + if (o == 2345) _exit(0); + if (o == 0) _exit(20); + _exit(1); + } + int status; + waitpid(pid, &status, 0); + int ec = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + if (ec == 0) CHECK(1, "nptl (a) main setreuid(2k,2k) → pthread getuid 见 2345 (NPTL ✓)"); + else if (ec == 20) printf(" KNOWN-STARRY-LIMITATION | nptl (a) starry pthread 仍见 0\n"); + else CHECK(0, "nptl (a) failed"); +} + +/* (b) pthread setregid → main sees new gid */ +static void nptl_setregid_pthread_to_main(void) +{ + /* 测什么: pthread 调 setregid 同步到 main thread. + * 怎么测: root fork → child 起 pthread setregid(1234, 5678) → join → main 验. + * setregid(r, e) 后 rgid=1234, egid=5678. + * 期望: NPTL ✓: main getgid==1234 + main getegid==5678; starry: 0/0. */ + if (getuid() != 0) { printf(" nptl (b) skip\n"); return; } + pid_t pid = fork(); + if (pid == 0) { + atomic_store(&observed_gid, -1); + pthread_t t; + if (pthread_create(&t, NULL, pthread_setregid_caller, NULL) != 0) _exit(99); + pthread_join(t, NULL); + if (atomic_load(&observed_gid) != 1) _exit(98); + gid_t rg = getgid(); + gid_t eg = getegid(); + if (rg == 1234 && eg == 5678) _exit(0); + if (rg == 0 && eg == 0) _exit(20); + printf(" main: rg=%u eg=%u (expected 1234, 5678)\n", rg, eg); + _exit(1); + } + int status; + waitpid(pid, &status, 0); + int ec = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + if (ec == 0) CHECK(1, "nptl (b) pthread setregid(1234, 5678) → main getgid 见 5678"); + else if (ec == 20) printf(" KNOWN-STARRY-LIMITATION | nptl (b) starry pthread setregid 不传播\n"); + else CHECK(0, "nptl (b) failed"); +} + +static void *pthread_setreuid_3k(void *arg) +{ + (void)arg; + setreuid(3000, 3000); + return NULL; +} + +/* (c) 2 pthread 并发 setreuid 收敛 */ +static void nptl_concurrent_setreuid(void) +{ + /* 测什么: 并发 setreuid 后全进程 cred 单一态. + * 怎么测: root fork → child 起 2 pthread (都 setreuid(3000)) → join → 验. + * 期望: getuid()==3000 (无 race / 部分态). */ + if (getuid() != 0) { printf(" nptl (c) skip\n"); return; } + pid_t pid = fork(); + if (pid == 0) { + pthread_t t1, t2; + if (pthread_create(&t1, NULL, pthread_setreuid_3k, NULL) != 0) _exit(99); + if (pthread_create(&t2, NULL, pthread_setreuid_3k, NULL) != 0) _exit(98); + pthread_join(t1, NULL); + pthread_join(t2, NULL); + if (getuid() == 3000) _exit(0); + if (getuid() == 0) _exit(20); + _exit(1); + } + int status; + waitpid(pid, &status, 0); + int ec = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + if (ec == 0) CHECK(1, "nptl (c) 2 pthread setreuid(3000) join → main 见 3000"); + else if (ec == 20) printf(" KNOWN-STARRY-LIMITATION | nptl (c) starry concurrent setreuid 不传播\n"); + else CHECK(0, "nptl (c) failed"); +} + +int nptl_sync_run(void) +{ + printf("\n----- nptl_sync (man V1) -----\n"); + nptl_setreuid_main_to_pthread(); + nptl_setregid_pthread_to_main(); + nptl_concurrent_setreuid(); + printf(" ----- nptl_sync: %d pass, %d fail -----\n", __pass, __fail); + return __fail; +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/procfs_visibility.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/procfs_visibility.c new file mode 100644 index 0000000000..aaa3db3e57 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/procfs_visibility.c @@ -0,0 +1,190 @@ +/* procfs_visibility.c — setreuid/setregid 后 /proc/self/status 同步反映. + * + * man proc(5) §/proc/[pid]/status: + * Uid:\t\t\t\t + * + * man 2 setreuid §"saved set-user-ID auto-update": + * "If the real user ID is set (i.e., ruid is not -1) or the effective user + * ID is set to a value not equal to the previous real user ID, the saved + * set-user-ID will be set to the new effective user ID." + * + * 5 维度覆盖 (a-e): + * (a) setreuid(1000, 2000) — ruid set → suid 应跟 new euid = 2000 + * (b) setreuid(-1, 3000) NOCHG ruid, euid != old r → suid 跟 new euid + * (c) setreuid(-1, 0) NOCHG ruid, euid == old r → suid NOT updated (keep old) + * (d) setregid 镜像 (a) + * (e) fsuid follow: setreuid 后 procfs Uid 第 4 字段 (fs) == new euid + */ + +#define _GNU_SOURCE +#include "test_framework.h" + +#include +#include +#include +#include +#include +#include +#include + +static int waitpid_safely_pv(pid_t pid, int *status) +{ + pid_t r = waitpid(pid, status, 0); + return r == pid ? 0 : -1; +} + +static int parse_proc_id_line(const char *prefix, + uint32_t *r, uint32_t *e, uint32_t *s, uint32_t *fs) +{ + FILE *f = fopen("/proc/self/status", "r"); + if (!f) return -1; + char line[256]; + int found = -1; + while (fgets(line, sizeof line, f)) { + if (strncmp(line, prefix, strlen(prefix)) == 0) { + if (sscanf(line + strlen(prefix), "%u %u %u %u", r, e, s, fs) == 4) + found = 0; + break; + } + } + fclose(f); + return found; +} + +/* (a) setreuid(1000, 2000) ruid set → suid = new e = 2000 */ +static void proc_uid_after_setreuid_full(void) +{ + /* 测什么: man setreuid §saved rule — ruid 被设 (!= -1) → suid = new e. + * 怎么测: fork → child setreuid(1000, 2000) → 读 Uid 行. + * 期望: r=1000, e=2000, s=2000 (跟 new e), fs=2000. + * 为什么: 验 starry saved-set-uid 规则 (RE-D5) 在 procfs 正确反映. */ + if (getuid() != 0) { printf(" procfs (a) skip\n"); return; } + pid_t pid = fork(); + if (pid == 0) { + if (setreuid(1000, 2000) != 0) _exit(99); + uint32_t r, e, s, fs; + if (parse_proc_id_line("Uid:", &r, &e, &s, &fs) != 0) _exit(98); + if (r != 1000 || e != 2000 || s != 2000 || fs != 2000) { + printf(" got %u %u %u %u\n", r, e, s, fs); + _exit(1); + } + _exit(0); + } + int status; + waitpid_safely_pv(pid, &status); + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "procfs (a) setreuid(1k,2k) → 1000 2000 2000 2000 (saved 跟 new e)"); +} + +/* (b) setreuid(-1, 3000) ruid NOCHG, euid != old r=0 → saved updates */ +static void proc_uid_after_setreuid_nochg_r(void) +{ + /* 测什么: NOCHG ruid 路径下若 new euid != prev real → saved 仍跟 new euid. + * 怎么测: fork → child setreuid(-1, 3000) (old r=0, new e=3000 != 0). + * 期望: r=0, e=3000, s=3000, fs=3000. + * 为什么: 验 RE-D5 第二条件 (euid != prev r). */ + if (getuid() != 0) { printf(" procfs (b) skip\n"); return; } + pid_t pid = fork(); + if (pid == 0) { + if (setreuid((uid_t)-1, 3000) != 0) _exit(99); + uint32_t r, e, s, fs; + if (parse_proc_id_line("Uid:", &r, &e, &s, &fs) != 0) _exit(98); + if (r != 0 || e != 3000 || s != 3000 || fs != 3000) { + printf(" got %u %u %u %u\n", r, e, s, fs); + _exit(1); + } + _exit(0); + } + int status; + waitpid_safely_pv(pid, &status); + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "procfs (b) setreuid(-1,3k) (e!=prev_r) → 0 3000 3000 3000"); +} + +/* (c) setreuid(-1, 0) NOCHG ruid, euid == old r=0 → saved NOT updated */ +static void proc_uid_after_setreuid_nochg_same(void) +{ + /* 测什么: NOCHG ruid + new euid == prev real → saved 不更 (RE-D5 反向). + * 怎么测: fork → child setreuid(-1, 0) (old r=0, new e=0 == prev r). + * 期望: r=0, e=0, s=0 (启动时 0), fs=0. + * 注: 这个 case 在 baseline 状态下 saved 就是 0, "不更" 和"更" 结果相同. + * 要真区分需要先 setreuid 改 s, 再这样调. + * 为什么: 验 RE-D5 反向 case (条件都不满足 → saved 不更). */ + if (getuid() != 0) { printf(" procfs (c) skip\n"); return; } + pid_t pid = fork(); + if (pid == 0) { + /* 先 setresuid(0, 0, 5000) — saved 设为 5000. + * 然后 setreuid(-1, 0) — 不应更 saved (仍 5000). */ + if (setresuid(0, 0, 5000) != 0) _exit(99); + if (setreuid((uid_t)-1, 0) != 0) _exit(98); + uint32_t r, e, s, fs; + if (parse_proc_id_line("Uid:", &r, &e, &s, &fs) != 0) _exit(97); + if (r != 0 || e != 0 || s != 5000 || fs != 0) { + printf(" got %u %u %u %u\n", r, e, s, fs); + _exit(1); + } + _exit(0); + } + int status; + waitpid_safely_pv(pid, &status); + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "procfs (c) setresuid(0,0,5k)+setreuid(-1,0) → s 仍 5000 (NOT updated)"); +} + +/* (d) setregid(1000, 2000) 镜像 (a) */ +static void proc_gid_after_setregid_full(void) +{ + /* 测什么: 镜像 (a) — RE-D5 saved rule 对 gid 同样成立. + * 怎么测: fork → child setregid(1000, 2000) → 读 Gid 行. + * 期望: r=1000, e=2000, s=2000, fs=2000. */ + if (getuid() != 0) { printf(" procfs (d) skip\n"); return; } + pid_t pid = fork(); + if (pid == 0) { + if (setregid(1000, 2000) != 0) _exit(99); + uint32_t r, e, s, fs; + if (parse_proc_id_line("Gid:", &r, &e, &s, &fs) != 0) _exit(98); + if (r != 1000 || e != 2000 || s != 2000 || fs != 2000) _exit(1); + _exit(0); + } + int status; + waitpid_safely_pv(pid, &status); + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "procfs (d) setregid(1k,2k) → 1000 2000 2000 2000"); +} + +/* (e) 复合: setregid + setreuid — uid/gid 独立 */ +static void proc_compound_setre(void) +{ + /* 测什么: setreuid 不串扰 gid, setregid 不串扰 uid. + * 怎么测: fork → child setregid + setreuid → 同时验. + * 期望: 两行精确. */ + if (getuid() != 0) { printf(" procfs (e) skip\n"); return; } + pid_t pid = fork(); + if (pid == 0) { + if (setregid(100, 200) != 0) _exit(99); + if (setreuid(300, 400) != 0) _exit(98); + uint32_t ur, ue, us, ufs; + uint32_t gr, ge, gs, gfs; + if (parse_proc_id_line("Uid:", &ur, &ue, &us, &ufs) != 0) _exit(97); + if (parse_proc_id_line("Gid:", &gr, &ge, &gs, &gfs) != 0) _exit(96); + if (ur != 300 || ue != 400 || us != 400 || ufs != 400) _exit(1); + if (gr != 100 || ge != 200 || gs != 200 || gfs != 200) _exit(2); + _exit(0); + } + int status; + waitpid_safely_pv(pid, &status); + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "procfs (e) compound setregid + setreuid (uid/gid 独立)"); +} + +int procfs_visibility_run(void) +{ + printf("\n----- procfs_visibility -----\n"); + proc_uid_after_setreuid_full(); + proc_uid_after_setreuid_nochg_r(); + proc_uid_after_setreuid_nochg_same(); + proc_gid_after_setregid_full(); + proc_compound_setre(); + printf(" ----- procfs_visibility: %d pass, %d fail -----\n", __pass, __fail); + return __fail; +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/saved_id_semantics.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/saved_id_semantics.c new file mode 100644 index 0000000000..38e27c2893 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/saved_id_semantics.c @@ -0,0 +1,224 @@ +#define _GNU_SOURCE +#include "test_framework.h" + +#include +#include +#include +#include +#include + +/* saved_id_semantics — setreuid/setregid 的 saved-set-id 自动更新规则。 + * + * man 2 setreuid §"...": + * "If the real user ID is set (i.e., ruid is not -1) or the effective user + * ID is set to a value not equal to the previous real user ID, the saved + * set-user-ID will be set to the new effective user ID." + * + * 三种情况导致 suid 被设为 new.euid(其他情况 suid 不变): + * (1) ruid 被设(ruid != -1) + * (2) euid 被设且 new.euid != old.ruid + * (3) (1) 和 (2) 同时 + * + * 否则 suid 保持不变。 + * + * 测试 root setreuid 后 suid 是否如规则更新(关键不变量): + * - root setreuid(2000, -1) — ruid 设;suid 应更新为 new.euid(=old.euid) + * - root setreuid(-1, 3000) — euid 设到非 old.ruid 的值;suid 应更新为 3000 + * - root setreuid(-1, current_euid) — euid 设到 == current_euid(== old.ruid 在 root 启动时);suid 不变 + * - root setreuid(0, 0) — ruid+euid 都设到 root;suid 应更新为 0 + */ + +static int waitpid_safely(pid_t pid, int *status) +{ + pid_t r = waitpid(pid, status, 0); + return r == pid ? 0 : -1; +} + +static void saved_id_ruid_set_updates_suid(void) +{ + if (getuid() != 0) { + printf(" saved_id (a) skip: requires root\n"); + return; + } + pid_t pid = fork(); + if (pid == 0) { + /* 启动 r=e=s=0 */ + /* setreuid(2000, -1):ruid 设;euid 不变 (=0);suid 应更新为 new.euid = 0 */ + if (setreuid(2000, (uid_t)-1) != 0) _exit(1); + uid_t r, e, s; + if (getresuid(&r, &e, &s) != 0) _exit(2); + /* r=2000 e=0 s=0(updated to new.euid=0;其实没变)*/ + if (r == 2000 && e == 0 && s == 0) _exit(0); + _exit(3); + } + if (pid > 0) { + int status; + if (waitpid_safely(pid, &status) == 0) { + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "saved_id (a) setreuid(2000, -1) by root → r=2000 e=0 s=0 (suid updated to new.euid)"); + } + } +} + +static void saved_id_euid_set_to_nonruid_updates_suid(void) +{ + if (getuid() != 0) { + printf(" saved_id (b) skip\n"); + return; + } + pid_t pid = fork(); + if (pid == 0) { + /* 启动 r=e=s=0 */ + /* setreuid(-1, 3000):euid 设到 3000 != old.ruid (=0);suid 应更新为 3000 */ + if (setreuid((uid_t)-1, 3000) != 0) _exit(1); + uid_t r, e, s; + if (getresuid(&r, &e, &s) != 0) _exit(2); + if (r == 0 && e == 3000 && s == 3000) _exit(0); + _exit(3); + } + if (pid > 0) { + int status; + if (waitpid_safely(pid, &status) == 0) { + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "saved_id (b) setreuid(-1, 3000) by root: r=0 e=3000 s=3000 (suid updated to euid)"); + } + } +} + +static void saved_id_both_set_root(void) +{ + if (getuid() != 0) { + printf(" saved_id (c) skip\n"); + return; + } + pid_t pid = fork(); + if (pid == 0) { + if (setreuid(0, 0) != 0) _exit(1); + uid_t r, e, s; + if (getresuid(&r, &e, &s) != 0) _exit(2); + if (r == 0 && e == 0 && s == 0) _exit(0); + _exit(3); + } + if (pid > 0) { + int status; + if (waitpid_safely(pid, &status) == 0) { + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "saved_id (c) setreuid(0, 0) by root: r=e=s=0"); + } + } +} + +static void saved_id_both_nochg_no_change(void) +{ + /* setreuid(-1, -1):什么都没设;suid 应保持原值 — 不被错误置 0 */ + uid_t r0, e0, s0; + getresuid(&r0, &e0, &s0); + if (setreuid((uid_t)-1, (uid_t)-1) != 0) { + CHECK(0, "saved_id (d) setreuid(-1, -1) failed"); + return; + } + uid_t r1, e1, s1; + getresuid(&r1, &e1, &s1); + CHECK(r0 == r1 && e0 == e1 && s0 == s1, + "saved_id (d) setreuid(-1, -1): all 3 IDs unchanged (no spurious suid update)"); +} + +/* (e) D5 反向 corner: ruid=-1 AND new.euid == old.ruid → suid 不变. + * + * man D5 的完整规则: + * "If the real user ID is set (ruid != -1) OR the effective user ID is + * set to a value NOT equal to the previous real user ID, the saved + * set-user-ID will be set to the new effective user ID." + * + * 反向: 两个条件都不满足 → suid 不更新. + * 即: ruid == -1 (NOCHG) AND new.euid == old.ruid → suid 保持原值. + * + * 启动 r=e=s=0 时这个反向测不出 — 需要先 setresuid 让 r != s. + * 设: r=0, e=2000, s=2000 → setreuid(-1, 0): + * - ruid=-1 → 条件 A 不满足 + * - new.euid=0 == old.ruid=0 → 条件 B 不满足 + * → suid 应保持 2000 (不被更新到 new.euid=0). + * + * Linux 行为: r=0 e=0 s=2000 (suid 保留 2000) + * starry 行为: 若 D5 实现错误 (任意 setreuid 都改 suid), s 会变成 0. + */ +static void saved_id_d5_reverse_corner(void) +{ + if (getuid() != 0) { + printf(" saved_id (e) skip: requires root to seed r=0 e=2000 s=2000\n"); + return; + } + pid_t pid = fork(); + if (pid == 0) { + /* seed: r=0 e=2000 s=2000 (使 r != s 才能测 D5 反向) */ + if (setresuid(0, 2000, 2000) != 0) _exit(99); + uid_t r0, e0, s0; + if (getresuid(&r0, &e0, &s0) != 0) _exit(98); + if (r0 != 0 || e0 != 2000 || s0 != 2000) _exit(97); + + /* setreuid(-1, 0): ruid=NOCHG, new.euid=0 == old.ruid=0 → 两条件都不满足 + * 此处必须 unpriv 路径 (euid=2000 时无 CAP_SETUID), euid 0 在 {r=0,s=2000} 集合内 OK */ + if (setreuid((uid_t)-1, 0) != 0) _exit(96); + + uid_t r1, e1, s1; + if (getresuid(&r1, &e1, &s1) != 0) _exit(95); + /* 期望: r=0 (不变), e=0 (设了), s=2000 (D5 不触发, 保留) */ + if (r1 == 0 && e1 == 0 && s1 == 2000) _exit(0); /* Linux 行为 */ + if (s1 == 0) _exit(20); /* starry bug: 误改 suid → 0 */ + _exit(1); + } + int status; + if (waitpid_safely(pid, &status) == 0) { + int ec = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + if (ec == 0) CHECK(1, "saved_id (e) D5 反向: setreuid(-1, old_ruid) 不改 suid (Linux 行为 r=0 e=0 s=2000)"); + else if (ec == 20) CHECK(0, "saved_id (e) FAIL: starry 误更新 suid (D5 反向条件未实现)"); + else CHECK(0, "saved_id (e) failed (ec != 0/20)"); + } +} + +/* (f) D5 反向 GID 维度: setregid(-1, old_rgid) → sgid 不变. + * 镜像 (e) 但 GID 维度, 验 sys_setregid 同样实现 D5 反向. */ +static void saved_id_d5_reverse_corner_gid(void) +{ + if (getuid() != 0) { + printf(" saved_id (f) skip: requires root\n"); + return; + } + pid_t pid = fork(); + if (pid == 0) { + /* seed: rgid=0, egid=2000, sgid=2000 */ + if (setresgid(0, 2000, 2000) != 0) _exit(99); + gid_t r0, e0, s0; + if (getresgid(&r0, &e0, &s0) != 0) _exit(98); + if (r0 != 0 || e0 != 2000 || s0 != 2000) _exit(97); + + /* setregid(-1, 0): egid=0 == old.rgid=0 → sgid 应保留 2000 */ + if (setregid((gid_t)-1, 0) != 0) _exit(96); + + gid_t r1, e1, s1; + if (getresgid(&r1, &e1, &s1) != 0) _exit(95); + if (r1 == 0 && e1 == 0 && s1 == 2000) _exit(0); + if (s1 == 0) _exit(20); + _exit(1); + } + int status; + if (waitpid_safely(pid, &status) == 0) { + int ec = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + if (ec == 0) CHECK(1, "saved_id (f) D5 反向 GID: setregid(-1, old_rgid) 不改 sgid"); + else if (ec == 20) CHECK(0, "saved_id (f) FAIL: starry 误更新 sgid (D5 反向 GID 未实现)"); + else CHECK(0, "saved_id (f) failed"); + } +} + +int saved_id_semantics_run(void) +{ + printf("\n----- saved_id_semantics -----\n"); + saved_id_ruid_set_updates_suid(); + saved_id_euid_set_to_nonruid_updates_suid(); + saved_id_both_set_root(); + saved_id_both_nochg_no_change(); + saved_id_d5_reverse_corner(); /* (e) Round 7-C — D5 反向 UID */ + saved_id_d5_reverse_corner_gid(); /* (f) Round 7-C — D5 反向 GID */ + printf(" ----- saved_id_semantics: %d pass, %d fail -----\n", __pass, __fail); + return __fail; +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/setregid.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/setregid.c new file mode 100644 index 0000000000..5acfd3a1f2 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/setregid.c @@ -0,0 +1,173 @@ +#define _GNU_SOURCE +#include "test_framework.h" + +#include +#include +#include +#include +#include +#include +#include + +/* setregid(2) — set real and/or effective group ID. Analogous to setreuid. + * + * man 2 setreuid: + * "setregid() does the same for the group IDs." + * + * 测试与 setreuid 镜像(gid_t 替代), 含 (g) supp groups invariant. + */ + +static int waitpid_safely(pid_t pid, int *status) +{ + pid_t r = waitpid(pid, status, 0); + return r == pid ? 0 : -1; +} + +static void setregid_both_nochg(void) +{ + gid_t r0, e0, s0; + getresgid(&r0, &e0, &s0); + int rc = setregid((gid_t)-1, (gid_t)-1); + CHECK(rc == 0, "setregid (a) (-1, -1) -> 0"); + gid_t r1, e1, s1; + getresgid(&r1, &e1, &s1); + CHECK(r0 == r1 && e0 == e1 && s0 == s1, "setregid (a) leaves cred unchanged"); +} + +static void setregid_rgid_self(void) +{ + /* 测什么: man §DESCRIPTION 镜像 setreuid — 部分 NOCHG (rgid=self, egid=-1). + * 怎么测: setregid(getgid(), -1), 验 rc=0 + rgid 不变. + * 期望: rc=0, rgid 仍 == self. + * 为什么: 验证 starry NOCHG 在 egid 槽 + rgid set-to-self 不 EPERM. */ + gid_t g = getgid(); + int rc = setregid(g, (gid_t)-1); + CHECK(rc == 0, "setregid (b) (gid, -1) -> 0"); + CHECK(getgid() == g, "setregid (b) rgid still == self"); +} + +static void setregid_egid_self(void) +{ + /* 测什么: man §DESCRIPTION 镜像 — 部分 NOCHG (rgid=-1, egid=self). + * 怎么测: setregid(-1, getegid()), 验 rc=0 + egid 不变. + * 期望: rc=0, egid 不变. + * 为什么: 镜像 (b) — NOCHG 在 rgid 槽 + egid set-to-self 不 EPERM. */ + gid_t e = getegid(); + int rc = setregid((gid_t)-1, e); + CHECK(rc == 0, "setregid (c) (-1, egid) -> 0"); + CHECK(getegid() == e, "setregid (c) egid still == self"); +} + +static void setregid_root_arbitrary(void) +{ + /* 测什么: man §DESCRIPTION — root (CAP_SETGID) 可设任意 rgid/egid. + * 怎么测: root fork → child setregid(2000, 3000) → 验 r=2000, e=3000. + * 期望: rc=0, r=2000, e=3000. + * 为什么: 验证 starry has_cap_setgid 路径下两参数都被 set. */ + if (getuid() != 0) { + printf(" setregid (d) skip: not root\n"); + return; + } + pid_t pid = fork(); + if (pid == 0) { + if (setregid(2000, 3000) != 0) _exit(1); + gid_t r, e, s; + if (getresgid(&r, &e, &s) != 0) _exit(2); + if (r == 2000 && e == 3000) _exit(0); + _exit(3); + } + if (pid > 0) { + int status; + if (waitpid_safely(pid, &status) == 0) { + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "setregid (d) root setregid(2000, 3000) → r=2000, e=3000"); + } + } +} + +static void setregid_unpriv_eperm(void) +{ + /* 测什么: man §EPERM — unpriv + rgid 不在 {real, effective} → EPERM. + * 怎么测: root fork → child setresgid + setresuid 切到非 root 状态 (cred=1000) + * → setregid(2000, -1) 因 2000 不在 {1000} → EPERM. + * 注意 setresgid 必须先, 否则 caps drop 后无法设 gid. + * 期望: rc=-1 errno=EPERM. + * 为什么: 验证 starry !has_cap_setgid 路径下 rgid in {r, e} 检查. */ + if (getuid() != 0) { + printf(" setregid (e) skip: requires root\n"); + return; + } + pid_t pid = fork(); + if (pid == 0) { + if (setresgid(1000, 1000, 1000) != 0) _exit(99); + if (setresuid(1000, 1000, 1000) != 0) _exit(98); + errno = 0; + int rc = setregid(2000, (gid_t)-1); + if (rc == -1 && errno == EPERM) _exit(0); + _exit(1); + } + if (pid > 0) { + int status; + if (waitpid_safely(pid, &status) == 0) { + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "setregid (e) unpriv setregid(2000, -1) → -1 EPERM"); + } + } +} + +static void setregid_raw_matches_libc(void) +{ + /* 测什么: libc 应直接转发 syscall (镜像 setreuid). + * 怎么测: raw syscall(SYS_setregid, getgid(), getegid()). + * 期望: rc=0. + * 为什么: 验证 starry sys_setregid ABI 与 libc 包装契约一致. */ + long rc = syscall(SYS_setregid, getgid(), getegid()); + CHECK(rc == 0, "setregid (f) raw syscall(self, self) -> 0"); +} + +static void setregid_keeps_supp_groups(void) +{ + /* 测什么: Linux kernel `sys_setregid` 只改 cred.gid/egid/sgid, + * 不动 cred.group_info. setregid 与 setgid/setresgid 一致: + * supplementary groups 不变. 仅 setgroups/initgroups 改 supp. + * 怎么测: root fork → child setgroups([7,8,9]) → setregid(2000, 2000) + * drop → getgroups 应仍 = [7,8,9]. + * 期望: getgroups 返 3 个, 值 [7,8,9]. + * 为什么: Linux invariant 镜像 setreuid (g). starry 应同款不动 supp groups. */ + if (getuid() != 0) { + printf(" setregid (g) skip: requires root\n"); + return; + } + pid_t pid = fork(); + if (pid == 0) { + gid_t pre[3] = {7, 8, 9}; + if (setgroups(3, pre) != 0) _exit(98); + if (setregid(2000, 2000) != 0) _exit(99); + gid_t got[16]; + int n = getgroups(16, got); + if (n != 3) _exit(100 + (n < 0 ? 0 : n)); + if (got[0] != 7 || got[1] != 8 || got[2] != 9) _exit(120); + _exit(0); + } + if (pid > 0) { + int status; + if (waitpid_safely(pid, &status) == 0) { + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "setregid (g) supp groups preserved after setregid(2000, 2000)"); + } + } +} + +int setregid_run(void) +{ + printf("\n----- setregid -----\n"); + setregid_both_nochg(); + setregid_rgid_self(); + setregid_egid_self(); + setregid_root_arbitrary(); + setregid_unpriv_eperm(); + setregid_raw_matches_libc(); + setregid_keeps_supp_groups(); + printf(" ----- setregid: %d pass, %d fail -----\n", __pass, __fail); + return __fail; +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/setreuid.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/setreuid.c new file mode 100644 index 0000000000..1bedc3d2c4 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/setreuid.c @@ -0,0 +1,191 @@ +#define _GNU_SOURCE +#include "test_framework.h" + +#include +#include +#include +#include +#include +#include +#include + +/* setreuid(2) — set real and/or effective user ID. + * + * man 2 setreuid: + * "setreuid() sets real and effective user IDs of the calling process." + * "Supplying a value of -1 for either the real or effective user ID forces + * the system to leave that ID unchanged." + * "Unprivileged processes may only set the effective user ID to the real + * user ID, the effective user ID, or the saved set-user-ID." + * "Unprivileged users may only set the real user ID to the real user ID + * or the effective user ID." + * + * 测试覆盖: + * (a) setreuid(-1, -1) — 都不变(NOCHG sentinel) + * (b) setreuid(getuid(), -1) — ruid 设回自己 + * (c) setreuid(-1, geteuid()) — euid 设回自己 + * (d) root setreuid(任意值) → 任意成功 + * (e) unpriv setreuid(任意, 任意) → EPERM 当不在允许集合 + * (f) raw vs libc + * (g) Linux invariant: setreuid 不动 supplementary groups (host gcc 实测验证) + */ + +static int waitpid_safely(pid_t pid, int *status) +{ + pid_t r = waitpid(pid, status, 0); + return r == pid ? 0 : -1; +} + +static void setreuid_both_nochg_idempotent(void) +{ + /* 怎么测:setreuid(-1, -1) — 全 NOCHG sentinel + * 期望:返回 0,cred 不变 + * 为什么:man "Supplying a value of -1 for either ... forces the system + * to leave that ID unchanged" */ + uid_t r0, e0, s0; + getresuid(&r0, &e0, &s0); + int rc = setreuid((uid_t)-1, (uid_t)-1); + CHECK(rc == 0, "setreuid (a) (-1, -1) -> 0"); + uid_t r1, e1, s1; + getresuid(&r1, &e1, &s1); + CHECK(r0 == r1 && e0 == e1 && s0 == s1, "setreuid (a) (-1, -1) leaves cred unchanged"); +} + +static void setreuid_ruid_self(void) +{ + /* 测什么: man §DESCRIPTION — 部分 NOCHG (ruid=self, euid=-1) 应成功且不动 e. + * 怎么测: setreuid(getuid(), -1), 验 rc=0 + ruid 仍 == self. + * 期望: rc=0, ruid 不变. + * 为什么: 验证 starry NOCHG 在 euid 槽生效 (即 sentinel 路径 + + * ruid set-to-self 不触发 EPERM). */ + uid_t u = getuid(); + int rc = setreuid(u, (uid_t)-1); + CHECK(rc == 0, "setreuid (b) (uid, -1) -> 0"); + CHECK(getuid() == u, "setreuid (b) ruid still == self"); +} + +static void setreuid_euid_self(void) +{ + /* 测什么: man §DESCRIPTION — 部分 NOCHG (ruid=-1, euid=self) 应成功. + * 怎么测: setreuid(-1, geteuid()), 验 rc=0 + euid 仍 == self. + * 期望: rc=0, euid 不变. + * 为什么: 镜像 (b) — 验 NOCHG 在 ruid 槽 + euid set-to-self 不触发 EPERM. */ + uid_t e = geteuid(); + int rc = setreuid((uid_t)-1, e); + CHECK(rc == 0, "setreuid (c) (-1, euid) -> 0"); + CHECK(geteuid() == e, "setreuid (c) euid still == self"); +} + +static void setreuid_root_arbitrary(void) +{ + /* 测什么: man §DESCRIPTION — root (CAP_SETUID) 可设任意 ruid/euid. + * 怎么测: root fork → child setreuid(2000, 3000) → 验 r=2000, e=3000. + * 期望: rc=0, r=2000, e=3000. + * 为什么: 验证 starry has_cap_setuid 路径下两参数都被 set. */ + if (getuid() != 0) { + printf(" setreuid (d) skip: not root\n"); + return; + } + pid_t pid = fork(); + if (pid == 0) { + if (setreuid(2000, 3000) != 0) _exit(1); + uid_t r, e, s; + if (getresuid(&r, &e, &s) != 0) _exit(2); + if (r == 2000 && e == 3000) _exit(0); + _exit(3); + } + if (pid > 0) { + int status; + if (waitpid_safely(pid, &status) == 0) { + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "setreuid (d) root setreuid(2000, 3000) → r=2000, e=3000"); + } + } +} + +static void setreuid_unpriv_eperm(void) +{ + /* 测什么: man §EPERM — unpriv + ruid 不在 {real, effective} 触发 EPERM. + * 怎么测: root fork → child setresuid(1000,1000,1000) → setreuid(2000, -1). + * 2000 不在 {1000} 集合内 → EPERM. + * 期望: rc=-1 errno=EPERM. + * 为什么: 验证 starry !has_cap 路径下 ruid 的 in_set {r, e} 检查 + * (注: ruid 仅 {r, e}, 不含 s — 与 euid 的 {r, e, s} 不同). */ + if (getuid() != 0) { + printf(" setreuid (e) skip: requires root\n"); + return; + } + pid_t pid = fork(); + if (pid == 0) { + if (setresuid(1000, 1000, 1000) != 0) _exit(99); + errno = 0; + int rc = setreuid(2000, (uid_t)-1); + if (rc == -1 && errno == EPERM) _exit(0); + _exit(1); + } + if (pid > 0) { + int status; + if (waitpid_safely(pid, &status) == 0) { + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "setreuid (e) unpriv setreuid(2000, -1) → -1 EPERM"); + } + } +} + +static void setreuid_raw_matches_libc(void) +{ + /* 测什么: man §HISTORY — libc 透明处理 setreuid32 差异. + * 怎么测: raw syscall(SYS_setreuid, getuid(), geteuid()) 应同 libc 路径成功. + * 期望: rc=0. + * 为什么: 验证 starry sys_setreuid ABI 与 libc 包装契约一致. */ + long rc = syscall(SYS_setreuid, getuid(), geteuid()); + CHECK(rc == 0, "setreuid (f) raw syscall(self, self) -> 0"); +} + +static void setreuid_keeps_supp_groups(void) +{ + /* 测什么: Linux kernel `sys_setreuid` (kernel/sys.c) 只改 cred.uid/euid/suid, + * 不动 cred.group_info. setreuid 与 setuid/setresuid 一致: + * supplementary groups 不变. 仅 setgroups/initgroups 改 supp. + * 怎么测: root fork → child setgroups([7,8,9]) → setreuid(1000,1000) + * drop → getgroups 应仍 = [7,8,9]. + * 期望: getgroups 返 3 个, 值 [7,8,9]. + * 为什么: Linux invariant — man 未显式但 kernel 行为如此 (host gcc 实测 PASS). + * starry 应同款不动 supp groups, 否则 setreuid 会污染 supp 列表. */ + if (getuid() != 0) { + printf(" setreuid (g) skip: requires root\n"); + return; + } + pid_t pid = fork(); + if (pid == 0) { + gid_t pre[3] = {7, 8, 9}; + if (setgroups(3, pre) != 0) _exit(98); + if (setreuid(1000, 1000) != 0) _exit(99); + gid_t got[16]; + int n = getgroups(16, got); + if (n != 3) _exit(100 + (n < 0 ? 0 : n)); + if (got[0] != 7 || got[1] != 8 || got[2] != 9) _exit(120); + _exit(0); + } + if (pid > 0) { + int status; + if (waitpid_safely(pid, &status) == 0) { + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "setreuid (g) supp groups preserved after setreuid(1000, 1000)"); + } + } +} + +int setreuid_run(void) +{ + printf("\n----- setreuid -----\n"); + setreuid_both_nochg_idempotent(); + setreuid_ruid_self(); + setreuid_euid_self(); + setreuid_root_arbitrary(); + setreuid_unpriv_eperm(); + setreuid_raw_matches_libc(); + setreuid_keeps_supp_groups(); + printf(" ----- setreuid: %d pass, %d fail -----\n", __pass, __fail); + return __fail; +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/test_framework.h b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/test_framework.h new file mode 100644 index 0000000000..7d914ceaba --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-uid-gid-re-setters/c/src/test_framework.h @@ -0,0 +1,100 @@ +#pragma once + +/* 必须在最前面定义,确保 pipe2/gettid 等可用 */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +/* + * StarryOS Syscall Test Framework + * + * 极简独立测试框架:每个文件测一个 syscall,独立编译运行。 + * 目标:出错时精确定位到 源文件:行号 -> 哪个调用 -> 什么结果 + * + * 用法: + * TEST_START("测试名"); + * CHECK(call == expected, "描述"); + * CHECK_ERR(call, EBADF, "描述"); + * TEST_DONE(); + */ + +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +/* ---- 核心: 带文件名+行号的检查宏 ---- */ + +/* 检查条件为真 */ +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while(0) + +/* 检查 syscall 返回特定值 */ +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +/* 检查 syscall 失败且 errno 符合预期 */ +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +/* 软断言 — 已知 starry 与 Linux 行为不一致(对应 bug-* 复现已存在)。 + * ok 真:PASS;ok 假:仅 KNOWN-STARRY-BUG 信息,不计 __fail。 + * 用于「test 分支主体绿 + bug-* 暴露问题」两层结构 — host 仍能通过 bug-* + * 复现验证;test 分支不阻塞 starry CI。 */ +#define CHECK_OR_BUG(ok, bug_name, msg) do { \ + if (ok) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" KNOWN-STARRY-BUG | %s:%d | %s | errno=%d (%s) | see %s\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno), bug_name); \ + /* don't increment __fail — bug-* covers the failing assertion */ \ + } \ +} while(0) + +/* ---- 测试边界 ---- */ +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0