# DF-2716 — nlookup() trailing-slash drops NLC_STICKY / NLC_APPENDONLY dflags
## (sticky-bit and append-only directory deletion restriction bypass)

## What

`sys/kern/vfs_nlookup.c` `nlookup()` decides whether to collect directory
feedback flags (`NLC_STICKY`, `NLC_APPENDONLY`, `NLC_IMMUTABLE`) into the
local `dflags` using

```c
if (*nptr == '/' || (saveflag & NLC_MODIFYING_MASK) == 0) {   /* :654 */
        naccess(..., NULL, 0);         /* no dflags */
} else {
        naccess(..., &dflags, 0);      /* collect dflags */
}
```

`nptr` points just past the current path component.  For a path whose LAST
component is followed by a trailing slash — `rmdir("/tmp/victim/")` —
`*nptr == '/'` is true even though `islastelement(nptr)` says this is the
final component.  The leaf's parent directory is therefore misclassified as
an intermediate directory and its feedback flags are never collected.  The
final leaf `naccess()` (`:1213`, `nd->nl_flags | dflags`) then runs with
`dflags == 0`, so `naccess_lva()` never sees `NLC_STICKY` (`:1902`) or
`NLC_APPENDONLY` (`:1838`) and the corresponding restrictions are skipped.

No filesystem re-checks the sticky bit for rmdir/rename on hammer2 (the
project's root filesystem), and tmpfs only re-checks APPEND/IMMUTABLE — not
sticky.  UFS's own old-API rmdir check is `#if 0`'d ("handled by kernel
now", `ufs_vnops.c:1483`).

## Reproduce (on the guest, as root)

    cc -O0 -g -o /tmp/df2716_bin/df2716 df2716.c
    sh run.sh /df2716h        # hammer2 root fs  (or: sh run.sh /tmp — tmpfs)

## Expected (vulnerable stock kernel)

    a1 control rmdir  /BASE/victim      => EACCES   (sticky enforced)
    a2 PoC     rmdir  /BASE/victim/     => 0  *** sticky BYPASSED ***
    a3 control rename /BASE/victim ...  => EACCES
    a4 PoC     rename /BASE/victim/ ... => 0  *** sticky BYPASSED ***
    a5 PoC     rename own -> victim/    => ENOENT (gate bypassed — no
                                             EACCES; FS-internal failure)
    b1 control rmdir  sappnd-dir/sub    => EPERM
    b2 PoC     rmdir  sappnd-dir/sub/   => 0  *** append-only BYPASSED ***
                                             (hammer2; tmpfs re-checks
                                             APPEND itself)

With fix.diff applied (islastelement-based condition) the same run yields
EACCES/EPERM for a2, a4 and b2; controls unchanged; regression suite
(non-sticky dir-over-dir rename, plain creates/renames) still passes.

## Files

* df2716.c   — phase-driven syscall PoC (a1..a5, b1, b2)
* run.sh     — root fixture driver + matrix (arg: base dir)
* fix.diff   — one-line semantic fix + comment
* build.log, run.log (tmpfs), run.hammer2.log, run.baseline.log (fresh-boot
  hammer2), run.fixed.log (patched kernel) — full outputs
* verdict.json / manifest.json / VERDICT.md
