Fix CVE-2025-69419: Arbitrary code execution due to out-of-bounds write in PKCS#12 processing

Resolves: RHEL-142010
Signed-off-by: Antonio Vieiro <avieirov@redhat.com>
This commit is contained in:
Antonio Vieiro 2026-02-12 11:46:43 +01:00
parent ad510221a6
commit 0c814b8079
3 changed files with 477 additions and 1 deletions

View File

@ -0,0 +1,80 @@
diff --git a/crypto/asn1/a_strex.c b/crypto/asn1/a_strex.c
index 17f7372026c3b..01e2269444cba 100644
--- a/crypto/asn1/a_strex.c
+++ b/crypto/asn1/a_strex.c
@@ -198,8 +198,10 @@ static int do_buf(unsigned char *buf, int buflen,
orflags = CHARTYPE_LAST_ESC_2253;
if (type & BUF_TYPE_CONVUTF8) {
unsigned char utfbuf[6];
- int utflen;
- utflen = UTF8_putc(utfbuf, sizeof(utfbuf), c);
+ int utflen = UTF8_putc(utfbuf, sizeof(utfbuf), c);
+
+ if (utflen < 0)
+ return -1; /* error happened with UTF8 */
for (i = 0; i < utflen; i++) {
/*
* We don't need to worry about setting orflags correctly
diff --git a/crypto/pkcs12/p12_utl.c b/crypto/pkcs12/p12_utl.c
index 50adce6b26fd2..8b5f2909e8d96 100644
--- a/crypto/pkcs12/p12_utl.c
+++ b/crypto/pkcs12/p12_utl.c
@@ -213,6 +213,11 @@ char *OPENSSL_uni2utf8(const unsigned char *uni, int unilen)
/* re-run the loop emitting UTF-8 string */
for (asclen = 0, i = 0; i < unilen; ) {
j = bmp_to_utf8(asctmp+asclen, uni+i, unilen-i);
+ /* when UTF8_putc fails */
+ if (j < 0) {
+ OPENSSL_free(asctmp);
+ return NULL;
+ }
if (j == 4) i += 4;
else i += 2;
asclen += j;
From 06f1b7338403a56a645de93b95fc68f3ab165fd5 Mon Sep 17 00:00:00 2001
From: Maurizio Barbaro <mbarbaro@redhat.com>
Date: Tue, 10 Feb 2026 10:39:46 +0100
Subject: [PATCH 1/1] asn1_internal_test
Signed-off-by: Maurizio Barbaro <mbarbaro@redhat.com>
---
test/asn1_internal_test.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/test/asn1_internal_test.c b/test/asn1_internal_test.c
index ccfb75a..92ed921 100644
--- a/test/asn1_internal_test.c
+++ b/test/asn1_internal_test.c
@@ -147,10 +147,27 @@ static int test_unicode_range(void)
return ok;
}
+static int test_mbstring_ncopy(void)
+{
+ ASN1_STRING *str = NULL;
+ const unsigned char in[] = { 0xFF, 0xFE, 0xFF, 0xFE };
+ int inlen = 4;
+ int inform = MBSTRING_UNIV;
+
+ if (!TEST_int_eq(ASN1_mbstring_ncopy(&str, in, inlen, inform, B_ASN1_GENERALSTRING, 0, 0), -1)
+ || !TEST_int_eq(ASN1_mbstring_ncopy(&str, in, inlen, inform, B_ASN1_VISIBLESTRING, 0, 0), -1)
+ || !TEST_int_eq(ASN1_mbstring_ncopy(&str, in, inlen, inform, B_ASN1_VIDEOTEXSTRING, 0, 0), -1)
+ || !TEST_int_eq(ASN1_mbstring_ncopy(&str, in, inlen, inform, B_ASN1_GENERALIZEDTIME, 0, 0), -1))
+ return 0;
+
+ return 1;
+}
+
int setup_tests(void)
{
ADD_TEST(test_tbl_standard);
ADD_TEST(test_standard_methods);
ADD_TEST(test_unicode_range);
+ ADD_TEST(test_mbstring_ncopy);
return 1;
}
--
2.52.0

View File

@ -0,0 +1,387 @@
From 23c0f08fa39a825003c9d7eb0c28260939e0d3ef Mon Sep 17 00:00:00 2001
From: Maurizio Barbaro <mbarbaro@redhat.com>
Date: Tue, 10 Feb 2026 11:48:41 +0100
Subject: [PATCH 1/1] hardening-from-openssl-3.0.1
Signed-off-by: Maurizio Barbaro <mbarbaro@redhat.com>
---
crypto/asn1/a_mbstr.c | 58 ++++++++++++++++++++++++----
crypto/asn1/a_utf8.c | 77 +++++++-------------------------------
include/internal/unicode.h | 31 +++++++++++++++
test/asn1_internal_test.c | 41 ++++++++++++++++++++
4 files changed, 135 insertions(+), 72 deletions(-)
create mode 100644 include/internal/unicode.h
diff --git a/crypto/asn1/a_mbstr.c b/crypto/asn1/a_mbstr.c
index bdb697a..6ef4137 100644
--- a/crypto/asn1/a_mbstr.c
+++ b/crypto/asn1/a_mbstr.c
@@ -1,7 +1,7 @@
/*
- * Copyright 1999-2017 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 1999-2021 The OpenSSL Project Authors. All Rights Reserved.
*
- * Licensed under the OpenSSL license (the "License"). You may not use
+ * 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
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
@@ -10,6 +10,7 @@
#include <stdio.h>
#include "crypto/ctype.h"
#include "internal/cryptlib.h"
+#include "internal/unicode.h"
#include <openssl/asn1.h>
static int traverse_string(const unsigned char *p, int len, int inform,
@@ -49,12 +50,15 @@ int ASN1_mbstring_ncopy(ASN1_STRING **out, const unsigned char *in, int len,
ASN1_STRING *dest;
unsigned char *p;
int nchar;
- char strbuf[32];
+ char strbuf[32];
+
int (*cpyfunc) (unsigned long, void *) = NULL;
if (len == -1)
len = strlen((const char *)in);
if (!mask)
mask = DIRSTRING_TYPE;
+ if (len < 0)
+ return -1;
/* First do a string check and work out the number of characters */
switch (inform) {
@@ -116,7 +120,10 @@ int ASN1_mbstring_ncopy(ASN1_STRING **out, const unsigned char *in, int len,
return -1;
}
- /* Now work out output format and string type */
+ /*
+ * Now work out output format and string type.
+ * These checks should be in sync with the checks in type_str.
+ */
outform = MBSTRING_ASC;
if (mask & B_ASN1_NUMERICSTRING)
str_type = V_ASN1_NUMERICSTRING;
@@ -182,7 +189,11 @@ int ASN1_mbstring_ncopy(ASN1_STRING **out, const unsigned char *in, int len,
case MBSTRING_UTF8:
outlen = 0;
- traverse_string(in, len, inform, out_utf8, &outlen);
+ ret = traverse_string(in, len, inform, out_utf8, &outlen);
+ if (ret < 0) {
+ ASN1err(ASN1_F_ASN1_MBSTRING_NCOPY, ASN1_R_INVALID_UTF8STRING);
+ return -1;
+ }
cpyfunc = cpy_utf8;
break;
}
@@ -247,6 +258,9 @@ static int traverse_string(const unsigned char *p, int len, int inform,
static int in_utf8(unsigned long value, void *arg)
{
int *nchar;
+
+ if (!is_unicode_valid(value))
+ return -2;
nchar = arg;
(*nchar)++;
return 1;
@@ -256,9 +270,13 @@ static int in_utf8(unsigned long value, void *arg)
static int out_utf8(unsigned long value, void *arg)
{
- int *outlen;
+ int *outlen, len;
+
+ len = UTF8_putc(NULL, -1, value);
+ if (len <= 0)
+ return len;
outlen = arg;
- *outlen += UTF8_putc(NULL, -1, value);
+ *outlen += len;
return 1;
}
@@ -269,9 +287,29 @@ static int out_utf8(unsigned long value, void *arg)
static int type_str(unsigned long value, void *arg)
{
- unsigned long types = *((unsigned long *)arg);
+ unsigned long usable_types = *((unsigned long *)arg);
+ unsigned long types = usable_types;
const int native = value > INT_MAX ? INT_MAX : ossl_fromascii(value);
+ /*
+ * Clear out all the types which are not checked later. If any of those
+ * is present in the mask, then the UTF8 type will be added and checked
+ * below.
+ */
+ types &= B_ASN1_NUMERICSTRING | B_ASN1_PRINTABLESTRING
+ | B_ASN1_IA5STRING | B_ASN1_T61STRING | B_ASN1_BMPSTRING
+ | B_ASN1_UNIVERSALSTRING | B_ASN1_UTF8STRING;
+
+ /*
+ * If any other types were in the input mask, they're effectively treated
+ * as UTF8
+ */
+ if (types != usable_types)
+ types |= B_ASN1_UTF8STRING;
+
+ /*
+ * These checks should be in sync with ASN1_mbstring_ncopy.
+ */
if ((types & B_ASN1_NUMERICSTRING) && !(ossl_isdigit(native)
|| native == ' '))
types &= ~B_ASN1_NUMERICSTRING;
@@ -283,6 +321,8 @@ static int type_str(unsigned long value, void *arg)
types &= ~B_ASN1_T61STRING;
if ((types & B_ASN1_BMPSTRING) && (value > 0xffff))
types &= ~B_ASN1_BMPSTRING;
+ if ((types & B_ASN1_UTF8STRING) && !is_unicode_valid(value))
+ types &= ~B_ASN1_UTF8STRING;
if (!types)
return -1;
*((unsigned long *)arg) = types;
@@ -338,6 +378,8 @@ static int cpy_utf8(unsigned long value, void *arg)
p = arg;
/* We already know there is enough room so pass 0xff as the length */
ret = UTF8_putc(*p, 0xff, value);
+ if (ret < 0)
+ return ret;
*p += ret;
return 1;
}
diff --git a/crypto/asn1/a_utf8.c b/crypto/asn1/a_utf8.c
index e2dc09f..6572726 100644
--- a/crypto/asn1/a_utf8.c
+++ b/crypto/asn1/a_utf8.c
@@ -1,7 +1,7 @@
/*
- * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved.
*
- * Licensed under the OpenSSL license (the "License"). You may not use
+ * 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
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
@@ -9,6 +9,7 @@
#include <stdio.h>
#include "internal/cryptlib.h"
+#include "internal/unicode.h"
#include <openssl/asn1.h>
/* UTF8 utilities */
@@ -58,6 +59,8 @@ int UTF8_getc(const unsigned char *str, int len, unsigned long *val)
value |= *p++ & 0x3f;
if (value < 0x800)
return -4;
+ if (is_unicode_surrogate(value))
+ return -2;
ret = 3;
} else if ((*p & 0xf8) == 0xf0) {
if (len < 4)
@@ -73,40 +76,6 @@ int UTF8_getc(const unsigned char *str, int len, unsigned long *val)
if (value < 0x10000)
return -4;
ret = 4;
- } else if ((*p & 0xfc) == 0xf8) {
- if (len < 5)
- return -1;
- if (((p[1] & 0xc0) != 0x80)
- || ((p[2] & 0xc0) != 0x80)
- || ((p[3] & 0xc0) != 0x80)
- || ((p[4] & 0xc0) != 0x80))
- return -3;
- value = ((unsigned long)(*p++ & 0x3)) << 24;
- value |= ((unsigned long)(*p++ & 0x3f)) << 18;
- value |= ((unsigned long)(*p++ & 0x3f)) << 12;
- value |= (*p++ & 0x3f) << 6;
- value |= *p++ & 0x3f;
- if (value < 0x200000)
- return -4;
- ret = 5;
- } else if ((*p & 0xfe) == 0xfc) {
- if (len < 6)
- return -1;
- if (((p[1] & 0xc0) != 0x80)
- || ((p[2] & 0xc0) != 0x80)
- || ((p[3] & 0xc0) != 0x80)
- || ((p[4] & 0xc0) != 0x80)
- || ((p[5] & 0xc0) != 0x80))
- return -3;
- value = ((unsigned long)(*p++ & 0x1)) << 30;
- value |= ((unsigned long)(*p++ & 0x3f)) << 24;
- value |= ((unsigned long)(*p++ & 0x3f)) << 18;
- value |= ((unsigned long)(*p++ & 0x3f)) << 12;
- value |= (*p++ & 0x3f) << 6;
- value |= *p++ & 0x3f;
- if (value < 0x4000000)
- return -4;
- ret = 6;
} else
return -2;
*val = value;
@@ -116,15 +85,15 @@ int UTF8_getc(const unsigned char *str, int len, unsigned long *val)
/*
* This takes a character 'value' and writes the UTF8 encoded value in 'str'
* where 'str' is a buffer containing 'len' characters. Returns the number of
- * characters written or -1 if 'len' is too small. 'str' can be set to NULL
- * in which case it just returns the number of characters. It will need at
- * most 6 characters.
+ * characters written, -1 if 'len' is too small or -2 if 'value' is out of
+ * range. 'str' can be set to NULL in which case it just returns the number of
+ * characters. It will need at most 4 characters.
*/
int UTF8_putc(unsigned char *str, int len, unsigned long value)
{
if (!str)
- len = 6; /* Maximum we will need */
+ len = 4; /* Maximum we will need */
else if (len <= 0)
return -1;
if (value < 0x80) {
@@ -142,6 +111,8 @@ int UTF8_putc(unsigned char *str, int len, unsigned long value)
return 2;
}
if (value < 0x10000) {
+ if (is_unicode_surrogate(value))
+ return -2;
if (len < 3)
return -1;
if (str) {
@@ -151,7 +122,7 @@ int UTF8_putc(unsigned char *str, int len, unsigned long value)
}
return 3;
}
- if (value < 0x200000) {
+ if (value < UNICODE_LIMIT) {
if (len < 4)
return -1;
if (str) {
@@ -162,27 +133,5 @@ int UTF8_putc(unsigned char *str, int len, unsigned long value)
}
return 4;
}
- if (value < 0x4000000) {
- if (len < 5)
- return -1;
- if (str) {
- *str++ = (unsigned char)(((value >> 24) & 0x3) | 0xf8);
- *str++ = (unsigned char)(((value >> 18) & 0x3f) | 0x80);
- *str++ = (unsigned char)(((value >> 12) & 0x3f) | 0x80);
- *str++ = (unsigned char)(((value >> 6) & 0x3f) | 0x80);
- *str = (unsigned char)((value & 0x3f) | 0x80);
- }
- return 5;
- }
- if (len < 6)
- return -1;
- if (str) {
- *str++ = (unsigned char)(((value >> 30) & 0x1) | 0xfc);
- *str++ = (unsigned char)(((value >> 24) & 0x3f) | 0x80);
- *str++ = (unsigned char)(((value >> 18) & 0x3f) | 0x80);
- *str++ = (unsigned char)(((value >> 12) & 0x3f) | 0x80);
- *str++ = (unsigned char)(((value >> 6) & 0x3f) | 0x80);
- *str = (unsigned char)((value & 0x3f) | 0x80);
- }
- return 6;
+ return -2;
}
diff --git a/include/internal/unicode.h b/include/internal/unicode.h
new file mode 100644
index 0000000..a6de835
--- /dev/null
+++ b/include/internal/unicode.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2021 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
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#ifndef OSSL_INTERNAL_UNICODE_H
+# define OSSL_INTERNAL_UNICODE_H
+# pragma once
+
+typedef enum {
+ SURROGATE_MIN = 0xd800UL,
+ SURROGATE_MAX = 0xdfffUL,
+ UNICODE_MAX = 0x10ffffUL,
+ UNICODE_LIMIT
+} UNICODE_CONSTANTS;
+
+static ossl_unused ossl_inline int is_unicode_surrogate(unsigned long value)
+{
+ return value >= SURROGATE_MIN && value <= SURROGATE_MAX;
+}
+
+static ossl_unused ossl_inline int is_unicode_valid(unsigned long value)
+{
+ return value <= UNICODE_MAX && !is_unicode_surrogate(value);
+}
+
+#endif
diff --git a/test/asn1_internal_test.c b/test/asn1_internal_test.c
index 865e058..ccfb75a 100644
--- a/test/asn1_internal_test.c
+++ b/test/asn1_internal_test.c
@@ -107,9 +107,50 @@ static int test_standard_methods(void)
return 0;
}
+/**********************************************************************
+ *
+ * Tests of the Unicode code point range
+ *
+ ***/
+
+static int test_unicode(const unsigned char *univ, size_t len, int expected)
+{
+ const unsigned char *end = univ + len;
+ int ok = 1;
+
+ for (; univ < end; univ += 4) {
+ if (!TEST_int_eq(ASN1_mbstring_copy(NULL, univ, 4, MBSTRING_UNIV,
+ B_ASN1_UTF8STRING),
+ expected))
+ ok = 0;
+ }
+ return ok;
+}
+
+static int test_unicode_range(void)
+{
+ const unsigned char univ_ok[] = "\0\0\0\0"
+ "\0\0\xd7\xff"
+ "\0\0\xe0\x00"
+ "\0\x10\xff\xff";
+ const unsigned char univ_bad[] = "\0\0\xd8\x00"
+ "\0\0\xdf\xff"
+ "\0\x11\x00\x00"
+ "\x80\x00\x00\x00"
+ "\xff\xff\xff\xff";
+ int ok = 1;
+
+ if (!test_unicode(univ_ok, sizeof univ_ok - 1, V_ASN1_UTF8STRING))
+ ok = 0;
+ if (!test_unicode(univ_bad, sizeof univ_bad - 1, -1))
+ ok = 0;
+ return ok;
+}
+
int setup_tests(void)
{
ADD_TEST(test_tbl_standard);
ADD_TEST(test_standard_methods);
+ ADD_TEST(test_unicode_range);
return 1;
}
--
2.52.0

View File

@ -22,7 +22,7 @@
Summary: Utilities from the general purpose cryptography library with TLS implementation
Name: openssl
Version: 1.1.1k
Release: 14%{?dist}
Release: 15%{?dist}
Epoch: 1
# We have to remove certain patented algorithms from the openssl source
# tarball with the hobble-openssl script which is included below.
@ -104,6 +104,9 @@ Patch108: openssl-1.1.1-pkcs1-implicit-rejection.patch
Patch109: openssl-1.1.1-fix-ssl-select-next-proto.patch
Patch110: openssl-1.1.1-cve-2025-9230.patch
Patch111: openssl-1.1.1-ticket_lifetime_hint.patch
# Fix for CVE-2025-69419
Patch112: openssl-1.1.1-hardening-from-openssl-3.0.1.patch
Patch113: openssl-1.1.1-cve-2025-69419.patch
License: OpenSSL and ASL 2.0
URL: http://www.openssl.org/
@ -240,6 +243,8 @@ cp %{SOURCE13} test/
%patch109 -p1 -b .cve-2024-5535
%patch110 -p1 -b .cve-2025-9230
%patch111 -p1 -b .ticket_lifetime_hint
%patch112 -p1 -b .cve-2025-69419-1
%patch113 -p1 -b .cve-2025-69419-2
%build
# Figure out which flags we want to use.
@ -523,6 +528,10 @@ export LD_LIBRARY_PATH
%postun libs -p /sbin/ldconfig
%changelog
* Thu Feb 12 2026 Antonio Vieiro <avieirov@redhat.com> - 1:1.1.1k-15
- Fix CVE-2025-69419: Arbitrary code execution due to out-of-bounds write in PKCS#12 processing
Resolves: RHEL-142010
* Mon Dec 08 2025 Nikita Sanjay Patwa <npatwa@redhat.com> - 1:1.1.1k-14
- Backport fix for Out-of-bounds read & write in RFC 3211 KEK Unwrap
Fix CVE-2025-9230