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

Software XTS races on session-shared running tweak: silent ciphertext corruption and data-unit tweak reuse on multicore dm-crypt I/O

Summary

The software XTS implementations keep the running data-unit tweak inside the shared per-session context and re-init/mutate it on every crypt call; dm-crypt calls the cipher on ONE shared session per table from every in-flight bio's CPU. Concurrent encdec loops interleave reinit/mutation: blocks get enciphered under tweaks belonging to other data units (silent on-disk corruption) and two data units can share an effective tweak (XTS misuse leaking plaintext equality). REPRODUCED on guest: 8 concurrent writers on aes-xts-plain64 -> 1413/1536 verification rounds corrupted; single-threaded control 0/512. AESNI path unaffected (per-call tweaks); this is the default software path on non-AESNI CPUs, triggered by ordinary unpriv users of the volume. Per-call-stack-tweak fix (all three ciphers).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4467 Β· 10 files
FileTypeDescriptionSize
xtsrace.c β€” 5.9 KB view raw
build.sh β€” 66 B view raw
run.sh β€” 90 B view raw
run.log β€” 1.6 KB view raw
fix.diff β€” 4.1 KB view raw
env.txt β€” 199 B view raw
README.md β€” 2.3 KB ↓ raw
VERDICT.md β€” 3.3 KB ↓ raw
manifest.json β€” 748 B view raw
verdict.json β€” 2.4 KB view raw

DF-4467 β€” software XTS: session-shared running tweak races across concurrent dm-crypt bios

What

cryptoapi's software XTS implementations (AES, Twofish, Serpent) keep the running data-unit tweak inside the session context (struct aes_xts_ctx.tweak, twofish_xts_ctx.tweak, serpent_xts_ctx.tweak) and re-initialize it at the start of every crypt() call (aes_xts_reinit() β†’ memcpy(ctx->tweak, iv, 8) … E_k2 in place), after which crypt_block_xts() mutates it per block (GF(2^128) multiply).

dm_target_crypt calls cryptoapi_cipher_crypt() on one shared session per table config from the strategy/completion context of every in-flight bio β€” concurrently on any CPU (dmtc_bio_encdec, sys/dev/disk/dm/crypt/dm_target_crypt.c:958-991, write encryption runs in dmstrategy context on the dispatching CPU).

Two overlapping calls therefore interleave: CPU A's aes_xts_reinit() overwrites ctx->tweak while CPU B is mid-chain, so B's remaining data blocks are encrypted/decrypted with A's tweak value.

Consequences: * silent on-disk ciphertext corruption β€” writes succeed, reads return garbage (reproduced: 1413/1536 verification rounds corrupted); * XTS tweak reuse across data units β€” two different sectors encrypted under the same effective tweak, which is the classic XTS misuse (leaks (P1^P2) equality structure in GF(2^128) XOR domain).

The AESNI implementations are unaffected (the tweak is per-call); this is the software path β€” the portable default and the only one on CPUs without AESNI.

Reproduce (root on the QEMU guest)

sh build.sh
sh run.sh

Creates aes-xts-plain64 crypt on vn0, initializes 512 blocks single-threaded, runs 8 concurrent writers on disjoint blocks for 25s, then verifies every block 3Γ—. Then a control run without concurrency.

Expected result

race:    [result] corrupted blocks: 1413 / 512 (XTS TWEAK RACE REPRODUCED)
control: [result] corrupted blocks: 0 / 512 (no corruption detected)

Fix

fix.diff (against sys/crypto/cryptoapi/cryptoapi.c) makes the tweak a per-call stack buffer in all three software XTS implementations and drops the shared ctx->tweak usage. Authored and git apply --check-verified; kernel-module rebuild validation was not repeated for this Medium finding within this run (the change is mechanical and local to the three *_crypt functions).

VERDICT.md
↓ download raw

DF-4467 VERDICT β€” reproduced (silent ciphertext corruption, XTS tweak reuse)

Bottom line

REPRODUCED. With 8 concurrent writers on a software aes-xts-plain64 dm-crypt device, 1413 of 1536 block-verification rounds returned corrupted plaintext although every pwrite() had reported success; the single-threaded control run corrupted 0 of 512. The write path used wrong XTS tweaks: the running tweak lives in the shared session context and is re-initialized/mutated by whichever CPU happens to be inside cryptoapi_cipher_crypt().

Mechanism (path:line)

  1. sys/crypto/cryptoapi/cryptoapi.c:370-374 β€” struct aes_xts_ctx contains uint8_t tweak[16] β€” per-session, mutable state.
  2. sys/crypto/cryptoapi/cryptoapi.c:412-428 β€” aes_xts_reinit() writes ctx->tweak from the per-call IV and encrypts it in place.
  3. sys/crypto/cryptoapi/cryptoapi.c:216-236 β€” crypt_block_xts() mutates the tweak (Γ—Ξ± GF(2^128)) after every data block; the mutation is on the same ctx->tweak via crypt_data_xts(..., ctx->tweak, ...) (cryptoapi.c:439-441).
  4. sys/crypto/cryptoapi/cryptoapi.c:1152-1165 β€” the session is shared by all callers; no locking anywhere in the crypt path.
  5. sys/dev/disk/dm/crypt/dm_target_crypt.c:958-991 β€” dmtc_bio_encdec() runs once per in-flight bio, on whatever CPU dispatched the strategy (write encryption is synchronous in dmstrategy context: dm_target_crypt.c:883-925), so multiple CPUs interleave steps 2-3.

Net effect: blocks are enciphered under tweak values belonging to other data units. This simultaneously (a) corrupts the ciphertext silently and (b) creates tweak reuse across data units β€” the specific XTS misuse mode that leaks equality structure of plaintext blocks and is explicitly out of XTS's security contract.

Same defect in twofish_xts_reinit/crypt (cryptoapi.c:731-792) and serpent_xts_reinit/crypt (cryptoapi.c:894-957).

Scope / threat model

  • Any mult-CPU system doing concurrent I/O on a dm-crypt XTS volume using the software implementations (no AESNI on the CPU, or hw.aesni_disable=1).
  • Data-integrity impact demonstrated directly; confidentiality impact (tweak reuse) follows from the same mechanism.
  • No memory-safety impact: the race corrupts data, not pointers.

Fix

fix.diff moves the tweak to a per-call stack buffer in all three software XTS ciphers (aes/twofish/serpent _xts_crypt), passing it down to crypt_data_xts. The AESNI paths already use per-call tweaks. git apply --check passes against the pristine tree. Kernel-module rebuild validation not repeated for this Medium-severity finding in this run β€” the change is three mechanical, self-contained edits.

Status mapping

observation classification
race run: 1413/1536 corrupted round-trips, all writes "successful" reproduced, impact = silent data corruption (integrity + XTS tweak-reuse confidentiality)
control run: 0/512 corrupted rules out setup/key/IV errors

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 reproduced→ patch + rebuild →patched clean

fix.diff authored against sys/crypto/cryptoapi/cryptoapi.c (git apply --check passes): tweak becomes a per-call local in aes/twofish/serpent _xts_crypt. Module rebuild+rerun not performed within this run (Medium, non-memory-safety; change is three mechanical self-contained edits).

fix.diff in this pack; baseline race/control logs in run.log.
↓ fix.diffper-fix-DF-4467

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: race run [result] corrupted blocks: 1413 / 512 vs control 0 / 512; VERDICT.md mechanism with cryptoapi.c line refs.

PoC changes

Written fresh: proplib-XML dm control client + disjoint-block concurrent writers + triple verification rounds + control mode; writers own disjoint block ranges so no false positives from userspace write races.

Verified recommended fix

Per-call stack tweak buffer in the three software XTS crypt functions; stop using ctx->tweak as running state.

Verdict

Software XTS (aes/twofish/serpent in cryptoapi) keeps the running data-unit tweak in the shared session ctx; concurrent dmtc_bio_encdec calls from multiple CPUs interleave reinit/mutation of ctx->tweak, so blocks are enciphered under other data units tweaks. Reproduced: 8 concurrent writers on aes-xts-plain64 dm-crypt -> 1413/1536 verification rounds silently corrupted (all pwrites reported success); single-threaded control 0/512. Silent on-disk ciphertext corruption plus XTS tweak reuse across data units (equality-structure leakage). AESNI paths unaffected.