Verify that opened message catalog is valid, i.e. avoid integer overflows
authortobias <tobias@openbsd.org>
Fri, 23 Oct 2015 18:49:07 +0000 (18:49 +0000)
committertobias <tobias@openbsd.org>
Fri, 23 Oct 2015 18:49:07 +0000 (18:49 +0000)
and out of boundary accesses.

with input by miod, ok stsp

lib/libc/nls/catopen.c

index e3ba472..3c47d09 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: catopen.c,v 1.17 2015/09/05 11:25:30 guenther Exp $ */
+/*     $OpenBSD: catopen.c,v 1.18 2015/10/23 18:49:07 tobias Exp $ */
 /*-
  * Copyright (c) 1996 The NetBSD Foundation, Inc.
  * All rights reserved.
 
 #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);
 
 nl_catd
 catopen(const char *name, int oflag)
@@ -165,6 +169,8 @@ load_msgcat(const char *path)
        void *data;
        int fd;
 
+       catd = NULL;
+
        if ((fd = open(path, O_RDONLY|O_CLOEXEC)) == -1)
                return (nl_catd) -1;
 
@@ -173,24 +179,106 @@ load_msgcat(const char *path)
                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 (st.st_size > INT_MAX || st.st_size < sizeof (struct _nls_cat_hdr)) {
+               errno = EINVAL;
+               close (fd);
                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;
-       }
+       data = mmap(0, (size_t)st.st_size, PROT_READ, MAP_SHARED, fd, (off_t)0);
+       close (fd);
 
-       if ((catd = malloc(sizeof (*catd))) == 0) {
-               munmap(data, (size_t) st.st_size);
+       if (data == MAP_FAILED)
                return (nl_catd) -1;
-       }
+
+       if (ntohl(((struct _nls_cat_hdr *) data)->__magic) != _NLS_MAGIC)
+               goto invalid;
+
+       if ((catd = malloc(sizeof (*catd))) == 0)
+               goto invalid;
 
        catd->__data = data;
        catd->__size = st.st_size;
+
+       if (verify_msgcat(catd))
+               goto invalid;
+
        return catd;
+
+invalid:
+       free(catd);
+       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 hdr_offset, i, index, j, msgs, nmsgs, nsets, off, txt_offset;
+
+       remain = catd->__size;
+       cat = (struct _nls_cat_hdr *) catd->__data;
+
+       hdr_offset = ntohl(cat->__msg_hdr_offset);
+       nsets = ntohl(cat->__nsets);
+       txt_offset = ntohl(cat->__msg_txt_offset);
+
+       /* catalog must contain at least one set and no negative offsets */
+       if (nsets < 1 || hdr_offset < 0 || txt_offset < 0)
+               return (1);
+
+       remain -= sizeof (*cat);
+
+       /* check if offsets or set size overflow */
+       if (remain <= hdr_offset || remain <= ntohl(cat->__msg_txt_offset) ||
+           remain / sizeof (*set) < 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 - hdr_offset < sizeof(*msg))
+               return (1);
+
+       msg = (struct _nls_msg_hdr *) ((char *) catd->__data + sizeof (*cat)
+           + hdr_offset);
+
+       /* validate and retrieve largest string offset from sets */
+       off = 0;
+       for (i = 0; i < nsets; i++) {
+               index = ntohl(set[i].__index);
+               nmsgs = ntohl(set[i].__nmsgs);
+               /* set must contain at least one message */
+               if (index < 0 || nmsgs < 1)
+                       return (1);
+
+               if (INT_MAX - nmsgs < index)
+                       return (1);
+               msgs = index + nmsgs;
+
+               /* avoid msg index overflow */
+               if ((remain - hdr_offset) / sizeof(*msg) < msgs)
+                       return (1);
+
+               /* retrieve largest string offset */
+               for (j = index; j < 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 - txt_offset < off ||
+           memchr((char *) catd->__data + sizeof(*cat) + txt_offset + off,
+           '\0', remain - txt_offset - off) == NULL)
+               return (1);
+
+       return (0);
+}
+