`MASK_L` over-shifts for RFC 3610-legal `L ≥ 4`, collapsing the CCM counter (keystream reuse at `L = 4`)
Los mantenedores suelen responder en 2 días
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 57/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Tranquilo
- Stack tecnológico
- c
- Área
- cryptography, security
Línea de trabajo
Read ccm.c around MASK_L and its uses in SET_COUNTER, then inspect tests/unit-tests/test_ccm.c and the L = 5 vectors in tests/ccm-testdata.c. Run the CCM tests with UBSan to confirm the shift issue; done means the undefined shifts are gone, all vectors still pass in both directions, and the reported L = 4 counter blocks no longer repeat.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
ccm.c:31:
#define MASK_L(_L) ((1 << 8 * _L) - 1)
1 has type int. RFC 3610 §2.1 permits L ∈ [2, 8], so 8 * _L reaches 64
and any L ≥ 4 shifts a 32-bit int by at least its width — undefined
behaviour. ccm.h documents M and L by referring the caller to RFC 3610 and
places no bound of its own, and dtls_ccm_decrypt_message has no documentation
block at all.
This is already happening in the test suite
tests/ccm-testdata.c has four vectors with L = 5, and the loops at
tests/unit-tests/test_ccm.c:33 and :56 run every vector through
dtls_ccm_encrypt_message / dtls_ccm_decrypt_message unfiltered. Running that
loop under UBSan:
ccm.c:221:3: runtime error: shift exponent 40 is too large for 32-bit type 'int'
ccm.c:147:3: runtime error: shift exponent 40 is too large for 32-bit type 'int'
ccm.c:291:3: runtime error: shift exponent 40 is too large for 32-bit type 'int'
35 vectors x 2 directions: 70 pass, 0 fail
The vectors pass while executing the undefined shift, which is presumably why it
has gone unnoticed: at L = 5 the counter is truncated to 8 bits and no vector
is long enough for that to change an output.
The consequence at L = 4 is keystream reuse, not just UB
SET_COUNTER only fills the counter field while C is non-zero:
memset((A) + DTLS_CCM_BLOCKSIZE - (L), 0, (L));
(C) = (cnt) & MASK_L(L);
for (i_ = DTLS_CCM_BLOCKSIZE - 1; (C) && (i_ > (L)); --i_, (C) >>= 8)
(A)[i_] |= (C) & 0xFF;
On x86-64 the shift count is taken mod 32, so the macro evaluates to:
| L | MASK_L(L) |
intended |
|---|---|---|
| 2 | 65535 | 65535 |
| 3 | 16777215 | 16777215 |
| 4 | 0 | 4294967295 |
| 5 | 255 | 1099511627775 |
| 6 | 65535 | 281474976710655 |
| 7 | 16777215 | 72057594037927935 |
At L = 4 the mask is 0, so C is always zero, the fill loop never executes,
and every block is encrypted under an identical counter block. L ∈ {5,6,7}
truncate the counter to 8/16/24 bits, which reuses the keystream once a message
exceeds 2^8 / 2^16 / 2^24 blocks.
Reproduction
Clean clone of current master (3482499), built with the project's own CMake
build, only adding -fsanitize=undefined:
git clone --depth 1 https://github.com/eclipse-tinydtls/tinydtls.git td && cd td
cmake -S . -B build -DCMAKE_C_FLAGS="-fsanitize=undefined" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=undefined"
cmake --build build -j4
reuse.c — 48 bytes of identical plaintext through the exported API:
#include <stdio.h>
#include <string.h>
#include "ccm.h"
static void run(size_t L) {
rijndael_ctx ctx;
unsigned char key[16] = {0}, nonce[16] = {0}, buf[64];
memset(buf, 0, sizeof buf);
rijndael_set_key_enc_only(&ctx, key, 128);
long e = dtls_ccm_encrypt_message(&ctx, 8, L, nonce, buf, 48, buf, 0);
printf("L=%zu ct[0:16]==ct[16:32]: %-3s ct[16:32]==ct[32:48]: %-3s encrypt->%ld",
L, memcmp(buf, buf+16, 16) ? "no" : "YES",
memcmp(buf+16, buf+32, 16) ? "no" : "YES", e);
long d = dtls_ccm_decrypt_message(&ctx, 8, L, nonce, buf, e, buf, 0);
printf(" decrypt->%ld (round trip %s)\n", d, d == 48 ? "OK" : "BROKEN");
}
int main(void) { run(3); run(4); return 0; }
gcc -fsanitize=undefined -I. -Ibuild -o reuse reuse.c build/libtinydtls.a && ./reuse
ccm.c:147:3: runtime error: shift exponent 32 is too large for 32-bit type 'int'
ccm.c:221:3: runtime error: shift exponent 32 is too large for 32-bit type 'int'
ccm.c:291:3: runtime error: shift exponent 32 is too large for 32-bit type 'int'
L=3 ct[0:16]==ct[16:32]: no ct[16:32]==ct[32:48]: no encrypt->56 decrypt->48 (round trip OK)
L=4 ct[0:16]==ct[16:32]: YES ct[16:32]==ct[32:48]: YES encrypt->56 decrypt->48 (round trip OK)
Note the round trip still succeeds at L = 4: encrypt and decrypt are broken
identically, so no encrypt/decrypt test vector can detect this.
Scope — deliberately not overstated
The DTLS layer always passes L = 3, so the protocol path is unaffected, and
I am not aware of any consumer that passes L ≥ 4. The L = 5 vectors in
this repository's own test data are the only occurrences I found. So the
demonstrated impact today is the undefined behaviour itself; the keystream reuse
is a latent consequence for a caller I cannot point to.
I am reporting it as an ordinary issue rather than through SECURITY.md for that
reason. If you would rather it had gone through the security process, say so and
I will not discuss it further in public.
contiki-ng/tinydtls carries the same macro at dtls-ccm.c:30; I have not filed
there.
Suggested fix, and what it was tested against
#define MASK_L(_L) \
((_L) >= sizeof(unsigned long) ? ~0UL : ((1UL << (8 * (_L))) - 1))
The guard has to be against sizeof(unsigned long) rather than a literal 4,
since C is unsigned long; on a 32-bit target it degrades to an all-ones mask,
which is the correct mask for a 32-bit counter.
Applied to ccm.c and rebuilt, over all 35 vectors in tests/ccm-testdata.c in
both directions:
| UBSan | vectors | |
|---|---|---|
unmodified master |
3 shift errors | 70 pass, 0 fail |
| with the fix | silent | 70 pass, 0 fail |
and the L = 4 keystream reuse above disappears (ct[0:16] != ct[16:32]).
Rejecting L > 8 at the API boundary would also be reasonable, but that changes
behaviour for the existing L = 5 vectors, so it seems better kept separate.
How it was found
Bounded model checking with ESBMC over a harness that calls
dtls_ccm_decrypt_message with every parameter unconstrained except
L ∈ [2,8] and M ∈ {4,…,16} — the range RFC 3610 permits and ccm.h refers
to. Everything above was then confirmed natively; the UBSan output and the
keystream comparison are real runs, not model output.
- Lenguaje dominante
- C
- Estrellas
- 122
- Forks
- 67
- Merge medio
- 6 d 8 h
- PR fusionados (30 d)
- 5
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de eclipse-tinydtls/tinydtls
-
ECDHE_ECDSA ClientKeyExchange rejected as too short when DTLS_PSK_MAX_CLIENT_IDENTITY_LEN > 64Posiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abiertobug good first issue please retest
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
eclipse-tinydtls/tinydtls#298 · 2 comentarios ·
Los mantenedores suelen responder en 2 días
-
`dtls_get_cookie`: dereference before the length check (currently masked by `dtls_create_cookie`)Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
eclipse-tinydtls/tinydtls#279 · 2 comentarios · 1 reacción ·
Los mantenedores suelen responder en 2 días
-
MSVC build is broken (undeclared `DTLS_DEBUG_BUF_SIZE`)Posiblemente ocupada @mrdeep1 la tomó hace 9 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 25/100
eclipse-tinydtls/tinydtls#283 · 5 comentarios ·
Los mantenedores suelen responder en 2 días
-
Dificultad 4/5 3-5 días Aptitud para principiantes 45/100
eclipse-tinydtls/tinydtls#274 · 2 comentarios ·
Los mantenedores suelen responder en 2 días
-
bug
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
eclipse-tinydtls/tinydtls#273 · 2 comentarios ·
Los mantenedores suelen responder en 2 días
Todos los issues de eclipse-tinydtls/tinydtls
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 63/100
Xinyuan-LilyGO/T-Display-S3#352 · 3 comentarios ·
-
documentation severity/minor
Dificultad 1/5 1-3 horas Aptitud para principiantes 88/100
Los mantenedores suelen responder en 1 día
-
docs: AGENTS.md has no rule against running deploy scripts into the owner's game installPosiblemente ocupada @thesprockee la tomó hoy. Abierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
EchoTools/nevr-runtime#524 ·
Los mantenedores suelen responder en 1 día
-
python library: btc_sign() sends antiklepto host nonce for taproot miniscript (tr() policy) inputs because is_taproot() only recognizes simple P2TRPosiblemente ocupada @benma-agent la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
BitBoxSwiss/bitbox02-firmware#2145 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 74/100
Genymobile/scrcpy#7097 ·
Los mantenedores suelen responder en 1 día