โฌข DragonFlyBSD Kernel Audit
DF-0281 / idiv_comparison.txt
โ† back to finding โ†“ download raw
DF-0281 โ€” OBJECT-CODE-LEVEL PROOF: unguarded `idiv` (unpatched) vs guarded (patched)
====================================================================================
Both snippets are from `ng_btsocket_rfcomm_send_credits()` inlined into
`ng_btsocket_rfcomm_sessions_task` in the ACTUAL compiled `ng_btsocket.ko`
module built from /usr/src/sys/netgraph7/bluetooth/socket/ against the
audit-source kernel (DragonFly 6.5-DEVELOPMENT #0, Thu Jul 2 06:02:54 UTC 2026).

The divisor is `pcb->mtu` (loaded from offset 0x24 of the PCB, a `u_int16_t`).
In the source this is line 3283:
    credits = ssb_space(&pcb->so->so_rcv) / pcb->mtu;
When `pcb->mtu == 0` (set by a peer PN MCC frame, line 3019, no validation),
the `idiv` raises #DE -> kernel panic.


UNPATCHED (vulnerable) โ€” `idiv %r8` at offset 0xb418, NO guard before it:
------------------------------------------------------------------------
    b3df:	movzwl 0x24(%r13),%edi      ; edi  = pcb->mtu (the divisor)
    b3e4:	test   %edx,%edx             ; (unrelated CFC flag check)
    b3e6:	jne    b6bc
    b3ec:	mov    0xf0(%rcx),%rsi       ; \  ssb_space(&pcb->so->so_rcv)
    b3f3:	mov    0xf8(%rcx),%rax       ;  > (the dividend)
    b3fa:	movzwl %di,%r8d              ; /  r8 = pcb->mtu (ZERO-EXTENDED = divisor)
    b3fe:	sub    0x90(%rcx),%rax
    b405:	sub    0x88(%rcx),%rsi
    b40c:	cmp    %rax,%rsi
    b40f:	cmovg  %rax,%rsi
    b413:	mov    %rsi,%rax             ; rax = dividend
    b416:	cqto                          ; sign-extend rax into rdx:rax
    b418:	idiv   %r8                   ; *** DIVIDE BY pcb->mtu โ€” NO GUARD ***
                                        ; *** if r8==0 -> #DE -> kernel panic ***
    b41b:	mov    %eax,%r9d             ; r9d = credits

NOTE: there is NO `test %r8d,%r8d; je` or any zero-check on the divisor
between loading pcb->mtu (b3df/b3fa) and the `idiv` (b418). The divide is
unconditional. This is the compiled form of the unguarded line 3283.


PATCHED (fix.diff applied) โ€” `idiv %rdi` at offset 0xb4b2, GUARDED by `test; je`:
--------------------------------------------------------------------------------
    b46b:	movzwl 0x24(%r13),%ecx       ; ecx = pcb->mtu (the divisor)
    b470:	test   %cx,%cx                ; *** THE GUARD: is pcb->mtu == 0? ***
    b473:	je     b7d3                   ; *** YES -> jump to clean return (b7d3),
                                        ;     SKIPPING the divide entirely ***
    b479:	mov    0x0(%r13),%rsi         ; only reached when pcb->mtu != 0
    b47d:	testb  $0x4,0xe1(%rsi)
    b484:	jne    b5b4
    b48a:	mov    0xf0(%rsi),%rax       ; \  ssb_space(...) dividend
    b491:	mov    0xf8(%rsi),%rdx       ;  >
    b498:	movzwl %cx,%edi               ; /  edi = pcb->mtu (KNOWN NON-ZERO here)
    b49b:	sub    0x90(%rsi),%rdx
    b4a2:	sub    0x88(%rsi),%rax
    b4a9:	cmp    %rdx,%rax
    b4ac:	cmovg  %rdx,%rax
    b4b0:	cqto
    b4b2:	idiv   %rdi                   ; *** DIVIDE โ€” only reached if pcb->mtu != 0 ***
    b4b5:	mov    %eax,%r9d             ; r9d = credits

    ...
    b7d3:	mov    0xb8(%rbx),%eax       ; <-- je target: clean return path
    b7d9:	jmpq   ab8f                   ;     (function epilogue / next iteration)

The `test %cx,%cx; je b7d3` at b470/b473 is exactly the compiler's emission of
the fix's `if (pcb->mtu == 0) return (EINVAL);` guard. When pcb->mtu == 0,
execution jumps past the `idiv` to b7d3 (clean return). The divide-by-zero is
now impossible.

CONCLUSION: fix.diff closes the bug at the object-code level. The unguarded
`idiv %r8` (unpatched) becomes a guarded `idiv %rdi` preceded by
`test %cx,%cx; je <return>` (patched).