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)
PoC verification
Evidence pack
findings/poc/DF-4467 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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).
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)
sys/crypto/cryptoapi/cryptoapi.c:370-374βstruct aes_xts_ctxcontainsuint8_t tweak[16]β per-session, mutable state.sys/crypto/cryptoapi/cryptoapi.c:412-428βaes_xts_reinit()writesctx->tweakfrom the per-call IV and encrypts it in place.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 samectx->tweakviacrypt_data_xts(..., ctx->tweak, ...)(cryptoapi.c:439-441).sys/crypto/cryptoapi/cryptoapi.c:1152-1165β the session is shared by all callers; no locking anywhere in the crypt path.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 indmstrategycontext: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_testablefix.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.
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.
No comments yet.