/* Provenance: openssl/openssl txp_pkt_commit (fixed). * repo: openssl/openssl * commit: c902e5f16d6a9e130e96d3ca6d8f64d71652e393 * parent: a1f348ccb328c3afbd4ba6883f9b7c813c043259 * commit_url: https://github.com/openssl/openssl/commit/c902e5f16d6a9e130e96d3ca6d8f64d71652e393 * cve: CVE-2026-63075 * license: Apache-2.0 * function: txp_pkt_commit * relpath: ssl/quic/quic_txp.c */ static int txp_pkt_commit(OSSL_QUIC_TX_PACKETISER *txp, struct txp_pkt *pkt, uint32_t archetype, int *txpim_pkt_reffed) { int ack_only, rc = 1; uint32_t enc_level = pkt->h.enc_level; uint32_t pn_space = ossl_quic_enc_level_to_pn_space(enc_level); QUIC_TXPIM_PKT *tpkt = pkt->tpkt; QUIC_STREAM *stream; OSSL_QTX_PKT txpkt; struct archetype_data a; *txpim_pkt_reffed = 0; /* Cannot send a packet with an empty payload. */ if (pkt->h.bytes_appended == 0) return 0; if (!txp_get_archetype_data(enc_level, archetype, &a)) return 0; /* Packet Information for QTX */ txpkt.hdr = &pkt->phdr; txpkt.iovec = txp->el[enc_level].iovec; txpkt.num_iovec = pkt->h.num_iovec; txpkt.local = NULL; txpkt.peer = BIO_ADDR_family(&txp->args.peer) == AF_UNSPEC ? NULL : &txp->args.peer; txpkt.pn = txp->next_pn[pn_space]; txpkt.flags = OSSL_QTX_PKT_FLAG_COALESCE; /* always try to coalesce */ /* Generate TXPIM chunks representing STOP_SENDING and RESET_STREAM frames. */ for (stream = pkt->stream_head; stream != NULL; stream = stream->txp_next) if (stream->txp_sent_stop_sending || stream->txp_sent_reset_stream) { /* Log STOP_SENDING/RESET_STREAM chunk to TXPIM. */ QUIC_TXPIM_CHUNK chunk; chunk.stream_id = stream->id; chunk.start = UINT64_MAX; chunk.end = 0; chunk.has_fin = 0; chunk.has_stop_sending = stream->txp_sent_stop_sending; chunk.has_reset_stream = stream->txp_sent_reset_stream; if (!ossl_quic_txpim_pkt_append_chunk(tpkt, &chunk)) return 0; /* alloc error */ } ack_only = txp_pkt_is_ack_only(tpkt); /* Dispatch packets that need loss/retransmit callbacks to FIFD. */ if (!ack_only && !ossl_quic_fifd_pkt_commit(&txp->fifd, tpkt)) return 0; /* * Transmission and Post-Packet Generation Bookkeeping * =================================================== * * No backing out anymore - at this point we need to increment our next PN * counter, or the ACKM will complain when we try to record a duplicate * packet with the same PN later. Non-ACK-only packets have also been * recorded in ACKM, so if QTX write fails they are handled as though they * were lost. ACK-only packets are not recorded and will be cleaned up by * the caller. */ ++txp->next_pn[pn_space]; if (!ack_only) *txpim_pkt_reffed = 1; /* Send the packet. */ if (!ossl_qtx_write_pkt(txp->args.qtx, &txpkt)) return 0; if (ack_only && !ossl_ackm_on_tx_ack_only_packet(txp->args.ackm, &tpkt->ackm_pkt)) rc = 0; /* * Record FC and stream abort frames as sent; deactivate streams which no * longer have anything to do. */ for (stream = pkt->stream_head; stream != NULL; stream = stream->txp_next) { if (stream->txp_sent_fc) { stream->want_max_stream_data = 0; ossl_quic_rxfc_has_cwm_changed(&stream->rxfc, 1); } if (stream->txp_sent_stop_sending) stream->want_stop_sending = 0; if (stream->txp_sent_reset_stream) stream->want_reset_stream = 0; if (stream->txp_txfc_new_credit_consumed > 0) { if (!ossl_assert(ossl_quic_txfc_consume_credit(&stream->txfc, stream->txp_txfc_new_credit_consumed))) /* * Should not be possible, but we should continue with our * bookkeeping as we have already committed the packet to the * FIFD. Just change the value we return. */ rc = 0; stream->txp_txfc_new_credit_consumed = 0; } /* * If we no longer need to generate any flow control (MAX_STREAM_DATA), * STOP_SENDING or RESET_STREAM frames, nor any STREAM frames (because * the stream is drained of data or TXFC-blocked), we can mark the * stream as inactive. */ ossl_quic_stream_map_update_state(txp->args.qsm, stream); if (ossl_quic_stream_has_send_buffer(stream) && !ossl_quic_sstream_has_pending(stream->sstream) && ossl_quic_sstream_get_final_size(stream->sstream, NULL)) /* * Transition to DATA_SENT if stream has a final size and we have * sent all data. */ ossl_quic_stream_map_notify_all_data_sent(txp->args.qsm, stream); } /* We have now sent the packet, so update state accordingly. */ if (tpkt->ackm_pkt.is_ack_eliciting) txp->force_ack_eliciting &= ~(1UL << pn_space); if (tpkt->had_handshake_done_frame) txp->want_handshake_done = 0; if (tpkt->had_max_data_frame) { txp->want_max_data = 0; ossl_quic_rxfc_has_cwm_changed(txp->args.conn_rxfc, 1); } if (tpkt->had_max_streams_bidi_frame) { txp->want_max_streams_bidi = 0; ossl_quic_rxfc_has_cwm_changed(txp->args.max_streams_bidi_rxfc, 1); } if (tpkt->had_max_streams_uni_frame) { txp->want_max_streams_uni = 0; ossl_quic_rxfc_has_cwm_changed(txp->args.max_streams_uni_rxfc, 1); } if (tpkt->had_ack_frame) txp->want_ack &= ~(1UL << pn_space); if (tpkt->had_conn_close) txp->want_conn_close = 0; /* * Decrement probe request counts if we have sent a packet that meets * the requirement of a probe, namely being ACK-eliciting. */ if (tpkt->ackm_pkt.is_ack_eliciting) { OSSL_ACKM_PROBE_INFO *probe_info = ossl_ackm_get0_probe_request(txp->args.ackm); if (enc_level == QUIC_ENC_LEVEL_INITIAL && probe_info->anti_deadlock_initial > 0) --probe_info->anti_deadlock_initial; if (enc_level == QUIC_ENC_LEVEL_HANDSHAKE && probe_info->anti_deadlock_handshake > 0) --probe_info->anti_deadlock_handshake; if (a.allow_force_ack_eliciting /* (i.e., not for 0-RTT) */ && probe_info->pto[pn_space] > 0) --probe_info->pto[pn_space]; } ossl_quic_fifd_pkt_discard_unreliable(&txp->fifd, tpkt); return rc; }