new file mode 100644
@@ -0,0 +1,158 @@
+From a0048a6b97a349a3cb4a5f955d350ab2921719cc Mon Sep 17 00:00:00 2001
+From: Jack Lloyd <jack@randombit.net>
+Date: Sun, 15 Mar 2026 11:39:50 -0400
+Subject: [PATCH] In SM2 verify the C3 field is of the required length
+
+Previously the decryption step assumed that C3 was equal in length to the MAC
+output. If C3 is shorter than expected, up to 31 bytes of arbitrary heap data
+would be compared with the computed MAC. This heap over-read would potentially
+result in denial of service.
+
+CVE: CVE-2026-32877
+Upstream-Status: Backport [https://github.com/randombit/botan/commit/f3c31f96f58f1d1d482032d8f4286dc9ebbc6712]
+Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
+---
+ src/lib/pubkey/sm2/sm2_enc.cpp | 7 +++-
+ src/tests/data/pubkey/sm2_invalid.vec | 60 +++++++++++++++++++++++++++
+ src/tests/test_sm2.cpp | 31 ++++++++++++++
+ 3 files changed, 97 insertions(+), 1 deletion(-)
+ create mode 100644 src/tests/data/pubkey/sm2_invalid.vec
+
+diff --git a/src/lib/pubkey/sm2/sm2_enc.cpp b/src/lib/pubkey/sm2/sm2_enc.cpp
+index 7a1b990..1141089 100644
+--- a/src/lib/pubkey/sm2/sm2_enc.cpp
++++ b/src/lib/pubkey/sm2/sm2_enc.cpp
+@@ -133,6 +133,11 @@ class SM2_Decryption_Operation final : public PK_Ops::Decryption {
+ .end_cons()
+ .verify_end();
+
++ // Wrong length so certainly invalid, reject immediately
++ if(C3.size() != m_hash->output_length()) {
++ return secure_vector<uint8_t>();
++ }
++
+ std::vector<uint8_t> recode_ctext;
+ DER_Encoder(recode_ctext)
+ .start_sequence()
+@@ -170,7 +175,7 @@ class SM2_Decryption_Operation final : public PK_Ops::Decryption {
+ m_hash->update(y2_bytes);
+ const auto u = m_hash->final();
+
+- if(!CT::is_equal(u.data(), C3.data(), m_hash->output_length()).as_bool()) {
++ if(!CT::is_equal<uint8_t>(u, C3).as_bool()) {
+ return secure_vector<uint8_t>();
+ }
+
+diff --git a/src/tests/data/pubkey/sm2_invalid.vec b/src/tests/data/pubkey/sm2_invalid.vec
+new file mode 100644
+index 0000000..2517510
+--- /dev/null
++++ b/src/tests/data/pubkey/sm2_invalid.vec
+@@ -0,0 +1,60 @@
++
++Key = 8C84F7F069CD09D59543ED980CFEB77E68C7D39B9B73D359EA67C0CDB2A86B6F
++
++
++# Empty
++Ctext =
++
++# Just the SEQUENCE marker
++Ctext = 30
++
++# Truncated
++Ctext = 3072022100A772DF5FFFDA85C05D9B82233B1E5F7DF7A87788504ABD4F74D265D49E5002C0022055C8A934A29DE58B31A063CDA81F12ABC712FB9ADC8988DA0FBD9FFF304E27CF04202E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F3682649386960409CA311F43DB0E5E51
++
++# C3 MAC too short
++Ctext = 3071022100A772DF5FFFDA85C05D9B82233B1E5F7DF7A87788504ABD4F74D265D49E5002C0022055C8A934A29DE58B31A063CDA81F12ABC712FB9ADC8988DA0FBD9FFF304E27CF041F2E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F36826493860409CA311F43DB0E5E516F
++
++# C3 MAC too long
++Ctext = 3073022100A772DF5FFFDA85C05D9B82233B1E5F7DF7A87788504ABD4F74D265D49E5002C0022055C8A934A29DE58B31A063CDA81F12ABC712FB9ADC8988DA0FBD9FFF304E27CF04212E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F368264938696000409CA311F43DB0E5E516F
++
++# C3 and C2 fields swapped
++Ctext = 3072022100A772DF5FFFDA85C05D9B82233B1E5F7DF7A87788504ABD4F74D265D49E5002C0022055C8A934A29DE58B31A063CDA81F12ABC712FB9ADC8988DA0FBD9FFF304E27CF0409CA311F43DB0E5E516F04202E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F368264938696
++
++# C3 MAC last bit flipped
++Ctext = 3072022100A772DF5FFFDA85C05D9B82233B1E5F7DF7A87788504ABD4F74D265D49E5002C0022055C8A934A29DE58B31A063CDA81F12ABC712FB9ADC8988DA0FBD9FFF304E27CF04202E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F3682649386970409CA311F43DB0E5E516F
++
++# C3 MAC first byte inverted
++Ctext = 3072022100A772DF5FFFDA85C05D9B82233B1E5F7DF7A87788504ABD4F74D265D49E5002C0022055C8A934A29DE58B31A063CDA81F12ABC712FB9ADC8988DA0FBD9FFF304E27CF0420D11B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F3682649386960409CA311F43DB0E5E516F
++
++# C1 y off by one
++Ctext = 3072022100A772DF5FFFDA85C05D9B82233B1E5F7DF7A87788504ABD4F74D265D49E5002C0022055C8A934A29DE58B31A063CDA81F12ABC712FB9ADC8988DA0FBD9FFF304E27D004202E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F3682649386960409CA311F43DB0E5E516F
++
++# C1 x/y serialization boundary shift
++Ctext = 3073022100A772DF5FFFDA85C05D9B82233B1E5F7DF7A87788504ABD4F74D265D49E500200022100C055C8A934A29DE58B31A063CDA81F12ABC712FB9ADC8988DA0FBD9FFF304E2704202E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F3682649386960409CA311F43DB0E5E516F
++
++# C1 is generator point
++Ctext = 3072022032C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7022100BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A004202E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F3682649386960409CA311F43DB0E5E516F
++
++# C1 is origin (0,0)
++Ctext = 303302010002010004202E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F3682649386960409CA311F43DB0E5E516F
++
++# C1 x equals field prime
++Ctext = 3072022100FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF022055C8A934A29DE58B31A063CDA81F12ABC712FB9ADC8988DA0FBD9FFF304E27CF04202E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F3682649386960409CA311F43DB0E5E516F
++
++# C2 last bit flipped
++Ctext = 3072022100A772DF5FFFDA85C05D9B82233B1E5F7DF7A87788504ABD4F74D265D49E5002C0022055C8A934A29DE58B31A063CDA81F12ABC712FB9ADC8988DA0FBD9FFF304E27CF04202E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F3682649386960409CA311F43DB0E5E516E
++
++# C2 replaced with empty
++Ctext = 3069022100A772DF5FFFDA85C05D9B82233B1E5F7DF7A87788504ABD4F74D265D49E5002C0022055C8A934A29DE58B31A063CDA81F12ABC712FB9ADC8988DA0FBD9FFF304E27CF04202E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F3682649386960400
++
++# Trailing byte after SEQUENCE
++Ctext = 3072022100A772DF5FFFDA85C05D9B82233B1E5F7DF7A87788504ABD4F74D265D49E5002C0022055C8A934A29DE58B31A063CDA81F12ABC712FB9ADC8988DA0FBD9FFF304E27CF04202E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F3682649386960409CA311F43DB0E5E516F00
++
++# SEQUENCE with non-minimal long-form length
++Ctext = 308172022100A772DF5FFFDA85C05D9B82233B1E5F7DF7A87788504ABD4F74D265D49E5002C0022055C8A934A29DE58B31A063CDA81F12ABC712FB9ADC8988DA0FBD9FFF304E27CF04202E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F3682649386960409CA311F43DB0E5E516F
++
++# INTEGER x1 with non-minimal leading zero
++Ctext = 307302220000A772DF5FFFDA85C05D9B82233B1E5F7DF7A87788504ABD4F74D265D49E5002C0022055C8A934A29DE58B31A063CDA81F12ABC712FB9ADC8988DA0FBD9FFF304E27CF04202E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F3682649386960409CA311F43DB0E5E516F
++
++# OCTET STRING C3 with non-minimal long-form length
++Ctext = 3073022100A772DF5FFFDA85C05D9B82233B1E5F7DF7A87788504ABD4F74D265D49E5002C0022055C8A934A29DE58B31A063CDA81F12ABC712FB9ADC8988DA0FBD9FFF304E27CF0481202E1B8A86EF14243EE2E9D74E0D0E7498DFAC2F0EFA8C5883D74F3682649386960409CA311F43DB0E5E516F
+diff --git a/src/tests/test_sm2.cpp b/src/tests/test_sm2.cpp
+index 2ffb14a..c5d2360 100644
+--- a/src/tests/test_sm2.cpp
++++ b/src/tests/test_sm2.cpp
+@@ -9,6 +9,7 @@
+
+ #if defined(BOTAN_HAS_SM2)
+ #include "test_pubkey.h"
++ #include <botan/pubkey.h>
+ #include <botan/sm2.h>
+ #endif
+
+@@ -108,4 +109,34 @@ BOTAN_REGISTER_TEST("pubkey", "sm2_keygen", SM2_Keygen_Tests);
+
+ #endif
+
++namespace {
++
++class SM2_Invalid_Ciphertexts : public Text_Based_Test {
++ public:
++ SM2_Invalid_Ciphertexts() : Text_Based_Test("pubkey/sm2_invalid.vec", "Key,Ctext") {}
++
++ bool clear_between_callbacks() const override { return false; }
++
++ Test::Result run_one_test(const std::string& /*header*/, const VarMap& vars) override {
++ Test::Result result("SM2 invalid ciphertext");
++
++ const auto key = vars.get_req_bin("Key");
++ const auto ctext = vars.get_req_bin("Ctext");
++
++ const auto group = Botan::EC_Group::from_name("sm2p256v1");
++ const auto pkey = Botan::SM2_PrivateKey(group, Botan::EC_Scalar::deserialize(group, key).value());
++
++ Botan::PK_Decryptor_EME dec(pkey, rng(), "SM3");
++
++ result.test_throws<Botan::Exception>("Decryption should fail for invalid ciphertext",
++ [&] { dec.decrypt(ctext); });
++
++ return result;
++ }
++};
++
++BOTAN_REGISTER_TEST("pubkey", "sm2_invalid_ctext", SM2_Invalid_Ciphertexts);
++
++} // namespace
++
+ } // namespace Botan_Tests
@@ -4,7 +4,9 @@ LICENSE = "BSD-2-Clause"
LIC_FILES_CHKSUM = "file://license.txt;md5=3f911cecfc74a2d9f1ead9a07bd92a6e"
SECTION = "libs"
-SRC_URI = "https://botan.randombit.net/releases/Botan-${PV}.tar.xz"
+SRC_URI = "https://botan.randombit.net/releases/Botan-${PV}.tar.xz \
+ file://CVE-2026-32877.patch \
+ "
SRC_URI[sha256sum] = "fde194236f6d5434f136ea0a0627f6cc9d26af8b96e9f1e1c7d8c82cd90f4f24"
S = "${UNPACKDIR}/Botan-${PV}"
Details: https://nvd.nist.gov/vuln/detail/CVE-2026-32877 Backport the patch that was identified by Debian[1]. The included test passed successfully (along with the other tests). [1]: https://security-tracker.debian.org/tracker/CVE-2026-32877 Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com> --- .../botan/botan/CVE-2026-32877.patch | 158 ++++++++++++++++++ meta-oe/recipes-crypto/botan/botan_3.10.0.bb | 4 +- 2 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 meta-oe/recipes-crypto/botan/botan/CVE-2026-32877.patch