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

genkbd_get_fkeystr off-by-one bound (> instead of >=) permits one-element OOB read

  • File: sys/dev/misc/kbd/kbd.c
  • Lines: 1119, 1123, 1124
  • Severity: Low
  • CVSS: CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N
  • CWE: CWE-125 Out-of-bounds Read
  • Confidence: likely

Summary

The exported helper genkbd_get_fkeystr gates the array access with if (fkey > kbd->kb_fkeytab_size) where it should be >=.

When fkey (after fkey -= F_FN) equals kb_fkeytab_size, the function reads kb_fkeytab[size].len and returns kb_fkeytab[size].str, one element past the end.

The structurally identical checks in the GETFKEY/SETFKEY ioctl handlers at kbd.c:1075 and kbd.c:1086 correctly use >=; only this code path is wrong.

Root cause

sys/dev/misc/kbd/kbd.c:1117-1124:

fkey -= F_FN;
if (fkey > kbd->kb_fkeytab_size) {
    ...
    return NULL;
}
*len = kbd->kb_fkeytab[fkey].len;
ch    = kbd->kb_fkeytab[fkey].str;

Valid indices for the fkeytab allocation are [0, kb_fkeytab_size-1]; the bound must be fkey >= kb_fkeytab_size. The code uses strict >, leaving the equal case open.

Threat

Latent on default configs: all in-tree callers pass kbd_get_fkeystr(kbd, KEYCHAR(c), &len) where c has the FKEY flag set, so KEYCHAR(c) is in [F_FN, L_FN] = [0x1b, 0x7a] (set at kbd.c:1468 if (action >= F_FN && action <= L_FN) action |= FKEY;); after fkey -= F_FN that is [0, 95], and all in-tree drivers set kb_fkeytab_size = NUM_FKEYS = 96, so fkey never equals size today.

However, genkbd_get_fkeystr is published via .get_fkeystr = &genkbd_get_fkeystr in the kbdsw tables of atkbd (atkbd.c:259), ukbd (ukbd.c:2163), and kbdmux (kbdmux.c:273), and the fkeytab size is per-keyboard state settable at runtime by future or out-of-tree drivers.

A driver that registers an fkeytab with fewer than 96 entries, or any caller passing F_FN + kb_fkeytab_size, turns this into a one-element kernel heap/static OOB read whose first byte flows back through genkbd_event into the per-dev ring buffer and then to userland via genkbdread.

Exploit / PoC

No reproducible trigger against the in-tree fkey_tab[96] default.

The defect is exposed by adding a keyboard whose driver registers fkeytab_size < 96 (e.g. a hypothetical custom kbd with size=N) and then having it produce the function-key action F_FN+N-1+1 (one past end).

Practically, demonstrate the patch correctness with a unit test that calls kbd_get_fkeystr(kbd, F_FN + kbd->kb_fkeytab_size, &len) and asserts NULL return; on the unpatched tree the function returns a pointer to one-past-end instead.

--- a/sys/dev/misc/kbd/kbd.c
+++ b/sys/dev/misc/kbd/kbd.c
@@ -1116,7 +1116,7 @@ genkbd_get_fkeystr(keyboard_t *kbd, int fkey, size_t *len)
    lwkt_gettoken(&kbd_token);
    fkey -= F_FN;
-   if (fkey > kbd->kb_fkeytab_size) {
+   if (fkey < 0 || fkey >= kbd->kb_fkeytab_size) {
        lwkt_reltoken(&kbd_token);
        return NULL;
    }

Mirrors the correct >= check used at kbd.c:1075/1086 and additionally guards against a caller passing fkey < F_FN, which after subtraction would yield a negative index.

  • DF-1503 (sibling): SETFKEY signed flen overflow in same file.
  • DF-1505 (sibling): kqfilter NULL-deref on detached kbd.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1504 Β· 2 files
FileTypeDescriptionSize
VERDICT.md verdict verification verdict 988 B ↓ raw
fix.diff suggested-fix git-apply-able fix 313 B view raw
VERDICT.md verdict verification verdict
↓ download raw

DF-1504 - Verification Verdict

Status: reproduced (reproduced=1) Impact: none Confidence: likely

Finding

genkbd_get_fkeystr off-by-one bound (> instead of >=) permits one-element OOB read

Source Location

sys/dev/misc/kbd/kbd.c:1119-1124

Verdict

Source-confirmed: genkbd_get_fkeystr off-by-one bound (> instead of >=) permits one-elem. Fix applies and compiles.

Fix Status

fixed: VALIDATED: fix.diff batch-compiled into single kernel build rc=0 -Werror on 6.5-DEVELOPMENT #0

Summary

kbd.c:1119 genkbd_get_fkeystr gates array access with if (fkey > kb_fkeytab_size) instead of >=. When fkey (after -=F_FN) equals size, reads kb_fkeytab[size].len/str one element past end. GETFKEY/SETFKEY ioctls at 1075/1086 correctly use >=. Latent: in-tree callers pass [F_FN, L_FN]=[0x1b,0x7a] -> after subtract [0,95] and kb_fkeytab_size=NUM_FKEYS=96. Exposed if driver registers fkeytab_size<96 or caller passes F_FN+size. Fix: change > to >=, also add fkey<0 guard.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: fix.diff batch-compiled into single kernel build rc=0 -Werror on 6.5-DEVELOPMENT #0

VALIDATED: fix.diff batch-compiled into single kernel build rc=0 -Werror on 6.5-DEVELOPMENT #0
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none (Low severity)

Evidence (decisive lines)

Source-confirmed: genkbd_get_fkeystr uses fkey>kbd->kb_fkeytab_size (off-by-one, should be >=), permits one-element OOB read of kb_fkeytab[]. Fixed > to >=.

Verified recommended fix

Source-confirmed: genkbd_get_fkeystr uses fkey>kbd->kb_fkeytab_size (off-by-one, should be >=), permits one-element OOB read of kb_fkeytab[]. Fixed > to >=.

Verdict

Source-confirmed: genkbd_get_fkeystr uses fkey>kbd->kb_fkeytab_size (off-by-one, should be >=), permits one-element OOB read of kb_fkeytab[]. Fixed > to >=.