Coverage Report

Created: 2026-04-01 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssl35/ssl/quic/quic_srtm.c
Line
Count
Source
1
/*
2
 * Copyright 2023-2024 The OpenSSL Project Authors. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License 2.0 (the "License").  You may not use
5
 * this file except in compliance with the License.  You can obtain a copy
6
 * in the file LICENSE in the source distribution or at
7
 * https://www.openssl.org/source/license.html
8
 */
9
10
#include "internal/quic_srtm.h"
11
#include "internal/common.h"
12
#include <openssl/lhash.h>
13
#include <openssl/core_names.h>
14
#include <openssl/rand.h>
15
16
/*
17
 * QUIC Stateless Reset Token Manager
18
 * ==================================
19
 */
20
typedef struct srtm_item_st SRTM_ITEM;
21
22
#define BLINDED_SRT_LEN 16
23
24
DEFINE_LHASH_OF_EX(SRTM_ITEM);
25
26
/*
27
 * The SRTM is implemented using two LHASH instances, one matching opaque pointers to
28
 * an item structure, and another matching a SRT-derived value to an item
29
 * structure. Multiple items with different seq_num values under a given opaque,
30
 * and duplicate SRTs, are handled using sorted singly-linked lists.
31
 *
32
 * The O(n) insert and lookup performance is tolerated on the basis that the
33
 * total number of entries for a given opaque (total number of extant CIDs for a
34
 * connection) should be quite small, and the QUIC protocol allows us to place a
35
 * hard limit on this via the active_connection_id_limit TPARAM. Thus there is
36
 * no risk of a large number of SRTs needing to be registered under a given
37
 * opaque.
38
 *
39
 * It is expected one SRTM will exist per QUIC_PORT and track all SRTs across
40
 * all connections for that QUIC_PORT.
41
 */
42
struct srtm_item_st {
43
    SRTM_ITEM *next_by_srt_blinded; /* SORT BY opaque  DESC */
44
    SRTM_ITEM *next_by_seq_num; /* SORT BY seq_num DESC */
45
    void *opaque; /* \__ unique identity for item */
46
    uint64_t seq_num; /* /                            */
47
    QUIC_STATELESS_RESET_TOKEN srt;
48
    unsigned char srt_blinded[BLINDED_SRT_LEN]; /* H(srt) */
49
50
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
51
    uint32_t debug_token;
52
#endif
53
};
54
55
struct quic_srtm_st {
56
    /* Crypto context used to calculate blinded SRTs H(srt). */
57
    EVP_CIPHER_CTX *blind_ctx; /* kept with key */
58
59
    LHASH_OF(SRTM_ITEM) *items_fwd; /* (opaque)  -> SRTM_ITEM */
60
    LHASH_OF(SRTM_ITEM) *items_rev; /* (H(srt))  -> SRTM_ITEM */
61
62
    /*
63
     * Monotonically transitions to 1 in event of allocation failure. The only
64
     * valid operation on such an object is to free it.
65
     */
66
    unsigned int alloc_failed : 1;
67
};
68
69
static unsigned long items_fwd_hash(const SRTM_ITEM *item)
70
4.09M
{
71
4.09M
    return (unsigned long)(uintptr_t)item->opaque;
72
4.09M
}
73
74
static int items_fwd_cmp(const SRTM_ITEM *a, const SRTM_ITEM *b)
75
1.39M
{
76
1.39M
    return a->opaque != b->opaque;
77
1.39M
}
78
79
static unsigned long items_rev_hash(const SRTM_ITEM *item)
80
6.73M
{
81
    /*
82
     * srt_blinded has already been through a crypto-grade hash function, so we
83
     * can just use bits from that.
84
     */
85
6.73M
    unsigned long l;
86
87
6.73M
    memcpy(&l, item->srt_blinded, sizeof(l));
88
6.73M
    return l;
89
6.73M
}
90
91
static int items_rev_cmp(const SRTM_ITEM *a, const SRTM_ITEM *b)
92
451k
{
93
    /*
94
     * We don't need to use CRYPTO_memcmp here as the relationship of
95
     * srt_blinded to srt is already cryptographically obfuscated.
96
     */
97
451k
    return memcmp(a->srt_blinded, b->srt_blinded, sizeof(a->srt_blinded));
98
451k
}
99
100
static int srtm_check_lh(QUIC_SRTM *srtm, LHASH_OF(SRTM_ITEM) *lh)
101
1.50M
{
102
1.50M
    if (lh_SRTM_ITEM_error(lh)) {
103
0
        srtm->alloc_failed = 1;
104
0
        return 0;
105
0
    }
106
107
1.50M
    return 1;
108
1.50M
}
109
110
QUIC_SRTM *ossl_quic_srtm_new(OSSL_LIB_CTX *libctx, const char *propq)
111
57.8k
{
112
57.8k
    QUIC_SRTM *srtm = NULL;
113
57.8k
    unsigned char key[16];
114
57.8k
    EVP_CIPHER *ecb = NULL;
115
116
57.8k
    if (RAND_priv_bytes_ex(libctx, key, sizeof(key), sizeof(key) * 8) != 1)
117
0
        goto err;
118
119
57.8k
    if ((srtm = OPENSSL_zalloc(sizeof(*srtm))) == NULL)
120
0
        return NULL;
121
122
    /* Use AES-128-ECB as a permutation over 128-bit SRTs. */
123
57.8k
    if ((ecb = EVP_CIPHER_fetch(libctx, "AES-128-ECB", propq)) == NULL)
124
0
        goto err;
125
126
57.8k
    if ((srtm->blind_ctx = EVP_CIPHER_CTX_new()) == NULL)
127
0
        goto err;
128
129
57.8k
    if (!EVP_EncryptInit_ex2(srtm->blind_ctx, ecb, key, NULL, NULL))
130
0
        goto err;
131
132
57.8k
    EVP_CIPHER_free(ecb);
133
57.8k
    ecb = NULL;
134
135
    /* Create mappings. */
136
57.8k
    if ((srtm->items_fwd = lh_SRTM_ITEM_new(items_fwd_hash, items_fwd_cmp)) == NULL
137
57.8k
        || (srtm->items_rev = lh_SRTM_ITEM_new(items_rev_hash, items_rev_cmp)) == NULL)
138
0
        goto err;
139
140
57.8k
    return srtm;
141
142
0
err:
143
    /*
144
     * No cleansing of key needed as blinding exists only for side channel
145
     * mitigation.
146
     */
147
0
    ossl_quic_srtm_free(srtm);
148
0
    EVP_CIPHER_free(ecb);
149
0
    return NULL;
150
57.8k
}
151
152
static void srtm_free_each(SRTM_ITEM *ihead)
153
608k
{
154
608k
    SRTM_ITEM *inext, *item = ihead;
155
156
675k
    for (item = item->next_by_seq_num; item != NULL; item = inext) {
157
67.4k
        inext = item->next_by_seq_num;
158
67.4k
        OPENSSL_free(item);
159
67.4k
    }
160
161
608k
    OPENSSL_free(ihead);
162
608k
}
163
164
void ossl_quic_srtm_free(QUIC_SRTM *srtm)
165
57.8k
{
166
57.8k
    if (srtm == NULL)
167
0
        return;
168
169
57.8k
    lh_SRTM_ITEM_free(srtm->items_rev);
170
57.8k
    if (srtm->items_fwd != NULL) {
171
        /*
172
         * We don't need to call lh_SRTM_ITEM_set_down_load(..., 0)
173
         * here because srtm_free_each() callback for _doall() does
174
         * not call to lh_SRTIM_ITEM_delete().
175
         */
176
57.8k
        lh_SRTM_ITEM_doall(srtm->items_fwd, srtm_free_each);
177
57.8k
        lh_SRTM_ITEM_free(srtm->items_fwd);
178
57.8k
    }
179
180
57.8k
    EVP_CIPHER_CTX_free(srtm->blind_ctx);
181
57.8k
    OPENSSL_free(srtm);
182
57.8k
}
183
184
/*
185
 * Find a SRTM_ITEM by (opaque, seq_num). Returns NULL if no match.
186
 * If head is non-NULL, writes the head of the relevant opaque list to *head if
187
 * there is one.
188
 * If prev is non-NULL, writes the previous node to *prev or NULL if it is
189
 * the first item.
190
 */
191
static SRTM_ITEM *srtm_find(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num,
192
    SRTM_ITEM **head_p, SRTM_ITEM **prev_p)
193
2.36M
{
194
2.36M
    SRTM_ITEM key, *item = NULL, *prev = NULL;
195
196
2.36M
    key.opaque = opaque;
197
198
2.36M
    item = lh_SRTM_ITEM_retrieve(srtm->items_fwd, &key);
199
2.36M
    if (head_p != NULL)
200
1.88M
        *head_p = item;
201
202
17.7M
    for (; item != NULL; prev = item, item = item->next_by_seq_num)
203
16.5M
        if (item->seq_num == seq_num) {
204
1.05M
            break;
205
15.5M
        } else if (item->seq_num < seq_num) {
206
            /*
207
             * List is sorted in descending order so there can't be any match
208
             * after this.
209
             */
210
193k
            item = NULL;
211
193k
            break;
212
193k
        }
213
214
2.36M
    if (prev_p != NULL)
215
489k
        *prev_p = prev;
216
217
2.36M
    return item;
218
2.36M
}
219
220
/*
221
 * Inserts a SRTM_ITEM into the singly-linked by-sequence-number linked list.
222
 * The new head pointer is written to *new_head (which may or may not be
223
 * unchanged).
224
 */
225
static void sorted_insert_seq_num(SRTM_ITEM *head, SRTM_ITEM *item, SRTM_ITEM **new_head)
226
210k
{
227
210k
    uint64_t seq_num = item->seq_num;
228
210k
    SRTM_ITEM *cur = head, **fixup = new_head;
229
230
210k
    *new_head = head;
231
232
1.96M
    while (cur != NULL && cur->seq_num > seq_num) {
233
1.75M
        fixup = &cur->next_by_seq_num;
234
1.75M
        cur = cur->next_by_seq_num;
235
1.75M
    }
236
237
210k
    item->next_by_seq_num = *fixup;
238
210k
    *fixup = item;
239
210k
}
240
241
/*
242
 * Inserts a SRTM_ITEM into the singly-linked by-SRT list.
243
 * The new head pointer is written to *new_head (which may or may not be
244
 * unchanged).
245
 */
246
static void sorted_insert_srt(SRTM_ITEM *head, SRTM_ITEM *item, SRTM_ITEM **new_head)
247
48.7k
{
248
48.7k
    uintptr_t opaque = (uintptr_t)item->opaque;
249
48.7k
    SRTM_ITEM *cur = head, **fixup = new_head;
250
251
48.7k
    *new_head = head;
252
253
544k
    while (cur != NULL && (uintptr_t)cur->opaque > opaque) {
254
495k
        fixup = &cur->next_by_srt_blinded;
255
495k
        cur = cur->next_by_srt_blinded;
256
495k
    }
257
258
48.7k
    item->next_by_srt_blinded = *fixup;
259
48.7k
    *fixup = item;
260
48.7k
}
261
262
/*
263
 * Computes the blinded SRT value used for internal lookup for side channel
264
 * mitigation purposes. We compute this once as a cached value when an SRTM_ITEM
265
 * is formed.
266
 */
267
static int srtm_compute_blinded(QUIC_SRTM *srtm, SRTM_ITEM *item,
268
    const QUIC_STATELESS_RESET_TOKEN *token)
269
5.60M
{
270
5.60M
    int outl = 0;
271
272
    /*
273
     * We use AES-128-ECB as a permutation using a random key to facilitate
274
     * blinding for side-channel purposes. Encrypt the token as a single AES
275
     * block.
276
     */
277
5.60M
    if (!EVP_EncryptUpdate(srtm->blind_ctx, item->srt_blinded, &outl,
278
5.60M
            (const unsigned char *)token, sizeof(*token)))
279
0
        return 0;
280
281
5.60M
    if (!ossl_assert(outl == sizeof(*token)))
282
0
        return 0;
283
284
5.60M
    return 1;
285
5.60M
}
286
287
int ossl_quic_srtm_add(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num,
288
    const QUIC_STATELESS_RESET_TOKEN *token)
289
1.88M
{
290
1.88M
    SRTM_ITEM *item = NULL, *head = NULL, *new_head, *r_item;
291
292
1.88M
    if (srtm->alloc_failed)
293
0
        return 0;
294
295
    /* (opaque, seq_num) duplicates not allowed */
296
1.88M
    if ((item = srtm_find(srtm, opaque, seq_num, &head, NULL)) != NULL)
297
1.03M
        return 0;
298
299
842k
    if ((item = OPENSSL_zalloc(sizeof(*item))) == NULL)
300
0
        return 0;
301
302
842k
    item->opaque = opaque;
303
842k
    item->seq_num = seq_num;
304
842k
    item->srt = *token;
305
842k
    if (!srtm_compute_blinded(srtm, item, &item->srt)) {
306
0
        OPENSSL_free(item);
307
0
        return 0;
308
0
    }
309
310
    /* Add to forward mapping. */
311
842k
    if (head == NULL) {
312
        /* First item under this opaque */
313
632k
        lh_SRTM_ITEM_insert(srtm->items_fwd, item);
314
632k
        if (!srtm_check_lh(srtm, srtm->items_fwd)) {
315
0
            OPENSSL_free(item);
316
0
            return 0;
317
0
        }
318
632k
    } else {
319
210k
        sorted_insert_seq_num(head, item, &new_head);
320
210k
        if (new_head != head) { /* head changed, update in lhash */
321
51.4k
            lh_SRTM_ITEM_insert(srtm->items_fwd, new_head);
322
51.4k
            if (!srtm_check_lh(srtm, srtm->items_fwd)) {
323
0
                OPENSSL_free(item);
324
0
                return 0;
325
0
            }
326
51.4k
        }
327
210k
    }
328
329
    /* Add to reverse mapping. */
330
842k
    r_item = lh_SRTM_ITEM_retrieve(srtm->items_rev, item);
331
842k
    if (r_item == NULL) {
332
        /* First item under this blinded SRT */
333
793k
        lh_SRTM_ITEM_insert(srtm->items_rev, item);
334
793k
        if (!srtm_check_lh(srtm, srtm->items_rev))
335
            /*
336
             * Can't free the item now as we would have to undo the insertion
337
             * into the forward mapping which would require an insert operation
338
             * to restore the previous value. which might also fail. However,
339
             * the item will be freed OK when we free the entire SRTM.
340
             */
341
0
            return 0;
342
793k
    } else {
343
48.7k
        sorted_insert_srt(r_item, item, &new_head);
344
48.7k
        if (new_head != r_item) { /* head changed, update in lhash */
345
19.3k
            lh_SRTM_ITEM_insert(srtm->items_rev, new_head);
346
19.3k
            if (!srtm_check_lh(srtm, srtm->items_rev))
347
                /* As above. */
348
0
                return 0;
349
19.3k
        }
350
48.7k
    }
351
352
842k
    return 1;
353
842k
}
354
355
/* Remove item from reverse mapping. */
356
static int srtm_remove_from_rev(QUIC_SRTM *srtm, SRTM_ITEM *item)
357
166k
{
358
166k
    SRTM_ITEM *rh_item;
359
360
166k
    rh_item = lh_SRTM_ITEM_retrieve(srtm->items_rev, item);
361
166k
    assert(rh_item != NULL);
362
166k
    if (rh_item == item) {
363
        /*
364
         * Change lhash to point to item after this one, or remove the entry if
365
         * this is the last one.
366
         */
367
150k
        if (item->next_by_srt_blinded != NULL) {
368
5.37k
            lh_SRTM_ITEM_insert(srtm->items_rev, item->next_by_srt_blinded);
369
5.37k
            if (!srtm_check_lh(srtm, srtm->items_rev))
370
0
                return 0;
371
145k
        } else {
372
145k
            lh_SRTM_ITEM_delete(srtm->items_rev, item);
373
145k
        }
374
150k
    } else {
375
        /* Find our entry in the SRT list */
376
178k
        for (; rh_item->next_by_srt_blinded != item;
377
162k
            rh_item = rh_item->next_by_srt_blinded)
378
162k
            ;
379
15.8k
        rh_item->next_by_srt_blinded = item->next_by_srt_blinded;
380
15.8k
    }
381
382
166k
    return 1;
383
166k
}
384
385
int ossl_quic_srtm_remove(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num)
386
489k
{
387
489k
    SRTM_ITEM *item, *prev = NULL;
388
389
489k
    if (srtm->alloc_failed)
390
0
        return 0;
391
392
489k
    if ((item = srtm_find(srtm, opaque, seq_num, NULL, &prev)) == NULL)
393
        /* No match */
394
475k
        return 0;
395
396
    /* Remove from forward mapping. */
397
13.6k
    if (prev == NULL) {
398
        /*
399
         * Change lhash to point to item after this one, or remove the entry if
400
         * this is the last one.
401
         */
402
6.10k
        if (item->next_by_seq_num != NULL) {
403
2.51k
            lh_SRTM_ITEM_insert(srtm->items_fwd, item->next_by_seq_num);
404
2.51k
            if (!srtm_check_lh(srtm, srtm->items_fwd))
405
0
                return 0;
406
3.58k
        } else {
407
3.58k
            lh_SRTM_ITEM_delete(srtm->items_fwd, item);
408
3.58k
        }
409
7.57k
    } else {
410
7.57k
        prev->next_by_seq_num = item->next_by_seq_num;
411
7.57k
    }
412
413
    /* Remove from reverse mapping. */
414
13.6k
    if (!srtm_remove_from_rev(srtm, item))
415
0
        return 0;
416
417
13.6k
    OPENSSL_free(item);
418
13.6k
    return 1;
419
13.6k
}
420
421
int ossl_quic_srtm_cull(QUIC_SRTM *srtm, void *opaque)
422
1.01M
{
423
1.01M
    SRTM_ITEM key, *item = NULL, *inext, *ihead;
424
425
1.01M
    key.opaque = opaque;
426
427
1.01M
    if (srtm->alloc_failed)
428
0
        return 0;
429
430
1.01M
    if ((ihead = lh_SRTM_ITEM_retrieve(srtm->items_fwd, &key)) == NULL)
431
994k
        return 1; /* nothing removed is a success condition */
432
433
173k
    for (item = ihead; item != NULL; item = inext) {
434
153k
        inext = item->next_by_seq_num;
435
153k
        if (item != ihead) {
436
133k
            srtm_remove_from_rev(srtm, item);
437
133k
            OPENSSL_free(item);
438
133k
        }
439
153k
    }
440
441
20.0k
    lh_SRTM_ITEM_delete(srtm->items_fwd, ihead);
442
20.0k
    srtm_remove_from_rev(srtm, ihead);
443
20.0k
    OPENSSL_free(ihead);
444
20.0k
    return 1;
445
1.01M
}
446
447
int ossl_quic_srtm_lookup(QUIC_SRTM *srtm,
448
    const QUIC_STATELESS_RESET_TOKEN *token,
449
    size_t idx,
450
    void **opaque, uint64_t *seq_num)
451
4.76M
{
452
4.76M
    SRTM_ITEM key, *item;
453
454
4.76M
    if (srtm->alloc_failed)
455
0
        return 0;
456
457
4.76M
    if (!srtm_compute_blinded(srtm, &key, token))
458
0
        return 0;
459
460
4.76M
    item = lh_SRTM_ITEM_retrieve(srtm->items_rev, &key);
461
5.02M
    for (; idx > 0 && item != NULL; --idx, item = item->next_by_srt_blinded)
462
258k
        ;
463
4.76M
    if (item == NULL)
464
4.76M
        return 0;
465
466
2.03k
    if (opaque != NULL)
467
20
        *opaque = item->opaque;
468
2.03k
    if (seq_num != NULL)
469
0
        *seq_num = item->seq_num;
470
471
2.03k
    return 1;
472
4.76M
}
473
474
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
475
476
static uint32_t token_next = 0x5eadbeef;
477
static size_t tokens_seen;
478
479
struct check_args {
480
    uint32_t token;
481
    int mode;
482
};
483
484
static void check_mark(SRTM_ITEM *item, void *arg)
485
3.46G
{
486
3.46G
    struct check_args *arg_ = arg;
487
3.46G
    uint32_t token = arg_->token;
488
3.46G
    uint64_t prev_seq_num = 0;
489
3.46G
    void *prev_opaque = NULL;
490
3.46G
    int have_prev = 0;
491
492
3.46G
    assert(item != NULL);
493
494
7.21G
    while (item != NULL) {
495
3.75G
        if (have_prev) {
496
292M
            assert(!(item->opaque == prev_opaque && item->seq_num == prev_seq_num));
497
292M
            if (!arg_->mode)
498
292M
                assert(item->opaque != prev_opaque || item->seq_num < prev_seq_num);
499
292M
        }
500
501
3.75G
        ++tokens_seen;
502
3.75G
        item->debug_token = token;
503
3.75G
        prev_opaque = item->opaque;
504
3.75G
        prev_seq_num = item->seq_num;
505
3.75G
        have_prev = 1;
506
507
3.75G
        if (arg_->mode)
508
1.87G
            item = item->next_by_srt_blinded;
509
1.87G
        else
510
1.87G
            item = item->next_by_seq_num;
511
3.75G
    }
512
3.46G
}
513
514
static void check_count(SRTM_ITEM *item, void *arg)
515
3.46G
{
516
3.46G
    struct check_args *arg_ = arg;
517
3.46G
    uint32_t token = arg_->token;
518
519
3.46G
    assert(item != NULL);
520
521
7.21G
    while (item != NULL) {
522
3.75G
        ++tokens_seen;
523
3.75G
        assert(item->debug_token == token);
524
525
3.75G
        if (arg_->mode)
526
1.87G
            item = item->next_by_seq_num;
527
1.87G
        else
528
1.87G
            item = item->next_by_srt_blinded;
529
3.75G
    }
530
3.46G
}
531
532
#endif
533
534
void ossl_quic_srtm_check(const QUIC_SRTM *srtm)
535
3.66M
{
536
3.66M
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
537
3.66M
    struct check_args args = { 0 };
538
3.66M
    size_t tokens_expected, tokens_expected_old;
539
540
3.66M
    args.token = token_next;
541
3.66M
    ++token_next;
542
543
3.66M
    assert(srtm != NULL);
544
3.66M
    assert(srtm->blind_ctx != NULL);
545
3.66M
    assert(srtm->items_fwd != NULL);
546
3.66M
    assert(srtm->items_rev != NULL);
547
548
3.66M
    tokens_seen = 0;
549
3.66M
    lh_SRTM_ITEM_doall_arg(srtm->items_fwd, check_mark, &args);
550
551
3.66M
    tokens_expected = tokens_seen;
552
3.66M
    tokens_seen = 0;
553
3.66M
    lh_SRTM_ITEM_doall_arg(srtm->items_rev, check_count, &args);
554
555
3.66M
    assert(tokens_seen == tokens_expected);
556
3.66M
    tokens_expected_old = tokens_expected;
557
558
3.66M
    args.token = token_next;
559
3.66M
    ++token_next;
560
561
3.66M
    args.mode = 1;
562
3.66M
    tokens_seen = 0;
563
3.66M
    lh_SRTM_ITEM_doall_arg(srtm->items_rev, check_mark, &args);
564
565
3.66M
    tokens_expected = tokens_seen;
566
3.66M
    tokens_seen = 0;
567
3.66M
    lh_SRTM_ITEM_doall_arg(srtm->items_fwd, check_count, &args);
568
569
3.66M
    assert(tokens_seen == tokens_expected);
570
3.66M
    assert(tokens_seen == tokens_expected_old);
571
3.66M
#endif
572
3.66M
}