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

camellia_set_key() performs no key-length validation: on production kernels any invalid bits is silently accepted, the schedule is never generated, and camellia_encrypt/camellia_decrypt degenerate to the identity function

Summary

camellia_set_key forwards `bits` to Camellia_Ekeygen unvalidated and stores ctx->bits unconditionally. The only guard is the KASSERT at camellia.c:1253-1254, compiled out on production kernels; the switch's default:break then leaves the 68-word schedule never generated while ctx->bits records the invalid length, and Camellia_EncryptBlock/DecryptBlock's own default paths copy the block src->dst with no rounds - encryption becomes the identity function. Unlike the rijndael twin DF-3568 there is no memory-safety violation; the failure is purely cryptographic. VERIFIED in-guest on production semantics (-DNDEBUG, real kernel sources built userland): bits in {64,0,-1,129} all accepted with ciphertext == plaintext (BUG-CONFIRMED exit 42); debug build aborts at camellia.c:1254 proving the guard is INVARIANTS-only; fix.diff (reject in set_key before touching key/ctx + refuse-invalid-schedule guards) validated at harness level: BUG-NOT-PRESENT exit 0 with RFC 3713 Appendix A KATs still passing. Latent: zero in-tree consumers; ships as dead code in option-crypto kernels and crypto.ko. A future consumer forwarding a user-influenced key length would silently get plaintext pass-through instead of encryption.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3569 Β· 12 files
FileTypeDescriptionSize
README.md β€” 4.2 KB ↓ raw
VERDICT.md β€” 5.0 KB ↓ raw
guard_cam_badbits.c β€” 7.4 KB view raw
fix.diff β€” 2.0 KB view raw
build.sh β€” 322 B view raw
run.sh β€” 187 B view raw
build.log β€” 1.1 KB view raw
run.log β€” 1.2 KB view raw
fixval.log β€” 983 B view raw
env.txt β€” 369 B view raw
manifest.json β€” 1.8 KB view raw
verdict.json β€” 4.6 KB view raw

DF-3569 β€” camellia_set_key() accepts any key length (API-boundary validation gap)

What

sys/crypto/camellia/camellia-api.c:37-42 β€” camellia_set_key() is a void wrapper that forwards bits to Camellia_Ekeygen() without validating anything, then unconditionally stores ctx->bits = bits. The only key-length guard in the whole path is the KASSERT at sys/crypto/camellia/camellia.c:1253-1254, which is compiled out on production kernels (no INVARIANTS). Under those semantics:

  • Camellia_Ekeygen()'s switch takes default: break; β€” the 68-word key schedule is never generated (whatever the caller left in ctx->subkey stays there),
  • ctx->bits records the invalid length anyway,
  • the subsequent Camellia_EncryptBlock/Camellia_DecryptBlock (camellia.c:1271-1330) switches take default: break; too, so the 16-byte block is copied srcβ†’dst with no round function: encryption silently degenerates to the identity function.

Twin of DF-3568 (rijndael_set_key, same class) and the API-side companion of DF-3560 (camellia.c unzeroed schedules / KASSERT-only guard). Unlike rijndael there is no memory-safety violation β€” camellia's default: paths touch neither the key buffer nor the schedule β€” the failure is purely cryptographic: a caller that forwards an unchecked length (e.g. user-controlled) gets a silent no-op cipher instead of an error.

Reachability (why Info, not higher)

  • Zero in-tree consumers. Tree-wide search finds no caller of camellia_set_key/camellia_encrypt/camellia_decrypt and no includer of crypto/camellia/camellia.h outside the directory itself. The only references are the build registrations sys/conf/files:2258-2259 (optional crypto) and sys/crypto/Makefile:19-20 (the crypto.ko primitive bundle).
  • Compiled into every option crypto kernel / crypto.ko as dead code; out-of-tree kmods could consume it, which is not a base-system attack path.
  • No unprivileged entry point exists today β†’ latent hardening of the API contract for future callers, filed Info per the batch precedent (DF-3568).

Build (on the audit guest, sources at /usr/src byte-identical to the tree)

cd /tmp/df3569   # contains guard_cam_badbits.c, fix.diff
cc -O2 -DNDEBUG -I /usr/src/sys -o guard_prod guard_cam_badbits.c \
   /usr/src/sys/crypto/camellia/camellia-api.c \
   /usr/src/sys/crypto/camellia/camellia.c
cc -O2 -I /usr/src/sys -o guard_dbg guard_cam_badbits.c \
   /usr/src/sys/crypto/camellia/camellia-api.c \
   /usr/src/sys/crypto/camellia/camellia.c
mkdir -p fixroot/sys/crypto && cp -R /usr/src/sys/crypto/camellia fixroot/sys/crypto/
(cd fixroot && patch -p1 < ../fix.diff)
cc -O2 -DNDEBUG -I /tmp/df3569/fixroot/sys -I /usr/src/sys -o guard_fix \
   guard_cam_badbits.c fixroot/sys/crypto/camellia/camellia-api.c \
   /usr/src/sys/crypto/camellia/camellia.c

camellia.c:40-43 maps KASSERT→assert() in userland, so -DNDEBUG reproduces exact production-kernel semantics and the no-flag build reproduces INVARIANTS semantics.

Run / expected

./guard_prod kat && ./guard_prod guard; echo exit=$?   # expect BUG-CONFIRMED, exit 42
./guard_dbg  guard; echo exit=$?                       # expect assert abort (SIGABRT)
./guard_fix  kat && ./guard_fix guard; echo exit=$?    # expect RESULT-PASS + BUG-NOT-PRESENT, exit 0

Observed (run.log / fixval.log): - prod kat: RFC 3713 Appendix A vectors for 128/192/256 all PASS, decrypt roundtrip OK, schedule-bounds canary OK (writes stay inside subkey[68]). - prod guard: bits ∈ {64, 0, βˆ’1, 129} β€” no trap, ctx->bits set to the invalid value, schedule sentinel 0xA5A5A5A5 intact (never generated), ciphertext == plaintext byte-for-byte β†’ BUG-CONFIRMED, exit 42. - dbg guard: Assertion failed ... camellia.c, line 1254 β†’ SIGABRT (the guard is debug-only). - fix kat: still RESULT-PASS (0 failures); fix guard: all invalid lengths REJECTED before touching key material, encrypt refuses and leaves dst untouched β†’ BUG-NOT-PRESENT, exit 0.

Harness-level fix validation only (no kernel build) β€” justified because no in-tree caller can deliver an invalid length today (same rationale as DF-3568).

VERDICT.md
↓ download raw

VERDICT β€” DF-3569 camellia_set_key() key-length validation gap

Status: reproduced (harness, production-kernel semantics) β€” impact: none today (latent; zero in-tree consumers). Confidence: certain.

The defect

camellia_set_key() (sys/crypto/camellia/camellia-api.c:36-42) is the API boundary of the camellia wrapper and the natural place a key length must be validated. It does nothing of the sort:

void
camellia_set_key(camellia_ctx *ctx, const u_char *key, int bits)
{
    Camellia_Ekeygen(bits, key, ctx->subkey);   /* camellia.c:1253 KASSERT is
                                                   the ONLY guard          */
    ctx->bits = bits;                            /* stored unconditionally  */
}

Camellia_Ekeygen() (sys/crypto/camellia/camellia.c:1249-1268) guards the length with KASSERT(keyBitLength == 128 || == 192 || == 256) at :1253-1254 and then switches with a bare default: break;. On a production kernel (KASSERT compiled out β€” no INVARIANTS), any other length:

  1. leaves ctx->subkey[68] untouched β€” the schedule for that key is never generated;
  2. still gets recorded in ctx->bits (camellia-api.c:41);
  3. makes later camellia_encrypt()/camellia_decrypt() (camellia-api.c:44-56) route into Camellia_EncryptBlock/Camellia_DecryptBlock's own default: break; (camellia.c:1283-1287, 1315-1319), which loads the 16-byte block into tmp[] and stores it back without executing any round β€” the "cipher" is the identity function.

Why this matters / why only Info

Cryptographic consequence, not memory corruption: unlike the rijndael twin (DF-3568, where an invalid length overreads the key buffer and a Nr=0 schedule walks wild), camellia's default: paths read neither the key buffer nor the schedule. The failure mode is silent plaintext pass-through in place of encryption β€” catastrophic if a future consumer forwards a length from an untrusted source, invisible to that consumer because the API returns void.

Reachability on this tree: none. rg over all of sys/ finds zero callers of camellia_set_key/camellia_encrypt/camellia_decrypt and zero includers of crypto/camellia/camellia.h outside the directory; the only references are build registrations (sys/conf/files:2258-2259, sys/crypto/Makefile:19-20). The code ships as dead code in option crypto kernels and in the crypto.ko primitive bundle (anchored by the dummy sys/crypto/crypto.c module, audited clean in the same batch). Filed Info (latent hardening) per the batch precedent (DF-3560/DF-3568).

Verification (guest, harness = real kernel sources in userland)

camellia.c:40-43 maps KASSERT→assert() in userland, so compiling the actual camellia-api.c + camellia.c with -DNDEBUG reproduces production kernel semantics exactly; without the flag it reproduces INVARIANTS semantics. Guest sources at /usr/src are cksum-identical to the audited tree (env.txt).

Baseline (run.log):

  • guard_prod kat β€” RFC 3713 Appendix A 128/192/256 known-answer vectors all PASS, decrypt roundtrip OK, and a canary after subkey[68] proves the schedule writes stay in bounds for every valid size (ctx-layout interplay checked clean).
  • guard_prod guard β€” for bits ∈ {64, 0, βˆ’1, 129}: no trap; ctx->bits set to the invalid value; 0xA5A5A5A5 schedule sentinel intact ("never generated"); ciphertext == plaintext byte-for-byte; BUG-CONFIRMED, exit 42.
  • guard_dbg guard β€” Assertion failed: (keyBitLength == 128 || ...) ... camellia.c, line 1254, SIGABRT β€” proving the only guard is debug-only.

Fixed (fixval.log): fix.diff applied to a guest copy of the pristine sources (patch: both hunks OK) and rebuilt:

  • guard_fix kat β€” still RESULT-PASS: 0 failures (valid sizes unaffected);
  • guard_fix guard β€” all four invalid lengths REJECTED before touching key material (ctx->bits keeps its marker, schedule sentinel intact) and camellia_encrypt refuses, leaving dst untouched; BUG-NOT-PRESENT, exit 0.

Harness-level validation only (no kernel build) β€” no in-tree caller can deliver an invalid length, so a kernel build would prove nothing the harness does not; identical rationale to the accepted DF-3568 validation.

Fix

fix.diff (validated above): camellia_set_key() returns int (-1 before touching key material or ctx for lengths outside {128,192,256}; 0 on success β€” statement-style calls remain source-compatible, and there are no in-tree consumers), plus encrypt/decrypt guards that refuse schedules with an invalid ctx->bits, leaving dst untouched (defense in depth for callers that ignore the return and use a stale ctx).

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

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff applied to a pristine source copy on the guest (both hunks OK, patch output in build.log) and rebuilt with -DNDEBUG: invalid lengths are rejected before any key access (ctx marker preserved, schedule sentinel intact) and encrypt refuses leaving dst untouched (BUG-NOT-PRESENT, exit 0), while RFC 3713 128/192/256 KATs still pass (RESULT-PASS: 0 failures). Harness-level validation only (no kernel build) - justified because no in-tree caller can deliver an invalid length today.

fixval.log: 'bits=64 : REJECTED (ctx untouched, encrypt refused)' ... 'BUG-NOT-PRESENT ... exit=0' and '=== [4] FIXED build KAT === ... RESULT-PASS: 0 failures'
↓ fix.diffper-fix-DF-3569

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: 'ciphertext0123456789abcdeffedcba9876543210' == plaintext, 'schedule : UNTOUCHED -- never generated', 'BUG-CONFIRMED ... exit=42', and dbg 'Assertion failed ... camellia.c, line 1254. exit=134'; fixval.log: 'RESULT-PASS: 0 failures' + 'REJECTED (ctx untouched, encrypt refused)' + 'BUG-NOT-PRESENT ... exit=0'

PoC changes

No seed PoC existed; authored guard_cam_badbits.c (kat + guard modes). Guest /bin/sh truncates long command lines (no base64(1) either) - file transfer switched to scp with vm.sh's ssh config.

Verified recommended fix

camellia-api.c: have camellia_set_key return int and reject bits not in {128,192,256} before touching key material or ctx; camellia_encrypt/decrypt refuse invalid ctx->bits leaving dst untouched (see fix.diff).

Verdict

camellia_set_key (camellia-api.c:36-42) is a void wrapper that passes bits to Camellia_Ekeygen unvalidated and stores ctx->bits unconditionally; the only guard is the KASSERT at camellia.c:1253-1254, compiled out on production kernels. Demonstrated in-guest with the real kernel sources built userland (-DNDEBUG == production semantics, per camellia.c:40-43's KASSERT->assert mapping): for bits in {64,0,-1,129} nothing traps, ctx->bits records the invalid length, the 0xA5A5A5A5 schedule sentinel survives intact (schedule NEVER generated), and camellia_encrypt copies the 16-byte block unchanged - encryption degenerates to the identity function (BUG-CONFIRMED, exit 42). Debug build aborts at camellia.c:1254, proving the guard is INVARIANTS-only. Unlike the rijndael twin DF-3568 there is NO memory-safety violation - the default paths read neither key nor schedule - the failure is purely cryptographic (silent plaintext pass-through). RFC 3713 Appendix A KATs for 128/192/256 pass and a canary after subkey[68] proves schedule writes stay in bounds (ctx-layout interplay clean). Impact is none today: zero in-tree consumers (only conf/files:2259 and crypto/Makefile:20 reference camellia outside the directory); the code ships as dead code in option-crypto kernels and crypto.ko. Filed Info hardening of the API contract for future callers, matching the DF-3560/DF-3568 batch precedent.