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)
PoC verification
Evidence pack
findings/poc/DF-3569 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 takesdefault: break;β the 68-word key schedule is never generated (whatever the caller left inctx->subkeystays there),ctx->bitsrecords the invalid length anyway,- the subsequent
Camellia_EncryptBlock/Camellia_DecryptBlock(camellia.c:1271-1330) switches takedefault: 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_decryptand no includer ofcrypto/camellia/camellia.houtside the directory itself. The only references are the build registrationssys/conf/files:2258-2259(optional crypto) andsys/crypto/Makefile:19-20(thecrypto.koprimitive bundle). - Compiled into every
option cryptokernel /crypto.koas 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 β 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:
- leaves
ctx->subkey[68]untouched β the schedule for that key is never generated; - still gets recorded in
ctx->bits(camellia-api.c:41); - makes later
camellia_encrypt()/camellia_decrypt()(camellia-api.c:44-56) route intoCamellia_EncryptBlock/Camellia_DecryptBlock's owndefault: break;(camellia.c:1283-1287, 1315-1319), which loads the 16-byte block intotmp[]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 aftersubkey[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->bitsset to the invalid value;0xA5A5A5A5schedule sentinel intact ("never generated");ciphertext == plaintextbyte-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β stillRESULT-PASS: 0 failures(valid sizes unaffected);guard_fix guardβ all four invalid lengths REJECTED before touching key material (ctx->bitskeeps its marker, schedule sentinel intact) andcamellia_encryptrefuses, 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
fixedfix.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'
Confirmed kernel references
- sys/crypto/camellia/camellia-api.c:36
- sys/crypto/camellia/camellia-api.c:40
- sys/crypto/camellia/camellia-api.c:41
- sys/crypto/camellia/camellia-api.c:48
- sys/crypto/camellia/camellia-api.c:55
- sys/crypto/camellia/camellia.h:41
- sys/crypto/camellia/camellia.c:1253
- sys/crypto/camellia/camellia.c:1266
- sys/conf/files:2259
- sys/crypto/Makefile:20
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.
No comments yet.