/* Provenance: openssl/openssl port_default_packet_handler (fixed). * repo: openssl/openssl * commit: 70cebd74d3592f5272945501b58a60374c4e13af * parent: 48f5785425ee6b5316f8e5f921978397ba632cc6 * commit_url: https://github.com/openssl/openssl/commit/70cebd74d3592f5272945501b58a60374c4e13af * cve: CVE-2026-18798 * license: Apache-2.0 * function: port_default_packet_handler * relpath: ssl/quic/quic_port.c */ static void port_default_packet_handler(QUIC_URXE *e, void *arg, const QUIC_CONN_ID *dcid) { QUIC_PORT *port = arg; PACKET pkt; QUIC_PKT_HDR hdr; QUIC_CHANNEL *ch = NULL, *new_ch = NULL; QUIC_CONN_ID odcid; uint8_t gen_new_token = 0; OSSL_QRX *qrx = NULL, *qrx_ref; OSSL_QRX *qrx_src = NULL; OSSL_QRX_ARGS qrx_args = { 0 }; uint64_t cause_flags = 0; OSSL_QRX_PKT *qrx_pkt = NULL; /* Don't handle anything if we are no longer running. */ if (!ossl_quic_port_is_running(port)) goto undesirable; if (port_try_handle_stateless_reset(port, e)) goto undesirable; if (dcid != NULL && ossl_quic_lcidm_lookup(port->lcidm, dcid, NULL, (void **)&ch)) { assert(ch != NULL); ossl_quic_channel_inject(ch, e); return; } /* * If we have an incoming packet which doesn't match any existing connection * we assume this is an attempt to make a new connection. */ if (!port->allow_incoming) goto undesirable; /* * packet without destination connection id is invalid/corrupted here. * stop wasting CPU cycles now. */ if (dcid == NULL) goto undesirable; /* * We have got a packet for an unknown DCID. This might be an attempt to * open a new connection. */ if (e->data_len < QUIC_MIN_INITIAL_DGRAM_LEN) goto undesirable; if (!PACKET_buf_init(&pkt, ossl_quic_urxe_data(e), e->data_len)) goto undesirable; /* * We set short_conn_id_len to SIZE_MAX here which will cause the decode * operation to fail if we get a 1-RTT packet. This is fine since we only * care about Initial packets. */ if (!ossl_quic_wire_decode_pkt_hdr(&pkt, SIZE_MAX, 1, 0, &hdr, NULL, &cause_flags)) { /* * If we fail due to a bad version, we know the packet up to the version * number was decoded, and we use it below to send a version * negotiation packet */ if ((cause_flags & QUIC_PKT_HDR_DECODE_BAD_VERSION) == 0) goto undesirable; } switch (hdr.version) { case QUIC_VERSION_1: break; case QUIC_VERSION_NONE: default: /* * If we get here, then we have a bogus version, and might need * to send a version negotiation packet. According to * RFC 9000 s. 6 and 14.1, we only do so however, if the UDP datagram * is a minimum of 1200 bytes in size */ if (e->data_len < 1200) goto undesirable; /* * If we don't get a supported version, respond with a ver * negotiation packet, and discard * TODO(QUIC FUTURE): Rate limit the reception of these */ port_send_version_negotiation(port, &e->peer, &hdr); goto undesirable; } /* * We only care about Initial packets which might be trying to establish a * connection. */ if (hdr.type != QUIC_PKT_TYPE_INITIAL) goto undesirable; if (port->max_pending_channels > 0 && ossl_list_incoming_ch_num(&port->incoming_channel_list) >= port->max_pending_channels) goto undesirable; odcid.id_len = 0; /* * Create qrx now so we can check integrity of packet * which does not belong to any channel. */ qrx_args.libctx = port->engine->libctx; qrx_args.demux = port->demux; qrx_args.short_conn_id_len = dcid->id_len; qrx_args.max_deferred = 32; qrx = ossl_qrx_new(&qrx_args); if (qrx == NULL) goto undesirable; /* * Derive secrets for qrx only. */ if (!ossl_quic_provide_initial_secret(port->engine->libctx, port->engine->propq, &hdr.dst_conn_id, /* is_server */ 1, qrx, NULL)) goto undesirable; if (ossl_qrx_validate_initial_packet(qrx, e, (const QUIC_CONN_ID *)dcid) == 0) goto undesirable; if (port->validate_addr == 0) { /* * Forget qrx, because it becomes (almost) useless here. We must let * channel to create a new QRX for connection ID server chooses. The * validation keys for new DCID will be derived by * ossl_quic_channel_on_new_conn() when we will be creating channel. * See RFC 9000 section 7.2 negotiating connection id to better * understand what's going on here. * * Did we say qrx is almost useless? Why? Because qrx remembers packets * we just validated. Those packets must be injected to channel we are * going to create. We use qrx_src alias so we can read packets from * qrx and inject them to channel. */ qrx_src = qrx; qrx = NULL; } /* * TODO(QUIC FUTURE): there should be some logic similar to accounting half-open * states in TCP. If we reach certain threshold, then we want to * validate clients. */ if (port->validate_addr == 1 && hdr.token == NULL) { port_send_retry(port, &e->peer, &hdr); goto undesirable; } /* * Note, even if we don't enforce the sending of retry frames for * server address validation, we may still get a token if we sent * a NEW_TOKEN frame during a prior connection, which we should still * validate here */ if (hdr.token != NULL && port_validate_token(&hdr, port, &e->peer, &odcid, &gen_new_token) == 0) { /* * RFC 9000 s 8.1.3 * When a server receives an Initial packet with an address * validation token, it MUST attempt to validate the token, * unless it has already completed address validation. * If the token is invalid, then the server SHOULD proceed as * if the client did not have a validated address, * including potentially sending a Retry packet * Note: If address validation is disabled, just act like * the request is valid */ if (port->validate_addr == 1) { /* * Again: we should consider saving initial encryption level * secrets to token here to save some CPU cycles. */ port_send_retry(port, &e->peer, &hdr); goto undesirable; } /* * client is under amplification limit, until it completes * handshake. * * forget qrx so channel can create a new one * with valid initial encryption level keys. */ if (qrx != NULL) { qrx_src = qrx; qrx = NULL; } } qrx_ref = NULL; if (qrx != NULL) { /* * if we are here, then client is validated via retry packet * (client sent a valid token). In this case the qrx has valid * secrets set for QUIC initial level encryption. We can pass * reference to qrx to newly created channel. * * Note: port_bind_channel()/channel becomes owner of qrx_ref. */ qrx_ref = ossl_qrx_newref(qrx); if (qrx_ref == NULL) goto undesirable; } port_bind_channel(port, &e->peer, &hdr.dst_conn_id, &odcid, qrx_ref, &new_ch); /* * if packet validates it gets moved to channel, we've just bound * to port. */ if (new_ch == NULL) goto undesirable; /* * Generate a token for sending in a later NEW_TOKEN frame */ if (gen_new_token == 1) generate_new_token(new_ch, &e->peer); if (qrx_src != NULL) { /* * Time to reinject packets from qrx to channel before * qrx will be destroyed here. */ while (ossl_qrx_read_pkt(qrx_src, &qrx_pkt) == 1) ossl_quic_channel_inject_pkt(new_ch, qrx_pkt); ossl_qrx_update_pn_space(qrx_src, new_ch->qrx); /* * transfer ownership back to qrx; */ qrx = qrx_src; qrx_src = NULL; } /* * If function reaches this place, then packet got validated in * ossl_qrx_validate_initial_packet(). Keep in mind the function * ossl_qrx_validate_initial_packet() decrypts the packet to validate it. * If packet validation was successful (and it was because we are here), * then the function puts the packet to qrx->rx_pending. We must not call * ossl_qrx_inject_urxe() here now, because we don't want to insert * the packet to qrx->urx_pending which keeps packet waiting for decryption. * * We are going to call ossl_quic_demux_release_urxe() to dispose buffer * which still holds encrypted data. */ undesirable: ossl_qrx_free(qrx); /* releases reference */ ossl_qrx_free(qrx_src); ossl_quic_demux_release_urxe(port->demux, e); }