ips: debug.ips.ignore_flush_cmd polarity inverted - the adapter cache-flush command is silently dropped on the DEFAULT configuration (fsync() durability broken)
Summary
The flush path tests !flag and skips issuing IPS_CACHE_FLUSH_CMD when the flag is CLEAR - but the flag defaults to 0. Every BUF_CMD_FLUSH bio on an ips disk (fsync/filesystem/unmount) is completed no-error WITHOUT the ServeRAID adapter's volatile write cache ever being flushed: the durability contract of fsync is silently violated; power loss after a returned fsync can lose the synced data. The sysctl description states the opposite semantics; the block is DF-specific. VERIFIED on guest: live default is 0, verbatim-branch harness shows flag=0 -> 0 adapter commands / bio finished no-error, flag=1 -> exactly one 0x0A command. Invert-the-condition fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4639 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.5 KB | β raw | |
| VERDICT.md | β | 3.6 KB | β raw | |
| flush_invert_harness.c | β | 3.5 KB | view raw | |
| build.sh | β | 66 B | view raw | |
| run.sh | β | 90 B | view raw | |
| build.log | β | 9 B | view raw | |
| run.log | β | 832 B | view raw | |
| env.txt | β | 208 B | view raw | |
| fix.diff | β | 478 B | view raw | |
| verdict.json | β | 4.0 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw |
DF-4639 β ips(4): debug.ips.ignore_flush_cmd polarity inverted β adapter cache-flush silently dropped on DEFAULT configuration
File: sys/dev/raid/ips/ips_commands.c:195-220 (supporting: :35-40, :249-252)
Severity: Medium (data integrity on default config) Β· Confidence: certain
Class: CWE-670 (Always-Incorrect Control Flow Implementation)
Build
sh build.sh # cc -O2 -Wall -o /tmp/flush_invert_harness flush_invert_harness.c
Run
sh run.sh # uname, live sysctl default, harness as unprivileged user
Expected output (bug present)
debug.ips.ignore_flush_cmd: 0 DEFAULT (flag=0): flag=0 -> adapter_cmds_issued=0 last_opcode=0x00 bio_finished=1(no error) DEBUG SET (flag=1): flag=1 -> adapter_cmds_issued=1 last_opcode=0x0a bio_finished=0(no error) RESULT: INVERTED β with the DEFAULT value the real adapter cache-flush ... harness exit code: 42
What it proves
- Live guest kernel (ips driver compiled into
X86_64_GENERIC): the sysctl exists and its default is0(run.logline 2; sourceips_commands.c:35-36TUNABLE_INT(..., &ips_debug_ignore_flush_cmd)β static, i.e. zero-initialized). - The branch (
ips_commands.c:202):if (!ips_debug_ignore_flush_cmd)is taken at the DEFAULT β it frees the command slot, completes the bio with no error, and returns without ever building/issuingIPS_CACHE_FLUSH_CMDto the adapter. Setting the debug flag to 1 β which per the sysctl's own description ("Do not issue IPS_CACHE_FLUSH_CMD on BUF_CMD_FLUSH",ips_commands.c:40) should SUPPRESS the command β instead issues it.
Impact
Every BUF_CMD_FLUSH bio on an ips disk (fsync(2)/close-time flush on the
raw device, filesystem metadata flushes, unmount flush) is reported complete
while the ServeRAID adapter's volatile write cache is never told to flush.
After fsync() returns, the just-synced data can still be lost on power
failure β the durability contract of fsync is silently violated on a stock
kernel. (The separate shutdown-path flush ips_flush_cache(),
ips_commands.c:476-493, does issue the real command, so the damage window
is "flush returned β next real flush/shutdown".)
Fix
fix.diff β invert the condition so the debug knob does what it documents
(skip the command only when SET). verdict.json records
fix_status: not_testable: an end-to-end A/B (observing opcode 0x0A on the
wire) requires ServeRAID hardware, which the audit guest cannot instantiate
(QEMU has no such device model; env.txt).
DF-4639 VERDICT β reproduced (logic inversion on live default; hardware end-to-end not observable on guest)
The claim
ips_send_flush_request (sys/dev/raid/ips/ips_commands.c:195-220) contains a
debug tunable whose polarity is inverted, so the default configuration
drops every BUF_CMD_FLUSH instead of forwarding it to the adapter as a
IPS_CACHE_FLUSH_CMD.
Proof chain
-
Default value is 0. - Source:
ips_commands.c:35-36βstatic int ips_debug_ignore_flush_cmd;TUNABLE_INT("debug.ips.ignore_flush_cmd", ...)(BSS β 0).- Live guest:
sysctl debug.ips.ignore_flush_cmdβ 0 (run.log; the ips driver is compiled into the guest's X86_64_GENERIC kernel).
-
Dispatch reaches the function for every flush bio.
ipsd_strategy(ips_disk.c:128-140) queues the bio and callsips_start_io_request(ips_commands.c:237-253), which at :249 branchesb_cmd == BUF_CMD_FLUSHβips_send_flush_request(:250). -
The branch skips the adapter command at flag==0.
ips_commands.c:202-206:c if (!ips_debug_ignore_flush_cmd) { ips_insert_free_cmd(sc, command); ipsd_finish(bio); /* completed, NO B_ERROR set */ return 0; }The real command build (flush_cmd->command = IPS_CACHE_FLUSH_CMD;:210) and issue (sc->ips_issue_cmd(command):218) are only reachable when the flag is SET (1). -
Documented semantics are the opposite.
ips_commands.c:38-40: SYSCTL description"Do not issue IPS_CACHE_FLUSH_CMD on BUF_CMD_FLUSH"β i.e. flag SET β skip. FreeBSD upstream has no such tunable (the whole flush-request block is DragonFly-added; verified against freebsd-src ips_commands.c HEAD β noignore_flush_cmd/ips_send_flush_requestthere), so this is DF-introduced, not inherited. -
Harness run (guest, unprivileged user maxx): the exact branch extracted verbatim into
flush_invert_harness.cβflag=0 β adapter_cmds_issued=0, bio_finished=1 (no error);flag=1 β adapter_cmds_issued=1, last_opcode=0x0A (IPS_CACHE_FLUSH_CMD). Exit marker 42. See run.log.
What was and was not reproduced
- REPRODUCED: the inverted control flow itself, at the live kernel's default flag value, from an unprivileged account (the harness is exact-logic; the sysctl default is read from the running kernel).
- NOT observable on this guest: the on-the-wire adapter command. The guest has zero ServeRAID PCI devices and QEMU has no ips device model (env.txt: 0 matches for vendor 0x1014/0x9005; /dev/ips* absent), so the path cannot be instantiated with hardware. The consequence β fsync() data lying in the adapter cache β follows mechanically from the proven branch.
Fix validation
fix.diff flips the condition to if (ips_debug_ignore_flush_cmd) so the
skip path only runs when the operator explicitly asks for it. An A/B kernel
run cannot change the observable (the branch is proven by harness either
way; the end-to-end effect needs hardware), so
fix_status: not_testable, fix_baseline_reproduced: 1,
fix_patched_reproduced: 0.
Related
- DF-4613..4616 (ips.c watchdog/queue/reinit/dump) β adjacent ips family, all distinct root causes.
- No other consumer of
ips_debug_ignore_flush_cmdexists (rg ignore_flush sys/β ips_commands.c only).
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testablefix.diff flips the polarity to match the documented tunable semantics. Baseline inversion reproduced via harness; an end-to-end patched-kernel A/B (observing IPS_CACHE_FLUSH_CMD reach a real adapter) is impossible on the audit guest - QEMU has no ServeRAID device model and the guest has zero 0x1014/0x9005 PCI devices (env.txt). The one-line fix is self-evident against the proven branch.
fix.diff (ips_commands.c:202 '!ips_debug_ignore_flush_cmd' -> 'ips_debug_ignore_flush_cmd'); run.log baseline harness output
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (live sysctl default 0 + harness both cases + exit 42), flush_invert_harness.c (verbatim branch extraction), VERDICT.md proof chain with ips_disk.c:128-140/ips_commands.c:35-40,195-220,249-252, fix.diff
PoC changes
n/a - evidence pack authored fresh in this run (harness + build/run scripts); orchestrator seed was only the ips.c reviewer's cross-file lead
Verified recommended fix
Invert the condition at ips_commands.c:202 to 'if (ips_debug_ignore_flush_cmd)' so the skip path only runs when the operator explicitly enables it (fix.diff).
Verdict
ips_send_flush_request (ips_commands.c:202-206) is inverted against its own debug knob: with the DEFAULT value debug.ips.ignore_flush_cmd=0 (static BSS + TUNABLE_INT, ips_commands.c:35-36; confirmed live on the guest kernel, which has ips compiled in) the function frees the command slot and completes the BUF_CMD_FLUSH bio with no error WITHOUT ever building/issuing IPS_CACHE_FLUSH_CMD; the real adapter cache-flush (opcode 0x0A, ips_commands.c:210-218) only runs when the flag is SET - the exact opposite of the sysctl description 'Do not issue IPS_CACHE_FLUSH_CMD on BUF_CMD_FLUSH' (ips_commands.c:40). Reproduced on the guest from an unprivileged account with a verbatim extraction of the branch (run.log: flag=0 -> 0 adapter commands, bio finished no-error; flag=1 -> 1 command opcode 0x0a; exit 42). Net effect on stock systems: every flush bio on an ips disk (fsync on the raw device, filesystem/unmount flushes via ipsd_strategy -> ips_start_io_request, ips_commands.c:249-250) reports durability while the adapter write cache is never flushed - power-loss data loss after a returned fsync. impact=none in the poc sense (no kernel memory corruption / privilege effect); the harm is silent data-integrity violation. The whole flush-request block is DragonFly-specific (no such tunable or function in FreeBSD upstream).
No comments yet.