This commit is contained in:
2026-08-18 09:20:44 +02:00
commit b826cd721a
522 changed files with 93730 additions and 0 deletions
@@ -0,0 +1,112 @@
# repo22 Cold Nested Namei Regression Report
- Date: 2026-08-08 UTC
- Baseline: `29a215dd4519579f6313b3df67d524a6b4bdf3ca`
- Guest: FreeBSD 15.0-RELEASE-p8 amd64, QEMU TCG
- Final module SHA-256:
`50a19ea96c7414f73d92049671e66a9eacc9ff5abe27950a46d16c8e6c763baf`
- Result: PASS
## Root Cause
FreeBSD pathname lookup passes a component as `cn_nameptr` plus
`cn_namelen`. An intermediate component is followed by `/` in the pathname
buffer and is not NUL-terminated at `cn_namelen`. The imported Linux EROFS
comparison assumed Linux dentry-name termination, ignored the supplied length,
and tested `qn_name[i] == '\0'` after matching the on-disk name. Therefore a
final component worked, while the same name used as an intermediate component
compared greater than the on-disk entry and returned `ENOENT`. Looking up the
parent as a final component first populated the FreeBSD namecache and hid the
bug on the next nested lookup.
The old error path also inserted a negative cache entry for every lookup
error, including integrity and I/O errors, which could mask later corruption
as `ENOENT`.
## Implementation
- `src/namei.c`
- Compares pathname components by explicit length without reading beyond
`cn_namelen`.
- Uses unsigned-byte ordering compatible with EROFS directory sorting.
- Validates the minimum block size before reading the first dirent.
- Validates the dirent-array boundary, strictly increasing name offsets,
name-slot bounds, name length, and zero-only NUL padding.
- Inserts negative namecache entries only for real `ENOENT` misses.
- `src/dir.c`
- Applies the same directory-block and name-padding validation to `readdir`.
- Determines the actual last-name length before enforcing `EROFS_NAME_LEN`,
so valid full-block zero padding is accepted.
- Keeps on-disk offsets unchanged and appends a synthetic `.` at `i_size`
for `dot_omitted`, matching Linux EROFS.
- Aligns restart positions relative to each directory block and preserves
the `i_size` cookie needed to resume the synthetic dot entry.
- Initializes returned cookie-array outputs before allocation.
## Deterministic Fixture
`prepare-fixtures.sh` creates the same image twice and requires `cmp` success.
The base image contains a cold multi-level path and a 320-file, multi-block
directory. Fixed timestamp, ownership, UUID, worker count, and uncompressed
layout are used.
Final image hashes:
```text
2e9fd75159011ced31646a38f942fa0822d5b3e8e19b7df55b844575fba991ea corrupt-nameoff.erofs
63d12232f9ea0dc7f7ff477d20b95a8412d191fc10bf4b67dacc47e050c379fb corrupt-padding.erofs
77f8a56f969e173d291ccbd957821f1ba284d3e54d875dbcdb9bc398024dfa5a corrupt-short-block.erofs
11064ffbb814ff41027aebc8f36e56d2f24affb7b50836948b3ffbf6d79dee0e dot-omitted.erofs
7a2c244ec10e9a43531b0e8b92e26b11f52d67bab00528b8ba2f584e20e57420 nested-repeat.erofs
7a2c244ec10e9a43531b0e8b92e26b11f52d67bab00528b8ba2f584e20e57420 nested.erofs
```
## Build Results
- `./build.sh`: PASS.
- Final `build/erofs.ko` SHA-256 remained
`50a19ea96c7414f73d92049671e66a9eacc9ff5abe27950a46d16c8e6c763baf`.
- `nm -u build/erofs.ko | grep -w bcmp`: no match.
- `git diff --check` for all scoped source and test files: PASS.
The final integration rerun used the same combined module and these guest
commands:
```sh
cd /root/repo22-namei
cc -Wall -Wextra -O2 -o readdir_probe readdir_probe.c
./vm-regression.sh
```
`vm-regression.sh` performs `kldload`, creates each vnode-backed md device,
mounts it with `mount -t erofs`, executes the cold lookup and readdir probes,
unmounts and detaches each image, and finishes with exact module unload.
## FreeBSD VM Results
- `kldload`: PASS.
- Cold direct read of
`/alpha/bravo/charlie/payload.txt` without parent lookup or `readdir`: PASS.
- Repeated lookup and sibling nested lookup: PASS.
- Two negative lookups followed by an existing nested lookup: PASS.
- Multi-block `wide` readdir: 322 dirents (`.`, `..`, 320 files), PASS.
- Resume from every one of the 322 returned `d_off` cookies: PASS.
- `dot_omitted` root cookies: `12`, `24`, `47`, `48`; resume at `47`
returns only `.`, and resume at `48` returns EOF: PASS.
- Short directory block: two lookups and direct `getdirentries` all return
`EINTEGRITY`, PASS.
- Non-monotonic `nameoff`: two lookups and direct `getdirentries` all return
`EINTEGRITY`, PASS.
- Nonzero data after NUL padding: two lookups and direct `getdirentries` all
return `EINTEGRITY`, PASS.
- `kldunload`: PASS.
- Post-test EROFS module, mount, and md-device state: clean.
- Final explicit state check: zero EROFS modules, zero EROFS mounts, zero md
devices, and no recent panic or fatal trap in dmesg.
## Remaining Scope
No unresolved issue remains for the requested cold lookup and directory
regression. The NFS-specific `a_cookies` consumer path was not exercised by an
NFS export; the tested `d_off` restart-cookie sequence uses the same generated
cookie values.