Coverage Report

Created: 2025-08-28 07:07

/src/openssl33/ssl/quic/quic_srtm.c
Line
Count
Source (jump to first uncovered line)
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
3.04M
{
71
3.04M
    return (unsigned long)(uintptr_t)item->opaque;
72
3.04M
}
73
74
static int items_fwd_cmp(const SRTM_ITEM *a, const SRTM_ITEM *b)
75
1.15M
{
76
1.15M
    return a->opaque != b->opaque;
77
1.15M
}
78
79
static unsigned long items_rev_hash(const SRTM_ITEM *item)
80
3.94M
{
81
    /*
82
     * srt_blinded has already been through a crypto-grade hash function, so we
83
     * can just use bits from that.
84
     */
85
3.94M
    unsigned long l;
86
87
3.94M
    memcpy(&l, item->srt_blinded, sizeof(l));
88
3.94M
    return l;
89
3.94M
}
90
91
static int items_rev_cmp(const SRTM_ITEM *a, const SRTM_ITEM *b)
92
440k
{
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
440k
    return memcmp(a->srt_blinded, b->srt_blinded, sizeof(a->srt_blinded));
98
440k
}
99
100
static int srtm_check_lh(QUIC_SRTM *srtm, LHASH_OF(SRTM_ITEM) *lh)
101
1.08M
{
102
1.08M
    if (lh_SRTM_ITEM_error(lh)) {
103
0
        srtm->alloc_failed = 1;
104
0
        return 0;
105
0
    }
106
107
1.08M
    return 1;
108
1.08M
}
109
110
QUIC_SRTM *ossl_quic_srtm_new(OSSL_LIB_CTX *libctx, const char *propq)
111
42.4k
{
112
42.4k
    QUIC_SRTM *srtm = NULL;
113
42.4k
    unsigned char key[16];
114
42.4k
    EVP_CIPHER *ecb = NULL;
115
116
42.4k
    if (RAND_priv_bytes_ex(libctx, key, sizeof(key), sizeof(key) * 8) != 1)
117
0
        goto err;
118
119
42.4k
    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
42.4k
    if ((ecb = EVP_CIPHER_fetch(libctx, "AES-128-ECB", propq)) == NULL)
124
0
        goto err;
125
126
42.4k
    if ((srtm->blind_ctx = EVP_CIPHER_CTX_new()) == NULL)
127
0
        goto err;
128
129
42.4k
    if (!EVP_EncryptInit_ex2(srtm->blind_ctx, ecb, key, NULL, NULL))
130
0
        goto err;
131
132
42.4k
    EVP_CIPHER_free(ecb);
133
42.4k
    ecb = NULL;
134
135
    /* Create mappings. */
136
42.4k
    if ((srtm->items_fwd = lh_SRTM_ITEM_new(items_fwd_hash, items_fwd_cmp)) == NULL
137
42.4k
        || (srtm->items_rev = lh_SRTM_ITEM_new(items_rev_hash, items_rev_cmp)) == NULL)
138
0
        goto err;
139
140
42.4k
    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
42.4k
}
151
152
static void srtm_free_each(SRTM_ITEM *ihead)
153
394k
{
154
394k
    SRTM_ITEM *inext, *item = ihead;
155
156
462k
    for (item = item->next_by_seq_num; item != NULL; item = inext) {
157
68.1k
        inext = item->next_by_seq_num;
158
68.1k
        OPENSSL_free(item);
159
68.1k
    }
160
161
394k
    OPENSSL_free(ihead);
162
394k
}
163
164
void ossl_quic_srtm_free(QUIC_SRTM *srtm)
165
42.4k
{
166
42.4k
    if (srtm == NULL)
167
0
        return;
168
169
42.4k
    lh_SRTM_ITEM_free(srtm->items_rev);
170
42.4k
    if (srtm->items_fwd != NULL) {
171
42.4k
        lh_SRTM_ITEM_doall(srtm->items_fwd, srtm_free_each);
172
42.4k
        lh_SRTM_ITEM_free(srtm->items_fwd);
173
42.4k
    }
174
175
42.4k
    EVP_CIPHER_CTX_free(srtm->blind_ctx);
176
42.4k
    OPENSSL_free(srtm);
177
42.4k
}
178
179
/*
180
 * Find a SRTM_ITEM by (opaque, seq_num). Returns NULL if no match.
181
 * If head is non-NULL, writes the head of the relevant opaque list to *head if
182
 * there is one.
183
 * If prev is non-NULL, writes the previous node to *prev or NULL if it is
184
 * the first item.
185
 */
186
static SRTM_ITEM *srtm_find(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num,
187
                            SRTM_ITEM **head_p, SRTM_ITEM **prev_p)
188
1.81M
{
189
1.81M
    SRTM_ITEM key, *item = NULL, *prev = NULL;
190
191
1.81M
    key.opaque  = opaque;
192
193
1.81M
    item = lh_SRTM_ITEM_retrieve(srtm->items_fwd, &key);
194
1.81M
    if (head_p != NULL)
195
1.43M
        *head_p = item;
196
197
11.2M
    for (; item != NULL; prev = item, item = item->next_by_seq_num)
198
10.4M
        if (item->seq_num == seq_num) {
199
813k
            break;
200
9.68M
        } else if (item->seq_num < seq_num) {
201
            /*
202
             * List is sorted in descending order so there can't be any match
203
             * after this.
204
             */
205
202k
            item = NULL;
206
202k
            break;
207
202k
        }
208
209
1.81M
    if (prev_p != NULL)
210
376k
        *prev_p = prev;
211
212
1.81M
    return item;
213
1.81M
}
214
215
/*
216
 * Inserts a SRTM_ITEM into the singly-linked by-sequence-number linked list.
217
 * The new head pointer is written to *new_head (which may or may not be
218
 * unchanged).
219
 */
220
static void sorted_insert_seq_num(SRTM_ITEM *head, SRTM_ITEM *item, SRTM_ITEM **new_head)
221
215k
{
222
215k
    uint64_t seq_num = item->seq_num;
223
215k
    SRTM_ITEM *cur = head, **fixup = new_head;
224
225
215k
    *new_head = head;
226
227
2.10M
    while (cur != NULL && cur->seq_num > seq_num) {
228
1.89M
        fixup = &cur->next_by_seq_num;
229
1.89M
        cur = cur->next_by_seq_num;
230
1.89M
    }
231
232
215k
    item->next_by_seq_num = *fixup;
233
215k
    *fixup = item;
234
215k
}
235
236
/*
237
 * Inserts a SRTM_ITEM into the singly-linked by-SRT list.
238
 * The new head pointer is written to *new_head (which may or may not be
239
 * unchanged).
240
 */
241
static void sorted_insert_srt(SRTM_ITEM *head, SRTM_ITEM *item, SRTM_ITEM **new_head)
242
59.8k
{
243
59.8k
    uintptr_t opaque = (uintptr_t)item->opaque;
244
59.8k
    SRTM_ITEM *cur = head, **fixup = new_head;
245
246
59.8k
    *new_head = head;
247
248
1.14M
    while (cur != NULL && (uintptr_t)cur->opaque > opaque) {
249
1.08M
        fixup = &cur->next_by_srt_blinded;
250
1.08M
        cur = cur->next_by_srt_blinded;
251
1.08M
    }
252
253
59.8k
    item->next_by_srt_blinded = *fixup;
254
59.8k
    *fixup = item;
255
59.8k
}
256
257
/*
258
 * Computes the blinded SRT value used for internal lookup for side channel
259
 * mitigation purposes. We compute this once as a cached value when an SRTM_ITEM
260
 * is formed.
261
 */
262
static int srtm_compute_blinded(QUIC_SRTM *srtm, SRTM_ITEM *item,
263
                                const QUIC_STATELESS_RESET_TOKEN *token)
264
3.02M
{
265
3.02M
    int outl = 0;
266
267
    /*
268
     * We use AES-128-ECB as a permutation using a random key to facilitate
269
     * blinding for side-channel purposes. Encrypt the token as a single AES
270
     * block.
271
     */
272
3.02M
    if (!EVP_EncryptUpdate(srtm->blind_ctx, item->srt_blinded, &outl,
273
3.02M
                           (const unsigned char *)token, sizeof(*token)))
274
0
        return 0;
275
276
3.02M
    if (!ossl_assert(outl == sizeof(*token)))
277
0
        return 0;
278
279
3.02M
    return 1;
280
3.02M
}
281
282
int ossl_quic_srtm_add(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num,
283
                       const QUIC_STATELESS_RESET_TOKEN *token)
284
1.43M
{
285
1.43M
    SRTM_ITEM *item = NULL, *head = NULL, *new_head, *r_item;
286
287
1.43M
    if (srtm->alloc_failed)
288
0
        return 0;
289
290
    /* (opaque, seq_num) duplicates not allowed */
291
1.43M
    if ((item = srtm_find(srtm, opaque, seq_num, &head, NULL)) != NULL)
292
801k
        return 0;
293
294
633k
    if ((item = OPENSSL_zalloc(sizeof(*item))) == NULL)
295
0
        return 0;
296
297
633k
    item->opaque    = opaque;
298
633k
    item->seq_num   = seq_num;
299
633k
    item->srt       = *token;
300
633k
    if (!srtm_compute_blinded(srtm, item, &item->srt)) {
301
0
        OPENSSL_free(item);
302
0
        return 0;
303
0
    }
304
305
    /* Add to forward mapping. */
306
633k
    if (head == NULL) {
307
        /* First item under this opaque */
308
417k
        lh_SRTM_ITEM_insert(srtm->items_fwd, item);
309
417k
        if (!srtm_check_lh(srtm, srtm->items_fwd)) {
310
0
            OPENSSL_free(item);
311
0
            return 0;
312
0
        }
313
417k
    } else {
314
215k
        sorted_insert_seq_num(head, item, &new_head);
315
215k
        if (new_head != head) { /* head changed, update in lhash */
316
59.0k
            lh_SRTM_ITEM_insert(srtm->items_fwd, new_head);
317
59.0k
            if (!srtm_check_lh(srtm, srtm->items_fwd)) {
318
0
                OPENSSL_free(item);
319
0
                return 0;
320
0
            }
321
59.0k
        }
322
215k
    }
323
324
    /* Add to reverse mapping. */
325
633k
    r_item = lh_SRTM_ITEM_retrieve(srtm->items_rev, item);
326
633k
    if (r_item == NULL) {
327
        /* First item under this blinded SRT */
328
573k
        lh_SRTM_ITEM_insert(srtm->items_rev, item);
329
573k
        if (!srtm_check_lh(srtm, srtm->items_rev))
330
            /*
331
             * Can't free the item now as we would have to undo the insertion
332
             * into the forward mapping which would require an insert operation
333
             * to restore the previous value. which might also fail. However,
334
             * the item will be freed OK when we free the entire SRTM.
335
             */
336
0
            return 0;
337
573k
    } else {
338
59.8k
        sorted_insert_srt(r_item, item, &new_head);
339
59.8k
        if (new_head != r_item) { /* head changed, update in lhash */
340
24.5k
            lh_SRTM_ITEM_insert(srtm->items_rev, new_head);
341
24.5k
            if (!srtm_check_lh(srtm, srtm->items_rev))
342
                /* As above. */
343
0
                return 0;
344
24.5k
        }
345
59.8k
    }
346
347
633k
    return 1;
348
633k
}
349
350
/* Remove item from reverse mapping. */
351
static int srtm_remove_from_rev(QUIC_SRTM *srtm, SRTM_ITEM *item)
352
170k
{
353
170k
    SRTM_ITEM *rh_item;
354
355
170k
    rh_item = lh_SRTM_ITEM_retrieve(srtm->items_rev, item);
356
170k
    assert(rh_item != NULL);
357
170k
    if (rh_item == item) {
358
        /*
359
         * Change lhash to point to item after this one, or remove the entry if
360
         * this is the last one.
361
         */
362
150k
        if (item->next_by_srt_blinded != NULL) {
363
4.51k
            lh_SRTM_ITEM_insert(srtm->items_rev, item->next_by_srt_blinded);
364
4.51k
            if (!srtm_check_lh(srtm, srtm->items_rev))
365
0
                return 0;
366
146k
        } else {
367
146k
            lh_SRTM_ITEM_delete(srtm->items_rev, item);
368
146k
        }
369
150k
    } else {
370
        /* Find our entry in the SRT list */
371
197k
        for (; rh_item->next_by_srt_blinded != item;
372
178k
               rh_item = rh_item->next_by_srt_blinded);
373
19.5k
        rh_item->next_by_srt_blinded = item->next_by_srt_blinded;
374
19.5k
    }
375
376
170k
    return 1;
377
170k
}
378
379
int ossl_quic_srtm_remove(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num)
380
376k
{
381
376k
    SRTM_ITEM *item, *prev = NULL;
382
383
376k
    if (srtm->alloc_failed)
384
0
        return 0;
385
386
376k
    if ((item = srtm_find(srtm, opaque, seq_num, NULL, &prev)) == NULL)
387
        /* No match */
388
363k
        return 0;
389
390
    /* Remove from forward mapping. */
391
12.7k
    if (prev == NULL) {
392
        /*
393
         * Change lhash to point to item after this one, or remove the entry if
394
         * this is the last one.
395
         */
396
4.76k
        if (item->next_by_seq_num != NULL) {
397
1.95k
            lh_SRTM_ITEM_insert(srtm->items_fwd, item->next_by_seq_num);
398
1.95k
            if (!srtm_check_lh(srtm, srtm->items_fwd))
399
0
                return 0;
400
2.80k
        } else {
401
2.80k
            lh_SRTM_ITEM_delete(srtm->items_fwd, item);
402
2.80k
        }
403
7.95k
    } else {
404
7.95k
        prev->next_by_seq_num = item->next_by_seq_num;
405
7.95k
    }
406
407
    /* Remove from reverse mapping. */
408
12.7k
    if (!srtm_remove_from_rev(srtm, item))
409
0
        return 0;
410
411
12.7k
    OPENSSL_free(item);
412
12.7k
    return 1;
413
12.7k
}
414
415
int ossl_quic_srtm_cull(QUIC_SRTM *srtm, void *opaque)
416
733k
{
417
733k
    SRTM_ITEM key, *item = NULL, *inext, *ihead;
418
419
733k
    key.opaque = opaque;
420
421
733k
    if (srtm->alloc_failed)
422
0
        return 0;
423
424
733k
    if ((ihead = lh_SRTM_ITEM_retrieve(srtm->items_fwd, &key)) == NULL)
425
713k
        return 1; /* nothing removed is a success condition */
426
427
177k
    for (item = ihead; item != NULL; item = inext) {
428
157k
        inext = item->next_by_seq_num;
429
157k
        if (item != ihead) {
430
137k
            srtm_remove_from_rev(srtm, item);
431
137k
            OPENSSL_free(item);
432
137k
        }
433
157k
    }
434
435
20.0k
    lh_SRTM_ITEM_delete(srtm->items_fwd, ihead);
436
20.0k
    srtm_remove_from_rev(srtm, ihead);
437
20.0k
    OPENSSL_free(ihead);
438
20.0k
    return 1;
439
733k
}
440
441
int ossl_quic_srtm_lookup(QUIC_SRTM *srtm,
442
                          const QUIC_STATELESS_RESET_TOKEN *token,
443
                          size_t idx,
444
                          void **opaque, uint64_t *seq_num)
445
2.39M
{
446
2.39M
    SRTM_ITEM key, *item;
447
448
2.39M
    if (srtm->alloc_failed)
449
0
        return 0;
450
451
2.39M
    if (!srtm_compute_blinded(srtm, &key, token))
452
0
        return 0;
453
454
2.39M
    item = lh_SRTM_ITEM_retrieve(srtm->items_rev, &key);
455
3.46M
    for (; idx > 0 && item != NULL; --idx, item = item->next_by_srt_blinded);
456
2.39M
    if (item == NULL)
457
2.39M
        return 0;
458
459
1.82k
    if (opaque != NULL)
460
14
        *opaque     = item->opaque;
461
1.82k
    if (seq_num != NULL)
462
0
        *seq_num    = item->seq_num;
463
464
1.82k
    return 1;
465
2.39M
}
466
467
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
468
469
static uint32_t token_next = 0x5eadbeef;
470
static size_t tokens_seen;
471
472
struct check_args {
473
    uint32_t token;
474
    int      mode;
475
};
476
477
static void check_mark(SRTM_ITEM *item, void *arg)
478
2.24G
{
479
2.24G
    struct check_args *arg_ = arg;
480
2.24G
    uint32_t token = arg_->token;
481
2.24G
    uint64_t prev_seq_num = 0;
482
2.24G
    void *prev_opaque = NULL;
483
2.24G
    int have_prev = 0;
484
485
2.24G
    assert(item != NULL);
486
487
4.85G
    while (item != NULL) {
488
2.61G
        if (have_prev) {
489
368M
            assert(!(item->opaque == prev_opaque && item->seq_num == prev_seq_num));
490
368M
            if (!arg_->mode)
491
223M
                assert(item->opaque != prev_opaque || item->seq_num < prev_seq_num);
492
368M
        }
493
494
2.61G
        ++tokens_seen;
495
2.61G
        item->debug_token = token;
496
2.61G
        prev_opaque  = item->opaque;
497
2.61G
        prev_seq_num = item->seq_num;
498
2.61G
        have_prev = 1;
499
500
2.61G
        if (arg_->mode)
501
1.30G
            item = item->next_by_srt_blinded;
502
1.30G
        else
503
1.30G
            item = item->next_by_seq_num;
504
2.61G
    }
505
2.24G
}
506
507
static void check_count(SRTM_ITEM *item, void *arg)
508
2.24G
{
509
2.24G
    struct check_args *arg_ = arg;
510
2.24G
    uint32_t token = arg_->token;
511
512
2.24G
    assert(item != NULL);
513
514
4.85G
    while (item != NULL) {
515
2.61G
        ++tokens_seen;
516
2.61G
        assert(item->debug_token == token);
517
518
2.61G
        if (arg_->mode)
519
1.30G
            item = item->next_by_seq_num;
520
1.30G
        else
521
1.30G
            item = item->next_by_srt_blinded;
522
2.61G
    }
523
2.24G
}
524
525
#endif
526
527
void ossl_quic_srtm_check(const QUIC_SRTM *srtm)
528
2.71M
{
529
2.71M
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
530
2.71M
    struct check_args args = {0};
531
2.71M
    size_t tokens_expected, tokens_expected_old;
532
533
2.71M
    args.token = token_next;
534
2.71M
    ++token_next;
535
536
2.71M
    assert(srtm != NULL);
537
2.71M
    assert(srtm->blind_ctx != NULL);
538
2.71M
    assert(srtm->items_fwd != NULL);
539
2.71M
    assert(srtm->items_rev != NULL);
540
541
2.71M
    tokens_seen = 0;
542
2.71M
    lh_SRTM_ITEM_doall_arg(srtm->items_fwd, check_mark, &args);
543
544
2.71M
    tokens_expected = tokens_seen;
545
2.71M
    tokens_seen = 0;
546
2.71M
    lh_SRTM_ITEM_doall_arg(srtm->items_rev, check_count, &args);
547
548
2.71M
    assert(tokens_seen == tokens_expected);
549
2.71M
    tokens_expected_old = tokens_expected;
550
551
2.71M
    args.token = token_next;
552
2.71M
    ++token_next;
553
554
2.71M
    args.mode = 1;
555
2.71M
    tokens_seen = 0;
556
2.71M
    lh_SRTM_ITEM_doall_arg(srtm->items_rev, check_mark, &args);
557
558
2.71M
    tokens_expected = tokens_seen;
559
2.71M
    tokens_seen = 0;
560
2.71M
    lh_SRTM_ITEM_doall_arg(srtm->items_fwd, check_count, &args);
561
562
2.71M
    assert(tokens_seen == tokens_expected);
563
2.71M
    assert(tokens_seen == tokens_expected_old);
564
2.71M
#endif
565
2.71M
}