update
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user