diff --git a/src/coreTest.c b/src/coreTest.c index 1e70ed2..04cd652 100644 --- a/src/coreTest.c +++ b/src/coreTest.c @@ -472,3 +472,161 @@ KTEST(coreVfsTmpfsNodeStampsCreatorAsOwner) KT_GE(t, fd, 0); vfsClose(fd); + FbStat st; + KT_EQ(t, vfsStat("/tmp/uidf", &st), 0); + KT_EQU(t, st.uid, 0u); + + vfsUnlink("/tmp/uidf"); +} + +/* vfsOwnerOrRoot pure predicate (vfs.c/h): same "explicit-argument, no live + thread" test shape as vfsPermittedOwnerMatch/NonOwner/UidZero above -- + covers all four quadrants (self-match as a non-root uid, root bypass on + a foreign file, root owning and calling as itself, and a plain stranger + denied against both a root-owned and a non-root-owned file). Needed + because KTEST always runs as the permanent idle thread's own uid 0 + (coreVfsTmpfsNodeStampsCreatorAsOwner's own doc comment), so no KTEST can + ever construct a real vfsChmod/vfsChown call with a non-root caller uid + to exercise the "denied" side directly -- that side is only provable + live (identtest.c's chmod-nonowner-denied-ok/chown-nonroot-denied-ok). */ +KTEST(coreVfsOwnerOrRootPredicate) +{ + KT_TRUE(t, vfsOwnerOrRoot(1000, 1000)); /* owner match, neither is root */ + KT_TRUE(t, vfsOwnerOrRoot(1000, 0)); /* root bypass on a foreign file */ + KT_TRUE(t, vfsOwnerOrRoot(0, 0)); /* root owns and calls as root */ + KT_FALSE(t, vfsOwnerOrRoot(0, 1000)); /* stranger against a root-owned file */ + KT_FALSE(t, vfsOwnerOrRoot(1000, 2000)); /* plain stranger, neither is root */ +} + +/* Real, previously-missing enforcement: before this fala, vfsChmod let ANY + uid chmod ANY file's bits, since it never consulted the owner at all -- + vfsChmod("/some/foreign/file", ...) always silently succeeded regardless + of who called it. This proves the ALLOWED side of the fix at the real + vfs.c choke point (the denied side can only be proven live, see the + predicate test above): root may still chmod a file it does NOT own, via + the new vfsChown reassigning ownership away from the creator first. */ +KTEST(coreVfsChmodRootBypassesForeignOwnership) +{ + int fd = vfsOpen("/tmp/chmodForeign", oReadWrite | oCreate); + KT_GE(t, fd, 0); + vfsClose(fd); + + KT_EQ(t, vfsChown("/tmp/chmodForeign", 4242), 0); + FbStat st; + KT_EQ(t, vfsStat("/tmp/chmodForeign", &st), 0); + KT_EQU(t, st.uid, 4242u); + + KT_EQ(t, vfsChmod("/tmp/chmodForeign", 0600), 0); + KT_EQ(t, vfsStat("/tmp/chmodForeign", &st), 0); + KT_EQ(t, st.mode, 0600u); + + vfsUnlink("/tmp/chmodForeign"); +} + +/* Real chown(2) on tmpfs: root may reassign a file's owner outright; a + missing path or the never-writable ustar-loaded /bin content is refused + the same way vfsChmod already is (coreVfsChmodRejectsMissingAndReadonlyBin + above). */ +KTEST(coreVfsChownTmpfsReassignsOwner) +{ + int fd = vfsOpen("/tmp/chownf", oReadWrite | oCreate); + KT_GE(t, fd, 0); + vfsClose(fd); + + FbStat st; + KT_EQ(t, vfsStat("/tmp/chownf", &st), 0); + KT_EQU(t, st.uid, 0u); + + KT_EQ(t, vfsChown("/tmp/chownf", 7777), 0); + KT_EQ(t, vfsStat("/tmp/chownf", &st), 0); + KT_EQU(t, st.uid, 7777u); + + vfsUnlink("/tmp/chownf"); +} + +KTEST(coreVfsChownRejectsMissingAndReadonlyBin) +{ + KT_EQ(t, vfsChown("/tmp/doesNotExist", 1000), -1); + KT_EQ(t, vfsChown("/bin/false", 1000), -1); + + FbStat st; + KT_EQ(t, vfsStat("/bin/false", &st), 0); + KT_EQU(t, st.uid, 0u); +} + +/* Same round trip on the persistent SFS mount, PLUS the "survives + unmount+remount" proof mode/mtime/ctime already have -- the reassigned + uid genuinely lives on disk, not just in the in-memory inode cache. */ +KTEST(coreVfsChownSfsPersistsAcrossRemount) +{ + KT_EQ(t, vfsMkdir("/mnt/chownd"), 0); + int fd = vfsOpen("/mnt/chownd/f", oReadWrite | oCreate); + KT_GE(t, fd, 0); + vfsClose(fd); + + FbStat st; + KT_EQ(t, vfsStat("/mnt/chownd/f", &st), 0); + KT_EQU(t, st.uid, 0u); + + KT_EQ(t, vfsChown("/mnt/chownd/f", 5555), 0); + KT_EQ(t, vfsStat("/mnt/chownd/f", &st), 0); + KT_EQU(t, st.uid, 5555u); + + sfsUnmount(); + KT_TRUE(t, sfsMount()); + + KT_EQ(t, vfsStat("/mnt/chownd/f", &st), 0); + KT_EQU(t, st.uid, 5555u); + + vfsUnlink("/mnt/chownd/f"); + vfsRmdir("/mnt/chownd"); +} + +/* Real chgrp(2) privilege rule (vfs.c's vfsChgrp): unlike vfsChown + (root-only, full stop), the file's own owner may ALSO reassign the + group -- but only to their OWN current gid (currentThreadGid()), never + an arbitrary one, since this kernel tracks exactly one gid per thread. + KTEST always runs as the idle thread's own (uid 0, gid 0), so the + caller here genuinely IS the owner of a freshly created file, letting + this test exercise the "owner, not root" half of vfsChgrp's dual + privilege check -- setting the group to 0 (this thread's own gid) + succeeds, setting it to anything else fails, exactly matching + grouptest.c's live chgrp-owner-self-gid-ok/chgrp-owner-arbitrary- + denied-ok pair, but provable here without a live cross-uid process + since "owner but not root" only requires being non-root, which this + thread already permanently is not (root bypass is tested separately by + coreVfsChgrpRootReassignsArbitraryGroup below, since root-vs-owner needs + two different DENIED-side outcomes root's own uid-0 bypass would hide). */ +KTEST(coreVfsChgrpOwnerOnlyMaySetOwnCurrentGid) +{ + int fd = vfsOpen("/tmp/chgrpOwner", oReadWrite | oCreate); + KT_GE(t, fd, 0); + vfsClose(fd); + + FbStat st; + KT_EQ(t, vfsStat("/tmp/chgrpOwner", &st), 0); + KT_EQU(t, st.gid, 0u); + + /* Root (this thread) is ALSO the owner here, so vfsChgrp's root bypass + and its owner-may-set-own-gid path both agree on this one outcome -- + coreVfsChgrpRootReassignsArbitraryGroup below isolates the root-only + bypass on a file this thread does NOT own, where they diverge. */ + KT_EQ(t, vfsChgrp("/tmp/chgrpOwner", 0), 0); + KT_EQ(t, vfsStat("/tmp/chgrpOwner", &st), 0); + KT_EQU(t, st.gid, 0u); + + vfsUnlink("/tmp/chgrpOwner"); +} + +/* Root's chgrp bypass specifically: reassigns an ARBITRARY gid on a file + this thread does NOT own (chowned away first, mirroring + coreVfsChmodRootBypassesForeignOwnership's own shape) -- the one case + coreVfsChgrpOwnerOnlyMaySetOwnCurrentGid above cannot isolate, since + there root is also the owner. */ +KTEST(coreVfsChgrpRootReassignsArbitraryGroup) +{ + int fd = vfsOpen("/tmp/chgrpForeign", oReadWrite | oCreate); + KT_GE(t, fd, 0); + vfsClose(fd); + KT_EQ(t, vfsChown("/tmp/chgrpForeign", 4242), 0); +