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

dpm_pg0_sz truncated to u16 in mps_mapping_initialize: DPM page silently under-read (benign direction proven)

Summary

uint16_t dpm_pg0_sz assigned 8 + max_dpm_entries*20; for MaxPersistentEntries >= 3277 the u16 wraps and the DPM read uses the truncated value. The allocation side is u32 and the config copy bounds with MIN() - under-read only: entries beyond ~3275 silently load as zero/unused. Functional impact (DPM persistence dropped), no memory-safety impact. u32-widen fix across 3 files.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4090 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.3 KB ↓ raw
VERDICT.md β€” 2.1 KB ↓ raw
env.txt β€” 1.4 KB view raw
fix.diff β€” 1.1 KB view raw
manifest.json β€” 555 B view raw
verdict.json β€” 2.1 KB view raw

DF-4090 β€” dpm_pg0_sz truncated to u16 in mps_mapping_initialize β€” DPM page silently under-read (integer truncation, benign direction proven)

File: sys/dev/raid/mps/mps_mapping.c:1721,1774-1789 (+ u16 sz parameter plumbing in mps_config.c:529-531 and mpsvar.h:718-719) Class: CWE-197 integer truncation (checklist item (d): persistent mapping config page copy sizes vs kmalloc) Severity: Low β€” pass 2 (GLM 5.3), bucket base:dev Confidence: certain

The bug

uint16_t volume_mapping_flags, dpm_pg0_sz;          /* :1721 β€” u16! */
...
dpm_pg0_sz = sizeof(MPI2_CONFIG_EXTENDED_PAGE_HEADER) +
    (sc->max_dpm_entries *
     sizeof(MPI2_CONFIG_PAGE_DRIVER_MAP0_ENTRY));   /* :1774-1776: 8 + 20*N */
retry_read_dpm:
        if (mps_config_get_dpm_pg0(sc, &mpi_reply, sc->dpm_pg0,
            dpm_pg0_sz)) {                          /* :1780-1781 */

The allocation in mps_mapping_allocate_memory:1418-1421 computes the size in uint32_t (8 + 20N bytes, N = ioc_pg8.MaxPersistentEntries, U16), but the read* size passed to mps_config_get_dpm_pg0() is held in a u16 and truncates for N β‰₯ 3277 (8 + 20Β·3277 = 65548 > 65535). The function's sz parameter is itself u16 (mps_config.c:530, decl mpsvar.h:719).

Direction analysis (why Low, not memory-unsafe)

mps_config_get_dpm_pg0 does memset(config_page, 0, sz) then finishes with bcopy(page, config_page, MIN(cm->cm_length, sz)) (mps_config.c:635) β€” the copy is always bounded by the truncated (smaller) sz into the full-size M_ZERO allocation, so this can only under-read, never overflow: persistent entries beyond index ~3275 silently read as zero (unused) instead of their NVRAM contents. Impact is functional (DPM persistence silently dropped for large tables; mapping rebuilt from events), requiring a malicious/odd firmware reporting MaxPersistentEntries β‰₯ 3277 β€” real HBAs report small values. Worth fixing while the file's bounds are being hardened; not exploitable memory corruption.

Fix

fix.diff (3 files): uint32_t dpm_pg0_sz in mps_mapping_initialize; widen mps_config_get_dpm_pg0's sz to u32 in definition (mps_config.c) and declaration (mpsvar.h). Compile-validated with the batch (-Werror, mps.ko linked β€” fix_build.log in DF-4085's pack).

VERDICT.md
↓ download raw

DF-4090 VERDICT

Status: untested β€” hardware-gated (no mps HBA on the audit guest, env.txt); Low-severity truncation with proven-benign direction, PoC not meaningful. Confidence: certain (arithmetic + copy bound verified).

  • Truncation: mps_mapping.c:1721 declares uint16_t dpm_pg0_sz; :1774-1776 assigns 8 + max_dpm_entries * sizeof(entry) where sizeof(MPI2_CONFIG_PAGE_DRIVER_MAP0_ENTRY) == 20 (mpi2_cnfg.h:2721-2730: 8+2+2+4+4). For MaxPersistentEntries β‰₯ 3277 the value exceeds 65535 and wraps; the wrapped (smaller) size is passed to mps_config_get_dpm_pg0 at :1780-1781.
  • Allocation side is correct: mps_mapping_allocate_memory:1391,1418-1421 computes in uint32_t; buffer is always β‰₯ the truncated read size.
  • Copy safety: mps_config.c:541 memset(config_page, 0, sz) (sz ≀ alloc) and mps_config.c:635 bcopy(page, config_page, MIN(cm->cm_length, sz)) β€” bounded by sz. Under-read only. (Had the copy been bounded by cm->cm_length β€” firmware ExtPageLength β€” this would instead be a heap overflow; the MIN() is the load-bearing guard and is present.)
  • Effect: DPM entries beyond ~index 3275 remain zero β†’ treated as unused at _mapping_process_dpm_pg0:1486-1488 β†’ persistent mapping silently lost, re-derived from topology events. Functional impact only.
  • Fix: fix.diff widens the local and the sz parameter (definition mps_config.c:529-531, declaration mpsvar.h:718-719) to u32. Compile-validated: applied to guest /usr/src with the batch's mps fixes; make in sys/dev/raid/mps with -Werror linked mps.ko (BUILD_EXIT=0; fix_build.log in the DF-4085 pack). /usr/src restored pristine; guest reset with clean-source snapshot.

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_testable
baseline no→ patch + rebuild →patched clean

Applied to the guest /usr/src together with this batch's other mps fixes (DF-4085..DF-4090); make in sys/dev/raid/mps compiled all sources with -Werror and linked mps.ko (BUILD_EXIT=0). Runtime fix validation not testable: the path requires an mps(4) HBA delivering firmware data, absent on this guest.

findings/poc/DF-4090/fix.diff, findings/poc/DF-4085/fix_build.log
↓ fix.diffper-fix-DF-4090

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (truncation + MIN() bound analysis), fix.diff (3 files), fix_build.log in DF-4085 pack

PoC changes

n/a (untested)

Verified recommended fix

Widen dpm_pg0_sz and mps_config_get_dpm_pg0's sz parameter to u32.

Verdict

Low - integer truncation with proven-benign direction, PoC not meaningful. mps_mapping_initialize declares uint16_t dpm_pg0_sz (:1721) and assigns 8 + max_dpm_entries*20 (:1774-1776); for MaxPersistentEntries >= 3277 the u16 wraps and the DPM read at :1780 uses the truncated (smaller) size. mps_config_get_dpm_pg0 memsets and bcopies with MIN(cm->cm_length, sz) (mps_config.c:541,635) so the copy is always bounded by sz into the full-size M_ZERO allocation - under-read only (persistent entries beyond ~3275 read as zero/unused). The sz parameter plumbing is u16 in mps_config.c:530 and mpsvar.h:719; fix widens all three to u32 and compile-validates with the batch.