Michael MIC verification uses non-constant-time memcmp (defense-in-depth)
| Field | Value |
|---|---|
| ID | DF-0595 |
| Status | new |
| Severity | Info |
| CVSS 3.1 | CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N |
| CWE | CWE-208 Observable Timing Discrepancy; CWE-697 Incorrect Comparison |
| File | sys/netproto/802_11/wlan_tkip/ieee80211_crypto_tkip.c |
| Lines | 359-361 |
| Area | netproto/802_11 (TKIP crypto) |
| Confidence | certain |
| Discovered | 2026-07-02 |
| Reported | pending |
Summary
tkip_demic compares the computed Michael MIC against the received MIC
using libc memcmp, which short-circuits on the first differing byte. This
is the canonical enabling side-channel for byte-by-byte MIC-forcing attacks
(Erik Tews' "chopchop" family) on TKIP. The practical bar is high β the
timing difference is sub-microsecond while WiFi RTT and the hostap_input
softirq path dominate β but the fix is trivial and the spec-recommended
practice is to compare tags in constant time.
Root cause
sys/netproto/802_11/wlan_tkip/ieee80211_crypto_tkip.c:361:
359: m_copydata(m, m->m_pkthdr.len - tkip.ic_miclen,
360: tkip.ic_miclen, mic0);
361: if (memcmp(mic, mic0, tkip.ic_miclen)) {
memcmp returns as soon as any byte differs; the branch and memory-access
pattern are attacker-observable in principle. The same pattern exists in
sys/netproto/802_11/wlan_ccmp/ieee80211_crypto_ccmp.c:642, so this is a
tree-wide habit rather than TKIP-specific. There is no kmemcmp /
timingsafe_bcmp use anywhere in netproto/802_11.
Threat model & preconditions
- Attacker position: remote adjacent-network attacker within RF range.
- Privileges gained or impact: in principle, byte-by-byte MIC recovery via timing; in practice, heavily throttled by 802.11 MIC countermeasures (at most 2 MIC failures per 60 s before the link is shut down). Useful only as a defense-in-depth hardening item.
- Required config or capabilities: attacker within RF range of a TKIP-protected BSS, capable of injecting many forged frames and measuring per-frame processing time finely.
- Reachability: requires the SW-MIC path
(
k->wk_flags & IEEE80211_KEY_SWDEMIC).
Proof of concept
Pure hardening; not exploited here. The methodology (Beck-Tews) would be:
keep TSC advancing (one QoS TID per attempt to avoid replay), submit a
frame whose last MIC byte is each of 0..255, time the AP's response. Wrong
byte β early memcmp return (statistically faster); right byte β
full-length compare (slightly slower). Repeat for the preceding byte using
the recovered one. Not provided because (1) it is well-known, (2)
countermeasures throttle it to ~1 byte/min in practice, and (3) the goal
of this finding is the trivial constant-time fix, not a new attack.
Impact
- Blast radius: any DragonFlyBSD system using the SW TKIP MIC path.
- Severity rationale: Info β hardening opportunity, defense-in-depth. No demonstrated impact; the timing channel is real in principle but dominated by channel jitter and throttled by MIC countermeasures in practice.
- Reliability: not currently exploited.
Recommended fix
Replace the memcmp with a constant-time comparison:
--- a/sys/netproto/802_11/wlan_tkip/ieee80211_crypto_tkip.c
+++ b/sys/netproto/802_11/wlan_tkip/ieee80211_crypto_tkip.c
@@ -358,7 +358,12 @@ tkip_demic(struct ieee80211_key *k, struct mbuf *m, int force)
m_copydata(m, m->m_pkthdr.len - tkip.ic_miclen,
tkip.ic_miclen, mic0);
- if (memcmp(mic, mic0, tkip.ic_miclen)) {
+ {
+ u_int _i;
+ u8 _d = 0;
+ for (_i = 0; _i < tkip.ic_miclen; _i++)
+ _d |= mic[_i] ^ mic0[_i];
+ if (_d != 0) {
/* NB: 802.11 layer handles statistic and debug msg */
ieee80211_notify_michael_failure(vap, wh,
k->wk_rxkeyix != IEEE80211_KEYIX_NONE ?
k->wk_rxkeyix : k->wk_keyix);
+ }
}
tkip.ic_miclen is fixed at compile time (= IEEE80211_WEP_MICLEN = 8),
so the loop is fully unrollable. The same treatment should be applied to
sys/netproto/802_11/wlan_ccmp/ieee80211_crypto_ccmp.c:642.
References
- Beck-Tews chopchop attack on TKIP β historical context for the timing channel.
memcmp(3)semantics: short-circuit on first differing byte.- DragonFlyBSD
bcmp(9)/ constant-time-compare idioms elsewhere in the kernel (e.g. crypto subsystem).
Timeline
- 2026-07-02 Discovered during automated DragonFlyBSD kernel security audit.
- 2026-07-02 Reported to DragonFlyBSD security contact (pending) as a defense-in-depth hardening item.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0595 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| mic_timing_demo.c | trigger-source | userspace timing-channel demonstration: proves guest libc memcmp is non-constant-time (principle) and that the libkern timingsafe_bcmp idiom removes the gap | 6.2 KB | view raw |
| build.sh | build-script | cc -O2 -o mic_timing_demo mic_timing_demo.c | 166 B | view raw |
| run.sh | run-script | ./mic_timing_demo | 108 B | view raw |
| build.log | build-log | final successful userspace build (BUILD_EXIT=0) | 13 B | view raw |
| run.log | run-log | decisive run #1, full output (256B ratio 2.248) | 1.8 KB | view raw |
| run.2.log | run-log | stress run #2 (256B ratio 2.001) | 1.8 KB | view raw |
| run.3.log | run-log | stress run #3 (256B ratio 2.258) | 1.8 KB | view raw |
| fix.diff | suggested-fix | memcmp -> timingsafe_bcmp in tkip.c:361 and ccmp.c:642; git apply --check clean | 1.1 KB | view raw |
| fix_build.log | build-log | full make nativekernel output, NK_DONE rc=0 | 5.6 MB | β download |
| fix_run.log | run-log | userspace demo re-run on patched kernel (libc control; kernel module change verified via nm in env.txt) | 804 B | view raw |
| env.txt | environment | uname, kern.version #1, sha256, module symbol audit (memcmp=0, timingsafe_bcmp=2) | 840 B | view raw |
| VERDICT.md | verdict | full narrative: reproduced? mechanism, primitive demo, fix, fix-validation | 6.8 KB | β raw |
| README.md | readme | human-readable reproduction guide | 2.4 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0595 β PoC: Michael MIC verification uses non-constant-time memcmp
DF-0595 documents that the DragonFlyBSD kernel's TKIP and CCMP Michael-MIC
verification paths compare the computed 8-byte MIC tag against the received
tag with libc memcmp, which short-circuits on the first differing byte.
That short-circuit is the textbook enabling primitive for byte-by-byte
MIC-forcing ("chopchop"-style) attacks against TKIP.
Files
mic_timing_demo.cβ runnable userspace timing-channel demonstration. Proves the guest's libcmemcmpis non-constant-time (clear, repeatable last-byte/first-byte timing gap at longer lengths; noisy-but-real at the 8-byte MIC length, exactly as the finding predicts) and that the constant- time idiom DragonFlyBSD already ships (timingsafe_bcmp) removes the gap.build.sh/run.shβ exact build & run.build.log,run.log,run.2.log,run.3.logβ full untrimmed output.fix.diffβ the kernel fix:memcmpβtimingsafe_bcmpin bothwlan_tkip/ieee80211_crypto_tkip.c:361andwlan_ccmp/ieee80211_crypto_ccmp.c:642.git apply --checkclean.fix_build.logβ fullmake nativekerneloutput (rc=0).fix_run.logβ userspace demo re-run on the patched kernel (libc control; kernel module change verified separately vianm/objdump in env.txt).env.txtβ guest environment, before/after kern.version, module symbol audit.VERDICT.mdβ full narrative.manifest.jsonβ artifact catalog for the static site.
How to reproduce
./build.sh && ./run.sh
You should see, in Test 1 (256-byte buffers), memcmp's last/first ratio
clearly > 1 (β2β3Γ) while timingsafe_bcmp's ratio is β1.0. Test 2 (the
actual 8-byte MIC tag) is noisy, empirically confirming the Info severity:
the channel is real in principle but dominated by WiFi RTT / softirq jitter
at the real MIC length.
The fix
The kernel-side fix is a one-token rename in two files: replace memcmp
with DragonFlyBSD's existing libkern timingsafe_bcmp (already declared in
<sys/libkern.h>, already pulled into both files via <sys/socket.h>).
See fix.diff. Validated by building a single-fix kernel (#1), installing
it, rebooting, and confirming via nm/objdump that the installed
/boot/kernel/wlan_tkip.ko and wlan_ccmp.ko reference timingsafe_bcmp
(no memcmp references remain) and that _tkip_demic+0xd5 issues
callq timingsafe_bcmp.
DF-0595 β VERDICT
Verdict
REPRODUCED (source-confirmed + primitive demonstrated) and FIX VALIDATED
(single-fix kernel built, installed, rebooted; vulnerable memcmp call
removed from both MIC-verification modules, replaced by timingsafe_bcmp).
The finding (what was claimed)
sys/netproto/802_11/wlan_tkip/ieee80211_crypto_tkip.c:361 and
sys/netproto/802_11/wlan_ccmp/ieee80211_crypto_ccmp.c:642 verify the
Michael MIC tag with libc memcmp. memcmp short-circuits on the first
differing byte, exposing a per-frame timing side-channel β the canonical
enabling primitive for Beck-Tews "chopchop"-style byte-by-byte MIC-forcing
attacks against TKIP. Severity: Info (defense-in-depth; in practice the
signal is sub-Β΅s and dominated by WiFi RTT / softirq jitter, and is further
throttled by 802.11 MIC countermeasures β at most 2 MIC failures per 60 s
before the link shuts down).
Confirmation (source-level)
Both cited lines are exactly as claimed, unmodified on the unpatched
6.5-DEVELOPMENT #0 baseline:
sys/netproto/802_11/wlan_tkip/ieee80211_crypto_tkip.c:361: if (memcmp(mic, mic0, tkip.ic_miclen)) { sys/netproto/802_11/wlan_ccmp/ieee80211_crypto_ccmp.c:642: if (memcmp(mic, a, ccmp.ic_trailer) != 0) {
Both compare a fixed 8-byte tag (IEEE80211_WEP_MICLEN = 8).
memcmp(3) is documented to short-circuit; the branch/memory-access pattern
is therefore attacker-observable in principle.
Primitive demonstration (runnable)
mic_timing_demo.c is a userspace timing-channel demonstration. It times
libc memcmp and the libkern timingsafe_bcmp XOR-accumulate idiom over
two adversarial cases of equal-length buffers:
- first-byte-diff β early
memcmpexit after 1 compare - last-byte-diff β full
memcmpscan
Three runs on the unpatched #0 guest:
| Run | memcmp ratio (256B) | ct_bcmp ratio (256B) | memcmp ratio (8B) | ct_bcmp ratio (8B) |
|---|---|---|---|---|
| 1 | 2.248 | 0.970 | 1.002 | 0.746 |
| 2 | 2.001 | 0.969 | 0.999 | 1.331 |
| 3 | 2.258 | 1.062 | 0.672 | 1.668 |
Test 1 (256-byte, principle): memcmp's last/first ratio is a clean,
consistent 2.0β2.3Γ across all runs; timingsafe_bcmp's is flat
(0.97β1.06). This unambiguously proves the guest libc's memcmp is
short-circuit / non-constant-time β i.e. the primitive the finding warns
about genuinely exists in this environment.
Test 2 (8-byte, the actual MIC length): noisy β sometimes >1, sometimes
<1. This is exactly the finding's own thesis: at the real MIC length the
signal is real but tiny and swamped by jitter, dominated by WiFi RTT and
the hostap_input softirq path in deployment. This empirically confirms
the Info severity rating (not Critical).
The demonstration is kernel-independent (it targets libc) by design: the guest has no WiFi hardware, so the in-kernel SWDEMIC path is not reachable at runtime. Source-level confirmation plus primitive demonstration is the correct bar for a timing side-channel of this class.
No escalation (correctly)
This is a timing side-channel / hardening finding, not a memory-corruption primitive. There is no escalation chain to develop β the impact ceiling is "byte-by-byte MIC recovery via many precisely-timed frame injections, heavily throttled by MIC countermeasures in practice", which is the defense-in-depth concern the finding documents.
Fix
fix.diff is a minimal, idiomatic one-token rename in each file: replace
memcmp with DragonFlyBSD's existing libkern timingsafe_bcmp. Rationale:
timingsafe_bcmpis already in the tree (sys/libkern/timingsafe_bcmp.c, declared insys/sys/libkern.h:99) and is already the idiom used by the crypto subsystem (sys/crypto/chachapoly.c:195,sys/crypto/curve25519/curve25519.c) and WireGuard (sys/net/wg/wg_noise.c,sys/net/wg/wg_cookie.c).- It is already declared in both files' include chain: both include
<sys/socket.h>, which includes<sys/libkern.h>"for bcmp()" atsys/sys/socket.h:241. - Semantics match perfectly:
timingsafe_bcmpreturns non-zero on difference (likebcmp), soif (memcmp(...))andif (memcmp(...) != 0)are exactly equivalent toif (timingsafe_bcmp(...))/if (timingsafe_bcmp(...) != 0). tkip.ic_miclen/ccmp.ic_trailerare compile-time-constant 8, so the XOR-accumulate loop is fully unrollable.
This supersedes the finding markdown's proposed inline-XOR-loop fix,
which is functionally equivalent but reinvents timingsafe_bcmp and
introduces awkward brace nesting. Using the existing libkern helper is the
DragonFlyBSD-idiomatic form.
Fix validation (Phase 8)
- Baseline (
#0, unpatched): confirmedmemcmpat the cited lines in/usr/src; primitive demonstrated (Test 1 ratios 2.0β2.3Γ). - Applied
fix.diffto in-guest/usr/srcviapatch -p1β both hunks applied cleanly (PATCH_EXIT=0). - Built single-fix kernel:
make -j6 nativekernel KERNCONF=X86_64_GENERICβNK_DONE rc=0. (Full build log infix_build.log.) - Object-level proof of the fix (before reboot):
-
ieee80211_crypto_tkip.o:U timingsafe_bcmp(wasmemcmp) -ieee80211_crypto_ccmp.o:U timingsafe_bcmp(wasmemcmp) -objdump -dr wlan_tkip.koshowstkip_demic+0xd5: callq ... R_X86_64_PLT32 timingsafe_bcmp-0x4 - Installed + rebooted (
make installkernel; overwrote bare/boot/kernel/kernelwith the stripped build after clearingschg; rebooted). Patched kernelkern.version:DragonFly 6.5-DEVELOPMENT #1: Tue Jul 14 21:30:33 UTC 2026(sha25656317050...17194f27). - Running-kernel module audit (after):
-
/boot/kernel/wlan_tkip.ko+/boot/kernel/wlan_ccmp.ko:U timingsafe_bcmpΓ2,U memcmpΓ0.
Before/after contrast (the negation of the bad-behavior marker
"memcmp present in MIC verification"):
- Before (
#0):memcmp(mic, mic0, ...)at tkip.c:361,memcmp(mic, a, ...) != 0at ccmp.c:642; modules referencememcmp. - After (
#1): both call sites usetimingsafe_bcmp; installed modules referencetimingsafe_bcmp(Γ2) andmemcmp(Γ0).
fix_status = fixed.
PoC changes
- Added
mic_timing_demo.c(runnable primitive demonstration) β the finding shipped with no PoC ("defense-in-depth, not exploited"). The demo makes the finding's premise concrete and runnable on the static site without requiring WiFi hardware. - Added
fix.diff(authored post-verification; supersedes the finding markdown's inline-loop proposal with the idiomatictimingsafe_bcmp). - Added
build.sh,run.sh,VERDICT.md,manifest.json, full logs,env.txt.
Fix verification
fixedVALIDATED: baseline memcmp at 2 sites; patched timingsafe_bcmp (nm: 0 memcmp refs, 2 timingsafe_bcmp refs in modules).
BEFORE: memcmp at :361/:642. AFTER: timingsafe_bcmp. nm: 0 memcmp, 2 timingsafe_bcmp.
Confirmed kernel references
Detail
Exploit chain
none -- timing side-channel, not memory corruption. Ceiling: byte-by-byte MIC recovery (Beck-Tews chopchop), throttled by MIC countermeasures.
Evidence (decisive lines)
Test1 256B: memcmp ratio 2.2-2.3x, timingsafe_bcmp ~1.0. Test2 8B: noisy ~1.0. Source: memcmp at :361/:642. Patched: timingsafe_bcmp.
PoC changes
Authored: mic_timing_demo.c (timing channel demo), fix.diff (memcmp->timingsafe_bcmp at 2 sites), VERDICT.md, manifest.json.
Verified recommended fix
Replace memcmp with timingsafe_bcmp at tkip.c:361 and ccmp.c:642. Existing libkern function, semantics-preserving. Supersedes finding's inline XOR-loop. Full diff in findings/poc/DF-0595/fix.diff.
Verdict
REPRODUCED (source+demo). memcmp at tkip.c:361 + ccmp.c:642 non-constant-time MIC comparison. Demo: 256B ratio 2.0-2.3x (short-circuit confirmed). 8B MIC noisy (Info severity confirmed). No WiFi HW.
No comments yet.