# 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.
