# DF-3561 — cryptoapi software XTS mutates shared per-session tweak (concurrency race → silent dm-crypt data corruption)

## What

`sys/crypto/cryptoapi/cryptoapi.c` — the software XTS implementations
(`aes_xts_crypt`, `twofish_xts_crypt`, `serpent_xts_crypt`) keep the 16-byte
tweak inside the **shared, heap-allocated session context**
(`struct aes_xts_ctx.tweak`, `cryptoapi.c:373`). Every call to
`cryptoapi_cipher_crypt()` on the same session:

1. `*_xts_reinit()` **overwrites** `ctx->tweak` (`cryptoapi.c:422-427`), and
2. `crypt_data_xts()` → `crypt_block_xts()` **reads and advances the same
   shared tweak** block-by-block for the whole operation
   (`cryptoapi.c:217-236, 242-250`).

cryptoapi exposes no locking. Its only in-tree consumer, dm_target_crypt
(`sys/dev/disk/dm/crypt/dm_target_crypt.c`), invokes
`cryptoapi_cipher_crypt()` on one shared `priv->crypto_session` from
concurrently-running bio paths with **no serialization**
(`dm_target_crypt_strategy` → `dmtc_bio_write_encrypt` → `dmtc_bio_encdec`,
`dm_target_crypt.c:670, 898, 959-983`).

Result: two simultaneous IOs on one software-XTS dm-crypt volume tear each
other's tweak → **wrong ciphertext is silently persisted to disk** and
decryption returns garbage. Persistent, silent data corruption of encrypted
volumes triggered by ordinary concurrent IO from any local user.

The CBC paths are unaffected (no context mutation — verified as a control).
The AES-NI XTS path (`aesni_encrypt_xts`, stack-local tweak) is unaffected.

## Reproduce (same-source userspace harness, exact kernel translation units)

```
# on the DragonFly guest (sources at /usr/src verified identical to audited tree)
sh guest_build.sh          # builds rijndael_kat, sha2_kat, curve25519_kat, cryptoapi_test, cross_check
./cryptoapi_test race      # 4 threads racing on ONE cryptoapi XTS session
# expected (vulnerable): "RACE aes-xts : ... corrupted outputs -> CORRUPTION OBSERVED", exit 3
#                       "RACE aes-cbc : ... 0 corrupted outputs" (control)
```

Observed (stock kernel sources, 6 vCPU guest):
`RACE aes-xts : 984352 iterations, 983991 corrupted outputs -> CORRUPTION OBSERVED`
`RACE aes-cbc : 3734502 iterations, 0 corrupted outputs -> no corruption`

## Reproduce (live kernel, end-to-end)

See `kernel_dm_crypt_demo.log`: kldload dm_target_crypt, vn-backed 64 MB
volume, `dmsetup create cry0 --table "0 131072 crypt aes-xts-plain64 <key> 0 /dev/vn0 0"`,
`sysctl hw.aesni_disable=1` (forces the software path). Serial write+readback:
OK. Two concurrent dd writers to disjoint halves: **5/5 rounds both halves
CORRUPT**.

## Fix validation

`fix.diff` (git-apply-able against `sys/`, verified with `git apply --check`)
moves the tweak to a stack buffer in all three software XTS functions and
drops the dead struct member. Rebuilt the identical harness against the
patched source:

`RACE aes-xts : 3401362 iterations, 0 corrupted outputs -> no corruption` —
and XTS correctness suite still 11/11 (reference cross-check + round-trips).

## Files

- `cryptoapi_test.c`   — race PoC + CBC/XTS correctness harness (the finding's trigger)
- `guest_build.sh`     — exact build commands
- `guest_race.sh`      — race + LibreSSL X25519 cross-match attempt
- `fix.diff`           — the verified fix
- `cryptoapi.c.fixed`  — patched translation unit used for fix validation
- `fix_build.log`, `fix_run.log` — patched-source build + race results
- `kernel_dm_crypt_demo.log` — end-to-end in-kernel demonstration transcript
- `verification/`      — the four-file correctness verification suite (rijndael/sha2/curve25519 KATs,
                          LibreSSL cross-match, full logs, env, static checks)
