# DF-0770 — Record sync error silently dropped in hammer_sync_inode

## Build

```
cc -O2 -o df0770 df0770_hammer_flush_error.c
```

## Run

```
# requires a HAMMER v1 FS mounted at /mnt/hammer, writable by the test user
./df0770
```

Full guest-side setup (as root, before running):

```
truncate -s 12G /hammer.img
newfs_hammer -f -L ROOT /hammer.img
vnconfig vn0 /hammer.img
mkdir -p /mnt/hammer
mount_hammer /dev/vn0 /mnt/hammer
chmod 777 /mnt/hammer
```

## Expected

This finding is a **data-integrity logic bug** (silently dropped error
propagation), not a memory-corruption primitive. The bug is at
`sys/vfs/hammer/hammer_inode.c:3066-3073`:

```c
if (error == 0) {
    tmp_error = RB_SCAN(hammer_rec_rb_tree, &ip->rec_tree, NULL,
                        hammer_sync_record_callback, &cursor);
    if (tmp_error < 0)
        tmp_error = -error;          /* BUG: should be -tmp_error */
    if (tmp_error)
        error = tmp_error;
}
```

Because the guard `if (error == 0)` is true, `-error` always evaluates to
`-0 == 0`, so the buggy line unconditionally clears `tmp_error`. The
record-sync error returned by `hammer_sync_record_callback` (which negates
the errno at line 2876-2877 `error = -error`) is silently lost.

`hammer_sync_inode` then returns 0; `hammer_sync_inode_done` (line 2553)
sets `ip->error = 0`; the VOP fsync handler `hammer_vop_fsync` (line 293)
returns `ip->error` (= 0) to userspace. **A failed record flush is
reported to fsync(2) as success.**

### Trigger

A *flush-time* record-sync failure (ENOSPC during B-tree allocation, or an
I/O error writing a B-tree node / UNDO record). When this happens,
`hammer_flush_record_done` calls `hammer_critical_error` which forces the
filesystem read-only — but the per-inode `ip->error` was already corrupted
to 0 by the bug, so:

- the immediate fsync returns 0 (success) to the application,
- the inode update at lines 3080+ proceeds (because `error == 0`),
- on crash the on-disk state is inconsistent: metadata changes (size,
  nlinks, deletion) committed while dependent records (directory entries,
  data writes, truncation records) lost.

### Runtime vs code-level proof

The runtime PoC `df0770_hammer_flush_error.c` floods a HAMMER v1 filesystem
to induce ENOSPC and exercises the fsync path. On this guest the FS
reservations cause write-time ENOSPC (properly reported) before the
flush-time path is reached, so the runtime PoC is **corroborating, not
deterministic**. The **definitive proof** is the code-level trace plus the
before/after disassembly of `hammer_sync_inode` (see VERDICT.md).

## Fix

The fix is a single-character source change at line 3070:

```diff
-           tmp_error = -error;
+           tmp_error = -tmp_error;
```

See `fix.diff`. Validated by building a single-fix kernel (`make -j6
nativekernel`), installing, booting, and confirming via disassembly that
the buggy `mov -0x128(%rbp),%eax; neg %eax` (load error then negate)
became `neg %eax; mov %eax,-0x128(%rbp)` (negate tmp_error in register,
then store as error) — and that the patched kernel boots and the HAMMER FS
continues to operate normally.
