git: 2329d94e49d3 - releng/14.3 - iconv(3): Fix problems in various encodings

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Tue, 30 Jun 2026 17:20:43 UTC
The branch releng/14.3 has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=2329d94e49d3a2d6e4272513c4e6a794f82e6a20

commit 2329d94e49d3a2d6e4272513c4e6a794f82e6a20
Author:     Taylor R Campbell <riastradh@NetBSD.org>
AuthorDate: 2026-06-22 21:15:25 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-06-30 02:32:02 +0000

    iconv(3): Fix problems in various encodings
    
    Fix null pointer dereference with HZ8 encoding.
    
    Fix output buffer overrun in UTF-7, VIQR, ZW encodings.
    
    Approved by:    so
    Security:       FreeBSD-SA-26:49.iconv
    Security:       CVE-2026-58081
    Reviewed by:    markj, kevans
    Differential Revision:  https://reviews.freebsd.org/D57947
---
 lib/libiconv_modules/HZ/citrus_hz.c     |  2 ++
 lib/libiconv_modules/UTF7/citrus_utf7.c | 20 +++++++++++++++-----
 lib/libiconv_modules/VIQR/citrus_viqr.c |  6 +++++-
 lib/libiconv_modules/ZW/citrus_zw.c     |  7 +++++--
 4 files changed, 27 insertions(+), 8 deletions(-)

diff --git a/lib/libiconv_modules/HZ/citrus_hz.c b/lib/libiconv_modules/HZ/citrus_hz.c
index f12938a7dab8..db738b8f812e 100644
--- a/lib/libiconv_modules/HZ/citrus_hz.c
+++ b/lib/libiconv_modules/HZ/citrus_hz.c
@@ -262,6 +262,8 @@ _citrus_HZ_mbrtowc_priv(_HZEncodingInfo * __restrict ei,
 				tail = psenc->chlen = 0;
 				continue;
 			}
+			if (graphic == NULL)
+				break;
 		} else if (ch & 0x80) {
 			if (graphic != GR(psenc->inuse))
 				break;
diff --git a/lib/libiconv_modules/UTF7/citrus_utf7.c b/lib/libiconv_modules/UTF7/citrus_utf7.c
index f924366551f1..e7c6a0862c54 100644
--- a/lib/libiconv_modules/UTF7/citrus_utf7.c
+++ b/lib/libiconv_modules/UTF7/citrus_utf7.c
@@ -296,7 +296,7 @@ done:
 
 static int
 _citrus_UTF7_utf16tomb(_UTF7EncodingInfo * __restrict ei,
-    char * __restrict s, size_t n __unused, uint16_t u16,
+    char * __restrict s, size_t n, uint16_t u16,
     _UTF7State * __restrict psenc, size_t * __restrict nresult)
 {
 	int bits, i;
@@ -336,6 +336,8 @@ _citrus_UTF7_utf16tomb(_UTF7EncodingInfo * __restrict ei,
 			psenc->ch[psenc->chlen++] = base64[i];
 		}
 	}
+	if (n < (size_t)psenc->chlen)
+		return (E2BIG);
 	memcpy(s, psenc->ch, psenc->chlen);
 	*nresult = psenc->chlen;
 	psenc->chlen = 0;
@@ -352,6 +354,8 @@ _citrus_UTF7_wcrtomb_priv(_UTF7EncodingInfo * __restrict ei,
 	uint16_t u16[2];
 	int err, i, len;
 	size_t nr, siz;
+	char buf[8], *bufp = buf;
+	_UTF7State psenc_save = *psenc;
 
 	u32 = (uint32_t)wchar;
 	if (u32 <= UTF16_MAX) {
@@ -367,14 +371,20 @@ _citrus_UTF7_wcrtomb_priv(_UTF7EncodingInfo * __restrict ei,
 		return (EILSEQ);
 	}
 	siz = 0;
+	if (n > sizeof(buf))
+		n = sizeof(buf);
 	for (i = 0; i < len; ++i) {
-		err = _citrus_UTF7_utf16tomb(ei, s, n, u16[i], psenc, &nr);
-		if (err != 0)
-			return (err); /* XXX: state has been modified */
-		s += nr;
+		err = _citrus_UTF7_utf16tomb(ei, bufp, n, u16[i], psenc, &nr);
+		if (err != 0) {
+			*nresult = (size_t)-1;
+			*psenc = psenc_save; /* restore state */
+			return (err);
+		}
+		bufp += nr;
 		n -= nr;
 		siz += nr;
 	}
+	memcpy(s, buf, siz);
 	*nresult = siz;
 
 	return (0);
diff --git a/lib/libiconv_modules/VIQR/citrus_viqr.c b/lib/libiconv_modules/VIQR/citrus_viqr.c
index 1747c0a1705e..c1e88f188aab 100644
--- a/lib/libiconv_modules/VIQR/citrus_viqr.c
+++ b/lib/libiconv_modules/VIQR/citrus_viqr.c
@@ -323,7 +323,11 @@ _citrus_VIQR_wcrtomb_priv(_VIQREncodingInfo * __restrict ei,
 	int ch = 0;
 
 	switch (psenc->chlen) {
-	case 0: case 1:
+	case 0:
+		break;
+	case 1:
+		if (n-- < 1)
+			goto e2big;
 		break;
 	default:
 		return (EINVAL);
diff --git a/lib/libiconv_modules/ZW/citrus_zw.c b/lib/libiconv_modules/ZW/citrus_zw.c
index 93590e550952..c59064e65465 100644
--- a/lib/libiconv_modules/ZW/citrus_zw.c
+++ b/lib/libiconv_modules/ZW/citrus_zw.c
@@ -264,9 +264,12 @@ _citrus_ZW_wcrtomb_priv(_ZWEncodingInfo * __restrict ei __unused,
 		ch = (unsigned char)wc;
 		switch (psenc->charset) {
 		case NONE:
-			if (ch == '\0' || ch == '\n')
+			if (ch == '\0' || ch == '\n') {
+				if (n < 1)
+					return (E2BIG);
+				n -= 1;
 				psenc->ch[psenc->chlen++] = ch;
-			else {
+			} else {
 				if (n < 4)
 					return (E2BIG);
 				n -= 4;