Path: blob/main/crypto/openssl/ssl/quic/quic_lcidm.c
48262 views
/*1* Copyright 2023-2025 The OpenSSL Project Authors. All Rights Reserved.2*3* Licensed under the Apache License 2.0 (the "License"). You may not use4* this file except in compliance with the License. You can obtain a copy5* in the file LICENSE in the source distribution or at6* https://www.openssl.org/source/license.html7*/89#include "internal/quic_lcidm.h"10#include "internal/quic_types.h"11#include "internal/quic_vlint.h"12#include "internal/common.h"13#include "crypto/siphash.h"14#include <openssl/lhash.h>15#include <openssl/rand.h>16#include <openssl/err.h>1718/*19* QUIC Local Connection ID Manager20* ================================21*/2223typedef struct quic_lcidm_conn_st QUIC_LCIDM_CONN;2425enum {26LCID_TYPE_ODCID, /* This LCID is the ODCID from the peer */27LCID_TYPE_INITIAL, /* This is our Initial SCID */28LCID_TYPE_NCID /* This LCID was issued via a NCID frame */29};3031typedef struct quic_lcid_st {32QUIC_CONN_ID cid;33uint64_t seq_num;3435/* copy of the hash key from lcidm */36uint64_t *hash_key;3738/* Back-pointer to the owning QUIC_LCIDM_CONN structure. */39QUIC_LCIDM_CONN *conn;4041/* LCID_TYPE_* */42unsigned int type : 2;43} QUIC_LCID;4445DEFINE_LHASH_OF_EX(QUIC_LCID);46DEFINE_LHASH_OF_EX(QUIC_LCIDM_CONN);4748struct quic_lcidm_conn_st {49size_t num_active_lcid;50LHASH_OF(QUIC_LCID) *lcids;51void *opaque;52QUIC_LCID *odcid_lcid_obj;53uint64_t next_seq_num;5455/* Have we enrolled an ODCID? */56unsigned int done_odcid : 1;57};5859struct quic_lcidm_st {60OSSL_LIB_CTX *libctx;61uint64_t hash_key[2]; /* random key for siphash */62LHASH_OF(QUIC_LCID) *lcids; /* (QUIC_CONN_ID) -> (QUIC_LCID *) */63LHASH_OF(QUIC_LCIDM_CONN) *conns; /* (void *opaque) -> (QUIC_LCIDM_CONN *) */64size_t lcid_len; /* Length in bytes for all LCIDs */65#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION66QUIC_CONN_ID next_lcid;67#endif68};6970static unsigned long lcid_hash(const QUIC_LCID *lcid_obj)71{72SIPHASH siphash = {0, };73unsigned long hashval = 0;7475if (!SipHash_set_hash_size(&siphash, sizeof(unsigned long)))76goto out;77if (!SipHash_Init(&siphash, (uint8_t *)lcid_obj->hash_key, 0, 0))78goto out;79SipHash_Update(&siphash, lcid_obj->cid.id, lcid_obj->cid.id_len);80if (!SipHash_Final(&siphash, (unsigned char *)&hashval,81sizeof(unsigned long)))82goto out;83out:84return hashval;85}8687static int lcid_comp(const QUIC_LCID *a, const QUIC_LCID *b)88{89return !ossl_quic_conn_id_eq(&a->cid, &b->cid);90}9192static unsigned long lcidm_conn_hash(const QUIC_LCIDM_CONN *conn)93{94return (unsigned long)(uintptr_t)conn->opaque;95}9697static int lcidm_conn_comp(const QUIC_LCIDM_CONN *a, const QUIC_LCIDM_CONN *b)98{99return a->opaque != b->opaque;100}101102QUIC_LCIDM *ossl_quic_lcidm_new(OSSL_LIB_CTX *libctx, size_t lcid_len)103{104QUIC_LCIDM *lcidm = NULL;105106if (lcid_len > QUIC_MAX_CONN_ID_LEN)107goto err;108109if ((lcidm = OPENSSL_zalloc(sizeof(*lcidm))) == NULL)110goto err;111112/* generate a random key for the hash tables hash function */113if (!RAND_bytes_ex(libctx, (unsigned char *)&lcidm->hash_key,114sizeof(uint64_t) * 2, 0))115goto err;116117if ((lcidm->lcids = lh_QUIC_LCID_new(lcid_hash, lcid_comp)) == NULL)118goto err;119120if ((lcidm->conns = lh_QUIC_LCIDM_CONN_new(lcidm_conn_hash,121lcidm_conn_comp)) == NULL)122goto err;123124lcidm->libctx = libctx;125lcidm->lcid_len = lcid_len;126return lcidm;127128err:129if (lcidm != NULL) {130lh_QUIC_LCID_free(lcidm->lcids);131lh_QUIC_LCIDM_CONN_free(lcidm->conns);132OPENSSL_free(lcidm);133}134return NULL;135}136137static void lcidm_delete_conn(QUIC_LCIDM *lcidm, QUIC_LCIDM_CONN *conn);138139static void lcidm_delete_conn_(QUIC_LCIDM_CONN *conn, void *arg)140{141lcidm_delete_conn((QUIC_LCIDM *)arg, conn);142}143144void ossl_quic_lcidm_free(QUIC_LCIDM *lcidm)145{146if (lcidm == NULL)147return;148149/*150* Calling OPENSSL_lh_delete during a doall call is unsafe with our151* current LHASH implementation for several reasons:152*153* - firstly, because deletes can cause the hashtable to be contracted,154* resulting in rehashing which might cause items in later buckets to155* move to earlier buckets, which might cause doall to skip an item,156* resulting in a memory leak;157*158* - secondly, because doall in general is not safe across hashtable159* size changes, as it caches hashtable size and pointer values160* while operating.161*162* The fix for this is to disable hashtable contraction using the following163* call, which guarantees that no rehashing will occur so long as we only164* call delete and not insert.165*/166lh_QUIC_LCIDM_CONN_set_down_load(lcidm->conns, 0);167168lh_QUIC_LCIDM_CONN_doall_arg(lcidm->conns, lcidm_delete_conn_, lcidm);169170lh_QUIC_LCID_free(lcidm->lcids);171lh_QUIC_LCIDM_CONN_free(lcidm->conns);172OPENSSL_free(lcidm);173}174175static QUIC_LCID *lcidm_get0_lcid(const QUIC_LCIDM *lcidm, const QUIC_CONN_ID *lcid)176{177QUIC_LCID key;178179key.cid = *lcid;180key.hash_key = (uint64_t *)lcidm->hash_key;181182if (key.cid.id_len > QUIC_MAX_CONN_ID_LEN)183return NULL;184185return lh_QUIC_LCID_retrieve(lcidm->lcids, &key);186}187188static QUIC_LCIDM_CONN *lcidm_get0_conn(const QUIC_LCIDM *lcidm, void *opaque)189{190QUIC_LCIDM_CONN key;191192key.opaque = opaque;193194return lh_QUIC_LCIDM_CONN_retrieve(lcidm->conns, &key);195}196197static QUIC_LCIDM_CONN *lcidm_upsert_conn(const QUIC_LCIDM *lcidm, void *opaque)198{199QUIC_LCIDM_CONN *conn = lcidm_get0_conn(lcidm, opaque);200201if (conn != NULL)202return conn;203204if ((conn = OPENSSL_zalloc(sizeof(*conn))) == NULL)205goto err;206207if ((conn->lcids = lh_QUIC_LCID_new(lcid_hash, lcid_comp)) == NULL)208goto err;209210conn->opaque = opaque;211212lh_QUIC_LCIDM_CONN_insert(lcidm->conns, conn);213if (lh_QUIC_LCIDM_CONN_error(lcidm->conns))214goto err;215216return conn;217218err:219if (conn != NULL) {220lh_QUIC_LCID_free(conn->lcids);221OPENSSL_free(conn);222}223return NULL;224}225226static void lcidm_delete_conn_lcid(QUIC_LCIDM *lcidm, QUIC_LCID *lcid_obj)227{228lh_QUIC_LCID_delete(lcidm->lcids, lcid_obj);229lh_QUIC_LCID_delete(lcid_obj->conn->lcids, lcid_obj);230assert(lcid_obj->conn->num_active_lcid > 0);231--lcid_obj->conn->num_active_lcid;232OPENSSL_free(lcid_obj);233}234235/* doall_arg wrapper */236static void lcidm_delete_conn_lcid_(QUIC_LCID *lcid_obj, void *arg)237{238lcidm_delete_conn_lcid((QUIC_LCIDM *)arg, lcid_obj);239}240241static void lcidm_delete_conn(QUIC_LCIDM *lcidm, QUIC_LCIDM_CONN *conn)242{243/* See comment in ossl_quic_lcidm_free */244lh_QUIC_LCID_set_down_load(conn->lcids, 0);245246lh_QUIC_LCID_doall_arg(conn->lcids, lcidm_delete_conn_lcid_, lcidm);247lh_QUIC_LCIDM_CONN_delete(lcidm->conns, conn);248lh_QUIC_LCID_free(conn->lcids);249OPENSSL_free(conn);250}251252static QUIC_LCID *lcidm_conn_new_lcid(QUIC_LCIDM *lcidm, QUIC_LCIDM_CONN *conn,253const QUIC_CONN_ID *lcid)254{255QUIC_LCID *lcid_obj = NULL;256257if (lcid->id_len > QUIC_MAX_CONN_ID_LEN)258return NULL;259260if ((lcid_obj = OPENSSL_zalloc(sizeof(*lcid_obj))) == NULL)261goto err;262263lcid_obj->cid = *lcid;264lcid_obj->conn = conn;265lcid_obj->hash_key = lcidm->hash_key;266267lh_QUIC_LCID_insert(conn->lcids, lcid_obj);268if (lh_QUIC_LCID_error(conn->lcids))269goto err;270271lh_QUIC_LCID_insert(lcidm->lcids, lcid_obj);272if (lh_QUIC_LCID_error(lcidm->lcids)) {273lh_QUIC_LCID_delete(conn->lcids, lcid_obj);274goto err;275}276277++conn->num_active_lcid;278return lcid_obj;279280err:281OPENSSL_free(lcid_obj);282return NULL;283}284285size_t ossl_quic_lcidm_get_lcid_len(const QUIC_LCIDM *lcidm)286{287return lcidm->lcid_len;288}289290size_t ossl_quic_lcidm_get_num_active_lcid(const QUIC_LCIDM *lcidm,291void *opaque)292{293QUIC_LCIDM_CONN *conn;294295conn = lcidm_get0_conn(lcidm, opaque);296if (conn == NULL)297return 0;298299return conn->num_active_lcid;300}301302static int lcidm_generate_cid(QUIC_LCIDM *lcidm,303QUIC_CONN_ID *cid)304{305#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION306int i;307308lcidm->next_lcid.id_len = (unsigned char)lcidm->lcid_len;309*cid = lcidm->next_lcid;310311for (i = lcidm->lcid_len - 1; i >= 0; --i)312if (++lcidm->next_lcid.id[i] != 0)313break;314315return 1;316#else317return ossl_quic_gen_rand_conn_id(lcidm->libctx, lcidm->lcid_len, cid);318#endif319}320321static int lcidm_generate(QUIC_LCIDM *lcidm,322void *opaque,323unsigned int type,324QUIC_CONN_ID *lcid_out,325uint64_t *seq_num)326{327QUIC_LCIDM_CONN *conn;328QUIC_LCID key, *lcid_obj;329size_t i;330#define MAX_RETRIES 8331332if ((conn = lcidm_upsert_conn(lcidm, opaque)) == NULL)333return 0;334335if ((type == LCID_TYPE_INITIAL && conn->next_seq_num > 0)336|| conn->next_seq_num > OSSL_QUIC_VLINT_MAX)337return 0;338339i = 0;340do {341if (i++ >= MAX_RETRIES)342/*343* Too many retries; should not happen but if it does, don't loop344* endlessly.345*/346return 0;347348if (!lcidm_generate_cid(lcidm, lcid_out))349return 0;350351key.cid = *lcid_out;352key.hash_key = lcidm->hash_key;353354/* If a collision occurs, retry. */355} while (lh_QUIC_LCID_retrieve(lcidm->lcids, &key) != NULL);356357if ((lcid_obj = lcidm_conn_new_lcid(lcidm, conn, lcid_out)) == NULL)358return 0;359360lcid_obj->seq_num = conn->next_seq_num;361lcid_obj->type = type;362363if (seq_num != NULL)364*seq_num = lcid_obj->seq_num;365366++conn->next_seq_num;367return 1;368}369370int ossl_quic_lcidm_enrol_odcid(QUIC_LCIDM *lcidm,371void *opaque,372const QUIC_CONN_ID *initial_odcid)373{374QUIC_LCIDM_CONN *conn;375QUIC_LCID key, *lcid_obj;376377if (initial_odcid == NULL || initial_odcid->id_len < QUIC_MIN_ODCID_LEN378|| initial_odcid->id_len > QUIC_MAX_CONN_ID_LEN)379return 0;380381if ((conn = lcidm_upsert_conn(lcidm, opaque)) == NULL)382return 0;383384if (conn->done_odcid)385return 0;386387key.cid = *initial_odcid;388key.hash_key = lcidm->hash_key;389if (lh_QUIC_LCID_retrieve(lcidm->lcids, &key) != NULL)390return 0;391392if ((lcid_obj = lcidm_conn_new_lcid(lcidm, conn, initial_odcid)) == NULL)393return 0;394395lcid_obj->seq_num = LCIDM_ODCID_SEQ_NUM;396lcid_obj->type = LCID_TYPE_ODCID;397398conn->odcid_lcid_obj = lcid_obj;399conn->done_odcid = 1;400return 1;401}402403int ossl_quic_lcidm_generate_initial(QUIC_LCIDM *lcidm,404void *opaque,405QUIC_CONN_ID *initial_lcid)406{407return lcidm_generate(lcidm, opaque, LCID_TYPE_INITIAL,408initial_lcid, NULL);409}410411int ossl_quic_lcidm_bind_channel(QUIC_LCIDM *lcidm, void *opaque,412const QUIC_CONN_ID *lcid)413{414QUIC_LCIDM_CONN *conn;415QUIC_LCID *lcid_obj;416417/*418* the plan is simple:419* make sure the lcid is still unused.420* do the same business as ossl_quic_lcidm_gnerate_initial() does,421* except we will use lcid instead of generating a new one.422*/423if (ossl_quic_lcidm_lookup(lcidm, lcid, NULL, NULL) != 0)424return 0;425426if ((conn = lcidm_upsert_conn(lcidm, opaque)) == NULL)427return 0;428429if ((lcid_obj = lcidm_conn_new_lcid(lcidm, conn, lcid)) == NULL) {430lcidm_delete_conn(lcidm, conn);431return 0;432}433434lcid_obj->seq_num = conn->next_seq_num;435lcid_obj->type = LCID_TYPE_INITIAL;436conn->next_seq_num++;437438return 1;439}440441int ossl_quic_lcidm_generate(QUIC_LCIDM *lcidm,442void *opaque,443OSSL_QUIC_FRAME_NEW_CONN_ID *ncid_frame)444{445ncid_frame->seq_num = 0;446ncid_frame->retire_prior_to = 0;447448return lcidm_generate(lcidm, opaque, LCID_TYPE_NCID,449&ncid_frame->conn_id,450&ncid_frame->seq_num);451}452453int ossl_quic_lcidm_retire_odcid(QUIC_LCIDM *lcidm, void *opaque)454{455QUIC_LCIDM_CONN *conn;456457if ((conn = lcidm_upsert_conn(lcidm, opaque)) == NULL)458return 0;459460if (conn->odcid_lcid_obj == NULL)461return 0;462463lcidm_delete_conn_lcid(lcidm, conn->odcid_lcid_obj);464conn->odcid_lcid_obj = NULL;465return 1;466}467468struct retire_args {469QUIC_LCID *earliest_seq_num_lcid_obj;470uint64_t earliest_seq_num, retire_prior_to;471};472473static void retire_for_conn(QUIC_LCID *lcid_obj, void *arg)474{475struct retire_args *args = arg;476477/* ODCID LCID cannot be retired via this API */478if (lcid_obj->type == LCID_TYPE_ODCID479|| lcid_obj->seq_num >= args->retire_prior_to)480return;481482if (lcid_obj->seq_num < args->earliest_seq_num) {483args->earliest_seq_num = lcid_obj->seq_num;484args->earliest_seq_num_lcid_obj = lcid_obj;485}486}487488int ossl_quic_lcidm_retire(QUIC_LCIDM *lcidm,489void *opaque,490uint64_t retire_prior_to,491const QUIC_CONN_ID *containing_pkt_dcid,492QUIC_CONN_ID *retired_lcid,493uint64_t *retired_seq_num,494int *did_retire)495{496QUIC_LCIDM_CONN key, *conn;497struct retire_args args = {0};498499key.opaque = opaque;500501if (did_retire == NULL)502return 0;503504*did_retire = 0;505if ((conn = lh_QUIC_LCIDM_CONN_retrieve(lcidm->conns, &key)) == NULL)506return 1;507508args.retire_prior_to = retire_prior_to;509args.earliest_seq_num = UINT64_MAX;510511lh_QUIC_LCID_doall_arg(conn->lcids, retire_for_conn, &args);512if (args.earliest_seq_num_lcid_obj == NULL)513return 1;514515if (containing_pkt_dcid != NULL516&& ossl_quic_conn_id_eq(&args.earliest_seq_num_lcid_obj->cid,517containing_pkt_dcid))518return 0;519520*did_retire = 1;521if (retired_lcid != NULL)522*retired_lcid = args.earliest_seq_num_lcid_obj->cid;523if (retired_seq_num != NULL)524*retired_seq_num = args.earliest_seq_num_lcid_obj->seq_num;525526lcidm_delete_conn_lcid(lcidm, args.earliest_seq_num_lcid_obj);527return 1;528}529530int ossl_quic_lcidm_cull(QUIC_LCIDM *lcidm, void *opaque)531{532QUIC_LCIDM_CONN key, *conn;533534key.opaque = opaque;535536if ((conn = lh_QUIC_LCIDM_CONN_retrieve(lcidm->conns, &key)) == NULL)537return 0;538539lcidm_delete_conn(lcidm, conn);540return 1;541}542543int ossl_quic_lcidm_lookup(QUIC_LCIDM *lcidm,544const QUIC_CONN_ID *lcid,545uint64_t *seq_num,546void **opaque)547{548QUIC_LCID *lcid_obj;549550if (lcid == NULL)551return 0;552553if ((lcid_obj = lcidm_get0_lcid(lcidm, lcid)) == NULL)554return 0;555556if (seq_num != NULL)557*seq_num = lcid_obj->seq_num;558559if (opaque != NULL)560*opaque = lcid_obj->conn->opaque;561562return 1;563}564565int ossl_quic_lcidm_debug_remove(QUIC_LCIDM *lcidm,566const QUIC_CONN_ID *lcid)567{568QUIC_LCID key, *lcid_obj;569570key.cid = *lcid;571key.hash_key = lcidm->hash_key;572if ((lcid_obj = lh_QUIC_LCID_retrieve(lcidm->lcids, &key)) == NULL)573return 0;574575lcidm_delete_conn_lcid(lcidm, lcid_obj);576return 1;577}578579int ossl_quic_lcidm_debug_add(QUIC_LCIDM *lcidm, void *opaque,580const QUIC_CONN_ID *lcid,581uint64_t seq_num)582{583QUIC_LCIDM_CONN *conn;584QUIC_LCID key, *lcid_obj;585586if (lcid == NULL || lcid->id_len > QUIC_MAX_CONN_ID_LEN)587return 0;588589if ((conn = lcidm_upsert_conn(lcidm, opaque)) == NULL)590return 0;591592key.cid = *lcid;593key.hash_key = lcidm->hash_key;594if (lh_QUIC_LCID_retrieve(lcidm->lcids, &key) != NULL)595return 0;596597if ((lcid_obj = lcidm_conn_new_lcid(lcidm, conn, lcid)) == NULL)598return 0;599600lcid_obj->seq_num = seq_num;601lcid_obj->type = LCID_TYPE_NCID;602return 1;603}604605int ossl_quic_lcidm_get_unused_cid(QUIC_LCIDM *lcidm, QUIC_CONN_ID *cid)606{607int i;608609for (i = 0; i < 10; i++) {610if (lcidm_generate_cid(lcidm, cid)611&& lcidm_get0_lcid(lcidm, cid) == NULL)612return 1; /* not found <=> radomly generated cid is unused */613}614615return 0;616}617618619