public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
To: git-commits@fedoraproject.org
Subject: [rpms/openssl] f43: Don't raise NOT_ENOUGH_DATA on a clean EOF at an object boundary
Date: Fri, 17 Jul 2026 19:00:28 GMT [thread overview]
Message-ID: <178431482856.1.9344236970534993944.rpms-openssl-1f3cf2866499@fedoraproject.org> (raw)
A new commit has been pushed.
Repo : rpms/openssl
Branch : f43
Commit : 1f3cf2866499a2de38190eb1b15a74766bae36e1
Author : Pavol Žáčik <pzacik@redhat.com>
Date : 2026-07-17T18:23:08+02:00
Stats : +319/-1 in 2 file(s)
URL : https://src.fedoraproject.org/rpms/openssl/c/1f3cf2866499a2de38190eb1b15a74766bae36e1?branch=f43
Log:
Don't raise NOT_ENOUGH_DATA on a clean EOF at an object boundary
(Backport from https://github.com/openssl/openssl/commit/3687ff7)
---
diff --git a/0058-Don-t-raise-NOT_ENOUGH_DATA-at-object-boundary.patch b/0058-Don-t-raise-NOT_ENOUGH_DATA-at-object-boundary.patch
new file mode 100644
index 0000000..b475c0c
--- /dev/null
+++ b/0058-Don-t-raise-NOT_ENOUGH_DATA-at-object-boundary.patch
@@ -0,0 +1,313 @@
+From 3687ff719503ffc6d29c97d77e00199251890774 Mon Sep 17 00:00:00 2001
+From: Marc Gutman <marcg@activestate.com>
+Date: Thu, 9 Jul 2026 15:23:28 -0500
+Subject: [PATCH] Don't raise NOT_ENOUGH_DATA on a clean EOF at an object
+ boundary
+
+asn1_d2i_read_bio() reads one ASN.1 object at a time from a BIO. Callers
+commonly loop, decoding concatenated DER values until the call fails, and
+rely on a failure with no queued error to recognise a clean end of input.
+CPython's ssl module does this in _add_ca_certs() when loading the Windows
+certificate store via SSLContext.load_verify_locations(cadata=...); it
+re-raises any leftover ASN.1 error other than ASN1_R_HEADER_TOO_LONG as
+fatal.
+
+Commit 9eb6922c59 ("asn1: raise NOT_ENOUGH_DATA on header EOF") changed the
+BIO_read() check from "i < 0" to "i <= 0", so a clean EOF (BIO_read()
+returning 0, as an exhausted BIO_new_mem_buf does) on an object boundary now
+raises ASN1_R_NOT_ENOUGH_DATA instead of failing with an empty error queue.
+The rewrite in commit 35852da1d9 carried this behaviour forward. As a
+result Python 3 on Windows fails to initialise an SSLContext with:
+
+ ssl.SSLError: [ASN1: NOT_ENOUGH_DATA] not enough data
+
+Raise ASN1_R_NOT_ENOUGH_DATA only on an actual read error, on an EOF in the
+middle of an object (some bytes already buffered), or on an EOF while still
+inside an indefinite-length value awaiting its end-of-contents octets - all
+of which are genuine truncation. A clean EOF at a top-level object boundary
+again fails without queuing an error, restoring the long-standing behaviour
+that looping callers depend on.
+
+Add regression tests covering the clean-EOF, truncated, indefinite-length
+truncation and partial-header cases, and document the read behaviour in
+ASN1_item_d2i_bio(3).
+
+Fixes #31807
+
+Assisted-by: Claude:claude-opus-4-8
+
+Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
+Reviewed-by: Igor Ustinov <igus@openssl.foundation>
+Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
+MergeDate: Mon Jul 13 08:05:03 2026
+(Merged from https://github.com/openssl/openssl/pull/31818)
+
+(cherry picked from commit d7e77b66cabb770932091ed5986221e0d1e57144)
+---
+ crypto/asn1/a_d2i_fp.c | 15 ++-
+ doc/man3/ASN1_item_d2i_bio.pod | 18 +++-
+ test/asn1_decode_test.c | 165 +++++++++++++++++++++++++++++++++
+ 3 files changed, 196 insertions(+), 2 deletions(-)
+
+diff --git a/crypto/asn1/a_d2i_fp.c b/crypto/asn1/a_d2i_fp.c
+index a23dea8ebd..19595683ef 100644
+--- a/crypto/asn1/a_d2i_fp.c
++++ b/crypto/asn1/a_d2i_fp.c
+@@ -139,7 +139,20 @@ int asn1_d2i_read_bio(BIO *in, BUF_MEM **pb)
+ }
+ i = BIO_read(in, &(b->data[len]), want);
+ if (i <= 0) {
+- ERR_raise(ERR_LIB_ASN1, ASN1_R_NOT_ENOUGH_DATA);
++ /*
++ * A read error (i < 0), an EOF in the middle of an object
++ * (diff != 0, some bytes already buffered), or an EOF while
++ * still inside an indefinite-length constructed value awaiting
++ * its end-of-contents octets (eos != 0) all mean the input is
++ * truncated. Only a clean EOF at a top-level object boundary
++ * (i == 0, diff == 0, eos == 0) is the normal end of input:
++ * fail without queuing an error so that callers looping over
++ * concatenated DER values (e.g. the libcrypto d2i_*_bio()
++ * consumers in CPython's ssl module) terminate cleanly instead
++ * of seeing a spurious ASN1_R_NOT_ENOUGH_DATA.
++ */
++ if (i < 0 || diff != 0 || eos != 0)
++ ERR_raise(ERR_LIB_ASN1, ASN1_R_NOT_ENOUGH_DATA);
+ goto err;
+ }
+ if (i > 0) {
+diff --git a/doc/man3/ASN1_item_d2i_bio.pod b/doc/man3/ASN1_item_d2i_bio.pod
+index f8e4678367..9b3f389a23 100644
+--- a/doc/man3/ASN1_item_d2i_bio.pod
++++ b/doc/man3/ASN1_item_d2i_bio.pod
+@@ -59,6 +59,16 @@ B<OSSL_LIB_CTX> provided in the I<libctx> parameter and the property query
+ string in I<propq>. See L<crypto(7)/ALGORITHM FETCHING> for more information
+ about algorithm fetching.
+
++When reading from I<in>, decoding consumes one complete DER-encoded structure
++and leaves any following bytes in the BIO, so concatenated structures can be
++read with successive calls. Reaching the end of the input cleanly, at a
++structure boundary, is not treated as an error: the function returns NULL
++without adding to the error queue. If the end of the input is reached in the
++middle of a structure, or an indefinite-length value is missing its
++end-of-contents octets (that is, the input is truncated), an error is queued
++with reason code B<ASN1_R_NOT_ENOUGH_DATA>. The same applies to
++ASN1_item_d2i_fp_ex().
++
+ ASN1_item_d2i_bio() is the same as ASN1_item_d2i_bio_ex() except that the
+ default B<OSSL_LIB_CTX> is used (i.e. NULL) and with a NULL property query
+ string.
+@@ -92,6 +102,12 @@ that the I<libctx> and I<propq> can be used when doing algorithm fetching.
+ ASN1_item_d2i_bio(), ASN1_item_unpack_ex() and ASN1_item_unpack() return a pointer to
+ an B<ASN1_VALUE> or NULL on error.
+
++The ASN1_item_d2i_bio() and ASN1_item_d2i_fp() functions, including their
++B<_ex> variants, also return NULL at a clean end of input. In that case the
++error queue is left unchanged, so a caller reading concatenated structures in
++a loop can distinguish a clean end of input from a decoding error by
++inspecting the error queue, for example with L<ERR_peek_error(3)>.
++
+ ASN1_item_i2d_mem_bio() returns a pointer to a memory BIO or NULL on error.
+
+ ASN1_item_pack() returns a pointer to an B<ASN1_STRING> or NULL on error.
+@@ -105,7 +121,7 @@ The function ASN1_item_unpack_ex() was added in OpenSSL 3.2.
+
+ =head1 COPYRIGHT
+
+-Copyright 2021-2023 The OpenSSL Project Authors. All Rights Reserved.
++Copyright 2021-2026 The OpenSSL Project Authors. All Rights Reserved.
+
+ Licensed under the Apache License 2.0 (the "License"). You may not use
+ this file except in compliance with the License. You can obtain a copy
+diff --git a/test/asn1_decode_test.c b/test/asn1_decode_test.c
+index 6e00d40896..8a9629c21d 100644
+--- a/test/asn1_decode_test.c
++++ b/test/asn1_decode_test.c
+@@ -14,7 +14,11 @@
+ #include <openssl/asn1.h>
+ #include <openssl/asn1t.h>
+ #include <openssl/obj_mac.h>
++#include <openssl/bio.h>
++#include <openssl/buffer.h>
++#include <openssl/err.h>
+ #include "internal/numbers.h"
++#include "internal/asn1.h"
+ #include "testutil.h"
+
+ #ifdef __GNUC__
+@@ -266,6 +270,163 @@ err:
+ return ret;
+ }
+
++/*
++ * A minimal, complete DER object: SEQUENCE { INTEGER 0 }.
++ * asn1_d2i_read_bio() should consume exactly these bytes.
++ */
++static const unsigned char one_obj[] = {
++ 0x30, 0x03, /* SEQUENCE, length 3 */
++ 0x02, 0x01, 0x00 /* INTEGER 0 */
++};
++
++/*
++ * Reading concatenated DER objects from a BIO must stop cleanly at EOF:
++ * once the input is exhausted on an object boundary, asn1_d2i_read_bio()
++ * returns < 0 and must NOT leave an error on the queue. Callers that loop
++ * over concatenated values (e.g. CPython's ssl module loading the Windows
++ * certificate store via d2i_X509_bio()) rely on this to detect end-of-input;
++ * a spurious ASN1_R_NOT_ENOUGH_DATA there is reported as a fatal error.
++ */
++static int test_d2i_read_bio_clean_eof(void)
++{
++ unsigned char two_objs[sizeof(one_obj) * 2];
++ BIO *bio = NULL;
++ BUF_MEM *buf = NULL;
++ int ret = 0;
++
++ memcpy(two_objs, one_obj, sizeof(one_obj));
++ memcpy(two_objs + sizeof(one_obj), one_obj, sizeof(one_obj));
++
++ if (!TEST_ptr(bio = BIO_new_mem_buf(two_objs, sizeof(two_objs))))
++ goto err;
++ ERR_clear_error();
++
++ /* Both complete objects are read, one per call. */
++ if (!TEST_int_eq(asn1_d2i_read_bio(bio, &buf), (int)sizeof(one_obj)))
++ goto err;
++ BUF_MEM_free(buf);
++ buf = NULL;
++ if (!TEST_int_eq(asn1_d2i_read_bio(bio, &buf), (int)sizeof(one_obj)))
++ goto err;
++ BUF_MEM_free(buf);
++ buf = NULL;
++
++ /* Clean EOF: failure return, but no error must be queued. */
++ if (!TEST_int_lt(asn1_d2i_read_bio(bio, &buf), 0))
++ goto err;
++ if (!TEST_ulong_eq(ERR_peek_error(), 0))
++ goto err;
++
++ ret = 1;
++err:
++ BUF_MEM_free(buf);
++ BIO_free(bio);
++ return ret;
++}
++
++/*
++ * In contrast, hitting EOF in the middle of an object is genuine truncation
++ * and must still be reported as ASN1_R_NOT_ENOUGH_DATA.
++ */
++static int test_d2i_read_bio_truncated(void)
++{
++ static const unsigned char truncated[] = {
++ 0x30, 0x05, /* SEQUENCE claims 5 content bytes ... */
++ 0x02, 0x01 /* ... but only 2 are present */
++ };
++ BIO *bio = NULL;
++ BUF_MEM *buf = NULL;
++ unsigned long e;
++ int ret = 0;
++
++ if (!TEST_ptr(bio = BIO_new_mem_buf(truncated, sizeof(truncated))))
++ goto err;
++ ERR_clear_error();
++
++ if (!TEST_int_lt(asn1_d2i_read_bio(bio, &buf), 0))
++ goto err;
++ e = ERR_peek_last_error();
++ if (!TEST_int_eq(ERR_GET_LIB(e), ERR_LIB_ASN1)
++ || !TEST_int_eq(ERR_GET_REASON(e), ASN1_R_NOT_ENOUGH_DATA))
++ goto err;
++
++ ret = 1;
++err:
++ BUF_MEM_free(buf);
++ BIO_free(bio);
++ return ret;
++}
++
++/*
++ * An EOF reached while still inside an indefinite-length constructed value,
++ * before its end-of-contents octets, is truncation too (not a clean boundary),
++ * so it must also report ASN1_R_NOT_ENOUGH_DATA rather than an empty queue.
++ */
++static int test_d2i_read_bio_indefinite_truncated(void)
++{
++ /* SEQUENCE (indefinite) { INTEGER 0 } with the 00 00 EOC missing */
++ static const unsigned char truncated_indefinite[] = {
++ 0x30, 0x80, /* SEQUENCE, indefinite length */
++ 0x02, 0x01, 0x00 /* INTEGER 0; no end-of-contents octets follow */
++ };
++ BIO *bio = NULL;
++ BUF_MEM *buf = NULL;
++ unsigned long e;
++ int ret = 0;
++
++ bio = BIO_new_mem_buf(truncated_indefinite, sizeof(truncated_indefinite));
++ if (!TEST_ptr(bio))
++ goto err;
++ ERR_clear_error();
++
++ if (!TEST_int_lt(asn1_d2i_read_bio(bio, &buf), 0))
++ goto err;
++ e = ERR_peek_last_error();
++ if (!TEST_int_eq(ERR_GET_LIB(e), ERR_LIB_ASN1)
++ || !TEST_int_eq(ERR_GET_REASON(e), ASN1_R_NOT_ENOUGH_DATA))
++ goto err;
++
++ ret = 1;
++err:
++ BUF_MEM_free(buf);
++ BIO_free(bio);
++ return ret;
++}
++
++/*
++ * An EOF reached part-way through an object's header, with some header bytes
++ * already buffered, is truncation as well. This exercises the "diff != 0" arm
++ * of the header-read check (distinct from the body read handled elsewhere).
++ */
++static int test_d2i_read_bio_partial_header(void)
++{
++ /* SEQUENCE with a 2-byte long-form length, but only one length byte given */
++ static const unsigned char partial_header[] = {
++ 0x30, 0x82, 0x01 /* SEQUENCE, length declared as 2 bytes, 1 present */
++ };
++ BIO *bio = NULL;
++ BUF_MEM *buf = NULL;
++ unsigned long e;
++ int ret = 0;
++
++ if (!TEST_ptr(bio = BIO_new_mem_buf(partial_header, sizeof(partial_header))))
++ goto err;
++ ERR_clear_error();
++
++ if (!TEST_int_lt(asn1_d2i_read_bio(bio, &buf), 0))
++ goto err;
++ e = ERR_peek_last_error();
++ if (!TEST_int_eq(ERR_GET_LIB(e), ERR_LIB_ASN1)
++ || !TEST_int_eq(ERR_GET_REASON(e), ASN1_R_NOT_ENOUGH_DATA))
++ goto err;
++
++ ret = 1;
++err:
++ BUF_MEM_free(buf);
++ BIO_free(bio);
++ return ret;
++}
++
+ int setup_tests(void)
+ {
+ #ifndef OPENSSL_NO_DEPRECATED_3_0
+@@ -279,5 +440,9 @@ int setup_tests(void)
+ ADD_TEST(test_utctime);
+ ADD_TEST(test_invalid_template);
+ ADD_TEST(test_reuse_asn1_object);
++ ADD_TEST(test_d2i_read_bio_clean_eof);
++ ADD_TEST(test_d2i_read_bio_truncated);
++ ADD_TEST(test_d2i_read_bio_indefinite_truncated);
++ ADD_TEST(test_d2i_read_bio_partial_header);
+ return 1;
+ }
+--
+2.54.0
+
diff --git a/openssl.spec b/openssl.spec
index 546343b..bd1d12a 100644
--- a/openssl.spec
+++ b/openssl.spec
@@ -34,7 +34,7 @@ print(string.sub(hash, 0, 16))
Summary: Utilities from the general purpose cryptography library with TLS implementation
Name: openssl
Version: 3.5.7
-Release: 1%{?dist}
+Release: 2%{?dist}
Epoch: 1
Source0: openssl-%{version}.tar.gz
Source1: fips-hmacify.sh
@@ -102,6 +102,7 @@ Patch0054: 0054-Temporarily-disable-SLH-DSA-FIPS-self-tests.patch
Patch0055: 0055-Add-a-define-to-disable-symver-attributes.patch
Patch0056: 0056-Add-targets-to-skip-build-of-non-installable-program.patch
Patch0057: 0057-Disable-RSA-PKCS1.5-FIPS-POST-not-relevant-for-RHEL.patch
+Patch0058: 0058-Don-t-raise-NOT_ENOUGH_DATA-at-object-boundary.patch
License: Apache-2.0
URL: http://www.openssl.org/
@@ -474,6 +475,10 @@ ln -s /etc/crypto-policies/back-ends/openssl_fips.config $RPM_BUILD_ROOT%{_sysco
%ldconfig_scriptlets libs
%changelog
+* Fri Jul 17 2026 Pavol Žáčik <pzacik@redhat.com> - 1:3.5.7-2
+- Don't raise NOT_ENOUGH_DATA on a clean EOF at an object boundary
+ (Backport from https://github.com/openssl/openssl/commit/3687ff7)
+
* Wed Jun 10 2026 Dmitry Belyavskiy <dbelyavs@redhat.com> - 1:3.5.7-1
- Rebase to OpenSSL 3.5.7
Resolves: CVE-2026-45447
reply other threads:[~2026-07-17 19:00 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=178431482856.1.9344236970534993944.rpms-openssl-1f3cf2866499@fedoraproject.org \
--to=git-commits@fedoraproject.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox