AUR AI Reviewer

Review Results

Version #2697 of openssl-1.1 · commit b2144c7724f9 · status Reviewed

Risk 0/5 · Safe CVE-2026-63072.patch
Result #4518

Comment

The patch is a targeted security fix for an OpenSSL CMS AES-WRAP-PAD unwrap heap OOB write. The code change only increases the temporary output allocation to the maximum of the queried cipher output length and input length, which is consistent with the documented failure-path behavior and does not introduce new execution, network, privilege, or persistence risks. The added regression test is self-contained, uses existing test assets, and only exercises CMS decrypt failure handling. No suspicious packaging behavior is present in this diff.

@@ -0,0 +1,214 @@
+From 00ee0c4faa77c9acbbe61b7b2fa538872e3c0791 Mon Sep 17 00:00:00 2001
+From: Daniel Kubec <kubec@openssl.foundation>
+Date: Sun, 2 Aug 2026 00:23:39 +0000
+Subject: [PATCH] Fix heap buffer overflow (8-byte OOB write) in AES-WRAP-PAD
+ unwrap
+
+On its integrity-failure paths that primitive writes and cleanses up to inlen
+bytes of the output buffer. Size the buffer for that worst case so a failed
+unwrap cannot write past the allocation.
+
+Fixes CVE-2026-63072
+
+Reviewed-by: Milan Broz <mbroz@openssl.org>
+Reviewed-by: Andrew Dinh <andrewd@openssl.org>
+Merge-date: Mon Aug 24 14:44:55 2026
+---
+ crypto/cms/cms_kari.c | 9 ++++++++-
+ 1 file changed, 8 insertions(+), 1 deletion(-)
+
+diff --git a/crypto/cms/cms_kari.c b/crypto/cms/cms_kari.c
+index cafc3040ac..22beb50513 100644
+--- a/crypto/cms/cms_kari.c
++++ b/crypto/cms/cms_kari.c
+@@ -193,6 +193,7 @@ static int cms_kek_cipher(unsigned char **pout, size_t *poutlen,
+     int rv = 0;
+     unsigned char *out = NULL;
+     int outlen;
++    size_t outsize;
+     keklen = EVP_CIPHER_CTX_key_length(kari->ctx);
+     if (keklen > EVP_MAX_KEY_LENGTH)
+         return 0;
+@@ -205,7 +206,13 @@ static int cms_kek_cipher(unsigned char **pout, size_t *poutlen,
+     /* obtain output length of ciphered key */
+     if (!EVP_CipherUpdate(kari->ctx, NULL, &outlen, in, inlen))
+         goto err;
+-    out = OPENSSL_malloc(outlen);
++    /*
++     * On its integrity-failure paths that primitive writes and cleanses up to
++     * inlen bytes of the output buffer. Size the buffer for that worst case so
++     * a failed unwrap cannot write past the allocation.
++     */
++    outsize = (size_t)outlen < inlen ? inlen : (size_t)outlen;
++    out = OPENSSL_malloc(outsize);
+     if (out == NULL)
+         goto err;
+     if (!EVP_CipherUpdate(kari->ctx, out, &outlen, in, inlen))
+-- 
+2.54.0
+
+From d307f61f1b341b6eb7d5d07bd6902a939c709bc6 Mon Sep 17 00:00:00 2001
+From: Daniel Kubec <kubec@openssl.foundation>
+Date: Thu, 23 Jul 2026 11:09:55 +0200
+Subject: [PATCH] Add test for CVE-2026-63072
+
+Assisted-by: Claude:claude-fable-5
+Reviewed-by: Milan Broz <mbroz@openssl.org>
+Reviewed-by: Andrew Dinh <andrewd@openssl.org>
+Merge-date: Mon Aug 24 14:44:54 2026
+---
+ test/cmsapitest.c             | 102 +++++++++++++++++++++++++++++++++-
+ test/recipes/80-test_cmsapi.t |   3 +-
+ 2 files changed, 103 insertions(+), 2 deletions(-)
+
+diff --git a/test/cmsapitest.c b/test/cmsapitest.c
+index a79ae8c..272b327 100644
+--- a/test/cmsapitest.c
++++ b/test/cmsapitest.c
+@@ -2,6 +2,7 @@
+ 
+ #include <openssl/cms.h>
+ #include <openssl/bio.h>
++#include <openssl/err.h>
+ #include <openssl/x509.h>
+ #include <openssl/pem.h>
+ 
+@@ -9,7 +10,8 @@
+ 
+ static X509 *cert = NULL;
+ static EVP_PKEY *privkey = NULL;
+ static char *pwri_kek_oob_der_in = NULL;
++static char *ec_recip_in = NULL;
+ 
+ static int test_encrypt_decrypt(void)
+ {
+@@ -92,1 +94,94 @@ int setup_tests(void)
++#ifndef OPENSSL_NO_EC
++
++/*
++ * Regression test for CVE-2026-63072: an 8-byte out-of-bounds heap write
++ * reachable through CMS_decrypt() when a KeyAgreeRecipientInfo names an
++ * id-aesNNN-wrap-pad key-wrap OID. CMS sizes the unwrap output buffer from
++ * the cipher's length query (inlen - 8), but AES-WRAP-PAD unwrap cleanses
++ * inlen bytes of it on every RFC 5649 integrity-failure path.
++ *
++ * We build a valid ECDH KARI message (which uses non-padded id-aes256-wrap),
++ * flip the single OID byte an attacker would flip on the wire to turn it into
++ * id-aes256-wrap-pad (key length unchanged), and decrypt with the matching
++ * private key. The unwrap must fail its integrity check without writing past
++ * the CMS-allocated buffer; CMS_decrypt() must fail cleanly.  Under a
++ * memory-checking build (e.g. valgrind) the overflow is flagged directly.
++ */
++static int test_kari_wrap_pad_unwrap_overflow(void)
++{
++    /* DER encoding of the id-aes256-wrap OID (2.16.840.1.101.3.4.1.45). */
++    static const unsigned char aes256_wrap_oid[] = {
++        0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2d
++    };
++    int ret = 0;
++    X509 *eccert = NULL;
++    EVP_PKEY *eckey = NULL;
++    BIO *certbio = NULL, *keybio = NULL, *msgbio = NULL, *outbio = NULL;
++    STACK_OF(X509) *recips = NULL;
++    CMS_ContentInfo *cms = NULL, *cms2 = NULL;
++    unsigned char *der = NULL;
++    const unsigned char *p;
++    int derlen, i, patched = 0;
++    const char *msg = "secret content for kari";
++
++    if ((certbio = BIO_new_file(ec_recip_in, "r")) == NULL
++        || PEM_read_bio_X509(certbio, &eccert, NULL, NULL) == NULL
++        || (keybio = BIO_new_file(ec_recip_in, "r")) == NULL
++        || PEM_read_bio_PrivateKey(keybio, &eckey, NULL, NULL) == NULL) {
++        goto end;
++    }
++
++    if (!TEST_ptr(recips = sk_X509_new_null())
++        || !TEST_int_gt(sk_X509_push(recips, eccert), 0))
++        goto end;
++
++    /* Build a normal ECDH KARI message; it uses non-padded id-aes256-wrap. */
++    if (!TEST_ptr(msgbio = BIO_new_mem_buf(msg, (int)strlen(msg)))
++        || !TEST_ptr(cms = CMS_encrypt(recips, msgbio, EVP_aes_256_cbc(),
++                         CMS_BINARY)))
++        goto end;
++
++    if (!TEST_int_gt(derlen = i2d_CMS_ContentInfo(cms, &der), 0))
++        goto end;
++
++    /* Swap id-aes256-wrap -> id-aes256-wrap-pad (0x2d -> 0x30). */
++    for (i = 0; i + (int)sizeof(aes256_wrap_oid) <= derlen; i++) {
++        if (memcmp(der + i, aes256_wrap_oid, sizeof(aes256_wrap_oid)) == 0) {
++            der[i + sizeof(aes256_wrap_oid) - 1] = 0x30;
++            patched = 1;
++            break;
++        }
++    }
++    if (!TEST_true(patched))
++        goto end;
++
++    p = der;
++    if (!TEST_ptr(cms2 = d2i_CMS_ContentInfo(NULL, &p, derlen)))
++        goto end;
++
++    /*
++     * The wrap-pad unwrap fails the AIV check; with the fix it does so without
++     * writing past the CMS-allocated buffer.  CMS_decrypt() must fail cleanly.
++     */
++    if (!TEST_ptr(outbio = BIO_new(BIO_s_mem()))
++        || !TEST_false(CMS_decrypt(cms2, eckey, eccert, NULL, outbio, 0)))
++        goto end;
++
++    ret = 1;
++end:
++    ERR_clear_error();
++    OPENSSL_free(der);
++    sk_X509_free(recips);
++    CMS_ContentInfo_free(cms);
++    CMS_ContentInfo_free(cms2);
++    BIO_free(certbio);
++    BIO_free(keybio);
++    BIO_free(msgbio);
++    BIO_free(outbio);
++    X509_free(eccert);
++    EVP_PKEY_free(eckey);
++    return ret;
++}
++#endif
++
+ int setup_tests(void)
+@@ -100,5 +194,6 @@ int setup_tests(void)
+     if (!TEST_ptr(certin = test_get_argument(0))
+             || !TEST_ptr(privkeyin = test_get_argument(1))
+-            || !TEST_ptr(pwri_kek_oob_der_in = test_get_argument(2)))
++            || !TEST_ptr(pwri_kek_oob_der_in = test_get_argument(2))
++            || !TEST_ptr(ec_recip_in = test_get_argument(3)))
+         return 0;
+ 
+     certbio = BIO_new_file(certin, "r");
+@@ -83,6 +179,10 @@ int setup_tests(void)
+ 
+     ADD_TEST(test_encrypt_decrypt);
+ 
++#ifndef OPENSSL_NO_EC
++    ADD_TEST(test_kari_wrap_pad_unwrap_overflow);
++#endif
++
+     return 1;
+ }
+ 
+diff --git a/test/recipes/80-test_cmsapi.t b/test/recipes/80-test_cmsapi.t
+index 990f8a7..9a2478e 100644
+--- a/test/recipes/80-test_cmsapi.t
++++ b/test/recipes/80-test_cmsapi.t
+@@ -17,6 +17,7 @@ plan skip_all => "CMS is disabled in this build" if disabled("cms");
+ plan tests => 1;
+ 
+ ok(run(test(["cmsapitest", srctop_file("test", "certs", "servercert.pem"),
+              srctop_file("test", "certs", "serverkey.pem"),
+-             srctop_file("test", "recipes", "80-test_cmsapi_data", "cms_pwri_kek_oob.der")])),
++             srctop_file("test", "recipes", "80-test_cmsapi_data", "cms_pwri_kek_oob.der"),
++             srctop_file("test", "smime-certs", "smec1.pem")])),
+              "running cmsapitest");
+-- 
+2.54.0
Risk 0/5 · Safe CVE-2026-84782.patch
Result #4519

Comment

The patch is a backport of an OpenSSL DTLS fix and only touches library logic plus tests. The code changes are narrowly scoped: resetting init_off before retransmission and skipping retransmit handling while a DTLS write is already parked. I did not see any supply-chain red flags, privilege changes, network fetches, install scripts, or packaging persistence mechanisms. The added test code is large but self-contained and does not introduce external execution paths. Overall this looks like a legitimate security fix with low risk.

@@ -0,0 +1,540 @@
+From acc646b33767477d8bbe34955bcd25cf17569240 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pavol=20=C5=BD=C3=A1=C4=8Dik?= <pzacik@redhat.com>
+Date: Wed, 23 Sep 2026 15:51:31 +0200
+Subject: [PATCH] dtls: reset init_off before retransmitting a message
+
+(backport from e961062f6b86727b63b0381ceeec7fa074026fef)
+
+dtls1_retransmit_message() copies a queued message's saved bytes into
+s->init_buf and sets s->init_num to its length, but left s->init_off
+untouched. If a different message was still mid-write (suspended on
+WANT_WRITE, e.g. because the underlying BIO couldn't take any more)
+when a retransmit fired, s->init_off would still hold whatever offset
+that suspended write had reached - and dtls1_do_write() would resume
+the retransmitted message from that stale, unrelated offset instead of
+from the start.
+
+The result is a corrupted retransmission: the message goes out
+mislabelled with a bogus fragment offset/length, and its body is
+whatever bytes happen to sit at that offset in the now-repurposed
+init_buf - typically leftover content from the other, larger message
+that was mid-write when the retransmit happened, rather than the
+message actually meant to be sent.
+
+Fix this by always resetting s->init_off to 0 before resending, so
+every retransmit starts from the beginning of the message being sent,
+regardless of what any other in-progress write had left behind.
+
+dtls1_handle_timeout() also needs a guard: if write_state is anything
+other than WRITE_STATE_TRANSITION, a write is still parked mid-flight
+from a previous call into the state machine, so there is nothing valid
+to retransmit yet. Retransmitting anyway would reconstruct an
+already-sent message from the retransmit queue into
+s->init_buf/s->init_off/s->init_num/s->d1->w_msg - the same fields the
+parked write is still using - corrupting that write's state out from
+under it. Skip retransmission in that case and let the next
+SSL_read()/SSL_write()/SSL_accept()/SSL_connect() call resume the
+parked write normally instead.
+
+Add test_dtls_client_retransmit() and test_dtls_server_retransmit() to
+test/dtlstest.c, covering both directions on DTLS1.2: use a
+fragment-limiting BIO to suspend a large in-flight write mid-fragment,
+then fire the retransmit timer several times via
+DTLSv1_get_timeout()/DTLSv1_handle_timeout() while it stays parked,
+verifying no retransmission is attempted and the suspended write's
+state is reproduced unchanged on every timer fire, and that resuming
+the write afterward completes as an ordinary handshake continuation
+instead of hitting dtls1_do_write()'s entry assertion.
+
+Assited-by: Claude:claude-sonnet-5
+Fixes: CVE-2026-84782
+---
+ ssl/d1_lib.c             |  17 ++
+ ssl/statem/statem_dtls.c |   2 +
+ test/dtlstest.c          | 415 +++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 434 insertions(+)
+
+diff --git a/ssl/d1_lib.c b/ssl/d1_lib.c
+index afbf015..fd95427 100644
+--- a/ssl/d1_lib.c
++++ b/ssl/d1_lib.c
+@@ -410,6 +410,23 @@ int dtls1_handle_timeout(SSL *s)
+     }
+ 
+     dtls1_start_timer(s);
++
++    /*
++     * If write_state is anything other than WRITE_STATE_TRANSITION, a write
++     * is still parked mid-flight (WANT_WRITE) from a previous call into the
++     * state machine - the current flight hasn't actually finished going out
++     * yet, so there's nothing valid to retransmit. Retransmitting anyway
++     * would reconstruct an already-sent message from the retransmit queue
++     * into s->init_buf/s->init_off/s->init_num/s->d1->w_msg_hdr - the same
++     * fields the parked write is still using - corrupting that write's
++     * state out from under it. Leave it alone and let the next
++     * SSL_read()/SSL_write()/SSL_accept()/SSL_connect() call resume the
++     * parked write normally instead.
++     */
++    if (s->statem.state == MSG_FLOW_WRITING
++        && s->statem.write_state != WRITE_STATE_TRANSITION)
++        return 0;
++
+     /* Calls SSLfatal() if required */
+     return dtls1_retransmit_buffered_messages(s);
+ }
+diff --git a/ssl/statem/statem_dtls.c b/ssl/statem/statem_dtls.c
+index 8e3fb68..c0ce042 100644
+--- a/ssl/statem/statem_dtls.c
++++ b/ssl/statem/statem_dtls.c
+@@ -1128,6 +1128,8 @@ int dtls1_retransmit_message(SSL *s, unsigned short seq, int *found)
+     memcpy(s->init_buf->data, frag->fragment,
+            frag->msg_header.msg_len + header_length);
+     s->init_num = frag->msg_header.msg_len + header_length;
++    /* Always retransmit from the start, not wherever init_off was left */
++    s->init_off = 0;
+ 
+     dtls1_set_message_header_int(s, frag->msg_header.type,
+                                  frag->msg_header.msg_len,
+diff --git a/test/dtlstest.c b/test/dtlstest.c
+index 1d7b105..8a9359b 100644
+--- a/test/dtlstest.c
++++ b/test/dtlstest.c
+@@ -8,6 +8,7 @@
+  */
+ 
+ #include <string.h>
++#include <unistd.h>
+ #include <openssl/bio.h>
+ #include <openssl/crypto.h>
+ #include <openssl/ssl.h>
+@@ -328,6 +329,416 @@ static int test_dtls_duplicate_records(void)
+     return testresult;
+ }
+ 
++/*
++ * frag_bio and the four retransmit tests below need DTLS1.2
++ */
++#ifndef OPENSSL_NO_DTLS1_2
++
++/*
++ * Number of times the retransmit timer is fired while a write is parked, in
++ * each of the four tests below
++ */
++#define NUM_RETRANSMITS 3
++
++typedef struct {
++    BIO *bio;
++    int allowed; /* number of fragment writes to let through before suspending */
++    int write_calls; /* number of times frag_write() has been invoked at all */
++} frag_bio;
++
++/*
++ * Each call to this function corresponds to exactly one DTLS fragment being
++ * handed to the BIO by dtls1_do_write(). Once |allowed| fragments have been
++ * let through, every further write suspends (WANT_WRITE) until the test
++ * bumps |allowed| again.
++ */
++static int frag_write(BIO *bio, const char *buf, size_t len, size_t *written)
++{
++    frag_bio *f = BIO_get_data(bio);
++
++    BIO_clear_retry_flags(bio);
++
++    f->write_calls++;
++
++    if (f->allowed <= 0) {
++        BIO_set_retry_write(bio);
++        *written = 0;
++        return 0;
++    }
++    f->allowed--;
++
++    if (!BIO_write_ex(f->bio, buf, len, written)) {
++        fprintf(stderr, "Failed to send data via BIO_write_ex\n");
++        return 0;
++    }
++
++    return 1;
++}
++
++static int frag_read(BIO *bio, char *buf, size_t buf_len, size_t *readbytes)
++{
++    frag_bio *f = BIO_get_data(bio);
++    return BIO_read_ex(f->bio, buf, buf_len, readbytes);
++}
++
++static long frag_ctrl(BIO *bio, int cmd, long num, void *ptr)
++{
++    frag_bio *f = BIO_get_data(bio);
++    return BIO_ctrl(f->bio, cmd, num, ptr);
++}
++
++static int frag_puts(BIO *bio, const char *str)
++{
++    size_t written;
++    return frag_write(bio, str, strlen(str), &written) ? (int)written : -1;
++}
++
++static int frag_create(BIO *bio)
++{
++    frag_bio *f = OPENSSL_zalloc(sizeof(*f));
++    if (f == NULL)
++        return 0;
++    BIO_set_data(bio, f);
++    BIO_set_init(bio, 1);
++    return 1;
++}
++
++static int frag_destroy(BIO *bio)
++{
++    frag_bio *f = BIO_get_data(bio);
++    if (f == NULL)
++        return 1;
++
++    BIO_free(f->bio);
++    OPENSSL_free(f);
++    BIO_set_data(bio, NULL);
++    BIO_set_init(bio, 0);
++    return 1;
++}
++
++static BIO_METHOD *frag_method(void)
++{
++    static BIO_METHOD *m = NULL;
++    if (m == NULL) {
++        m = BIO_meth_new(BIO_TYPE_SOURCE_SINK | BIO_TYPE_FILTER, "fragment-limited dgram");
++        BIO_meth_set_write_ex(m, frag_write);
++        BIO_meth_set_read_ex(m, frag_read);
++        BIO_meth_set_ctrl(m, frag_ctrl);
++        BIO_meth_set_puts(m, frag_puts);
++        BIO_meth_set_create(m, frag_create);
++        BIO_meth_set_destroy(m, frag_destroy);
++    }
++    return m;
++}
++
++static BIO *frag_new(BIO *bio, int allowed)
++{
++    BIO *b = BIO_new(frag_method());
++    frag_bio *f;
++    if (b == NULL) {
++        BIO_free(bio);
++        return NULL;
++    }
++    f = BIO_get_data(b);
++    f->bio = bio;
++    f->allowed = allowed;
++    return b;
++}
++
++/*
++ * DTLS1.2 only. Exercises dtls1_handle_timeout()'s write_state guard (see
++ * d1_lib.c): while a write is parked mid-flight (WANT_WRITE, suspended here
++ * by a fragment-limiting BIO), a firing retransmit timer must not touch the
++ * retransmit queue at all - dtls1_retransmit_buffered_messages() should never
++ * be entered, since retransmitting a message concurrently with the live write
++ * reusing the very same s->init_off/s->init_num/s->d1->w_msg_hdr fields would
++ * corrupt whichever write resumes second.
++ *
++ * Without the guard, letting that retransmit run ClientHello to completion
++ * while its own live write is still parked resets s->init_off/s->init_num
++ * to 0/0 out from under that write - so when the app resumes with
++ * SSL_connect(), dtls1_do_write() re-enters believing s->init_off == 0 means
++ * a brand new message is starting, when s->init_num no longer matches
++ * ClientHello's real length the way a fresh message's would - hitting an
++ * internal entry assertion and calling OPENSSL_die()/abort().
++ *
++ * The client never calls SSL_connect() again until the very end, so the
++ * server is never driven far enough to respond - there's nothing to
++ * retransmit ClientHello against except the timer, driven purely by
++ * DTLSv1_get_timeout()/DTLSv1_handle_timeout().
++ */
++static int test_dtls_client_retransmit(void)
++{
++    SSL_CTX *sctx = NULL, *cctx = NULL;
++    SSL *serverssl = NULL, *clientssl = NULL;
++    int testresult = 0;
++    int ret, err, i;
++    struct timeval tv;
++    static unsigned char alpn[750];
++    size_t j, used = 0;
++    BIO *c_to_s_bio = NULL;
++    BIO *frag_wbio;
++    frag_bio *fb;
++    int write_calls_before;
++
++    if (!TEST_true(create_ssl_ctx_pair(DTLS_server_method(),
++            DTLS_client_method(),
++            DTLS1_2_VERSION, DTLS1_2_VERSION,
++            &sctx, &cctx, cert, privkey)))
++        return 0;
++
++    SSL_CTX_set_verify(cctx, SSL_VERIFY_NONE, NULL);
++
++    /* Pad the ClientHello out via ALPN so it needs multiple fragments. */
++    for (j = 0; j < 3; j++) {
++        char name[250];
++        int n = BIO_snprintf(name, sizeof(name),
++            "proto-%04u-%s", (unsigned int)j,
++            "padpadpadpadpadpadpadpadpadpadpadpadpadpadpad"
++            "padpadpadpadpadpadpadpadpadpadpadpadpadpadpad"
++            "padpadpadpadpadpadpadpadpadpadpadpadpadpadpad"
++            "padpadpadpadpadpadpadpadpadpadpadpadpadpadpad"
++            "padpadpadpadpadpadpadpadpadpadpadpadpadpad");
++
++        if (!TEST_int_ge(n, 0) || !TEST_size_t_lt((size_t)n, sizeof(name)))
++            goto end;
++        if (!TEST_size_t_le(used + 1 + (size_t)n, sizeof(alpn)))
++            goto end;
++
++        alpn[used++] = (unsigned char)n;
++        memcpy(alpn + used, name, (size_t)n);
++        used += (size_t)n;
++    }
++    if (!TEST_false(SSL_CTX_set_alpn_protos(cctx, alpn, (unsigned int)used)))
++        goto end;
++
++    if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
++            NULL, NULL)))
++        goto end;
++
++    /*
++     * Pin the MTU so fragmentation of the ClientHello (padded out via ALPN
++     * above) is deterministic. SSL_OP_NO_QUERY_MTU stops dtls1_query_mtu()
++     * from overriding this with a value queried from the BIO.
++     */
++    SSL_set_options(clientssl, SSL_OP_NO_QUERY_MTU);
++    if (!TEST_true(SSL_set_mtu(clientssl, 256)))
++        goto end;
++
++    /*
++     * Wrap the client's write BIO in our fragment-limiting filter, initially
++     * allowing only the ClientHello's first fragment through. SSL_get_wbio()
++     * is a borrowed reference, and SSL_set0_wbio() will free whatever the old
++     * wbio pointer was as soon as we install the replacement - so we need our
++     * own ref on the underlying bio before frag_new() stores it internally,
++     * otherwise the wrapper is left holding a dangling pointer.
++     */
++    c_to_s_bio = SSL_get_wbio(clientssl);
++
++    if (!TEST_ptr(c_to_s_bio) || !TEST_true(BIO_up_ref(c_to_s_bio)))
++        goto end;
++
++    frag_wbio = frag_new(c_to_s_bio, 1);
++    if (!TEST_ptr(frag_wbio)) {
++        BIO_free(c_to_s_bio);
++        goto end;
++    }
++    fb = BIO_get_data(frag_wbio);
++
++    SSL_set0_wbio(clientssl, frag_wbio);
++
++    DTLS_set_timer_cb(clientssl, timer_cb);
++
++    /*
++     * Flight 1: frag_wbio's budget of 1 write buys exactly ClientHello's
++     * first fragment - that's all that goes out. The next write (its
++     * second fragment) is what actually suspends, leaving write_state
++     * parked at WRITE_STATE_SEND, waiting to resume ClientHello.
++     */
++    ret = SSL_connect(clientssl);
++    if (!TEST_int_le(ret, 0)
++        || !TEST_int_eq(SSL_get_error(clientssl, ret), SSL_ERROR_WANT_WRITE))
++        goto end;
++
++    /*
++     * Let the retransmit timer fire, NUM_RETRANSMITS times, while the write
++     * above is still parked. fb->allowed = 100 so our custom BIO isn't what
++     * would block a retransmit, if one were sent. frag_write()'s call
++     * counter shouldn't move at all, round after round.
++     */
++    fb->allowed = 100;
++
++    for (i = 0; i < NUM_RETRANSMITS; i++) {
++        write_calls_before = fb->write_calls;
++
++        if (!TEST_int_gt((int)DTLSv1_get_timeout(clientssl, &tv), 0))
++            goto end;
++
++        /* Wait for the retransmit timer to actually expire */
++        usleep((tv.tv_sec * 1000 + tv.tv_usec / 1000 + 10) * 1000);
++
++        if (!TEST_int_ge((int)DTLSv1_handle_timeout(clientssl), 0))
++            goto end;
++
++        if (!TEST_int_eq(fb->write_calls, write_calls_before))
++            goto end;
++    }
++
++    /*
++     * Resume the original suspended write. This is the call expected to
++     * hit the entry assertion described above if the guard weren't there.
++     */
++    ret = SSL_connect(clientssl);
++    err = SSL_get_error(clientssl, ret);
++
++    /*
++     * If we get here at all (i.e. we didn't just abort()), the resume
++     * should look like an ordinary handshake continuation - WANT_READ or
++     * WANT_WRITE, not an internal failure.
++     */
++    if (!TEST_false(err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL))
++        goto end;
++
++    testresult = 1;
++end:
++    SSL_free(serverssl);
++    SSL_free(clientssl);
++    SSL_CTX_free(sctx);
++    SSL_CTX_free(cctx);
++
++    return testresult;
++}
++
++/*
++ * DTLS1.2 only. Server-side counterpart to test_dtls_client_retransmit():
++ * same write_state guard, but Certificate suspends mid-write during the
++ * server's flight instead of ClientHello. The cipher is pinned to a
++ * static-RSA suite so the flight is just ServerHello + Certificate +
++ * ServerHelloDone (no ServerKeyExchange); with a 256 byte MTU, ServerHello
++ * fits in a single fragment but Certificate does not.
++ *
++ * The client never calls SSL_accept() again until the very end, so the
++ * server is driven purely by DTLSv1_get_timeout()/DTLSv1_handle_timeout().
++ */
++static int test_dtls_server_retransmit(void)
++{
++    SSL_CTX *sctx = NULL, *cctx = NULL;
++    SSL *serverssl = NULL, *clientssl = NULL;
++    int testresult = 0;
++    int ret, err, i;
++    struct timeval tv;
++    BIO *s_to_c_bio = NULL;
++    BIO *frag_wbio;
++    frag_bio *fb;
++    int write_calls_before;
++
++    if (!TEST_true(create_ssl_ctx_pair(DTLS_server_method(),
++            DTLS_client_method(),
++            DTLS1_2_VERSION, DTLS1_2_VERSION,
++            &sctx, &cctx, cert, privkey)))
++        return 0;
++
++    /* Static RSA cipher: no ServerKeyExchange, keeps the flight simple. */
++    if (!TEST_true(SSL_CTX_set_cipher_list(cctx, "AES128-SHA")))
++        goto end;
++
++    if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
++            NULL, NULL)))
++        goto end;
++
++    /*
++     * Pin the server's MTU so its flight fragments deterministically. The
++     * client is left alone - its ClientHello is small and unfragmented.
++     */
++    SSL_set_options(serverssl, SSL_OP_NO_QUERY_MTU);
++    if (!TEST_true(SSL_set_mtu(serverssl, 256)))
++        goto end;
++
++    /* Wrap only the server's write BIO in our fragment-limiting filter. */
++    s_to_c_bio = SSL_get_wbio(serverssl);
++
++    if (!TEST_ptr(s_to_c_bio) || !TEST_true(BIO_up_ref(s_to_c_bio)))
++        goto end;
++
++    /* Only let ServerHello and Certificate's first fragment out initially. */
++    frag_wbio = frag_new(s_to_c_bio, 2);
++    if (!TEST_ptr(frag_wbio)) {
++        BIO_free(s_to_c_bio);
++        goto end;
++    }
++    fb = BIO_get_data(frag_wbio);
++
++    SSL_set0_wbio(serverssl, frag_wbio);
++
++    DTLS_set_timer_cb(serverssl, timer_cb);
++
++    /* Flight 1: the client sends its ClientHello without any restriction. */
++    if (!TEST_int_le(SSL_connect(clientssl), 0))
++        goto end;
++
++    /*
++     * Flight 2: frag_wbio's budget of 2 writes buys exactly ServerHello in
++     * full plus the first fragment of Certificate - that's all that goes
++     * out. The next write (Certificate's second fragment) is what actually
++     * suspends, leaving write_state parked at WRITE_STATE_SEND, waiting to
++     * resume Certificate specifically.
++     */
++    ret = SSL_accept(serverssl);
++    if (!TEST_int_le(ret, 0)
++        || !TEST_int_eq(SSL_get_error(serverssl, ret), SSL_ERROR_WANT_WRITE))
++        goto end;
++
++    /*
++     * Let the retransmit timer fire, NUM_RETRANSMITS times, while the write
++     * above is still parked. fb->allowed = 100 so our custom BIO isn't what
++     * would block a retransmit, if one were sent. frag_write()'s call
++     * counter shouldn't move at all, round after round.
++     */
++    fb->allowed = 100;
++
++    for (i = 0; i < NUM_RETRANSMITS; i++) {
++        write_calls_before = fb->write_calls;
++
++        if (!TEST_int_gt((int)DTLSv1_get_timeout(serverssl, &tv), 0))
++            goto end;
++
++        usleep((tv.tv_sec * 1000 + tv.tv_usec / 1000 + 10) * 1000);
++
++        if (!TEST_int_ge((int)DTLSv1_handle_timeout(serverssl), 0))
++            goto end;
++
++        if (!TEST_int_eq(fb->write_calls, write_calls_before))
++            goto end;
++    }
++
++    /*
++     * Resume the original suspended write. This is the call expected to
++     * hit the entry assertion described in test_dtls_client_retransmit() if
++     * the guard weren't there.
++     */
++    ret = SSL_accept(serverssl);
++    err = SSL_get_error(serverssl, ret);
++
++    /*
++     * If we get here at all (i.e. we didn't just abort()), the resume
++     * should look like an ordinary handshake continuation - WANT_READ or
++     * WANT_WRITE, not an internal failure.
++     */
++    if (!TEST_false(err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL))
++        goto end;
++
++    testresult = 1;
++end:
++    SSL_free(serverssl);
++    SSL_free(clientssl);
++    SSL_CTX_free(sctx);
++    SSL_CTX_free(cctx);
++
++    return testresult;
++}
++
++#endif
++
+ int setup_tests(void)
+ {
+     if (!TEST_ptr(cert = test_get_argument(0))
+@@ -426,7 +837,11 @@ int setup_tests(void)
+     ADD_ALL_TESTS(test_dtls_drop_records, TOTAL_RECORDS);
+     ADD_TEST(test_cookie);
+     ADD_TEST(test_dtls_duplicate_records);
++#ifndef OPENSSL_NO_DTLS1_2
++    ADD_TEST(test_dtls_client_retransmit);
++    ADD_TEST(test_dtls_server_retransmit);
++#endif
+     ADD_TEST(test_swap_app_data);
+
+     return 1;
+ }
+-- 
+2.55.0
Risk 0/5 · Safe PKGBUILD
Result #4520

Comment

This change only bumps pkgrel from 11 to 12 in PKGBUILD; no source URLs, build steps, dependencies, install scripts, or packaging behavior changed. There are no new integrity, execution, persistence, or privilege-escalation concerns in the provided diff.

@@ -7,7 +7,7 @@ _pkgname=openssl
 _ver=1.1.1w
 # use a pacman compatible version scheme
 pkgver=${_ver/[a-z]/.${_ver//[0-9.]/}}
-pkgrel=11
+pkgrel=12
 pkgdesc='The Open Source toolkit for Secure Sockets Layer and Transport Layer Security'
 arch=('aarch64' 'x86_64')
 url='https://www.openssl.org'
Risk 0/5 · Safe PKGBUILD
Result #4521

Comment

The PKGBUILD change only adds two new local patch sources and their checksums, and wires them into prepare(). I inspected both patch files: they are straightforward upstream security backports for OpenSSL CVE fixes, with no network fetches, no shell execution, no privilege escalation, and no packaging changes beyond applying the patches. The new sources are local files in the package tree and are checksum-pinned. No suspicious metadata or install-time behavior is introduced by this diff.

@@ -63,6 +63,8 @@ source=(
 	# https://git.almalinux.org/rpms/openssl/src/branch/c8s
 	CVE-2025-69419-1.patch
 	CVE-2025-69419-2.patch
+	CVE-2026-63072.patch
+	CVE-2026-84782.patch
 )
 sha256sums=('cf3098950cb4d853ad95c0841f1f9c6d3dc102dccfcacd521d93925208b76ac8'
             'SKIP'
Risk 0/5 · Safe PKGBUILD
Result #4522

Comment

The PKGBUILD change only adds two new source patches and their checksums, and wires them into prepare(). The patches themselves are legitimate upstream-style security fixes/tests for OpenSSL, fetched from the same trusted source set as the rest of the package, with no new network fetches, no execution changes, and no packaging/persistence red flags. I did not find any suspicious install scripts, privilege changes, or integrity regressions in this diff. Risk is low.

@@ -109,7 +111,9 @@ sha256sums=('cf3098950cb4d853ad95c0841f1f9c6d3dc102dccfcacd521d93925208b76ac8'
             'f5689d1652045fe14c70e6eb68b080b1a26ad1c49d5308cd7212b417e10f2afc'
             '4fe62fbbce088325f5b62fab3edfe59d4d9a8108ddfd06cf2a53fa1bd758c5b3'
             'd89423836be0cb5ca076e173ea373c4deeef3d6ffa17085ca8a0850b364d7d3b'
-            '933c61f15a81ac9c50f66f8c6c059d09f94a116d7b4c4d534a41b1c8f0140160')
+            '933c61f15a81ac9c50f66f8c6c059d09f94a116d7b4c4d534a41b1c8f0140160'
+            '4da3e07ceed3790c0bd0dd8ff93b97759ff38ed50e123f46ebe44381eea00b93'
+            'f41abfd62f213e489afcc3d4608b034e1c71a8cecc10b2bb81fc3a8ece31cc21')
 validpgpkeys=(
 	'EFC0A467D613CB83C7ED6D30D894E2CE8B3D79F5'
 )
Risk 0/5 · Safe PKGBUILD
Result #4523

Comment

The change only adds two new OpenSSL security-fix patches to the PKGBUILD and updates the patch application list/checksums. I reviewed the added patch contents: one fixes an AES-WRAP-PAD heap OOB write in upstream CMS code, and the other fixes a DTLS retransmit state bug plus adds regression tests. Both are standard upstream-style security backports, sourced over HTTPS from the package’s existing OpenSSL patch set, with no new build-time network access, no suspicious scripts, and no packaging/persistence changes. Risk is low because the diff is limited to incorporating upstream security fixes and checksum updates.

@@ -188,6 +192,8 @@ prepare() {
 		CVE-2026-42766-2 \
 		CVE-2026-45447-1 \
 		CVE-2026-45447-2 \
+		CVE-2026-63072 \
+		CVE-2026-84782 \
 		; do
 		patch -p1 -i "${srcdir}/${patch}.patch"
 	done
Risk 0/5 · Safe REUSE.toml
Result #4524

Comment

The change only updates REUSE metadata to include two additional patch files in the license manifest. This does not affect build logic, package installation, privileges, or network behavior. I did not see any evidence in this diff of malicious code or supply-chain risk; the security impact is effectively nil for this metadata-only update.

@@ -80,6 +80,8 @@ SPDX-License-Identifier = "TODO-Choose-a-license"
 path = [
     "CVE-2025-69419-1.patch",
     "CVE-2025-69419-2.patch",
+    "CVE-2026-63072.patch",
+    "CVE-2026-84782.patch",
 ]
 SPDX-FileCopyrightText = "RHEL contributors"
 SPDX-License-Identifier = "TODO-Choose-a-license"