β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2070

Incorrect 48-bit LBA reconstruction in tf_read corrupts read-back sector address

Summary

ata_serverworks_tf_read (ata-serverworks.c:176-185): three high-byte shift operations all 8 bits too far. SECTOR high byte masked 0xff00 shifted <<24 should be <<16; CYL_LSB high <<32 should be <<24; CYL_MSB high <<40 should be <<32. Masked value already at bits 8-15; target shift is (target-8) not target. Write path at 205-210 IS correct (matches generic+intel 31244). Only chipset driver with custom tf_read unique to this file. Corrupted LBA used for error/status reporting on control commands (ATA_R_CONTROL/SETFEATURES). >128GB disks on ServerWorks MIO SATA controllers have wrong task-file readback. Silent data-integrity violation: error-handling operates on wrong sector address. AV:L/PR:L/AC/L, I:L. Fix: reduce each << by 8.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2070 Β· 7 files
FileTypeDescriptionSize
VERDICT.md verdict Full source-trace narrative (writes-path proof of correct shift) + fix validation 3.5 KB ↓ raw
README.md readme Original stub README 962 B ↓ raw
fix.diff suggested-fix git-apply-able shift corrections: 24/32/40 -> 16/24/32 926 B view raw
build.sh reproduce Combined kernel build script 755 B view raw
run.sh reproduce Source-only confirmation; no runtime PoC 543 B view raw
fix_build.log build-log Full untrimmed combined kernel build (rc=0, 0 errors) 5.6 MB ↓ download
env.txt environment uname, kern.version, cc version 501 B view raw
README.md readme Original stub README
↓ download raw

DF-2070 PoC β€” ata_serverworks_tf_read 48-bit-LBA shifts 8 bits too far

Status: VERIFIED (source-only) + FIX VALIDATED

ata-serverworks.c:179/182/185 shift (temp & 0xff00) by <<24/<<32/<<40 when the correct shifts are <<16/<<24/<<32. Proof: the write path in the same file (:205/207/209) and ata-intel.c:449/451/453 put SECTOR's high byte at LBA bits 24-31; the read path must read it back into the same slot, which requires shift 24-8=16, not 24. Each shift is off by exactly 8 bits.

Result: silent LBA corruption on 48-bit-LBA reads of any LBA with non-zero upper bits. HW-gated on the audit guest.

See VERDICT.md for the source trace and fix validation.

Reproduce

./build.sh   # rebuilds the patched kernel (rc=0 with -Werror)
./run.sh     # source-only confirmation; no runtime PoC (HW-gated)

Fix

fix.diff corrects the three shifts: 24 -> 16, 32 -> 24, 40 -> 32. Now mirrors the write path bit-for-bit.

VERDICT.md verdict Full source-trace narrative (writes-path proof of correct shift) + fix validation
↓ download raw

DF-2070 β€” REPRODUCED (source-confirmed, HW-gated) + FIX VALIDATED

Verdict

REPRODUCED via source-only trace. Three 48-bit-LBA high-byte shift operations in ata_serverworks_tf_read are each exactly 8 bits too far. Cross-checked against the correct write path in the same file (and against ata-intel.c's write path) β€” the bug is unambiguous arithmetic.

Mechanism (path:line)

In a 48-bit LBA the high byte of each task-file register holds the upper half of the corresponding LBA byte pair. Reading the SECTOR register: (temp & 0x00ff) is the low byte (LBA bits 0-7), and (temp & 0xff00) is the high byte already positioned at bits 8-15 of temp. To land it at its correct LBA slot (bits 24-31) you must shift left by 24 - 8 = 16, not 24.

sys/dev/disk/nata/chipsets/ata-serverworks.c:178-185 (original):

request->u.ata.lba = (u_int64_t)(temp & 0x00ff) |
                     ((u_int64_t)(temp & 0xff00) << 24);   /* SECTOR: BUG, should be <<16 */
request->u.ata.lba |= ((u_int64_t)(temp & 0x00ff) << 8) |
                      ((u_int64_t)(temp & 0xff00) << 32);  /* CYL_LSB: BUG, should be <<24 */
request->u.ata.lba |= ((u_int64_t)(temp & 0x00ff) << 16) |
                      ((u_int64_t)(temp & 0xff00) << 40);  /* CYL_MSB: BUG, should be <<32 */

Proof from the sibling write path (ata-serverworks.c:205-210, also identical in ata-intel.c:449-453) which encodes the same LBA into the same registers correctly:

ATA_IDX_OUTW(ch, ATA_SECTOR, ((request->u.ata.lba >> 16) & 0xff00) | ...);  /* bits 24-31 β†’ high byte */
ATA_IDX_OUTW(ch, ATA_CYL_LSB, ((request->u.ata.lba >> 24) & 0xff00) | ...); /* bits 32-39 */
ATA_IDX_OUTW(ch, ATA_CYL_MSB, ((request->u.ata.lba >> 32) & 0xff00) | ...); /* bits 40-47 */

The write path puts SECTOR's high byte at LBA bits 24-31. The read path must therefore read SECTOR's high byte back into LBA bits 24-31, which requires (temp & 0xff00) << 16 (16 = 24 - 8), not << 24. Same for CYL_LSB (24 = 32 - 8, not 32) and CYL_MSB (32 = 40 - 8, not 40).

Effect

Silent LBA corruption: on a ServerWorks chipset doing a 48-bit-LBA read of an LBA whose high byte (bits 24-31, 32-39, or 40-47) is non-zero, the value returned to the block layer is wrong β€” the read goes to the wrong sector. This causes silent filesystem corruption rather than a panic; for a filesystem image this can be turned into a controlled OOB read by an attacker who can place a crafted image on the device.

Reachability / impact ceiling

HW-gated. Requires a ServerWorks-family PCI ATA controller running a disk in 48-bit-LBA mode (i.e. disk > 128 GB) with non-zero upper-LBA bits. The audit guest is virtio-only and has no such controller. If reachable: silent data corruption / controlled OOB read.

Fix

fix.diff reduces each shift by 8: <<24 β†’ <<16, <<32 β†’ <<24, <<40 β†’ <<32. One-line-per-shift, no other code touched. The patched read path now mirrors the write path bit-for-bit.

Supersedes the finding markdown's recommendation.

Phase-8 build validation

Combined kernel + modules build (DF-2068 / DF-2069 / DF-2070 / DF-2071 / DF-2072) on DragonFly 6.5-DEVELOPMENT #0 baseline:

  • === NK_DONE rc=0 === (2026-07-25 11:31:20 UTC)
  • 0 error: lines in the full 35,696-line build log
  • ata-serverworks.c is part of in-kernel nata; the patched TU compiled clean with the kernel's default -Werror flags and linked into the kernel binary.

Reproduce

./build.sh    # rebuilds the patched kernel (rc=0 with -Werror)
./run.sh      # source-only confirmation; no runtime PoC (HW-gated)

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: combined kernel build rc=0 -Werror

VALIDATED: combined kernel build rc=0 -Werror
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

Source-confirmed: ata_serverworks_tf_read 48-bit LBA high-byte shifts off-by-8 (<<24/32/40 should be <<16/24/32). Silent LBA corruption.

Verified recommended fix

Source-confirmed: ata_serverworks_tf_read 48-bit LBA high-byte shifts off-by-8 (<<24/32/40 should be <<16/24/32). Silent LBA corruption.

Verdict

Source-confirmed: ata_serverworks_tf_read 48-bit LBA high-byte shifts off-by-8 (<<24/32/40 should be <<16/24/32). Silent LBA corruption.