DragonFlyBSD Kernel Audit
DF-3038 / alias.c
← back to finding ↓ download raw
/*
 * DF-3038 — FAT32 root-alias proof of concept (analysis-only, not guest-run).
 *
 * Crafted FAT32 image facts (built with newfs_msdos -F 32 + binary patch):
 *   - the FAT32 root directory contains ONLY unused entries (all 0x00);
 *   - a subdirectory /S contains an entry "rootalias" with
 *       ATTR_DIRECTORY, deStartCluster = low16(rootdirblk),
 *       deHighClust = high16(rootdirblk).
 *
 * lookup("/mnt/S/rootalias") -> lookup.c:411-418 maps
 * scn == pmp->pm_rootdirblk to MSDOSFSROOT -> deget(0, MSDOSFSROOT_OFS)
 * -> denode.c:327-347 returns THE VROOT root denode.  The alias is
 * therefore the mount root itself.
 *
 * rmdir("/mnt/S/rootalias") then:
 *   msdosfs_vnops.c:1451  dosdirempty(root) == 1   (crafted empty root)
 *   msdosfs_vnops.c:1463  removede() marks the alias entry deleted and
 *                         drops a ref on the ROOT denode
 *   msdosfs_vnops.c:1469  detrunc(root_denode, 0, IO_SYNC):
 *                         denode.c:502 VROOT guard is "!FAT32(pmp)" only,
 *                         so on FAT32 it proceeds and
 *                         denode.c:523-524+602 freeclusterchain() frees
 *                         THE ROOT DIRECTORY CHAIN of the live mount.
 *
 * rename("/mnt/S/rootalias", "/mnt/T/x") (T empty dir):
 *   doscheckpath blind spot (lookup.c:843 vs 847: source->de_StartCluster
 *   is pm_rootdirblk but the target chain reports ".." == 0 == MSDOSFSROOT,
 *   which stops the walk as "reached root" without matching the source)
 *   lets the rename proceed; msdosfs_vnops.c:1238-1251 then overwrites
 *   entry 1 (the ".." slot) of the ROOT directory with the new parent's
 *   cluster, clobbering whatever real entry lived there and installing a
 *   live namespace cycle (the mount root now sits "inside" its own tree).
 *
 * Impact: corruption confined to the attacker-crafted volume + a mounted
 * namespace cycle (Low).  No kernel memory-safety violation.  Not executed
 * on the guest (Low severity => Phase V skipped); the code path is fully
 * traced in VERDICT.md.
 */
int main(void) { return 0; }