6909f1
From 9d2ec74b76d220f5343e548fcb7058d723293a22 Mon Sep 17 00:00:00 2001
6909f1
From: Qualys Security Advisory <qsa@qualys.com>
6909f1
Date: Thu, 1 Jan 1970 00:00:00 +0000
6909f1
Subject: [PATCH 1/3] proc/alloc.*: Use size_t, not unsigned int.
6909f1
6909f1
Otherwise this can truncate sizes on 64-bit platforms, and is one of the
6909f1
reasons the integer overflows in file2strvec() are exploitable at all.
6909f1
Also: catch potential integer overflow in xstrdup() (should never
6909f1
happen, but better safe than sorry), and use memcpy() instead of
6909f1
strcpy() (faster).
6909f1
6909f1
Warnings:
6909f1
6909f1
- in glibc, realloc(ptr, 0) is equivalent to free(ptr), but not here,
6909f1
  because of the ++size;
6909f1
6909f1
- here, xstrdup() can return NULL (if str is NULL), which goes against
6909f1
  the idea of the xalloc wrappers.
6909f1
6909f1
We were tempted to call exit() or xerrx() in those cases, but decided
6909f1
against it, because it might break things in unexpected places; TODO?
6909f1
---
6909f1
 proc/alloc.c | 20 ++++++++++++--------
6909f1
 proc/alloc.h |  4 ++--
6909f1
 2 files changed, 14 insertions(+), 10 deletions(-)
6909f1
6909f1
diff --git a/proc/alloc.c b/proc/alloc.c
6909f1
index 94af47f..1768d73 100644
6909f1
--- a/proc/alloc.c
6909f1
+++ b/proc/alloc.c
6909f1
@@ -37,14 +37,14 @@ static void xdefault_error(const char *restrict fmts, ...) {
6909f1
 message_fn xalloc_err_handler = xdefault_error;
6909f1
 
6909f1
 
6909f1
-void *xcalloc(unsigned int size) {
6909f1
+void *xcalloc(size_t size) {
6909f1
     void * p;
6909f1
 
6909f1
     if (size == 0)
6909f1
         ++size;
6909f1
     p = calloc(1, size);
6909f1
     if (!p) {
6909f1
-        xalloc_err_handler("%s failed to allocate %u bytes of memory", __func__, size);
6909f1
+        xalloc_err_handler("%s failed to allocate %zu bytes of memory", __func__, size);
6909f1
         exit(EXIT_FAILURE);
6909f1
     }
6909f1
     return p;
6909f1
@@ -57,20 +57,20 @@ void *xmalloc(size_t size) {
6909f1
         ++size;
6909f1
     p = malloc(size);
6909f1
     if (!p) {
6909f1
-	xalloc_err_handler("%s failed to allocate %zu bytes of memory", __func__, size);
6909f1
+        xalloc_err_handler("%s failed to allocate %zu bytes of memory", __func__, size);
6909f1
         exit(EXIT_FAILURE);
6909f1
     }
6909f1
     return(p);
6909f1
 }
6909f1
 
6909f1
-void *xrealloc(void *oldp, unsigned int size) {
6909f1
+void *xrealloc(void *oldp, size_t size) {
6909f1
     void *p;
6909f1
 
6909f1
     if (size == 0)
6909f1
         ++size;
6909f1
     p = realloc(oldp, size);
6909f1
     if (!p) {
6909f1
-        xalloc_err_handler("%s failed to allocate %u bytes of memory", __func__, size);
6909f1
+        xalloc_err_handler("%s failed to allocate %zu bytes of memory", __func__, size);
6909f1
         exit(EXIT_FAILURE);
6909f1
     }
6909f1
     return(p);
6909f1
@@ -80,13 +80,17 @@ char *xstrdup(const char *str) {
6909f1
     char *p = NULL;
6909f1
 
6909f1
     if (str) {
6909f1
-        unsigned int size = strlen(str) + 1;
6909f1
+        size_t size = strlen(str) + 1;
6909f1
+        if (size < 1) {
6909f1
+            xalloc_err_handler("%s refused to allocate %zu bytes of memory", __func__, size);
6909f1
+            exit(EXIT_FAILURE);
6909f1
+        }
6909f1
         p = malloc(size);
6909f1
         if (!p) {
6909f1
-            xalloc_err_handler("%s failed to allocate %u bytes of memory", __func__, size);
6909f1
+            xalloc_err_handler("%s failed to allocate %zu bytes of memory", __func__, size);
6909f1
             exit(EXIT_FAILURE);
6909f1
         }
6909f1
-        strcpy(p, str);
6909f1
+        memcpy(p, str, size);
6909f1
     }
6909f1
     return(p);
6909f1
 }
6909f1
diff --git a/proc/alloc.h b/proc/alloc.h
6909f1
index 19c91d7..6787a72 100644
6909f1
--- a/proc/alloc.h
6909f1
+++ b/proc/alloc.h
6909f1
@@ -8,9 +8,9 @@ EXTERN_C_BEGIN
6909f1
  /* change xalloc_err_handler to override the default fprintf(stderr... */
6909f1
 extern message_fn xalloc_err_handler;
6909f1
 
6909f1
-extern void *xcalloc(unsigned int size) MALLOC;
6909f1
+extern void *xcalloc(size_t size) MALLOC;
6909f1
 extern void *xmalloc(size_t size) MALLOC;
6909f1
-extern void *xrealloc(void *oldp, unsigned int size) MALLOC;
6909f1
+extern void *xrealloc(void *oldp, size_t size) MALLOC;
6909f1
 extern char *xstrdup(const char *str) MALLOC;
6909f1
 
6909f1
 EXTERN_C_END
6909f1
-- 
6909f1
2.14.3
6909f1
6909f1
6909f1
From de660b14b80188d9b323c4999d1b91a9456ed687 Mon Sep 17 00:00:00 2001
6909f1
From: Qualys Security Advisory <qsa@qualys.com>
6909f1
Date: Thu, 1 Jan 1970 00:00:00 +0000
6909f1
Subject: [PATCH 2/3] proc/readproc.c: Harden file2str().
6909f1
6909f1
1/ Replace sprintf() with snprintf() (and check for truncation).
6909f1
6909f1
2/ Prevent an integer overflow of ub->siz. The "tot_read--" is needed to
6909f1
avoid an off-by-one overflow in "ub->buf[tot_read] = '\0'". It is safe
6909f1
to decrement tot_read here, because we know that tot_read is equal to
6909f1
ub->siz (and ub->siz is very large).
6909f1
6909f1
We believe that truncation is a better option than failure (implementing
6909f1
failure instead should be as easy as replacing the "tot_read--" with
6909f1
"tot_read = 0").
6909f1
---
6909f1
 proc/readproc.c | 10 ++++++++--
6909f1
 1 file changed, 8 insertions(+), 2 deletions(-)
6909f1
6909f1
diff --git a/proc/readproc.c b/proc/readproc.c
6909f1
index 9e3afc9..39235c7 100644
6909f1
--- a/proc/readproc.c
6909f1
+++ b/proc/readproc.c
6909f1
@@ -35,6 +35,7 @@
6909f1
 #include <signal.h>
6909f1
 #include <fcntl.h>
6909f1
 #include <dirent.h>
6909f1
+#include <limits.h>
6909f1
 #include <sys/types.h>
6909f1
 #include <sys/stat.h>
6909f1
 #ifdef WITH_SYSTEMD
6909f1
@@ -622,7 +623,7 @@ static void statm2proc(const char* s, proc_t *restrict P) {
6909f1
 static int file2str(const char *directory, const char *what, struct utlbuf_s *ub) {
6909f1
  #define buffGRW 1024
6909f1
     char path[PROCPATHLEN];
6909f1
-    int fd, num, tot_read = 0;
6909f1
+    int fd, num, tot_read = 0, len;
6909f1
 
6909f1
     /* on first use we preallocate a buffer of minimum size to emulate
6909f1
        former 'local static' behavior -- even if this read fails, that
6909f1
@@ -630,11 +631,16 @@ static int file2str(const char *directory, const char *what, struct utlbuf_s *ub
6909f1
        ( besides, with this xcalloc we will never need to use memcpy ) */
6909f1
     if (ub->buf) ub->buf[0] = '\0';
6909f1
     else ub->buf = xcalloc((ub->siz = buffGRW));
6909f1
-    sprintf(path, "%s/%s", directory, what);
6909f1
+    len = snprintf(path, sizeof path, "%s/%s", directory, what);
6909f1
+    if (len <= 0 || (size_t)len >= sizeof path) return -1;
6909f1
     if (-1 == (fd = open(path, O_RDONLY, 0))) return -1;
6909f1
     while (0 < (num = read(fd, ub->buf + tot_read, ub->siz - tot_read))) {
6909f1
         tot_read += num;
6909f1
         if (tot_read < ub->siz) break;
6909f1
+        if (ub->siz >= INT_MAX - buffGRW) {
6909f1
+            tot_read--;
6909f1
+            break;
6909f1
+        }
6909f1
         ub->buf = xrealloc(ub->buf, (ub->siz += buffGRW));
6909f1
     };
6909f1
     ub->buf[tot_read] = '\0';
6909f1
-- 
6909f1
2.14.3
6909f1
6909f1
6909f1
From 44a4b658f45bc3fbd7170662a52038a7b35c83de Mon Sep 17 00:00:00 2001
6909f1
From: Qualys Security Advisory <qsa@qualys.com>
6909f1
Date: Thu, 1 Jan 1970 00:00:00 +0000
6909f1
Subject: [PATCH 3/3] proc/readproc.c: Fix bugs and overflows in file2strvec().
6909f1
6909f1
Note: this is by far the most important and complex patch of the whole
6909f1
series, please review it carefully; thank you very much!
6909f1
6909f1
For this patch, we decided to keep the original function's design and
6909f1
skeleton, to avoid regressions and behavior changes, while fixing the
6909f1
various bugs and overflows. And like the "Harden file2str()" patch, this
6909f1
patch does not fail when about to overflow, but truncates instead: there
6909f1
is information available about this process, so return it to the caller;
6909f1
also, we used INT_MAX as a limit, but a lower limit could be used.
6909f1
6909f1
The easy changes:
6909f1
6909f1
- Replace sprintf() with snprintf() (and check for truncation).
6909f1
6909f1
- Replace "if (n == 0 && rbuf == 0)" with "if (n <= 0 && tot <= 0)" and
6909f1
  do break instead of return: it simplifies the code (only one place to
6909f1
  handle errors), and also guarantees that in the while loop either n or
6909f1
  tot is > 0 (or both), even if n is reset to 0 when about to overflow.
6909f1
6909f1
- Remove the "if (n < 0)" block in the while loop: it is (and was) dead
6909f1
  code, since we enter the while loop only if n >= 0.
6909f1
6909f1
- Rewrite the missing-null-terminator detection: in the original
6909f1
  function, if the size of the file is a multiple of 2047, a null-
6909f1
  terminator is appended even if the file is already null-terminated.
6909f1
6909f1
- Replace "if (n <= 0 && !end_of_file)" with "if (n < 0 || tot <= 0)":
6909f1
  originally, it was equivalent to "if (n < 0)", but we added "tot <= 0"
6909f1
  to handle the first break of the while loop, and to guarantee that in
6909f1
  the rest of the function tot is > 0.
6909f1
6909f1
- Double-force ("belt and suspenders") the null-termination of rbuf:
6909f1
  this is (and was) essential to the correctness of the function.
6909f1
6909f1
- Replace the final "while" loop with a "for" loop that behaves just
6909f1
  like the preceding "for" loop: in the original function, this would
6909f1
  lead to unexpected results (for example, if rbuf is |\0|A|\0|, this
6909f1
  would return the array {"",NULL} but should return {"","A",NULL}; and
6909f1
  if rbuf is |A|\0|B| (should never happen because rbuf should be null-
6909f1
  terminated), this would make room for two pointers in ret, but would
6909f1
  write three pointers to ret).
6909f1
6909f1
The hard changes:
6909f1
6909f1
- Prevent the integer overflow of tot in the while loop, but unlike
6909f1
  file2str(), file2strvec() cannot let tot grow until it almost reaches
6909f1
  INT_MAX, because it needs more space for the pointers: this is why we
6909f1
  introduced ARG_LEN, which also guarantees that we can add "align" and
6909f1
  a few sizeof(char*)s to tot without overflowing.
6909f1
6909f1
- Prevent the integer overflow of "tot + c + align": when INT_MAX is
6909f1
  (almost) reached, we write the maximal safe amount of pointers to ret
6909f1
  (ARG_LEN guarantees that there is always space for *ret = rbuf and the
6909f1
  NULL terminator).
6909f1
---
6909f1
 proc/readproc.c | 53 ++++++++++++++++++++++++++++++++---------------------
6909f1
 1 file changed, 32 insertions(+), 21 deletions(-)
6909f1
6909f1
diff --git a/proc/readproc.c b/proc/readproc.c
6909f1
index 39235c7..94ca4e9 100644
6909f1
--- a/proc/readproc.c
6909f1
+++ b/proc/readproc.c
6909f1
@@ -652,11 +652,12 @@ static int file2str(const char *directory, const char *what, struct utlbuf_s *ub
6909f1
 
6909f1
 static char** file2strvec(const char* directory, const char* what) {
6909f1
     char buf[2048];	/* read buf bytes at a time */
6909f1
-    char *p, *rbuf = 0, *endbuf, **q, **ret;
6909f1
+    char *p, *rbuf = 0, *endbuf, **q, **ret, *strp;
6909f1
     int fd, tot = 0, n, c, end_of_file = 0;
6909f1
     int align;
6909f1
 
6909f1
-    sprintf(buf, "%s/%s", directory, what);
6909f1
+    const int len = snprintf(buf, sizeof buf, "%s/%s", directory, what);
6909f1
+    if(len <= 0 || (size_t)len >= sizeof buf) return NULL;
6909f1
     fd = open(buf, O_RDONLY, 0);
6909f1
     if(fd==-1) return NULL;
6909f1
 
6909f1
@@ -664,18 +665,23 @@ static char** file2strvec(const char* directory, const char* what) {
6909f1
     while ((n = read(fd, buf, sizeof buf - 1)) >= 0) {
6909f1
 	if (n < (int)(sizeof buf - 1))
6909f1
 	    end_of_file = 1;
6909f1
-	if (n == 0 && rbuf == 0) {
6909f1
-	    close(fd);
6909f1
-	    return NULL;	/* process died between our open and read */
6909f1
+	if (n <= 0 && tot <= 0) { /* nothing read now, nothing read before */
6909f1
+	    break;		/* process died between our open and read */
6909f1
 	}
6909f1
-	if (n < 0) {
6909f1
-	    if (rbuf)
6909f1
-		free(rbuf);
6909f1
-	    close(fd);
6909f1
-	    return NULL;	/* read error */
6909f1
+	/* ARG_LEN is our guesstimated median length of a command-line argument
6909f1
+	   or environment variable (the minimum is 1, the maximum is 131072) */
6909f1
+	#define ARG_LEN 64
6909f1
+	if (tot >= INT_MAX / (ARG_LEN + (int)sizeof(char*)) * ARG_LEN - n) {
6909f1
+	    end_of_file = 1; /* integer overflow: null-terminate and break */
6909f1
+	    n = 0; /* but tot > 0 */
6909f1
 	}
6909f1
-	if (end_of_file && (n == 0 || buf[n-1]))/* last read char not null */
6909f1
+	#undef ARG_LEN
6909f1
+	if (end_of_file &&
6909f1
+	    ((n > 0 && buf[n-1] != '\0') ||	/* last read char not null */
6909f1
+	     (n <= 0 && rbuf[tot-1] != '\0')))	/* last read char not null */
6909f1
 	    buf[n++] = '\0';			/* so append null-terminator */
6909f1
+
6909f1
+	if (n <= 0) break; /* unneeded (end_of_file = 1) but avoid realloc */
6909f1
 	rbuf = xrealloc(rbuf, tot + n);		/* allocate more memory */
6909f1
 	memcpy(rbuf + tot, buf, n);		/* copy buffer into it */
6909f1
 	tot += n;				/* increment total byte ctr */
6909f1
@@ -683,29 +689,34 @@ static char** file2strvec(const char* directory, const char* what) {
6909f1
 	    break;
6909f1
     }
6909f1
     close(fd);
6909f1
-    if (n <= 0 && !end_of_file) {
6909f1
+    if (n < 0 || tot <= 0) {	/* error, or nothing read */
6909f1
 	if (rbuf) free(rbuf);
6909f1
 	return NULL;		/* read error */
6909f1
     }
6909f1
+    rbuf[tot-1] = '\0'; /* belt and suspenders (the while loop did it, too) */
6909f1
     endbuf = rbuf + tot;			/* count space for pointers */
6909f1
     align = (sizeof(char*)-1) - ((tot + sizeof(char*)-1) & (sizeof(char*)-1));
6909f1
-    for (c = 0, p = rbuf; p < endbuf; p++) {
6909f1
-	if (!*p || *p == '\n')
6909f1
+    c = sizeof(char*);				/* one extra for NULL term */
6909f1
+    for (p = rbuf; p < endbuf; p++) {
6909f1
+	if (!*p || *p == '\n') {
6909f1
+	    if (c >= INT_MAX - (tot + (int)sizeof(char*) + align)) break;
6909f1
 	    c += sizeof(char*);
6909f1
+	}
6909f1
 	if (*p == '\n')
6909f1
 	    *p = 0;
6909f1
     }
6909f1
-    c += sizeof(char*);				/* one extra for NULL term */
6909f1
 
6909f1
     rbuf = xrealloc(rbuf, tot + c + align);	/* make room for ptrs AT END */
6909f1
     endbuf = rbuf + tot;			/* addr just past data buf */
6909f1
     q = ret = (char**) (endbuf+align);		/* ==> free(*ret) to dealloc */
6909f1
-    *q++ = p = rbuf;				/* point ptrs to the strings */
6909f1
-    endbuf--;					/* do not traverse final NUL */
6909f1
-    while (++p < endbuf)
6909f1
-    	if (!*p)				/* NUL char implies that */
6909f1
-	    *q++ = p+1;				/* next string -> next char */
6909f1
-
6909f1
+    for (strp = p = rbuf; p < endbuf; p++) {
6909f1
+	if (!*p) {				/* NUL char implies that */
6909f1
+	    if (c < 2 * (int)sizeof(char*)) break;
6909f1
+	    c -= sizeof(char*);
6909f1
+	    *q++ = strp;			/* point ptrs to the strings */
6909f1
+	    strp = p+1;				/* next string -> next char */
6909f1
+	}
6909f1
+    }
6909f1
     *q = 0;					/* null ptr list terminator */
6909f1
     return ret;
6909f1
 }
6909f1
-- 
6909f1
2.14.3
6909f1