fix integer overflows leading to heap overflow (CVE-2018-1124 CVE-2018-1126)
This commit is contained in:
parent
d912675fa7
commit
cb90eff3ca
337
procps-ng-3.3.14-CVE-2018-1124.patch
Normal file
337
procps-ng-3.3.14-CVE-2018-1124.patch
Normal file
@ -0,0 +1,337 @@
|
||||
From 9d2ec74b76d220f5343e548fcb7058d723293a22 Mon Sep 17 00:00:00 2001
|
||||
From: Qualys Security Advisory <qsa@qualys.com>
|
||||
Date: Thu, 1 Jan 1970 00:00:00 +0000
|
||||
Subject: [PATCH 1/3] proc/alloc.*: Use size_t, not unsigned int.
|
||||
|
||||
Otherwise this can truncate sizes on 64-bit platforms, and is one of the
|
||||
reasons the integer overflows in file2strvec() are exploitable at all.
|
||||
Also: catch potential integer overflow in xstrdup() (should never
|
||||
happen, but better safe than sorry), and use memcpy() instead of
|
||||
strcpy() (faster).
|
||||
|
||||
Warnings:
|
||||
|
||||
- in glibc, realloc(ptr, 0) is equivalent to free(ptr), but not here,
|
||||
because of the ++size;
|
||||
|
||||
- here, xstrdup() can return NULL (if str is NULL), which goes against
|
||||
the idea of the xalloc wrappers.
|
||||
|
||||
We were tempted to call exit() or xerrx() in those cases, but decided
|
||||
against it, because it might break things in unexpected places; TODO?
|
||||
---
|
||||
proc/alloc.c | 20 ++++++++++++--------
|
||||
proc/alloc.h | 4 ++--
|
||||
2 files changed, 14 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/proc/alloc.c b/proc/alloc.c
|
||||
index 94af47f..1768d73 100644
|
||||
--- a/proc/alloc.c
|
||||
+++ b/proc/alloc.c
|
||||
@@ -37,14 +37,14 @@ static void xdefault_error(const char *restrict fmts, ...) {
|
||||
message_fn xalloc_err_handler = xdefault_error;
|
||||
|
||||
|
||||
-void *xcalloc(unsigned int size) {
|
||||
+void *xcalloc(size_t size) {
|
||||
void * p;
|
||||
|
||||
if (size == 0)
|
||||
++size;
|
||||
p = calloc(1, size);
|
||||
if (!p) {
|
||||
- xalloc_err_handler("%s failed to allocate %u bytes of memory", __func__, size);
|
||||
+ xalloc_err_handler("%s failed to allocate %zu bytes of memory", __func__, size);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return p;
|
||||
@@ -57,20 +57,20 @@ void *xmalloc(size_t size) {
|
||||
++size;
|
||||
p = malloc(size);
|
||||
if (!p) {
|
||||
- xalloc_err_handler("%s failed to allocate %zu bytes of memory", __func__, size);
|
||||
+ xalloc_err_handler("%s failed to allocate %zu bytes of memory", __func__, size);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return(p);
|
||||
}
|
||||
|
||||
-void *xrealloc(void *oldp, unsigned int size) {
|
||||
+void *xrealloc(void *oldp, size_t size) {
|
||||
void *p;
|
||||
|
||||
if (size == 0)
|
||||
++size;
|
||||
p = realloc(oldp, size);
|
||||
if (!p) {
|
||||
- xalloc_err_handler("%s failed to allocate %u bytes of memory", __func__, size);
|
||||
+ xalloc_err_handler("%s failed to allocate %zu bytes of memory", __func__, size);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return(p);
|
||||
@@ -80,13 +80,17 @@ char *xstrdup(const char *str) {
|
||||
char *p = NULL;
|
||||
|
||||
if (str) {
|
||||
- unsigned int size = strlen(str) + 1;
|
||||
+ size_t size = strlen(str) + 1;
|
||||
+ if (size < 1) {
|
||||
+ xalloc_err_handler("%s refused to allocate %zu bytes of memory", __func__, size);
|
||||
+ exit(EXIT_FAILURE);
|
||||
+ }
|
||||
p = malloc(size);
|
||||
if (!p) {
|
||||
- xalloc_err_handler("%s failed to allocate %u bytes of memory", __func__, size);
|
||||
+ xalloc_err_handler("%s failed to allocate %zu bytes of memory", __func__, size);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
- strcpy(p, str);
|
||||
+ memcpy(p, str, size);
|
||||
}
|
||||
return(p);
|
||||
}
|
||||
diff --git a/proc/alloc.h b/proc/alloc.h
|
||||
index 19c91d7..6787a72 100644
|
||||
--- a/proc/alloc.h
|
||||
+++ b/proc/alloc.h
|
||||
@@ -10,9 +10,9 @@ typedef void (*message_fn)(const char *__restrict, ...) __attribute__((format(pr
|
||||
/* change xalloc_err_handler to override the default fprintf(stderr... */
|
||||
extern message_fn xalloc_err_handler;
|
||||
|
||||
-extern void *xcalloc(unsigned int size) MALLOC;
|
||||
+extern void *xcalloc(size_t size) MALLOC;
|
||||
extern void *xmalloc(size_t size) MALLOC;
|
||||
-extern void *xrealloc(void *oldp, unsigned int size) MALLOC;
|
||||
+extern void *xrealloc(void *oldp, size_t size) MALLOC;
|
||||
extern char *xstrdup(const char *str) MALLOC;
|
||||
|
||||
EXTERN_C_END
|
||||
--
|
||||
2.14.3
|
||||
|
||||
|
||||
From de660b14b80188d9b323c4999d1b91a9456ed687 Mon Sep 17 00:00:00 2001
|
||||
From: Qualys Security Advisory <qsa@qualys.com>
|
||||
Date: Thu, 1 Jan 1970 00:00:00 +0000
|
||||
Subject: [PATCH 2/3] proc/readproc.c: Harden file2str().
|
||||
|
||||
1/ Replace sprintf() with snprintf() (and check for truncation).
|
||||
|
||||
2/ Prevent an integer overflow of ub->siz. The "tot_read--" is needed to
|
||||
avoid an off-by-one overflow in "ub->buf[tot_read] = '\0'". It is safe
|
||||
to decrement tot_read here, because we know that tot_read is equal to
|
||||
ub->siz (and ub->siz is very large).
|
||||
|
||||
We believe that truncation is a better option than failure (implementing
|
||||
failure instead should be as easy as replacing the "tot_read--" with
|
||||
"tot_read = 0").
|
||||
---
|
||||
proc/readproc.c | 10 ++++++++--
|
||||
1 file changed, 8 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/proc/readproc.c b/proc/readproc.c
|
||||
index 9e3afc9..39235c7 100644
|
||||
--- a/proc/readproc.c
|
||||
+++ b/proc/readproc.c
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
+#include <limits.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#ifdef WITH_SYSTEMD
|
||||
@@ -635,7 +636,7 @@ static void statm2proc(const char* s, proc_t *restrict P) {
|
||||
static int file2str(const char *directory, const char *what, struct utlbuf_s *ub) {
|
||||
#define buffGRW 1024
|
||||
char path[PROCPATHLEN];
|
||||
- int fd, num, tot_read = 0;
|
||||
+ int fd, num, tot_read = 0, len;
|
||||
|
||||
/* on first use we preallocate a buffer of minimum size to emulate
|
||||
former 'local static' behavior -- even if this read fails, that
|
||||
@@ -643,11 +644,16 @@ static int file2str(const char *directory, const char *what, struct utlbuf_s *ub
|
||||
( besides, with this xcalloc we will never need to use memcpy ) */
|
||||
if (ub->buf) ub->buf[0] = '\0';
|
||||
else ub->buf = xcalloc((ub->siz = buffGRW));
|
||||
- sprintf(path, "%s/%s", directory, what);
|
||||
+ len = snprintf(path, sizeof path, "%s/%s", directory, what);
|
||||
+ if (len <= 0 || (size_t)len >= sizeof path) return -1;
|
||||
if (-1 == (fd = open(path, O_RDONLY, 0))) return -1;
|
||||
while (0 < (num = read(fd, ub->buf + tot_read, ub->siz - tot_read))) {
|
||||
tot_read += num;
|
||||
if (tot_read < ub->siz) break;
|
||||
+ if (ub->siz >= INT_MAX - buffGRW) {
|
||||
+ tot_read--;
|
||||
+ break;
|
||||
+ }
|
||||
ub->buf = xrealloc(ub->buf, (ub->siz += buffGRW));
|
||||
};
|
||||
ub->buf[tot_read] = '\0';
|
||||
--
|
||||
2.14.3
|
||||
|
||||
|
||||
From 44a4b658f45bc3fbd7170662a52038a7b35c83de Mon Sep 17 00:00:00 2001
|
||||
From: Qualys Security Advisory <qsa@qualys.com>
|
||||
Date: Thu, 1 Jan 1970 00:00:00 +0000
|
||||
Subject: [PATCH 3/3] proc/readproc.c: Fix bugs and overflows in file2strvec().
|
||||
|
||||
Note: this is by far the most important and complex patch of the whole
|
||||
series, please review it carefully; thank you very much!
|
||||
|
||||
For this patch, we decided to keep the original function's design and
|
||||
skeleton, to avoid regressions and behavior changes, while fixing the
|
||||
various bugs and overflows. And like the "Harden file2str()" patch, this
|
||||
patch does not fail when about to overflow, but truncates instead: there
|
||||
is information available about this process, so return it to the caller;
|
||||
also, we used INT_MAX as a limit, but a lower limit could be used.
|
||||
|
||||
The easy changes:
|
||||
|
||||
- Replace sprintf() with snprintf() (and check for truncation).
|
||||
|
||||
- Replace "if (n == 0 && rbuf == 0)" with "if (n <= 0 && tot <= 0)" and
|
||||
do break instead of return: it simplifies the code (only one place to
|
||||
handle errors), and also guarantees that in the while loop either n or
|
||||
tot is > 0 (or both), even if n is reset to 0 when about to overflow.
|
||||
|
||||
- Remove the "if (n < 0)" block in the while loop: it is (and was) dead
|
||||
code, since we enter the while loop only if n >= 0.
|
||||
|
||||
- Rewrite the missing-null-terminator detection: in the original
|
||||
function, if the size of the file is a multiple of 2047, a null-
|
||||
terminator is appended even if the file is already null-terminated.
|
||||
|
||||
- Replace "if (n <= 0 && !end_of_file)" with "if (n < 0 || tot <= 0)":
|
||||
originally, it was equivalent to "if (n < 0)", but we added "tot <= 0"
|
||||
to handle the first break of the while loop, and to guarantee that in
|
||||
the rest of the function tot is > 0.
|
||||
|
||||
- Double-force ("belt and suspenders") the null-termination of rbuf:
|
||||
this is (and was) essential to the correctness of the function.
|
||||
|
||||
- Replace the final "while" loop with a "for" loop that behaves just
|
||||
like the preceding "for" loop: in the original function, this would
|
||||
lead to unexpected results (for example, if rbuf is |\0|A|\0|, this
|
||||
would return the array {"",NULL} but should return {"","A",NULL}; and
|
||||
if rbuf is |A|\0|B| (should never happen because rbuf should be null-
|
||||
terminated), this would make room for two pointers in ret, but would
|
||||
write three pointers to ret).
|
||||
|
||||
The hard changes:
|
||||
|
||||
- Prevent the integer overflow of tot in the while loop, but unlike
|
||||
file2str(), file2strvec() cannot let tot grow until it almost reaches
|
||||
INT_MAX, because it needs more space for the pointers: this is why we
|
||||
introduced ARG_LEN, which also guarantees that we can add "align" and
|
||||
a few sizeof(char*)s to tot without overflowing.
|
||||
|
||||
- Prevent the integer overflow of "tot + c + align": when INT_MAX is
|
||||
(almost) reached, we write the maximal safe amount of pointers to ret
|
||||
(ARG_LEN guarantees that there is always space for *ret = rbuf and the
|
||||
NULL terminator).
|
||||
---
|
||||
proc/readproc.c | 53 ++++++++++++++++++++++++++++++++---------------------
|
||||
1 file changed, 32 insertions(+), 21 deletions(-)
|
||||
|
||||
diff --git a/proc/readproc.c b/proc/readproc.c
|
||||
index 39235c7..94ca4e9 100644
|
||||
--- a/proc/readproc.c
|
||||
+++ b/proc/readproc.c
|
||||
@@ -665,11 +665,12 @@ static int file2str(const char *directory, const char *what, struct utlbuf_s *ub
|
||||
|
||||
static char** file2strvec(const char* directory, const char* what) {
|
||||
char buf[2048]; /* read buf bytes at a time */
|
||||
- char *p, *rbuf = 0, *endbuf, **q, **ret;
|
||||
+ char *p, *rbuf = 0, *endbuf, **q, **ret, *strp;
|
||||
int fd, tot = 0, n, c, end_of_file = 0;
|
||||
int align;
|
||||
|
||||
- sprintf(buf, "%s/%s", directory, what);
|
||||
+ const int len = snprintf(buf, sizeof buf, "%s/%s", directory, what);
|
||||
+ if(len <= 0 || (size_t)len >= sizeof buf) return NULL;
|
||||
fd = open(buf, O_RDONLY, 0);
|
||||
if(fd==-1) return NULL;
|
||||
|
||||
@@ -677,18 +678,23 @@ static char** file2strvec(const char* directory, const char* what) {
|
||||
while ((n = read(fd, buf, sizeof buf - 1)) >= 0) {
|
||||
if (n < (int)(sizeof buf - 1))
|
||||
end_of_file = 1;
|
||||
- if (n == 0 && rbuf == 0) {
|
||||
- close(fd);
|
||||
- return NULL; /* process died between our open and read */
|
||||
+ if (n <= 0 && tot <= 0) { /* nothing read now, nothing read before */
|
||||
+ break; /* process died between our open and read */
|
||||
}
|
||||
- if (n < 0) {
|
||||
- if (rbuf)
|
||||
- free(rbuf);
|
||||
- close(fd);
|
||||
- return NULL; /* read error */
|
||||
+ /* ARG_LEN is our guesstimated median length of a command-line argument
|
||||
+ or environment variable (the minimum is 1, the maximum is 131072) */
|
||||
+ #define ARG_LEN 64
|
||||
+ if (tot >= INT_MAX / (ARG_LEN + (int)sizeof(char*)) * ARG_LEN - n) {
|
||||
+ end_of_file = 1; /* integer overflow: null-terminate and break */
|
||||
+ n = 0; /* but tot > 0 */
|
||||
}
|
||||
- if (end_of_file && (n == 0 || buf[n-1]))/* last read char not null */
|
||||
+ #undef ARG_LEN
|
||||
+ if (end_of_file &&
|
||||
+ ((n > 0 && buf[n-1] != '\0') || /* last read char not null */
|
||||
+ (n <= 0 && rbuf[tot-1] != '\0'))) /* last read char not null */
|
||||
buf[n++] = '\0'; /* so append null-terminator */
|
||||
+
|
||||
+ if (n <= 0) break; /* unneeded (end_of_file = 1) but avoid realloc */
|
||||
rbuf = xrealloc(rbuf, tot + n); /* allocate more memory */
|
||||
memcpy(rbuf + tot, buf, n); /* copy buffer into it */
|
||||
tot += n; /* increment total byte ctr */
|
||||
@@ -696,29 +702,34 @@ static char** file2strvec(const char* directory, const char* what) {
|
||||
break;
|
||||
}
|
||||
close(fd);
|
||||
- if (n <= 0 && !end_of_file) {
|
||||
+ if (n < 0 || tot <= 0) { /* error, or nothing read */
|
||||
if (rbuf) free(rbuf);
|
||||
return NULL; /* read error */
|
||||
}
|
||||
+ rbuf[tot-1] = '\0'; /* belt and suspenders (the while loop did it, too) */
|
||||
endbuf = rbuf + tot; /* count space for pointers */
|
||||
align = (sizeof(char*)-1) - ((tot + sizeof(char*)-1) & (sizeof(char*)-1));
|
||||
- for (c = 0, p = rbuf; p < endbuf; p++) {
|
||||
- if (!*p || *p == '\n')
|
||||
+ c = sizeof(char*); /* one extra for NULL term */
|
||||
+ for (p = rbuf; p < endbuf; p++) {
|
||||
+ if (!*p || *p == '\n') {
|
||||
+ if (c >= INT_MAX - (tot + (int)sizeof(char*) + align)) break;
|
||||
c += sizeof(char*);
|
||||
+ }
|
||||
if (*p == '\n')
|
||||
*p = 0;
|
||||
}
|
||||
- c += sizeof(char*); /* one extra for NULL term */
|
||||
|
||||
rbuf = xrealloc(rbuf, tot + c + align); /* make room for ptrs AT END */
|
||||
endbuf = rbuf + tot; /* addr just past data buf */
|
||||
q = ret = (char**) (endbuf+align); /* ==> free(*ret) to dealloc */
|
||||
- *q++ = p = rbuf; /* point ptrs to the strings */
|
||||
- endbuf--; /* do not traverse final NUL */
|
||||
- while (++p < endbuf)
|
||||
- if (!*p) /* NUL char implies that */
|
||||
- *q++ = p+1; /* next string -> next char */
|
||||
-
|
||||
+ for (strp = p = rbuf; p < endbuf; p++) {
|
||||
+ if (!*p) { /* NUL char implies that */
|
||||
+ if (c < 2 * (int)sizeof(char*)) break;
|
||||
+ c -= sizeof(char*);
|
||||
+ *q++ = strp; /* point ptrs to the strings */
|
||||
+ strp = p+1; /* next string -> next char */
|
||||
+ }
|
||||
+ }
|
||||
*q = 0; /* null ptr list terminator */
|
||||
return ret;
|
||||
}
|
||||
--
|
||||
2.14.3
|
||||
|
@ -4,7 +4,7 @@
|
||||
Summary: System and process monitoring utilities
|
||||
Name: procps-ng
|
||||
Version: 3.3.14
|
||||
Release: 1%{?dist}
|
||||
Release: 2%{?dist}
|
||||
License: GPL+ and GPLv2 and GPLv2+ and GPLv3+ and LGPLv2+
|
||||
Group: Applications/System
|
||||
URL: https://sourceforge.net/projects/procps-ng/
|
||||
@ -16,6 +16,9 @@ Source1: README.md
|
||||
# wget https://gitlab.com/procps-ng/procps/raw/e0784ddaed30d095bb1d9a8ad6b5a23d10a212c4/top/README.top
|
||||
Source2: README.top
|
||||
|
||||
# fix integer overflows leading to heap overflow (CVE-2018-1124 CVE-2018-1126)
|
||||
Patch1: procps-ng-3.3.14-CVE-2018-1124.patch
|
||||
|
||||
BuildRequires: ncurses-devel
|
||||
BuildRequires: libtool
|
||||
BuildRequires: autoconf
|
||||
@ -89,6 +92,7 @@ Internationalization pack for procps-ng
|
||||
%setup -q -n %{name}-%{version}
|
||||
cp -p %{SOURCE1} .
|
||||
cp -p %{SOURCE2} top/
|
||||
%patch1 -p1
|
||||
|
||||
%build
|
||||
# The following stuff is needed for git archives only
|
||||
@ -161,6 +165,9 @@ ln -s %{_bindir}/pidof %{buildroot}%{_sbindir}/pidof
|
||||
%files i18n -f %{name}.lang
|
||||
|
||||
%changelog
|
||||
* Fri May 18 2018 Kamil Dudka <kdudka@redhat.com> - 3.3.14-2
|
||||
- fix integer overflows leading to heap overflow (CVE-2018-1124 CVE-2018-1126)
|
||||
|
||||
* Mon Apr 16 2018 Jan Rybar <jrybar@redhat.com> - 3.3.14-1
|
||||
- Rebase to 3.3.14
|
||||
- Translated man-pages returned
|
||||
|
Loading…
Reference in New Issue
Block a user