e1000_read_i2c_byte_generic double-releases the SW_FW_SYNC PHY0 semaphore on retry exhaustion
Summary
When all I2C transfer attempts fail, e1000_read_i2c_byte_generic() releases the E1000_SWFW_PHY0_SM bit once per failed attempt and then unconditionally again - 10 releases for 9 acquires - silently clearing a SW_FW_SYNC bit the function no longer owns and defeating the SW/FW mutual-exclusion protocol shared with management firmware and sibling ports. Contrast the balanced writer twin. On 82575/82576/82580/i350/i354 silicon with a failing/unseated SFP module or wedged I2C bus, the spurious release clears a PHY0 ownership bit that another port or the NVM path may have legitimately re-acquired during the >=100ms window between the two releases - allowing two concurrent PHY/MDI0 accesses and torn PHY register sequences; no kernel memory-safety impact. VERIFIED: logic-level PoC on the guest - 'acquires=9 releases=10 -> DOUBLE RELEASE' on the exhaustion path with both controls balanced.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3826 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.9 KB | β raw | |
| VERDICT.md | β | 4.3 KB | β raw | |
| swfw_double_release.c | β | 4.1 KB | view raw | |
| build.sh | β | 122 B | view raw | |
| run.sh | β | 119 B | view raw | |
| build.log | β | 148 B | view raw | |
| run.log | β | 216 B | view raw | |
| env.txt | β | 873 B | view raw | |
| fix.diff | β | 679 B | view raw | |
| manifest.json | β | 959 B | view raw | |
| verdict.json | β | 2.8 KB | view raw |
DF-3826 β e1000 HAL: double release of the SW_FW_SYNC PHY0 semaphore in e1000_read_i2c_byte_generic
What
sys/dev/netif/ig_hal/e1000_82575.c, e1000_read_i2c_byte_generic()
(lines 3270-3349): when every I2C transfer attempt fails and the retry loop
is exhausted, the fail: label (line 3333) releases the
E1000_SWFW_PHY0_SM semaphore for each failed attempt, and then the
unconditional post-loop release at line 3344 releases it once more β
releases == acquires + 1. The sibling writer,
e1000_write_i2c_byte_generic() (lines 3361-3422), acquires once before the
loop, does not release inside fail:, and releases once after the loop β
proving the read path's asymmetry is a defect, not a protocol choice.
Impact
The extra e1000_release_swfw_sync_82575(hw, E1000_SWFW_PHY0_SM) clears the
PHY0 bit in E1000_SW_FW_SYNC at a moment when this code path no longer owns
it. The two releases are separated by ~100 ms (msec_delay(100) at 3334 +
e1000_i2c_bus_clear bit-banging at 3335). If, in that window, another
function (multi-port i350 path, NVM/EEPROM path via
e1000_acquire_nvm_82575, or management firmware) legitimately acquires
PHY0, its ownership bit is silently dropped and a second party can then
acquire the "same" resource β concurrent PHY register / I2C access, torn
MDIO transfers, possible EEPROM/NVM access corruption. Logic/lifetime defect
in the SW/FW semaphore protocol; not kernel memory corruption.
Hardware-gated: reachable only on real 82575/82576/82580/i350/i354/i210
silicon with a failing/unseated SFP or wedged I2C bus (the read is used by
SFP detection, e1000_set_sfp_media_type_82575).
Reproduce (logic-level replica β no hardware required)
scp swfw_double_release.c dfbsd-maxx:/tmp/ ssh dfbsd 'cc -O2 -Wall -o /tmp/swfw_double_release /tmp/swfw_double_release.c' ssh dfbsd '/tmp/swfw_double_release; echo exit=$?'
Expected (see run.log):
case1 (all I2C ops fail, retries exhausted): acquires=9 releases=10 -> DOUBLE RELEASE case2 (first attempt succeeds): acquires=1 releases=1 -> balanced case3 (acquire fails): acquires=0 releases=0 -> balanced exit=0
swfw_double_release.c replicates the exact control flow of
e1000_read_i2c_byte_generic() (retry starts at 1, while (retry < 10),
release in fail:, unconditional release after the loop) with mocked
acquire/release; case 1 shows 9 successful acquires and 10 releases β the
kernel loop arithmetic is identical.
Kernel-path verification status
The KVM audit guest has no Intel 8257x-class NIC (pciconf -l:
virtio-net + virtio-blk + bochs VGA only, see env.txt), so the in-kernel
path cannot be executed there β verdict not_reproduced for the kernel
reachability; the control-flow defect itself is proven at source level
(certain) and by the replica.
Fix
fix.diff β only the post-loop release when the loop was exited via
break (semaphore still held). Never applied to sys/ (read-only audit).
DF-3826 VERDICT
Bottom line
Source-certain logic defect; kernel-path reproduction impossible on this
guest (target hardware absent); logic-level reproduction succeeded.
status: not_reproduced (kernel reach), impact: none (as observed), the
defect itself is proven with certainty from the source and the state-machine
replica.
The defect
e1000_read_i2c_byte_generic() in sys/dev/netif/ig_hal/e1000_82575.c:
3284 do {
3285 if (hw->mac.ops.acquire_swfw_sync(hw, swfw_mask) != E1000_SUCCESS) {
3287 status = E1000_ERR_SWFW_SYNC;
3288 goto read_byte_out; <- (a) no release: correct
}
... I2C steps, each "goto fail" on error ...
3330 break; <- (b) success: semaphore held
3332 fail:
3333 hw->mac.ops.release_swfw_sync(hw, swfw_mask); <- release per failed attempt
msec_delay(100);
e1000_i2c_bus_clear(hw);
retry++;
3342 } while (retry < max_retry); <- exhausted: exit WITHOUT holding
3344 hw->mac.ops.release_swfw_sync(hw, swfw_mask); <- ALWAYS releases again
With retry = 1 and max_retry = 10 there are exactly 9 in-loop attempts;
each failure releases once (3333), and the exhausted exit falls into the
unconditional release at 3344 β 10 releases for 9 acquires. Exit (a)
(acquire failure) correctly skips the post-loop release via
goto read_byte_out; exit (b) (success) needs the post-loop release β so the
missing distinction is only for exit path "exhausted", which is precisely the
bug.
Contrast: e1000_write_i2c_byte_generic() (3373: acquire once before loop;
fail: at 3408 does NOT release; single release at 3417) β balanced by
construction.
Why it matters (semaphore symmetry β checklist item (e))
e1000_acquire_swfw_sync_82575/release_swfw_sync_82575 (1052-1118)
implement a bit-mask mutual-exclusion protocol over E1000_SW_FW_SYNC
shared with management firmware: acquire = set bit while holding the global
hw semaphore; release = clear bit. There are no reference counts. A release
without ownership clears a bit that may have been legitimately re-acquired
in the β₯100 ms window between the fail: release and the post-loop release
(delay 3334 + nine I2C clock pulses of e1000_i2c_bus_clear, 3750-3777).
The next acquire then succeeds for two concurrent parties β concurrent
PHY/MDIO/I2C access, torn PHY register writes (e.g. the M88E1512/1543
multi-step init scripts at 2827-3007 each assume exclusive PHY access).
Every other acquire/release pair in this file was checked and is balanced:
e1000_acquire_nvm_82575 (991β1022 error-path release),
e1000_reset_hw_82580 (2496β2545), the PHY wrappers (608-614, 641-647,
2376-2382, 2402-2408), and the two i210 variants live in e1000_i210.c.
Reproduction
run.log: replica state machine βacquires=9 releases=10 -> DOUBLE RELEASEon the exhaustion path; both control cases balanced. Exit 0.- Kernel path: guest
pciconf -lshows no 8086:8257x/i350/i210 device and no igb interface (env.txt) β the HAL entry point cannot be reached on this guest. Real-hardware reproduction would need an igb-attached port with an SFP cage whose module read fails 9 consecutive times while another port holds/re-acquires PHY0 β a lab setup, not available here.
Classification per honest-status table
Guest reachable + target device absent β not_reproduced, reproduced=0,
impact=none for the observed run. Theoretical impact on real hardware:
loss of SW/FW mutual exclusion (logic, availability of the PHY path);
no kernel memory-safety consequence β severity Low, confidence certain.
Fix validation
fix.diff gates the post-loop release on status == E1000_SUCCESS.
Fix-kernel build validation is not meaningful for a hardware-gated HAL path
on a guest without the device (nothing observable changes), so
fix_status: not_testable with the one-line rationale; the logic replica
was re-run against the patched control flow (post-loop release skipped when
the last attempt failed): releases == acquires for all three cases.
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 gates the release; validated against the same state-machine replica (releases==acquires for all cases). A patched-kernel build on this guest has no observable behavior difference because the igb device is absent, so hardware fix validation is not_testable here.
fix.diff; VERDICT.md 'Fix validation' section
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (acquires=9 releases=10 -> DOUBLE RELEASE, controls balanced); VERDICT.md (control-flow trace of :3284-3344 and the write-path contrast); env.txt (no igb device on guest)
PoC changes
No seed existed; harness authored from scratch. Replicates the do/while(retry<10) structure verbatim with mocked acquire/release and failing I2C primitives.
Verified recommended fix
Gate the post-loop release on status == E1000_SUCCESS (only the break path still holds the semaphore).
Verdict
Source-certain semaphore-symmetry defect: e1000_read_i2c_byte_generic() releases the E1000_SWFW_PHY0_SM bit once per failed retry at e1000_82575.c:3333 and then unconditionally again at :3344 (10 releases for 9 acquires), dropping a SW_FW_SYNC bit the function no longer owns and breaking the SW/FW mutual-exclusion protocol shared with management firmware and sibling ports. Proven by a 1:1 control-flow replica (run.log: acquires=9 releases=10) and by contrast with the balanced writer e1000_write_i2c_byte_generic (:3373-3417). Kernel-path reproduction impossible on the audit guest: no Intel 8257x/i350/i210 NIC is present (env.txt pciconf), so the HAL cannot attach; verdict is not_reproduced for the kernel reach with the defect itself certain at source level.
No comments yet.