Path: blob/main/crypto/openssl/ssl/quic/quic_lcidm.c
109445 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 = {730,74};75unsigned long hashval = 0;7677if (!SipHash_set_hash_size(&siphash, sizeof(unsigned long)))78goto out;79if (!SipHash_Init(&siphash, (uint8_t *)lcid_obj->hash_key, 0, 0))80goto out;81SipHash_Update(&siphash, lcid_obj->cid.id, lcid_obj->cid.id_len);82if (!SipHash_Final(&siphash, (unsigned char *)&hashval,83sizeof(unsigned long)))84goto out;85out:86return hashval;87}8889static int lcid_comp(const QUIC_LCID *a, const QUIC_LCID *b)90{91return !ossl_quic_conn_id_eq(&a->cid, &b->cid);92}9394static unsigned long lcidm_conn_hash(const QUIC_LCIDM_CONN *conn)95{96return (unsigned long)(uintptr_t)conn->opaque;97}9899static int lcidm_conn_comp(const QUIC_LCIDM_CONN *a, const QUIC_LCIDM_CONN *b)100{101return a->opaque != b->opaque;102}103104QUIC_LCIDM *ossl_quic_lcidm_new(OSSL_LIB_CTX *libctx, size_t lcid_len)105{106QUIC_LCIDM *lcidm = NULL;107108if (lcid_len > QUIC_MAX_CONN_ID_LEN)109goto err;110111if ((lcidm = OPENSSL_zalloc(sizeof(*lcidm))) == NULL)112goto err;113114/* generate a random key for the hash tables hash function */115if (!RAND_bytes_ex(libctx, (unsigned char *)&lcidm->hash_key,116sizeof(uint64_t) * 2, 0))117goto err;118119if ((lcidm->lcids = lh_QUIC_LCID_new(lcid_hash, lcid_comp)) == NULL)120goto err;121122if ((lcidm->conns = lh_QUIC_LCIDM_CONN_new(lcidm_conn_hash,123lcidm_conn_comp))124== NULL)125goto err;126127lcidm->libctx = libctx;128lcidm->lcid_len = lcid_len;129return lcidm;130131err:132if (lcidm != NULL) {133lh_QUIC_LCID_free(lcidm->lcids);134lh_QUIC_LCIDM_CONN_free(lcidm->conns);135OPENSSL_free(lcidm);136}137return NULL;138}139140static void lcidm_delete_conn(QUIC_LCIDM *lcidm, QUIC_LCIDM_CONN *conn);141142static void lcidm_delete_conn_(QUIC_LCIDM_CONN *conn, void *arg)143{144lcidm_delete_conn((QUIC_LCIDM *)arg, conn);145}146147void ossl_quic_lcidm_free(QUIC_LCIDM *lcidm)148{149if (lcidm == NULL)150return;151152/*153* Calling OPENSSL_lh_delete during a doall call is unsafe with our154* current LHASH implementation for several reasons:155*156* - firstly, because deletes can cause the hashtable to be contracted,157* resulting in rehashing which might cause items in later buckets to158* move to earlier buckets, which might cause doall to skip an item,159* resulting in a memory leak;160*161* - secondly, because doall in general is not safe across hashtable162* size changes, as it caches hashtable size and pointer values163* while operating.164*165* The fix for this is to disable hashtable contraction using the following166* call, which guarantees that no rehashing will occur so long as we only167* call delete and not insert.168*/169lh_QUIC_LCIDM_CONN_set_down_load(lcidm->conns, 0);170171lh_QUIC_LCIDM_CONN_doall_arg(lcidm->conns, lcidm_delete_conn_, lcidm);172173lh_QUIC_LCID_free(lcidm->lcids);174lh_QUIC_LCIDM_CONN_free(lcidm->conns);175OPENSSL_free(lcidm);176}177178static QUIC_LCID *lcidm_get0_lcid(const QUIC_LCIDM *lcidm, const QUIC_CONN_ID *lcid)179{180QUIC_LCID key;181182key.cid = *lcid;183key.hash_key = (uint64_t *)lcidm->hash_key;184185if (key.cid.id_len > QUIC_MAX_CONN_ID_LEN)186return NULL;187188return lh_QUIC_LCID_retrieve(lcidm->lcids, &key);189}190191static QUIC_LCIDM_CONN *lcidm_get0_conn(const QUIC_LCIDM *lcidm, void *opaque)192{193QUIC_LCIDM_CONN key;194195key.opaque = opaque;196197return lh_QUIC_LCIDM_CONN_retrieve(lcidm->conns, &key);198}199200static QUIC_LCIDM_CONN *lcidm_upsert_conn(const QUIC_LCIDM *lcidm, void *opaque)201{202QUIC_LCIDM_CONN *conn = lcidm_get0_conn(lcidm, opaque);203204if (conn != NULL)205return conn;206207if ((conn = OPENSSL_zalloc(sizeof(*conn))) == NULL)208goto err;209210if ((conn->lcids = lh_QUIC_LCID_new(lcid_hash, lcid_comp)) == NULL)211goto err;212213conn->opaque = opaque;214215lh_QUIC_LCIDM_CONN_insert(lcidm->conns, conn);216if (lh_QUIC_LCIDM_CONN_error(lcidm->conns))217goto err;218219return conn;220221err:222if (conn != NULL) {223lh_QUIC_LCID_free(conn->lcids);224OPENSSL_free(conn);225}226return NULL;227}228229static void lcidm_delete_conn_lcid(QUIC_LCIDM *lcidm, QUIC_LCID *lcid_obj)230{231lh_QUIC_LCID_delete(lcidm->lcids, lcid_obj);232lh_QUIC_LCID_delete(lcid_obj->conn->lcids, lcid_obj);233assert(lcid_obj->conn->num_active_lcid > 0);234--lcid_obj->conn->num_active_lcid;235OPENSSL_free(lcid_obj);236}237238/* doall_arg wrapper */239static void lcidm_delete_conn_lcid_(QUIC_LCID *lcid_obj, void *arg)240{241lcidm_delete_conn_lcid((QUIC_LCIDM *)arg, lcid_obj);242}243244static void lcidm_delete_conn(QUIC_LCIDM *lcidm, QUIC_LCIDM_CONN *conn)245{246/* See comment in ossl_quic_lcidm_free */247lh_QUIC_LCID_set_down_load(conn->lcids, 0);248249lh_QUIC_LCID_doall_arg(conn->lcids, lcidm_delete_conn_lcid_, lcidm);250lh_QUIC_LCIDM_CONN_delete(lcidm->conns, conn);251lh_QUIC_LCID_free(conn->lcids);252OPENSSL_free(conn);253}254255static QUIC_LCID *lcidm_conn_new_lcid(QUIC_LCIDM *lcidm, QUIC_LCIDM_CONN *conn,256const QUIC_CONN_ID *lcid)257{258QUIC_LCID *lcid_obj = NULL;259260if (lcid->id_len > QUIC_MAX_CONN_ID_LEN)261return NULL;262263if ((lcid_obj = OPENSSL_zalloc(sizeof(*lcid_obj))) == NULL)264goto err;265266lcid_obj->cid = *lcid;267lcid_obj->conn = conn;268lcid_obj->hash_key = lcidm->hash_key;269270lh_QUIC_LCID_insert(conn->lcids, lcid_obj);271if (lh_QUIC_LCID_error(conn->lcids))272goto err;273274lh_QUIC_LCID_insert(lcidm->lcids, lcid_obj);275if (lh_QUIC_LCID_error(lcidm->lcids)) {276lh_QUIC_LCID_delete(conn->lcids, lcid_obj);277goto err;278}279280++conn->num_active_lcid;281return lcid_obj;282283err:284OPENSSL_free(lcid_obj);285return NULL;286}287288size_t ossl_quic_lcidm_get_lcid_len(const QUIC_LCIDM *lcidm)289{290return lcidm->lcid_len;291}292293size_t ossl_quic_lcidm_get_num_active_lcid(const QUIC_LCIDM *lcidm,294void *opaque)295{296QUIC_LCIDM_CONN *conn;297298conn = lcidm_get0_conn(lcidm, opaque);299if (conn == NULL)300return 0;301302return conn->num_active_lcid;303}304305static int lcidm_generate_cid(QUIC_LCIDM *lcidm,306QUIC_CONN_ID *cid)307{308#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION309int i;310311lcidm->next_lcid.id_len = (unsigned char)lcidm->lcid_len;312*cid = lcidm->next_lcid;313314for (i = lcidm->lcid_len - 1; i >= 0; --i)315if (++lcidm->next_lcid.id[i] != 0)316break;317318return 1;319#else320return ossl_quic_gen_rand_conn_id(lcidm->libctx, lcidm->lcid_len, cid);321#endif322}323324static int lcidm_generate(QUIC_LCIDM *lcidm,325void *opaque,326unsigned int type,327QUIC_CONN_ID *lcid_out,328uint64_t *seq_num)329{330QUIC_LCIDM_CONN *conn;331QUIC_LCID key, *lcid_obj;332size_t i;333#define MAX_RETRIES 8334335if ((conn = lcidm_upsert_conn(lcidm, opaque)) == NULL)336return 0;337338if ((type == LCID_TYPE_INITIAL && conn->next_seq_num > 0)339|| conn->next_seq_num > OSSL_QUIC_VLINT_MAX)340return 0;341342i = 0;343do {344if (i++ >= MAX_RETRIES)345/*346* Too many retries; should not happen but if it does, don't loop347* endlessly.348*/349return 0;350351if (!lcidm_generate_cid(lcidm, lcid_out))352return 0;353354key.cid = *lcid_out;355key.hash_key = lcidm->hash_key;356357/* If a collision occurs, retry. */358} while (lh_QUIC_LCID_retrieve(lcidm->lcids, &key) != NULL);359360if ((lcid_obj = lcidm_conn_new_lcid(lcidm, conn, lcid_out)) == NULL)361return 0;362363lcid_obj->seq_num = conn->next_seq_num;364lcid_obj->type = type;365366if (seq_num != NULL)367*seq_num = lcid_obj->seq_num;368369++conn->next_seq_num;370return 1;371}372373int ossl_quic_lcidm_enrol_odcid(QUIC_LCIDM *lcidm,374void *opaque,375const QUIC_CONN_ID *initial_odcid)376{377QUIC_LCIDM_CONN *conn;378QUIC_LCID key, *lcid_obj;379380if (initial_odcid == NULL || initial_odcid->id_len < QUIC_MIN_ODCID_LEN381|| initial_odcid->id_len > QUIC_MAX_CONN_ID_LEN)382return 0;383384if ((conn = lcidm_upsert_conn(lcidm, opaque)) == NULL)385return 0;386387if (conn->done_odcid)388return 0;389390key.cid = *initial_odcid;391key.hash_key = lcidm->hash_key;392if (lh_QUIC_LCID_retrieve(lcidm->lcids, &key) != NULL)393return 0;394395if ((lcid_obj = lcidm_conn_new_lcid(lcidm, conn, initial_odcid)) == NULL)396return 0;397398lcid_obj->seq_num = LCIDM_ODCID_SEQ_NUM;399lcid_obj->type = LCID_TYPE_ODCID;400401conn->odcid_lcid_obj = lcid_obj;402conn->done_odcid = 1;403return 1;404}405406int ossl_quic_lcidm_generate_initial(QUIC_LCIDM *lcidm,407void *opaque,408QUIC_CONN_ID *initial_lcid)409{410return lcidm_generate(lcidm, opaque, LCID_TYPE_INITIAL,411initial_lcid, NULL);412}413414int ossl_quic_lcidm_bind_channel(QUIC_LCIDM *lcidm, void *opaque,415const QUIC_CONN_ID *lcid)416{417QUIC_LCIDM_CONN *conn;418QUIC_LCID *lcid_obj;419420/*421* the plan is simple:422* make sure the lcid is still unused.423* do the same business as ossl_quic_lcidm_gnerate_initial() does,424* except we will use lcid instead of generating a new one.425*/426if (ossl_quic_lcidm_lookup(lcidm, lcid, NULL, NULL) != 0)427return 0;428429if ((conn = lcidm_upsert_conn(lcidm, opaque)) == NULL)430return 0;431432if ((lcid_obj = lcidm_conn_new_lcid(lcidm, conn, lcid)) == NULL) {433lcidm_delete_conn(lcidm, conn);434return 0;435}436437lcid_obj->seq_num = conn->next_seq_num;438lcid_obj->type = LCID_TYPE_INITIAL;439conn->next_seq_num++;440441return 1;442}443444int ossl_quic_lcidm_generate(QUIC_LCIDM *lcidm,445void *opaque,446OSSL_QUIC_FRAME_NEW_CONN_ID *ncid_frame)447{448ncid_frame->seq_num = 0;449ncid_frame->retire_prior_to = 0;450451return lcidm_generate(lcidm, opaque, LCID_TYPE_NCID,452&ncid_frame->conn_id,453&ncid_frame->seq_num);454}455456int ossl_quic_lcidm_retire_odcid(QUIC_LCIDM *lcidm, void *opaque)457{458QUIC_LCIDM_CONN *conn;459460if ((conn = lcidm_upsert_conn(lcidm, opaque)) == NULL)461return 0;462463if (conn->odcid_lcid_obj == NULL)464return 0;465466lcidm_delete_conn_lcid(lcidm, conn->odcid_lcid_obj);467conn->odcid_lcid_obj = NULL;468return 1;469}470471struct retire_args {472QUIC_LCID *earliest_seq_num_lcid_obj;473uint64_t earliest_seq_num, retire_prior_to;474};475476static void retire_for_conn(QUIC_LCID *lcid_obj, void *arg)477{478struct retire_args *args = arg;479480/* ODCID LCID cannot be retired via this API */481if (lcid_obj->type == LCID_TYPE_ODCID482|| lcid_obj->seq_num >= args->retire_prior_to)483return;484485if (lcid_obj->seq_num < args->earliest_seq_num) {486args->earliest_seq_num = lcid_obj->seq_num;487args->earliest_seq_num_lcid_obj = lcid_obj;488}489}490491int ossl_quic_lcidm_retire(QUIC_LCIDM *lcidm,492void *opaque,493uint64_t retire_prior_to,494const QUIC_CONN_ID *containing_pkt_dcid,495QUIC_CONN_ID *retired_lcid,496uint64_t *retired_seq_num,497int *did_retire)498{499QUIC_LCIDM_CONN key, *conn;500struct retire_args args = { 0 };501502key.opaque = opaque;503504if (did_retire == NULL)505return 0;506507*did_retire = 0;508if ((conn = lh_QUIC_LCIDM_CONN_retrieve(lcidm->conns, &key)) == NULL)509return 1;510511args.retire_prior_to = retire_prior_to;512args.earliest_seq_num = UINT64_MAX;513514lh_QUIC_LCID_doall_arg(conn->lcids, retire_for_conn, &args);515if (args.earliest_seq_num_lcid_obj == NULL)516return 1;517518if (containing_pkt_dcid != NULL519&& ossl_quic_conn_id_eq(&args.earliest_seq_num_lcid_obj->cid,520containing_pkt_dcid))521return 0;522523*did_retire = 1;524if (retired_lcid != NULL)525*retired_lcid = args.earliest_seq_num_lcid_obj->cid;526if (retired_seq_num != NULL)527*retired_seq_num = args.earliest_seq_num_lcid_obj->seq_num;528529lcidm_delete_conn_lcid(lcidm, args.earliest_seq_num_lcid_obj);530return 1;531}532533int ossl_quic_lcidm_cull(QUIC_LCIDM *lcidm, void *opaque)534{535QUIC_LCIDM_CONN key, *conn;536537key.opaque = opaque;538539if ((conn = lh_QUIC_LCIDM_CONN_retrieve(lcidm->conns, &key)) == NULL)540return 0;541542lcidm_delete_conn(lcidm, conn);543return 1;544}545546int ossl_quic_lcidm_lookup(QUIC_LCIDM *lcidm,547const QUIC_CONN_ID *lcid,548uint64_t *seq_num,549void **opaque)550{551QUIC_LCID *lcid_obj;552553if (lcid == NULL)554return 0;555556if ((lcid_obj = lcidm_get0_lcid(lcidm, lcid)) == NULL)557return 0;558559if (seq_num != NULL)560*seq_num = lcid_obj->seq_num;561562if (opaque != NULL)563*opaque = lcid_obj->conn->opaque;564565return 1;566}567568int ossl_quic_lcidm_debug_remove(QUIC_LCIDM *lcidm,569const QUIC_CONN_ID *lcid)570{571QUIC_LCID key, *lcid_obj;572573key.cid = *lcid;574key.hash_key = lcidm->hash_key;575if ((lcid_obj = lh_QUIC_LCID_retrieve(lcidm->lcids, &key)) == NULL)576return 0;577578lcidm_delete_conn_lcid(lcidm, lcid_obj);579return 1;580}581582int ossl_quic_lcidm_debug_add(QUIC_LCIDM *lcidm, void *opaque,583const QUIC_CONN_ID *lcid,584uint64_t seq_num)585{586QUIC_LCIDM_CONN *conn;587QUIC_LCID key, *lcid_obj;588589if (lcid == NULL || lcid->id_len > QUIC_MAX_CONN_ID_LEN)590return 0;591592if ((conn = lcidm_upsert_conn(lcidm, opaque)) == NULL)593return 0;594595key.cid = *lcid;596key.hash_key = lcidm->hash_key;597if (lh_QUIC_LCID_retrieve(lcidm->lcids, &key) != NULL)598return 0;599600if ((lcid_obj = lcidm_conn_new_lcid(lcidm, conn, lcid)) == NULL)601return 0;602603lcid_obj->seq_num = seq_num;604lcid_obj->type = LCID_TYPE_NCID;605return 1;606}607608int ossl_quic_lcidm_get_unused_cid(QUIC_LCIDM *lcidm, QUIC_CONN_ID *cid)609{610int i;611612for (i = 0; i < 10; i++) {613if (lcidm_generate_cid(lcidm, cid)614&& lcidm_get0_lcid(lcidm, cid) == NULL)615return 1; /* not found <=> radomly generated cid is unused */616}617618return 0;619}620621622