Hi,

our catopen implementation does not check the parsed message catalog,
making it vulnerable to all sorts of out of boundary accesses.

Take this minimalistic proof of concept file:

$ printf '\xff\x88\xff\x89\x01\x00\x00\x00' > poc.cat

If you are too lazy to write code to open it yourself, take this one:

---poc.c---
#include <err.h>
#include <nl_types.h>
#include <stdio.h>

int
main(int argc, char *argv[])
{
        nl_catd cat;

        if (argc != 2) {
                fprintf(stderr, "usage: poc file.cat\n");
                return (1);
        }

        if ((cat = catopen(argv[1], 0)) == (nl_catd) -1)
                err(2, "catopen");
        printf("%s\n", catgets(cat, 1, 1, "default text"));
        catclose(cat);
        return (0);
}
---poc.c---

$ ./poc $PWD/poc.cat # yes, it takes an absolute path
Segmentation fault (core dumped)
$ _

I've added all sorts of checks, making sure that whatever offset and
index is inside the catalog is actually valid. Unfortunately it looks
rather messy, because I even have to check if there are negative
values in it -- it's all int32_t.

There are also cases in which catopen() returns -1 but does not set
errno properly. I took the glibc approach and set errno to EINVAL
whenever we encounter an invalid value.

Also, make sure that we directly ignore files which are too small or
too large.

Successfully passes the libc.cat files we have in base, so I'm rather
confident that there are no false positives.

Any advices to make this look nicer though? Or how to handle INT_MAX
and int32_t types? They are basically the same, can I trust that it's
true on all our archs?


Tobias

Index: lib/libc/nls/catopen.c
===================================================================
RCS file: /cvs/src/lib/libc/nls/catopen.c,v
retrieving revision 1.16
diff -u -p -u -p -r1.16 catopen.c
--- catopen.c   16 Jan 2015 16:48:51 -0000      1.16
+++ catopen.c   3 Sep 2015 20:48:07 -0000
@@ -30,20 +30,24 @@
 
 #define _NLS_PRIVATE
 
-#include <limits.h>
-#include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/mman.h>
-#include <unistd.h>
+#include <errno.h>
 #include <fcntl.h>
+#include <limits.h>
 #include <nl_types.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define MAXIMUM(a, b)  (((a) > (b)) ? (a) : (b))
 
 #define NLS_DEFAULT_PATH 
"/usr/share/nls/%L/%N.cat:/usr/share/nls/%l.%c/%N.cat:/usr/share/nls/%l/%N.cat"
 #define NLS_DEFAULT_LANG "C"
 
-static nl_catd load_msgcat(const char *);
+static nl_catd load_msgcat(const char *);
+static int     verify_msgcat(nl_catd);
 
 /* ARGSUSED */
 nl_catd
@@ -173,24 +177,106 @@ load_msgcat(const char *path)
                return (nl_catd) -1;
        }
 
+       if (st.st_size > INT_MAX || st.st_size < sizeof (struct _nls_cat_hdr)) {
+               errno = EINVAL;
+               close (fd);
+               return (nl_catd) -1;
+       }
+
        data = mmap(0, (size_t) st.st_size, PROT_READ, MAP_SHARED, fd, 
(off_t)0);
        close (fd);
 
-       if (data == MAP_FAILED) {
+       if (data == MAP_FAILED)
                return (nl_catd) -1;
-       }
 
-       if (ntohl(((struct _nls_cat_hdr *) data)->__magic) != _NLS_MAGIC) {
-               munmap(data, (size_t) st.st_size);
-               return (nl_catd) -1;
-       }
+       if (ntohl(((struct _nls_cat_hdr *) data)->__magic) != _NLS_MAGIC)
+               goto invalid;
 
-       if ((catd = malloc(sizeof (*catd))) == 0) {
-               munmap(data, (size_t) st.st_size);
-               return (nl_catd) -1;
-       }
+       if ((catd = malloc(sizeof (*catd))) == 0)
+               goto invalid;
 
        catd->__data = data;
        catd->__size = st.st_size;
+
+       if (verify_msgcat(catd)) {
+               free(catd);
+               goto invalid;
+       }
+
        return catd;
+
+invalid:
+       munmap(data, (size_t) st.st_size);
+       errno = EINVAL;
+       return (nl_catd) -1;
 }
+
+static int
+verify_msgcat(nl_catd catd)
+{
+       struct _nls_cat_hdr *cat;
+       struct _nls_set_hdr *set;
+       struct _nls_msg_hdr *msg;
+       size_t remain;
+       int i, j, msgs, off;
+
+       remain = catd->__size;
+       cat = (struct _nls_cat_hdr *) catd->__data;
+
+       /* catalog must contain at least one set and no negative offsets */
+       if (ntohl(cat->__nsets) < 1 ||
+           ntohl(cat->__msg_hdr_offset) < 0 ||
+           ntohl(cat->__msg_txt_offset) < 0)
+               return (1);
+
+       remain -= sizeof (*cat);
+
+       /* check if offsets or set size overflow */
+       if (remain <= ntohl(cat->__msg_hdr_offset) ||
+           remain <= ntohl(cat->__msg_txt_offset) ||
+           remain / sizeof (*set) < ntohl(cat->__nsets))
+               return (1);
+
+       set = (struct _nls_set_hdr *) ((char *) catd->__data + sizeof (*cat));
+
+       /* make sure that msg has space for at least one index */
+       if (remain - ntohl(cat->__msg_hdr_offset) < sizeof(*msg))
+               return (1);
+
+       msg = (struct _nls_msg_hdr *) ((char *) catd->__data + sizeof (*cat)
+           + ntohl(cat->__msg_hdr_offset));
+
+       /* validate and retrieve largest string offset from sets */
+       off = 0;
+       for (i = 0; i < ntohl(cat->__nsets); i++) {
+               /* set must contain at least one message */
+               if (ntohl(set[i].__index) < 0 ||
+                   ntohl(set[i].__nmsgs) < 1)
+                       return (1);
+
+               if (INT_MAX - ntohl(set[i].__nmsgs) < ntohl(set[i].__index))
+                       return (1);
+               msgs = ntohl(set[i].__index) + ntohl(set[i].__nmsgs);
+
+               /* avoid msg index overflow */
+               if (remain - ntohl(cat->__msg_hdr_offset) / sizeof(*msg) < msgs)
+                       return (1);
+
+               /* retrieve largest string offset */
+               for (j = ntohl(set[i].__index); j < ntohl(set[i].__nmsgs); j++) 
{
+                       if (ntohl(msg[j].__offset) < 0)
+                               return (1);
+                       off = MAXIMUM(off, ntohl(msg[j].__offset));
+               }
+       }
+
+       /* check if largest string offset is nul-terminated */
+       if (remain - ntohl(cat->__msg_txt_offset) < off ||
+           memchr((char *) catd->__data + sizeof(*cat) +
+           ntohl(cat->__msg_txt_offset) + off, '\0',
+           remain - ntohl(cat->__msg_txt_offset) - off) == NULL)
+               return (1);
+
+       return (0);
+}
+

Reply via email to