Blame SOURCES/procps-ng-3.3.10-CVE-2018-1124.patch

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