#include "ulib.h" #include "fs.h" /* * Real ring-3 proof of chmod(2) + real owner rwx permission bits (fs.h's * fsModeOwner*), enforced at the two real choke points this fala added: * resolveOpen (vfs.c) refuses to open an existing file for writing without * fsModeOwnerWrite, and jobctlSpawn/loadImageByPath (jobctl.c/syscall.c) * refuse to spawn a file without fsModeOwnerExec. Fully synchronous like * rmtest.c/timetest.c/nicetest.c: no injected keystrokes, no shared shell * channel, so there is no scheduling-timing race to flake on regardless of * host load. */ static int copyFile(const char* srcPath, const char* dstPath) { int src = (int)userOpen(srcPath, oReadOnly); if (src < 0) return -1; int dst = (int)userOpen(dstPath, oReadWrite | oCreate | oTrunc); if (dst < 0) { userClose(src); return -1; } char buf[512]; long got; while ((got = userRead(src, buf, sizeof(buf))) > 0) { if (userWrite(dst, buf, (unsigned long)got) != got) { userClose(src); userClose(dst); return -1; } } userClose(src); userClose(dst); return got < 0 ? -1 : 0; } int main(void) { print("permtest: start, pid="); printDec(userGetpid()); print("\n"); /* --- write permission: a fresh tmpfs file starts at fsModeDefaultFile (0644, owner-writable); chmod to 0444 makes a SUBSEQUENT open-for- write fail while reads keep working, and chmod back restores write access immediately -- real Unix only checks permission at open() time, never re-checks an fd already held. --- */ if (userMkdir("/tmp/permd") != 0) { print("permtest: FAIL mkdir /tmp/permd\n"); return 1; } int fd = (int)userOpen("/tmp/permd/data.txt", oReadWrite | oCreate); if (fd < 0) { print("permtest: FAIL create data.txt\n"); return 1; } if (userWrite(fd, "hi", 2) != 2) { print("permtest: FAIL initial write\n"); return 1; } userClose(fd); FbStat st; if (userStat("/tmp/permd/data.txt", &st) != 0 || st.mode != 0644) { print("permtest: FAIL default mode not 0644\n"); return 1; } print("permtest: default-mode-ok\n"); if (userChmod("/tmp/permd/data.txt", 0444) != 0) { print("permtest: FAIL chmod to read-only\n"); return 1; } if (userStat("/tmp/permd/data.txt", &st) != 0 || st.mode != 0444) { print("permtest: FAIL stat after chmod 0444\n"); return 1; } if (userOpen("/tmp/permd/data.txt", oWriteOnly) >= 0) { print("permtest: FAIL write-only open succeeded on a read-only file\n"); return 1; } int rfd = (int)userOpen("/tmp/permd/data.txt", oReadOnly); if (rfd < 0) { print("permtest: FAIL read-only open failed on a readable file\n"); return 1; } char rdbuf[4]; if (userRead(rfd, rdbuf, 2) != 2) { print("permtest: FAIL read from read-only file\n"); return 1; } userClose(rfd); print("permtest: write-denied-ok\n"); if (userChmod("/tmp/permd/data.txt", 0644) != 0) { print("permtest: FAIL chmod back to 0644\n"); return 1; } int wfd = (int)userOpen("/tmp/permd/data.txt", oWriteOnly | oAppend); if (wfd < 0) { print("permtest: FAIL write open failed after chmod restored write bit\n"); return 1; } if (userWrite(wfd, "!", 1) != 1) { print("permtest: FAIL write after chmod restored\n"); return 1; } userClose(wfd); print("permtest: write-restored-ok\n"); /* --- exec permission: copying an existing binary's bytes into a NEW tmpfs file (no fork/exec, just plain read+write, like `cp` would) never carries the exec bit over -- the copy starts at the same fsModeDefaultFile every other fresh file gets, exactly like a real shell script you just wrote needs `chmod +x` before `./it` works. Reuses /bin/false (always exits 1) as the payload -- cheap, already built, and its own fixed exit code doubles as proof the COPY, not just some cached original, is what actually ran. --- */ if (copyFile("/bin/false", "/tmp/permd/prog") != 0) { print("permtest: FAIL copy /bin/false to prog\n"); return 1; } if (userStat("/tmp/permd/prog", &st) != 0 || (st.mode & 0100) != 0) { print("permtest: FAIL fresh copy already executable\n"); return 1; } print("permtest: copy-not-executable-ok\n"); if (userSpawn("/tmp/permd/prog", 0) >= 0) { print("permtest: FAIL spawn succeeded without exec bit\n"); return 1; } print("permtest: exec-denied-ok\n"); if (userChmod("/tmp/permd/prog", 0755) != 0) { print("permtest: FAIL chmod +x\n"); return 1; } long pid = userSpawn("/tmp/permd/prog", 0); if (pid < 0) { print("permtest: FAIL spawn after chmod +x\n"); return 1; } long status = userWait(pid); if (status != 1) { print("permtest: FAIL spawned copy did not exit 1\n"); return 1; } print("permtest: exec-after-chmod-ok\n"); print("permtest: PASS\n"); return 0; }