Fix for CVE-2012-6711 reported in bsc#1138676

---
 lib/sh/unicode.c |  140 +++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 100 insertions(+), 40 deletions(-)

--- lib/sh/unicode.c
+++ lib/sh/unicode.c	2019-11-08 13:49:17.538469163 +0000
@@ -1,6 +1,6 @@
 /* unicode.c - functions to convert unicode characters */
 
-/* Copyright (C) 2010 Free Software Foundation, Inc.
+/* Copyright (C) 2010-2012 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -61,6 +61,8 @@ static iconv_t localconv;
 #endif
 
 #ifndef HAVE_LOCALE_CHARSET
+static char charsetbuf[40];
+
 static char *
 stub_charset ()
 {
@@ -68,32 +70,32 @@ stub_charset ()
 
   locale = get_locale_var ("LC_CTYPE");
   if (locale == 0 || *locale == 0)
-    return "ASCII";
+    {
+      strcpy (charsetbuf, "ASCII");
+      return charsetbuf;
+    }
   s = strrchr (locale, '.');
   if (s)
     {
-      t = strchr (s, '@');
+      strcpy (charsetbuf, s+1);
+      t = strchr (charsetbuf, '@');
       if (t)
 	*t = 0;
-      return ++s;
+      return charsetbuf;
     }
-  else if (STREQ (locale, "UTF-8"))
-    return "UTF-8";
-  else
-    return "ASCII";
+  strcpy (charsetbuf, locale);
+  return charsetbuf;
 }
 #endif
 
 /* u32toascii ? */
 int
-u32tochar (wc, s)
-     wchar_t wc;
+u32tochar (x, s)
+     unsigned long x;
      char *s;
 {
-  unsigned long x;
   int l;
 
-  x = wc;
   l = (x <= UCHAR_MAX) ? 1 : ((x <= USHORT_MAX) ? 2 : 4);
 
   if (x <= UCHAR_MAX)
@@ -116,30 +118,91 @@ u32tochar (wc, s)
 
 int
 u32toutf8 (wc, s)
-     wchar_t wc;
+     u_bits32_t wc;
      char *s;
 {
   int l;
 
-  l = (wc < 0x0080) ? 1 : ((wc < 0x0800) ? 2 : 3);
-
   if (wc < 0x0080)
-    s[0] = (unsigned char)wc;
+    {
+      s[0] = (char)wc;
+      l = 1;
+    }
   else if (wc < 0x0800)
     {
       s[0] = (wc >> 6) | 0xc0;
       s[1] = (wc & 0x3f) | 0x80;
+      l = 2;
     }
-  else
+  else if (wc < 0x10000)
     {
+      /* Technically, we could return 0 here if 0xd800 <= wc <= 0x0dfff */
       s[0] = (wc >> 12) | 0xe0;
       s[1] = ((wc >> 6) & 0x3f) | 0x80;
       s[2] = (wc & 0x3f) | 0x80;
+      l = 3;
+    }
+  else if (wc < 0x200000)
+    {
+      s[0] = (wc >> 18) | 0xf0;
+      s[1] = ((wc >> 12) & 0x3f) | 0x80;
+      s[2] = ((wc >>  6) & 0x3f) | 0x80;
+      s[3] = (wc & 0x3f) | 0x80;
+      l = 4;
+    }
+  /* Strictly speaking, UTF-8 doesn't have characters longer than 4 bytes */
+  else if (wc < 0x04000000)
+    {
+      s[0] = (wc >> 24) | 0xf8;
+      s[1] = ((wc >> 18) & 0x3f) | 0x80;
+      s[2] = ((wc >> 12) & 0x3f) | 0x80;
+      s[3] = ((wc >>  6) & 0x3f) | 0x80;
+      s[4] = (wc & 0x3f) | 0x80;
+      l = 5;
     }
+  else if (wc < 0x080000000)
+    {
+      s[0] = (wc >> 30) | 0xf8;
+      s[1] = ((wc >> 24) & 0x3f) | 0x80;
+      s[2] = ((wc >> 18) & 0x3f) | 0x80;
+      s[3] = ((wc >> 12) & 0x3f) | 0x80;
+      s[4] = ((wc >>  6) & 0x3f) | 0x80;
+      s[5] = (wc & 0x3f) | 0x80;
+      l = 6;
+    }
+  else
+    l = 0;
+
   s[l] = '\0';
   return l;
 }
 
+/* Convert a 32-bit unsigned int (unicode) to a UTF-16 string.  Rarely used,
+   only if sizeof(wchar_t) == 2. */
+int
+u32toutf16 (c, s)
+     u_bits32_t c;
+     unsigned short *s;
+{
+  int l;
+
+  l = 0;
+  if (c < 0x0d800)
+    {
+      s[0] = (unsigned short) (c & 0xFFFF);
+      l = 1;
+    }
+  else if (c >= 0x0e000 && c <= 0x010ffff)
+    {
+      c -= 0x010000;
+      s[0] = (unsigned short)((c >> 10) + 0xd800);
+      s[1] = (unsigned short)((c & 0x3ff) + 0xdc00);
+      l = 2;
+    }
+  s[l] = 0;
+  return l;
+}
+
 /* convert a single unicode-32 character into a multibyte string and put the
    result in S, which must be large enough (at least MB_LEN_MAX bytes) */
 int
@@ -148,6 +211,7 @@ u32cconv (c, s)
      char *s;
 {
   wchar_t wc;
+  wchar_t ws[3];
   int n;
 #if HAVE_ICONV
   const char *charset;
@@ -157,21 +221,23 @@ u32cconv (c, s)
   size_t sn;
 #endif
 
-  wc = c;
-
 #if __STDC_ISO_10646__
-  if (sizeof (wchar_t) == 4)
-    {
-      n = wctomb (s, wc);
-      return n;
-    }
+  wc = c;
+  if (sizeof (wchar_t) == 4 && c <= 0x7fffffff)
+    n = wctomb (s, wc);
+  else if (sizeof (wchar_t) == 2 && c <= 0x10ffff && u32toutf16 (c, ws))
+    n = wcstombs (s, ws, MB_LEN_MAX);
+  else
+    n = -1;
+  if (n != -1)
+    return n;
 #endif
 
 #if HAVE_NL_LANGINFO
   codeset = nl_langinfo (CODESET);
   if (STREQ (codeset, "UTF-8"))
     {
-      n = u32toutf8 (wc, s);
+      n = u32toutf8 (c, s);
       return n;
     }
 #endif
@@ -196,19 +262,9 @@ u32cconv (c, s)
       u32init = 1;
     }
 
-  if (utf8locale)
-    {
-      n = u32toutf8 (wc, s);
-      return n;
-    }
-
-  if (localconv == (iconv_t)-1)
-    {
-      n = u32tochar (wc, s);
-      return n;
-    }
-
-  n = u32toutf8 (wc, s);
+  n = u32toutf8 (c, s);
+  if (utf8locale || localconv == (iconv_t)-1)
+    return n;
 
   optr = obuf;
   obytesleft = sizeof (obuf);
@@ -228,8 +284,12 @@ u32cconv (c, s)
   return (optr - obuf);
 #endif
 
-  n = u32tochar (wc, s);	/* fallback */
+  n = u32tochar (c, s);	/* fallback */
   return n;
 }
-
+#else
+void
+u32reset ()
+{
+}
 #endif /* HANDLE_MULTIBYTE */
