diff --git a/compat-openssl10-1.0.2o-CVE-2026-54874.patch b/compat-openssl10-1.0.2o-CVE-2026-54874.patch new file mode 100644 index 0000000000000000000000000000000000000000..98b639096e20197f330a7c2bc3c45e3e9afcc47f --- /dev/null +++ b/compat-openssl10-1.0.2o-CVE-2026-54874.patch @@ -0,0 +1,271 @@ +From 1ca5a124b48f1ffe6980ad33724b452fbd62da1e Mon Sep 17 00:00:00 2001 +From: Matt Caswell +Date: Tue, 23 Jun 2026 17:21:48 +0100 +Subject: [PATCH] Avoid full read buffer allocation when buffering DTLS records + +dtls1_buffer_record() is used to hold onto a DTLS record across three +different scenarios: a record arriving early for the next epoch while +a handshake is in progress, re-queuing a record once the epoch catches +up and it has been decrypted, and application data arriving between +ChangeCipherSpec and Finished. In all three cases it took ownership of +the entire live read buffer (sized for the largest possible record, +~16.7KB) and allocated a brand new one to carry on reading, regardless +of how small the buffered record actually was. With each of the three +queues capped at 100 entries, a peer could send a stream of tiny +bogus records and force a disproportionate amount of heap allocation +per connection. + +Instead, copy only the record's own on-wire bytes (header plus +ciphertext, or header plus plaintext for an already-decrypted record) +into the queue entry, and leave the live read buffer untouched. On +retrieval, copy those bytes back into the live buffer and rebase any +SSL3_RECORD data/input pointers that pointed into the original packet, +rather than handing out a pointer to a standalone allocation. Memory +use is now proportional to what the peer actually sends. + +Fixes CVE-2026-54874 + +Assisted-by: Claude:claude-sonnet-4-6 +Assisted-by: Claude:claude-opus-5 +Reviewed-by: Milan Broz +Reviewed-by: Andrew Dinh +Merge-date: Mon Aug 24 15:40:45 2026 + +Adapted-by: PkgAgent/deepseek-v4 (modified to adapt to opencloudos-stream) + +--- + ssl/d1_lib.c | 12 ++---- + ssl/d1_pkt.c | 112 ++++++++++++++++++++++++++++++++++++++++++++-------------- + ssl/dtls1.h | 1 - + 3 files changed, 87 insertions(+), 38 deletions(-) + +diff --git a/ssl/d1_lib.c b/ssl/d1_lib.c +index 95b5033..0be0b1e 100644 +--- a/ssl/d1_lib.c ++++ b/ssl/d1_lib.c +@@ -174,27 +174,21 @@ static void dtls1_clear_queues(SSL *s) + + while ((item = pqueue_pop(s->d1->unprocessed_rcds.q)) != NULL) { + rdata = (DTLS1_RECORD_DATA *)item->data; +- if (rdata->rbuf.buf) { +- OPENSSL_free(rdata->rbuf.buf); +- } ++ OPENSSL_free(rdata->packet); + OPENSSL_free(item->data); + pitem_free(item); + } + + while ((item = pqueue_pop(s->d1->processed_rcds.q)) != NULL) { + rdata = (DTLS1_RECORD_DATA *)item->data; +- if (rdata->rbuf.buf) { +- OPENSSL_free(rdata->rbuf.buf); +- } ++ OPENSSL_free(rdata->packet); + OPENSSL_free(item->data); + pitem_free(item); + } + + while ((item = pqueue_pop(s->d1->buffered_app_data.q)) != NULL) { + rdata = (DTLS1_RECORD_DATA *)item->data; +- if (rdata->rbuf.buf) { +- OPENSSL_free(rdata->rbuf.buf); +- } ++ OPENSSL_free(rdata->packet); + OPENSSL_free(item->data); + pitem_free(item); + } +diff --git a/ssl/d1_pkt.c b/ssl/d1_pkt.c +index f5deddf..ca55307 100644 +--- a/ssl/d1_pkt.c ++++ b/ssl/d1_pkt.c +@@ -226,16 +226,48 @@ static int dtls1_copy_record(SSL *s, pitem *item) + + rdata = (DTLS1_RECORD_DATA *)item->data; + +- if (s->s3->rbuf.buf != NULL) +- OPENSSL_free(s->s3->rbuf.buf); ++ /* ++ * The record was read into a read buffer at least this size, so it must ++ * fit - see the length checks in ssl3_read_n(). Verify it rather than risk ++ * overrunning the buffer if that ever ceases to hold. ++ */ ++ if (rdata->packet_length > s->s3->rbuf.len) { ++ OPENSSL_free(rdata->packet); ++ return (0); ++ } + +- s->packet = rdata->packet; ++ /* ++ * rdata->packet is a standalone copy of this record's on-wire bytes (see ++ * dtls1_buffer_record()). Copy it into the live read buffer so that ++ * s->packet continues to point inside s->s3->rbuf.buf, as it does for ++ * every other record, then free our standalone copy. ++ */ ++ memcpy(s->s3->rbuf.buf, rdata->packet, rdata->packet_length); ++ s->s3->rbuf.offset = 0; ++ s->s3->rbuf.left = 0; ++ s->packet = s->s3->rbuf.buf; + s->packet_length = rdata->packet_length; +- memcpy(&(s->s3->rbuf), &(rdata->rbuf), sizeof(SSL3_BUFFER)); + memcpy(&(s->s3->rrec), &(rdata->rrec), sizeof(SSL3_RECORD)); + ++ /* ++ * dtls1_buffer_record() rebased rrec.data/input onto rdata->packet if they ++ * pointed into this record's own bytes, so translate them again onto the ++ * record's new location in the read buffer. Anything still pointing ++ * outside rdata->packet is either a separate allocation (rr->comp) or a ++ * leftover from a previously processed record that will be overwritten ++ * before use, so leave it alone. ++ */ ++ if (rdata->rrec.data >= rdata->packet ++ && rdata->rrec.data < rdata->packet + rdata->packet_length) ++ s->s3->rrec.data = s->packet + (rdata->rrec.data - rdata->packet); ++ if (rdata->rrec.input >= rdata->packet ++ && rdata->rrec.input < rdata->packet + rdata->packet_length) ++ s->s3->rrec.input = s->packet + (rdata->rrec.input - rdata->packet); ++ ++ OPENSSL_free(rdata->packet); ++ + /* Set proper sequence number for mac calculation */ +- memcpy(&(s->s3->read_sequence[2]), &(rdata->packet[5]), 6); ++ memcpy(&(s->s3->read_sequence[2]), &(s->packet[5]), 6); + + return (1); + } +@@ -262,11 +294,39 @@ dtls1_buffer_record(SSL *s, record_pqueue *queue, unsigned char *priority) + return -1; + } + +- rdata->packet = s->packet; ++ /* ++ * Take a copy of just this record's own on-wire bytes - the header plus the ++ * record body, which for an already-processed record holds the plaintext ++ * decrypted in place - rather than the whole (much larger) read buffer. The ++ * live s->s3->rbuf is left untouched and continues to be used for ++ * subsequent reads. ++ */ + rdata->packet_length = s->packet_length; +- memcpy(&(rdata->rbuf), &(s->s3->rbuf), sizeof(SSL3_BUFFER)); ++ rdata->packet = OPENSSL_malloc(rdata->packet_length); ++ if (rdata->packet == NULL) { ++ OPENSSL_free(rdata); ++ pitem_free(item); ++ SSLerr(SSL_F_DTLS1_BUFFER_RECORD, ERR_R_MALLOC_FAILURE); ++ return (-1); ++ } ++ memcpy(rdata->packet, s->packet, rdata->packet_length); ++ + memcpy(&(rdata->rrec), &(s->s3->rrec), sizeof(SSL3_RECORD)); + ++ /* ++ * The copied rrec.data/input still point into the live read buffer. Rebase ++ * any that point within this record's own bytes onto our standalone copy, ++ * so that dtls1_copy_record() can translate them again on retrieval. ++ * Pointers elsewhere (e.g. into rr->comp, or left over from a previously ++ * processed record) are not ours to move and are left alone. ++ */ ++ if (rdata->rrec.data >= s->packet ++ && rdata->rrec.data < s->packet + s->packet_length) ++ rdata->rrec.data = rdata->packet + (rdata->rrec.data - s->packet); ++ if (rdata->rrec.input >= s->packet ++ && rdata->rrec.input < s->packet + s->packet_length) ++ rdata->rrec.input = rdata->packet + (rdata->rrec.input - s->packet); ++ + item->data = rdata; + + #ifndef OPENSSL_NO_SCTP +@@ -279,25 +339,10 @@ dtls1_buffer_record(SSL *s, record_pqueue *queue, unsigned char *priority) + } + #endif + +- s->packet = NULL; +- s->packet_length = 0; +- memset(&(s->s3->rbuf), 0, sizeof(SSL3_BUFFER)); +- memset(&(s->s3->rrec), 0, sizeof(SSL3_RECORD)); +- +- if (!ssl3_setup_buffers(s)) { +- SSLerr(SSL_F_DTLS1_BUFFER_RECORD, ERR_R_INTERNAL_ERROR); +- if (rdata->rbuf.buf != NULL) +- OPENSSL_free(rdata->rbuf.buf); +- OPENSSL_free(rdata); +- pitem_free(item); +- return (-1); +- } +- + /* insert should not fail, since duplicates are dropped */ + if (pqueue_insert(queue->q, item) == NULL) { + SSLerr(SSL_F_DTLS1_BUFFER_RECORD, ERR_R_INTERNAL_ERROR); +- if (rdata->rbuf.buf != NULL) +- OPENSSL_free(rdata->rbuf.buf); ++ OPENSSL_free(rdata->packet); + OPENSSL_free(rdata); + pitem_free(item); + return (-1); +@@ -309,15 +354,16 @@ dtls1_buffer_record(SSL *s, record_pqueue *queue, unsigned char *priority) + static int dtls1_retrieve_buffered_record(SSL *s, record_pqueue *queue) + { + pitem *item; ++ int ret; + + item = pqueue_pop(queue->q); + if (item) { +- dtls1_copy_record(s, item); ++ ret = dtls1_copy_record(s, item); + + OPENSSL_free(item->data); + pitem_free(item); + +- return (1); ++ return ret; + } + + return (0); +@@ -370,7 +416,13 @@ static int dtls1_process_buffered_records(SSL *s) + + /* Process all the records. */ + while (pqueue_peek(s->d1->unprocessed_rcds.q)) { +- dtls1_get_unprocessed_record(s); ++ if (!dtls1_get_unprocessed_record(s)) { ++ /* ++ * Should not happen. The record has been dropped, so move on ++ * to the next one. ++ */ ++ continue; ++ } + bitmap = dtls1_get_bitmap(s, rr, &is_next_epoch); + if (bitmap == NULL) { + /* +@@ -621,8 +673,11 @@ static int dtls1_process_record(SSL *s, DTLS1_BITMAP *bitmap) + * after use :-). + */ + +- /* we have pulled in a full packet so zero things */ +- s->packet_length = 0; ++ /* ++ * Leave s->packet_length alone: ssl3_read_n() starts each new record by ++ * resetting it, and it must still describe this record's on-wire bytes for ++ * dtls1_buffer_record() should this record end up being buffered. ++ */ + + /* Mark receipt of record. */ + dtls1_record_bitmap_update(s, bitmap); +@@ -925,6 +980,7 @@ int dtls1_read_bytes(SSL *s, int type, unsigned char *buf, int len, int peek) + } + #endif + ++ /* On failure the record is simply dropped */ + dtls1_copy_record(s, item); + + OPENSSL_free(item->data); +diff --git a/ssl/dtls1.h b/ssl/dtls1.h +index f522176..7fda637 100644 +--- a/ssl/dtls1.h ++++ b/ssl/dtls1.h +@@ -251,7 +251,6 @@ typedef struct dtls1_state_st { + typedef struct dtls1_record_data_st { + unsigned char *packet; + unsigned int packet_length; +- SSL3_BUFFER rbuf; + SSL3_RECORD rrec; + # ifndef OPENSSL_NO_SCTP + struct bio_dgram_sctp_rcvinfo recordinfo; diff --git a/compat-openssl10.spec b/compat-openssl10.spec index 601be82184e8d9ebac162dc99fd1eeb204ad6117..5cbc1ed518c4c510cbe549e99a7b55cc8321c4c2 100644 --- a/compat-openssl10.spec +++ b/compat-openssl10.spec @@ -12,7 +12,7 @@ Summary: Compatibility version of the OpenSSL library Name: compat-openssl10 Version: 1.0.2o -Release: 5%{?dist} +Release: 6%{?dist} # We have to remove certain patented algorithms from the openssl source # tarball with the hobble-openssl script which is included below. # The original openssl upstream tarball cannot be shipped in the .src.rpm. @@ -89,6 +89,7 @@ Patch087: openssl-1.0.2o-CVE-2025-69421.patch Patch088: openssl-1.0.2o-CVE-2025-68160.patch Patch089: compat-openssl10-1.0.2o-CVE-2026-22796.patch Patch091: compat-openssl10-1.0.2o-CVE-2026-45447.patch +Patch100: compat-openssl10-1.0.2o-CVE-2026-54874.patch License: OpenSSL Group: System Environment/Libraries @@ -176,6 +177,7 @@ cp %{SOURCE12} %{SOURCE13} crypto/ec/ %patch088 -p1 -b .cve-2025-68160 %patch089 -p1 -b .cve-2026-22796 %patch091 -p1 -b .cve-2026-45447 +%patch100 -p1 -b .cve-2026-54874 sed -i 's/SHLIB_VERSION_NUMBER "1.0.0"/SHLIB_VERSION_NUMBER "%{version}"/' crypto/opensslv.h @@ -354,6 +356,10 @@ install -m 644 apps/openssl10.cnf $RPM_BUILD_ROOT%{_sysconfdir}/pki/openssl10.cn %postun -p /sbin/ldconfig %changelog +* Wed Sep 23 2026 PkgAgent Robot - 1.0.2o-6 +- [Type] security +- [DESC] Fix CVE-2026-54874: DTLS record buffering allocates a full read buffer per queued record (DoS) + * Mon Jun 15 2026 PkgAgent Robot - 1.0.2o-5 - [Type] security - [DESC] Fix CVE-2026-45447: use-after-free in PKCS7_verify() with empty digestAlgorithms SET