strcoll(1): Fix named locale case where conversion fails
authorJohn Marino <draco@marino.st>
Sun, 2 Aug 2015 11:19:16 +0000 (13:19 +0200)
committerJohn Marino <draco@marino.st>
Sun, 2 Aug 2015 11:28:42 +0000 (13:28 +0200)
When strcoll is used with a locale other than C/POSIX, it uses the
mbsrtowcs_l(1) function which can alter the src pointer upon failure.
If that happens in the current implementation of strcoll, a null pointer
is send to strcmp (resulting in wrong answer) and it will change the s
or s2 argument of strcoll(1), which is not behavior expected by the POSIX
standard.

Using a copy of the s and s2 arguments prevents this situation.  Note that
Illumos, which which the source came, still has this vulnerability.

Reported by: Romick (YRabbit) on IRC

lib/libc/string/strcoll.c

index 0e2f6fa..80fcf61 100644 (file)
@@ -50,9 +50,20 @@ strcoll_l(const char *s, const char *s2, locale_t locale)
        int ret;
        wchar_t *t1 = NULL, *t2 = NULL;
        wchar_t *w1 = NULL, *w2 = NULL;
+       const char *cs1, *cs2;
        mbstate_t mbs1 = { 0 };
        mbstate_t mbs2 = { 0 };
        size_t sz1, sz2;
+
+       /*
+        * The mbsrtowcs_l function can set the src pointer to null upon
+        * failure, so it should act on a copy to avoid:
+        *   - sending null pointer to strcmp
+        *   - having strcoll/strcoll_l change *s or *s2 to null
+        */
+       cs1 = s;
+       cs2 = s2;
+
        FIX_LOCALE(locale);
        struct xlocale_collate *table =
                (struct xlocale_collate*)locale->components[XLC_COLLATE];
@@ -75,10 +86,10 @@ strcoll_l(const char *s, const char *s2, locale_t locale)
                goto error;
        w2 = t2;
 
-       if ((mbsrtowcs_l(w1, &s, sz1, &mbs1, locale)) == (size_t)-1)
+       if ((mbsrtowcs_l(w1, &cs1, sz1, &mbs1, locale)) == (size_t)-1)
                goto error;
 
-       if ((mbsrtowcs_l(w2, &s2, sz2, &mbs2, locale)) == (size_t)-1)
+       if ((mbsrtowcs_l(w2, &cs2, sz2, &mbs2, locale)) == (size_t)-1)
                goto error;
 
        ret = wcscoll_l(w1, w2, locale);