The mem_iconveh functions is documented to return an offsets[] array:

   If OFFSETS is not NULL, it should point to an array of SRCLEN integers; this
   array is filled with offsets into the result, i.e. the character starting
   at SRC[i] corresponds to the character starting at (*RESULTP)[OFFSETS[i]],
   and other offsets are set to (size_t)(-1).

In a couple of cases, OFFSETS[i] can point to the end of the result string:
  - When the input ends in an invalid character and handler == iconveh_error,
  - When the input is in a stateful encoding and ends in a shift sequence,
  - When the input ends in one or more Unicode LANGUAGE TAG characters.

None of these leads to a bug in libunistring. But it's worth fixing anyway.


2026-08-16  Bruno Haible  <[email protected]>

        striconveh: Ensure that none of the offsets[] are out-of-range.
        Reported by AISLE via Mike Fabian <[email protected]>.
        * lib/striconveh.c (mem_cd_iconveh_internal): Ensure that no offsets[i]
        is == length.
        * tests/test-striconveh.c (main): Adjust expected contents of the
        offsets[] array.
        * lib/striconveh.c (main): Likewise.
        * tests/test-striconveh.c (main): Likewise.
        * tests/test-striconveha.c (main): Likewise.
        * tests/uniconv/test-u8-conv-from-enc.c (main): Likewise.
        * tests/uniconv/test-u16-conv-from-enc.c (main): Likewise.
        * tests/uniconv/test-u32-conv-from-enc.c (main): Likewise.
        * tests/uniconv/test-u8-conv-to-enc.c (main): Likewise.
        * tests/uniconv/test-u16-conv-to-enc.c (main): Likewise.

        striconveh tests: Add some more test cases.
        * tests/test-striconveh.c (main): Add some more test cases for
        mem_iconveh() with offsets.

From e42d012275045cfa68c857fa648ab94c653fea0e Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sun, 16 Aug 2026 14:34:27 +0200
Subject: [PATCH 1/2] striconveh tests: Add some more test cases.

* tests/test-striconveh.c (main): Add some more test cases for
mem_iconveh() with offsets.
---
 ChangeLog               |  6 +++
 tests/test-striconveh.c | 83 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 89 insertions(+)

diff --git a/ChangeLog b/ChangeLog
index b2e2619314..a82a816666 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2026-08-16  Bruno Haible  <[email protected]>
+
+	striconveh tests: Add some more test cases.
+	* tests/test-striconveh.c (main): Add some more test cases for
+	mem_iconveh() with offsets.
+
 2026-08-16  Bruno Haible  <[email protected]>
 
 	malloc-posix: Fix file list (regression 2026-07-27).
diff --git a/tests/test-striconveh.c b/tests/test-striconveh.c
index ebb0a775c0..d4c45383ad 100644
--- a/tests/test-striconveh.c
+++ b/tests/test-striconveh.c
@@ -1367,6 +1367,89 @@ main ()
         }
     }
 
+# if (defined _LIBICONV_VERSION && !(_LIBICONV_VERSION == 0x10b && defined __APPLE__)) || (defined __GLIBC__ && !defined __UCLIBC__)
+  /* Test conversion from ISO-2022-JP-2 to UTF-8 with no errors,
+     but a trailing shift sequence.  */
+  {
+    static const char input[] = "Sch" "\033$(D+S\033(B" "ner Gru" "\033$(D)N\033(B"; /* "Schöner Gruß" */
+    static const char expected[] = "Sch\303\266ner Gru\303\237";
+    size_t *offsets = new_offsets (strlen (input));
+    char *result = NULL;
+    size_t length = 0;
+    int retval = mem_iconveh (input, strlen (input),
+                              "ISO-2022-JP-2", "UTF-8",
+                              iconveh_question_mark,
+                              offsets,
+                              &result, &length);
+    ASSERT (retval == 0);
+    ASSERT (length == strlen (expected));
+    ASSERT (result != NULL && memeq (result, expected, strlen (expected)));
+    for (size_t i = 0; i < 28; i++)
+      ASSERT (offsets[i] == (i <= 3 ? i :
+                             i == 9 ? 5 :
+                             i >= 13 && i <= 19 ? i - 7 :
+                             i == 25 ? 14 :
+                             (size_t)(-1)));
+    ASSERT (offsets[28] == MAGIC);
+    free (offsets);
+    free (result);
+  }
+
+  /* Test conversion from UTF-8 to ISO-2022-JP-2 with no errors,
+     but a trailing shift sequence.  */
+  {
+    static const char input[] = "Sch\303\266ner Gru\303\237"; /* "Schöner Gruß" */
+    static const char expected[] = "Sch" "\033$(D+S\033(B" "ner Gru" "\033$(D)N\033(B";
+    size_t *offsets = new_offsets (strlen (input));
+    char *result = NULL;
+    size_t length = 0;
+    int retval = mem_iconveh (input, strlen (input),
+                              "UTF-8", "ISO-2022-JP-2",
+                              iconveh_question_mark,
+                              offsets,
+                              &result, &length);
+    ASSERT (retval == 0);
+    ASSERT (length == strlen (expected));
+    ASSERT (result != NULL && memeq (result, expected, strlen (expected)));
+    for (size_t i = 0; i < 14; i++)
+      ASSERT (offsets[i] == (i <= 3 ? i :
+                             i == 5 ? 9 :
+                             i >= 6 && i <= 12 ? i + 7 :
+                             (size_t)(-1)));
+    ASSERT (offsets[14] == MAGIC);
+    free (offsets);
+    free (result);
+  }
+# endif
+
+# if (defined __GLIBC__ && !defined __UCLIBC__)
+  /* Test conversion from UTF-8 to ASCII with no errors,
+     but some discarded characters.  */
+  {
+    static const char input[] = "a\363\240\201\270bc\363\240\201\246\363\240\201\262"; /* "a<U+E0078>bc<U+E0066><U+E0072>" */
+    static const char expected[] = "abc";
+    size_t *offsets = new_offsets (strlen (input));
+    char *result = NULL;
+    size_t length = 0;
+    int retval = mem_iconveh (input, strlen (input),
+                              "UTF-8", "ASCII",
+                              iconveh_question_mark,
+                              offsets,
+                              &result, &length);
+    ASSERT (retval == 0);
+    ASSERT (length == strlen (expected));
+    ASSERT (result != NULL && memeq (result, expected, strlen (expected)));
+    for (size_t i = 0; i < 15; i++)
+      ASSERT (offsets[i] == (i <= 1 ? i :
+                             i == 6 ? 2 :
+                             i == 7 ? 3 :
+                             (size_t)(-1)));
+    ASSERT (offsets[15] == MAGIC);
+    free (offsets);
+    free (result);
+  }
+# endif
+
   /* ------------------------- Test str_iconveh() ------------------------- */
 
   /* Test conversion from ISO-8859-2 to ISO-8859-1 with no errors.  */
-- 
2.53.0

>From 5f1137d6ba2f64dd9c1cf2eb05f0cac3172f210b Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sun, 16 Aug 2026 15:21:13 +0200
Subject: [PATCH 2/2] striconveh: Ensure that none of the offsets[] are
 out-of-range.

Reported by AISLE via Mike Fabian <[email protected]>.

* lib/striconveh.c (mem_cd_iconveh_internal): Ensure that no offsets[i]
is == length.
* tests/test-striconveh.c (main): Adjust expected contents of the
offsets[] array.
* lib/striconveh.c (main): Likewise.
* tests/test-striconveh.c (main): Likewise.
* tests/test-striconveha.c (main): Likewise.
* tests/uniconv/test-u8-conv-from-enc.c (main): Likewise.
* tests/uniconv/test-u16-conv-from-enc.c (main): Likewise.
* tests/uniconv/test-u32-conv-from-enc.c (main): Likewise.
* tests/uniconv/test-u8-conv-to-enc.c (main): Likewise.
* tests/uniconv/test-u16-conv-to-enc.c (main): Likewise.
---
 ChangeLog                              | 15 +++++++++++++++
 lib/striconveh.c                       | 17 +++++++++++++++++
 tests/test-striconveh.c                |  6 ++----
 tests/test-striconveha.c               |  3 +--
 tests/uniconv/test-u16-conv-from-enc.c |  1 -
 tests/uniconv/test-u16-conv-to-enc.c   |  2 +-
 tests/uniconv/test-u32-conv-from-enc.c |  1 -
 tests/uniconv/test-u8-conv-from-enc.c  |  1 -
 tests/uniconv/test-u8-conv-to-enc.c    |  2 +-
 9 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index a82a816666..f4efbc514b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,20 @@
 2026-08-16  Bruno Haible  <[email protected]>
 
+	striconveh: Ensure that none of the offsets[] are out-of-range.
+	Reported by AISLE via Mike Fabian <[email protected]>.
+	* lib/striconveh.c (mem_cd_iconveh_internal): Ensure that no offsets[i]
+	is == length.
+	* tests/test-striconveh.c (main): Adjust expected contents of the
+	offsets[] array.
+	* lib/striconveh.c (main): Likewise.
+	* tests/test-striconveh.c (main): Likewise.
+	* tests/test-striconveha.c (main): Likewise.
+	* tests/uniconv/test-u8-conv-from-enc.c (main): Likewise.
+	* tests/uniconv/test-u16-conv-from-enc.c (main): Likewise.
+	* tests/uniconv/test-u32-conv-from-enc.c (main): Likewise.
+	* tests/uniconv/test-u8-conv-to-enc.c (main): Likewise.
+	* tests/uniconv/test-u16-conv-to-enc.c (main): Likewise.
+
 	striconveh tests: Add some more test cases.
 	* tests/test-striconveh.c (main): Add some more test cases for
 	mem_iconveh() with offsets.
diff --git a/lib/striconveh.c b/lib/striconveh.c
index b345923d4e..cac2ce2d57 100644
--- a/lib/striconveh.c
+++ b/lib/striconveh.c
@@ -976,6 +976,23 @@ mem_cd_iconveh_internal (const char *src, size_t srclen,
   }
 
  done:
+  if (offsets != NULL)
+    {
+      /* Ensure that the last assigned offsets[i] is < length.
+         (We know that the sequence of assigned offsets[i] is strictly
+         monotonically increasing.)  */
+      for (size_t i = srclen; i > 0; )
+        {
+          i--;
+          if (offsets[i] != (size_t)(-1))
+            {
+              if (offsets[i] == length)
+                offsets[i] = (size_t)(-1);
+              break;
+            }
+        }
+    }
+
   /* Now the final memory allocation.  */
   if (result == tmpbuf)
     {
diff --git a/tests/test-striconveh.c b/tests/test-striconveh.c
index d4c45383ad..71d0871e44 100644
--- a/tests/test-striconveh.c
+++ b/tests/test-striconveh.c
@@ -606,7 +606,7 @@ main ()
           ASSERT (length == 0);
           if (o)
             {
-              ASSERT (offsets[0] == 0);
+              ASSERT (offsets[0] == (size_t)(-1));
               ASSERT (offsets[1] == MAGIC);
               free (offsets);
             }
@@ -1359,7 +1359,7 @@ main ()
           ASSERT (length == 0);
           if (o)
             {
-              ASSERT (offsets[0] == 0);
+              ASSERT (offsets[0] == (size_t)(-1));
               ASSERT (offsets[1] == MAGIC);
               free (offsets);
             }
@@ -1388,7 +1388,6 @@ main ()
       ASSERT (offsets[i] == (i <= 3 ? i :
                              i == 9 ? 5 :
                              i >= 13 && i <= 19 ? i - 7 :
-                             i == 25 ? 14 :
                              (size_t)(-1)));
     ASSERT (offsets[28] == MAGIC);
     free (offsets);
@@ -1442,7 +1441,6 @@ main ()
     for (size_t i = 0; i < 15; i++)
       ASSERT (offsets[i] == (i <= 1 ? i :
                              i == 6 ? 2 :
-                             i == 7 ? 3 :
                              (size_t)(-1)));
     ASSERT (offsets[15] == MAGIC);
     free (offsets);
diff --git a/tests/test-striconveha.c b/tests/test-striconveha.c
index 52d8434b15..1626db9204 100644
--- a/tests/test-striconveha.c
+++ b/tests/test-striconveha.c
@@ -297,7 +297,7 @@ main ()
           ASSERT (length == 0);
           if (o)
             {
-              ASSERT (offsets[0] == 0);
+              ASSERT (offsets[0] == (size_t)(-1));
               ASSERT (offsets[1] == MAGIC);
               free (offsets);
             }
@@ -393,7 +393,6 @@ main ()
                                            i == 7 ? 6 :
                                            i == 9 ? 9 :
                                            i == 11 ? 12 :
-                                           i == 13 ? 15 :
                                            (size_t)(-1)));
                   ASSERT (offsets[16] == MAGIC);
                   free (offsets);
diff --git a/tests/uniconv/test-u16-conv-from-enc.c b/tests/uniconv/test-u16-conv-from-enc.c
index ef19ed98ec..49ffc8bbba 100644
--- a/tests/uniconv/test-u16-conv-from-enc.c
+++ b/tests/uniconv/test-u16-conv-from-enc.c
@@ -205,7 +205,6 @@ main ()
                                            i == 7 ? 2 :
                                            i == 9 ? 3 :
                                            i == 11 ? 4 :
-                                           i == 13 ? 5 :
                                            (size_t)(-1)));
                   ASSERT (offsets[16] == MAGIC);
                   free (offsets);
diff --git a/tests/uniconv/test-u16-conv-to-enc.c b/tests/uniconv/test-u16-conv-to-enc.c
index 5a5abd5e8d..15700da2c9 100644
--- a/tests/uniconv/test-u16-conv-to-enc.c
+++ b/tests/uniconv/test-u16-conv-to-enc.c
@@ -162,7 +162,7 @@ main ()
           ASSERT (length == strlen (""));
           if (o)
             {
-              ASSERT (offsets[0] == 0);
+              ASSERT (offsets[0] == (size_t)(-1));
               ASSERT (offsets[1] == MAGIC);
               free (offsets);
             }
diff --git a/tests/uniconv/test-u32-conv-from-enc.c b/tests/uniconv/test-u32-conv-from-enc.c
index 597005dad6..70c8005a2a 100644
--- a/tests/uniconv/test-u32-conv-from-enc.c
+++ b/tests/uniconv/test-u32-conv-from-enc.c
@@ -205,7 +205,6 @@ main ()
                                            i == 7 ? 2 :
                                            i == 9 ? 3 :
                                            i == 11 ? 4 :
-                                           i == 13 ? 5 :
                                            (size_t)(-1)));
                   ASSERT (offsets[16] == MAGIC);
                   free (offsets);
diff --git a/tests/uniconv/test-u8-conv-from-enc.c b/tests/uniconv/test-u8-conv-from-enc.c
index c3f679bacb..cd98f82564 100644
--- a/tests/uniconv/test-u8-conv-from-enc.c
+++ b/tests/uniconv/test-u8-conv-from-enc.c
@@ -191,7 +191,6 @@ main ()
                                            i == 7 ? 6 :
                                            i == 9 ? 9 :
                                            i == 11 ? 12 :
-                                           i == 13 ? 15 :
                                            (size_t)(-1)));
                   ASSERT (offsets[16] == MAGIC);
                   free (offsets);
diff --git a/tests/uniconv/test-u8-conv-to-enc.c b/tests/uniconv/test-u8-conv-to-enc.c
index 362a280531..f02d9181e9 100644
--- a/tests/uniconv/test-u8-conv-to-enc.c
+++ b/tests/uniconv/test-u8-conv-to-enc.c
@@ -166,7 +166,7 @@ main ()
           ASSERT (length == strlen (""));
           if (o)
             {
-              ASSERT (offsets[0] == 0);
+              ASSERT (offsets[0] == (size_t)(-1));
               ASSERT (offsets[1] == MAGIC);
               free (offsets);
             }
-- 
2.53.0

Reply via email to