From 8e1277032aaf3f8da6e89d4775bf9f0147a8f931 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 12 Sep 2026 21:12:55 +0200 Subject: [PATCH] test: extend coverage of streq --- user/grouptest.c | 127 +++++++++++++++++++++++++++++++++++++++++++++++ user/herestest.c | 53 ++++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 user/herestest.c diff --git a/user/grouptest.c b/user/grouptest.c index 46ea5fe..20558fe 100644 --- a/user/grouptest.c +++ b/user/grouptest.c @@ -151,3 +151,130 @@ int main(void) return 1; } + /* --- real setgid(2): root may set its own gid to anything. --- */ + if (userSetgid(7000) != 0) { + print("grouptest: FAIL root could not setgid(7000)\n"); + return 1; + } + if (userGetgid() != 7000) { + print("grouptest: FAIL gid did not actually change to 7000\n"); + return 1; + } + print("grouptest: setgid-root-arbitrary-ok\n"); + + /* --- real, permanent privilege drop: root becoming a plain uid, gid + already set above -- both credentials independent, matching real + Unix's separate uid/gid drop order. --- */ + if (userSetuid(6000) != 0) { + print("grouptest: FAIL root could not setuid(6000)\n"); + return 1; + } + if (userGetuid() != 6000 || userGetgid() != 7000) { + print("grouptest: FAIL identity after drop is not (6000,7000)\n"); + return 1; + } + print("grouptest: drop-to-6000-7000-ok\n"); + + /* --- no way back: setgid is a one-way trapdoor too, same shape as + setuid's own cannot-regain-root-ok. --- */ + if (userSetgid(9999) != -1) { + print("grouptest: FAIL non-root regained arbitrary gid via setgid\n"); + return 1; + } + if (userGetgid() != 7000) { + print("grouptest: FAIL gid changed despite rejected setgid\n"); + return 1; + } + print("grouptest: setgid-nonroot-denied-ok\n"); + + if (userSetgid(7000) != 0) { + print("grouptest: FAIL setgid to own current gid was rejected\n"); + return 1; + } + print("grouptest: setgid-self-noop-ok\n"); + + /* --- the actual point of this fala: a caller that is NOT the owner + (uid 6000 vs 9000) but DOES share the file's group (7000) is + governed by the group bits, not other. groupOnly has ONLY + group-read set, so read succeeds and write fails. --- */ + int rfd = (int)userOpen("/tmp/groupd/groupOnly", oReadOnly); + if (rfd < 0) { + print("grouptest: FAIL group-matching read denied\n"); + return 1; + } + userClose(rfd); + print("grouptest: group-read-allowed-ok\n"); + + if (userOpen("/tmp/groupd/groupOnly", oWriteOnly) >= 0) { + print("grouptest: FAIL group-write succeeded without the bit\n"); + return 1; + } + print("grouptest: group-write-denied-ok\n"); + + /* --- a caller matching NEITHER uid (6000 vs 9000) NOR gid (7000 vs + 8000) must fall through to other, which mismatch also leaves + clear. --- */ + if (userOpen("/tmp/groupd/mismatch", oReadOnly) >= 0) { + print("grouptest: FAIL non-member read succeeded via wrong branch\n"); + return 1; + } + print("grouptest: other-fallback-denied-ok\n"); + + /* --- the same group model gates real exec, not just open(). --- */ + long pid = userSpawn("/tmp/groupd/progOk", 0); + if (pid < 0) { + print("grouptest: FAIL group-exec spawn denied\n"); + return 1; + } + if (userWait(pid) != 1) { + print("grouptest: FAIL group-exec spawned copy did not exit 1\n"); + return 1; + } + print("grouptest: group-exec-allowed-ok\n"); + + if (userSpawn("/tmp/groupd/progDenied", 0) >= 0) { + print("grouptest: FAIL group-exec spawn succeeded across groups\n"); + return 1; + } + print("grouptest: group-exec-denied-ok\n"); + + /* --- real chgrp(2) privilege: the owner (this process really does own + "owned" now, uid 6000) may set the group to its OWN current gid + (7000) -- but not to anything else, even though it owns the + file. --- */ + if (userChgrp("/tmp/groupd/owned", 7000) != 0) { + print("grouptest: FAIL owner chgrp to own gid denied\n"); + return 1; + } + if (userStat("/tmp/groupd/owned", &st) != 0 || st.gid != 7000) { + print("grouptest: FAIL owned gid not actually reassigned\n"); + return 1; + } + print("grouptest: chgrp-owner-self-gid-ok\n"); + + if (userChgrp("/tmp/groupd/owned", 8000) != -1) { + print("grouptest: FAIL owner chgrp to arbitrary gid succeeded\n"); + return 1; + } + if (userStat("/tmp/groupd/owned", &st) != 0 || st.gid != 7000) { + print("grouptest: FAIL owned gid changed by denied chgrp\n"); + return 1; + } + print("grouptest: chgrp-owner-arbitrary-denied-ok\n"); + + /* --- a non-owner, non-root caller can never chgrp at all, even to its + own gid -- mismatch is owned by 9000, this process is neither owner + nor root. --- */ + if (userChgrp("/tmp/groupd/mismatch", 7000) != -1) { + print("grouptest: FAIL non-owner non-root chgrp succeeded\n"); + return 1; + } + if (userStat("/tmp/groupd/mismatch", &st) != 0 || st.gid != 8000) { + print("grouptest: FAIL mismatch gid changed by denied chgrp\n"); + return 1; + } + print("grouptest: chgrp-nonowner-nonroot-denied-ok\n"); + + print("grouptest: PASS\n"); + return 0; +} diff --git a/user/herestest.c b/user/herestest.c new file mode 100644 index 0000000..1fe69de --- /dev/null +++ b/user/herestest.c @@ -0,0 +1,53 @@ +#include "ulib.h" + +/* + * Live ring-3 proof for shell.c's new `cmd <<< TEXT` here-string syntax + * (jobctl.c's jobctlSpawnForegroundHereString/jobctlSpawnHereStringPiped): + * from this program's own point of view fd 0 is just an ordinary blocking + * pipe read end, exactly like pipesink.c's -- it has no idea the other end + * was ever the SHELL ITSELF writing literal typed text into a pipe rather + * than another spawned process's stdout. Reads to real EOF, then compares + * byte-for-byte against the exact text kmain.c's hereDriverThread types + * after `<<<` plus the single trailing newline jobctlSpawnHereStringPiped + * always appends (matching a real shell's own `<<<` behavior of feeding + * the text as one full line, terminator included). + */ +#define hereStringPayload "hs-ok\n" + +static int streq(const char* a, const char* b) +{ + while (*a && *a == *b) { + a++; + b++; + } + return *a == *b; +} + +int main(void) +{ + print("herestest: reading redirected stdin\n"); + + char buffer[32]; + unsigned long total = 0; + for (;;) { + long got = userRead(0, buffer + total, sizeof(buffer) - 1 - total); + if (got < 0) { + print("herestest: FAIL read\n"); + return 1; + } + if (got == 0) + break; + total += (unsigned long)got; + if (total >= sizeof(buffer) - 1) + break; + } + buffer[total] = 0; + + print("herestest: got: "); + print(buffer); + + int ok = streq(buffer, hereStringPayload); + print(ok ? "herestest: payload-ok\n" : "herestest: payload-FAIL\n"); + + return ok ? 0 : 1; +}