Control: tags -1 + patch On Tue, May 19, 2020 at 07:30:53PM +0200, Salvatore Bonaccorso wrote: > Source: netqmail > Version: 1.06-6.1 > Severity: grave > Tags: security upstream > Justification: user security hole > Control: found -1 1.06-6 > Control: found -1 1.06-5 > > Hi > > See https://www.openwall.com/lists/oss-security/2020/05/19/8 for the > Qualys advisory covering CVE-2020-3811 and CVE-2020-3812.
debdiff based on the above attached. Salvatore
diff -u netqmail-1.06/debian/changelog netqmail-1.06/debian/changelog --- netqmail-1.06/debian/changelog +++ netqmail-1.06/debian/changelog @@ -1,3 +1,10 @@ +netqmail (1.06-6.2) unstable; urgency=high + + * Address CVE-2005-1513, CVE-2005-1514, CVE-2005-1515, CVE-2020-3811 and + CVE-2020-3812 (Closes: #961060) + + -- Salvatore Bonaccorso <car...@debian.org> Wed, 20 May 2020 22:23:21 +0200 + netqmail (1.06-6.1) unstable; urgency=medium * Non-maintainer upload. only in patch2: unchanged: --- netqmail-1.06.orig/debian/diff/0004-Remote-Code-Execution-in-qmail.diff +++ netqmail-1.06/debian/diff/0004-Remote-Code-Execution-in-qmail.diff @@ -0,0 +1,515 @@ +From e80dc4ad2b0ee51315e336253606c0effdd0f117 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory <q...@qualys.com> +Date: Tue, 19 May 2020 10:05:06 -0700 +Subject: [PATCH] Remote Code Execution in qmail (CVE-2005-1513) + +Qualys Security Advisory + +15 years later: Remote Code Execution in qmail (CVE-2005-1513) + +======================================================================== +Contents +======================================================================== + +Summary +Analysis +Exploitation +qmail-verify +- CVE-2020-3811 +- CVE-2020-3812 +Mitigations +Acknowledgments +Patches + +======================================================================== +Summary +======================================================================== + +TLDR: In 2005, three vulnerabilities were discovered in qmail but were +never fixed because they were believed to be unexploitable in a default +installation. We recently re-discovered these vulnerabilities and were +able to exploit one of them remotely in a default installation. + +------------------------------------------------------------------------ + +In May 2005, Georgi Guninski published "64 bit qmail fun", three +vulnerabilities in qmail (CVE-2005-1513, CVE-2005-1514, CVE-2005-1515): + + http://www.guninski.com/where_do_you_want_billg_to_go_today_4.html + +Surprisingly, we re-discovered these vulnerabilities during a recent +qmail audit; they have never been fixed because, as stated by qmail's +author Daniel J. Bernstein (in https://cr.yp.to/qmail/guarantee.html): + + "This claim is denied. Nobody gives gigabytes of memory to each + qmail-smtpd process, so there is no problem with qmail's assumption + that allocated array lengths fit comfortably into 32 bits." + +Indeed, the memory consumption of each qmail-smtpd process is severely +limited by default (by qmail-smtpd's startup script); for example, on +Debian 10 (the latest stable release), it is limited to roughly 7MB. + +Unfortunately, we discovered that these vulnerabilities also affect +qmail-local, which is reachable remotely and is not memory-limited by +default (we investigated many qmail packages, and *all* of them limit +qmail-smtpd's memory, but *none* of them limits qmail-local's memory). + +As a proof of concept, we developed a reliable, local and remote exploit +against Debian's qmail package in its default configuration. This proof +of concept requires 4GB of disk space and 8GB of memory, and allows an +attacker to execute arbitrary shell commands as any user, except root +(and a few system users who do not own their home directory). We will +publish our proof-of-concept exploit in the near future. + +About our new discovery, Daniel J. Bernstein issues the following +statement: + + "https://cr.yp.to/qmail/guarantee.html has for many years mentioned + qmail's assumption that allocated array lengths fit comfortably into + 32 bits. I run each qmail service under softlimit -m12345678, and I + recommend the same for other installations." + +Finally, we also discovered two minor vulnerabilities in qmail-verify (a +third-party qmail patch that is included in, for example, Debian's qmail +package): CVE-2020-3811 (a mail-address verification bypass), and +CVE-2020-3812 (a local information disclosure). + +======================================================================== +Analysis +======================================================================== + +We decided to exploit Georgi Guninski's vulnerability "1. integer +overflow in stralloc_readyplus" (CVE-2005-1513). There are, in fact, +four potential integer overflows in stralloc_readyplus; three in the +GEN_ALLOC_readyplus() macro (which generates the stralloc_readyplus() +function), at line 21 (n += x->len), line 23 (x->a = base + n + ...), +and line 24 (x->a * sizeof(type)): + +------------------------------------------------------------------------ + 17 #define GEN_ALLOC_readyplus(ta,type,field,len,a,i,n,x,base,ta_rplus) \ + 18 int ta_rplus(x,n) register ta *x; register unsigned int n; \ + 19 { register unsigned int i; \ + 20 if (x->field) { \ + 21 i = x->a; n += x->len; \ + 22 if (n > i) { \ + 23 x->a = base + n + (n >> 3); \ + 24 if (alloc_re(&x->field,i * sizeof(type),x->a * sizeof(type))) return 1; \ + 25 x->a = i; return 0; } \ + 26 return 1; } \ + 27 x->len = 0; \ + 28 return !!(x->field = (type *) alloc((x->a = n) * sizeof(type))); } +------------------------------------------------------------------------ + +and, in theory, one integer overflow in the alloc() function itself +(which is called by the alloc_re() function), at line 18: + +------------------------------------------------------------------------ + 14 /*@null@*//*@out@*/char *alloc(n) + 15 unsigned int n; + 16 { + 17 char *x; + 18 n = ALIGNMENT + n - (n & (ALIGNMENT - 1)); /* XXX: could overflow */ + .. + 20 x = malloc(n); + .. + 22 return x; + 23 } +------------------------------------------------------------------------ + +In practice, the integer overflows at line 21 (in GEN_ALLOC_readyplus()) +and line 18 (in alloc()) are very hard to trigger; and the one at line +24 (in GEN_ALLOC_readyplus()) is irrelevant to stralloc_readyplus's case +(because type is char and sizeof(type) is therefore 1). + +On the other hand, the integer overflow at line 23 (in +GEN_ALLOC_readyplus()) is easy to trigger, because the size x->a of the +buffer is increased by one eighth every time it is re-allocated: we send +a very large mail message that contains a very long header line (nearly +4GB), and this line triggers stralloc_readyplus's integer overflow while +in the getln() function, which is called by the bouncexf() function, at +the beginning of the qmail-local program. qmail-local is responsible for +the local delivery of mail messages, and runs with the privileges of the +local recipient (or qmail's "alias" user, if the local recipient is +"root", for example). + +After the size of the buffer is overflowed (at line 23), the alloc_re() +function is called (at line 24), but with n < m, where n is the size of +the new buffer y, and m is the size of the old buffer x: + +------------------------------------------------------------------------ + 4 int alloc_re(x,m,n) + 5 char **x; + 6 unsigned int m; + 7 unsigned int n; + 8 { + 9 char *y; + 10 + 11 y = alloc(n); + 12 if (!y) return 0; + 13 byte_copy(y,m,*x); + 14 alloc_free(*x); + 15 *x = y; + 16 return 1; + 17 } +------------------------------------------------------------------------ + +In other words, we transformed stralloc_readyplus's integer overflow +into an mmap-based buffer overflow at line 13 (byte_copy() is qmail's +version of memcpy()): m is nearly 4GB (the length of our very long +header line), but n is roughly 512MB (one eighth of m). + +======================================================================== +Exploitation +======================================================================== + +To survive this large buffer overflow, we carefully choose the number +and lengths of the very first lines in our mail message (they crucially +influence the sequence of buffer re-allocations that eventually lead to +the integer and buffer overflows), and obtain the following mmap layout: + +-------|-------|-------------------------------------------------|------ +XXXXXXX| y | x | libc +-------|-------|-------------------------------------------------|------ + | 512MB | 4GB | + +Consequently, we safely overflow the new buffer y, and overwrite the +malloc header of the old buffer x, with the contents of our very long +header line. To exploit this malloc-header corruption when free(x) is +called (at line 14), we devised an unusual method that bypasses NX and +ASLR, but does not work against a full-RELRO binary (but the qmail-local +binary on Debian 10 is partial-RELRO only). This does not mean, however, +that a full-RELRO binary is not exploitable: other methods may exist, +the only limit to malloc exploitation is the imagination. + +First, we overwrite the prev_size and size fields of x's malloc header, +we set its IS_MMAPPED bit to 1, and therefore enter the munmap_chunk() +function in __libc_free() (where p is a pointer to x's malloc header): + +------------------------------------------------------------------------ +2810 static void +2811 munmap_chunk (mchunkptr p) +2812 { +2813 INTERNAL_SIZE_T size = chunksize (p); +.... +2822 uintptr_t block = (uintptr_t) p - prev_size (p); +2823 size_t total_size = prev_size (p) + size; +.... +2838 __munmap ((char *) block, total_size); +2839 } +------------------------------------------------------------------------ + +Because we completely control the size field (at line 2813) and the +prev_size field (at lines 2822 and 2823), we completely control the +block address (relative to p, and hence x) and the total_size of the +__munmap() call (at line 2838). In other words, we can munmap() an +arbitrary mmap region, without knowing the ASLR; we munmap() roughly +576MB at the end of x, including the first few pages of the libc: + +-------|-------|-----------------------------------------|-------+-|---- +XXXXXXX| y | x |XXXXXXXXX|ibc +-------|-------|-----------------------------------------|-------+-|---- + +The first pages of the libc do not actually contain executable code: +they contain the ELF .dynsym section, which associates a symbol (for +example, the "open" function) with the address of this symbol (relative +to the start of the libc). + +Next, we end our very long header line (with a '\n' character), and +start a new header line of nearly 576MB. This new header line is first +written to the buffer y, but when y is full, stralloc_readyplus() +allocates a new buffer t of roughly 576MB (the size of y plus one +eighth), the exact size of the mmap region that we previously +munmap()ed: + +-------|-------|-----------------------------------------|-------+-|---- +XXXXXXX| y | x | t |ibc +-------|-------|-----------------------------------------|-------+-|---- + +Consequently, we completely control the first pages of the libc (they +contain the end of our new header line): we control the .dynsym section, +and we replace the address of the "open" function with the address of +the "system" function. This method works because Debian's qmail-local +binary is partial-RELRO only, and because the open() function has not +been called yet, and has therefore not been resolved yet. + +Last, we end our new header line, and when qmail-local returns from +bouncexf() and calls qmesearch() to open() the ".qmail-extension" file, +system(".qmail-extension") is called instead. Because we control this +"extension" (it is an extension of the local recipient's mail address, +for example localuser-extension@localdomain), we can execute arbitrary +shell commands as any user (except root, and a few system users who do +not own their home directory), by sending our large mail message to +"localuser-;command;@localdomain". + +Last-minute note: the exploitation of glibc's free() to munmap() +arbitrary memory regions has been discussed before, in +http://tukan.farm/2016/07/27/munmap-madness/. + +======================================================================== +qmail-verify +======================================================================== + +------------------------------------------------------------------------ +CVE-2020-3811 +------------------------------------------------------------------------ + +Although the original qmail-smtpd does accept our recipient address +"localuser-;command;@localdomain", Debian's qmail-smtpd should not, +because it validates the recipient address with an external program +qmail-verify (which should reject our recipient address, because the +file "~localuser/.qmail-;command;" does not exist). Unfortunately, +qmail-verify does reject "localuser-;command;@localdomain", but it +accepts the unqualified "localuser-;command;" (without the +@localdomain), because: + +- it never calls the control_init() function; + +- it therefore initializes its default domain to the hard-coded string + "envnoathost"; + +- and accepts any unqualified mail address as valid by default (because + its default domain "envnoathost" is not one of qmail's local domains, + and is therefore unverifiable). + +------------------------------------------------------------------------ +CVE-2020-3812 +------------------------------------------------------------------------ + +We also discovered a minor information disclosure in qmail-verify: +a local attacker can test for the existence of files and directories +anywhere in the filesystem (even in inaccessible directories), because +qmail-verify runs as root and tests for the existence of files in the +attacker's home directory, without dropping its privileges first. For +example (qmail-verify listens on 127.0.0.1:11113 by default): + +------------------------------------------------------------------------ +$ ls -l /root/.bashrc +ls: cannot access '/root/.bashrc': Permission denied + +$ rm -f ~john/.qmail-test +$ ln -s /root/.bashrc ~john/.qmail-test + +$ echo -n 'john-test@localdomain' | nc -w 2 -u 127.0.0.1 11113 | hexdump -C +00000000 a0 6a 6f 68 6e 2d 74 65 73 74 |.john-test| +------------------------------------------------------------------------ + +The least significant bit of this response's first byte (a0) is 0: the +file "/root/.bashrc" exists. + +------------------------------------------------------------------------ +$ ls -l /root/.abcdef +ls: cannot access '/root/.abcdef': Permission denied + +$ rm -f ~john/.qmail-test +$ ln -s /root/.abcdef ~john/.qmail-test + +$ echo -n 'john-test@localdomain' | nc -w 2 -u 127.0.0.1 11113 | hexdump -C +00000000 e1 6a 6f 68 6e 2d 74 65 73 74 |.john-test| +------------------------------------------------------------------------ + +The least significant bit of this response's first byte (e1) is 1: the +file "/root/.abcdef" does not exist. + +======================================================================== +Mitigations +======================================================================== + +As recommended by Daniel J. Bernstein, qmail can be protected against +all three 2005 CVEs by placing a low, configurable memory limit (a +"softlimit") in the startup scripts of all qmail services. + +Alternatively: + +qmail can be protected against the RCE (Remote Code Execution) by +configuring the file "control/databytes", which contains the maximum +size of a mail message (this file does not exist by default, and qmail +is therefore remotely exploitable in its default configuration). + +Unfortunately, this does not protect qmail against the LPE (Local +Privilege Escalation), because the file "control/databytes" is used +exclusively by qmail-smtpd. + +======================================================================== +Acknowledgments +======================================================================== + +We thank Andrew Richards, Alexander Peslyak, the members of +distros@openwall, and the developers of notqmail for their hard work on +this coordinated release. We also thank Daniel J. Bernstein, and Georgi +Guninski. Finally, we thank Julien Barthelemy, Stephane Bellenger, and +Jean-Paul Michel for their inspiring work. + +======================================================================== +Patches +======================================================================== + +We wrote a simple patch for Debian's qmail package (below) that fixes +CVE-2020-3811 and CVE-2020-3812 in qmail-verify, and fixes all three +2005 CVEs in qmail (by hard-coding a safe, upper memory limit in the +alloc() function). + +Alternatively: + +- an updated version of qmail-verify will be available at + https://free.acrconsulting.co.uk/email/qmail-verify.html after the + Coordinated Release Date; + +- the developers of notqmail (https://notqmail.org/) have written their + own patches for the three 2005 CVEs and have started to systematically + fix all integer overflows and signedness errors in qmail. + +------------------------------------------------------------------------ +--- +diff -r -u netqmail_1.06-6/alloc.c netqmail_1.06-6+patches/alloc.c +--- netqmail_1.06-6/alloc.c 1998-06-15 03:53:16.000000000 -0700 ++++ netqmail_1.06-6+patches/alloc.c 2020-05-04 16:43:32.923310325 -0700 +@@ -1,3 +1,4 @@ ++#include <limits.h> + #include "alloc.h" + #include "error.h" + extern char *malloc(); +@@ -15,6 +16,10 @@ + unsigned int n; + { + char *x; ++ if (n >= (INT_MAX >> 3)) { ++ errno = error_nomem; ++ return 0; ++ } + n = ALIGNMENT + n - (n & (ALIGNMENT - 1)); /* XXX: could overflow */ + if (n <= avail) { avail -= n; return space + avail; } + x = malloc(n); +diff -r -u netqmail_1.06-6/qmail-verify.c netqmail_1.06-6+patches/qmail-verify.c +--- netqmail_1.06-6/qmail-verify.c 2020-05-02 09:02:51.954415101 -0700 ++++ netqmail_1.06-6+patches/qmail-verify.c 2020-05-08 04:47:27.555539058 -0700 +@@ -16,6 +16,8 @@ + #include <sys/types.h> + #include <sys/stat.h> + #include <unistd.h> ++#include <limits.h> ++#include <grp.h> + #include <pwd.h> + #include <sys/socket.h> + #include <netinet/in.h> +@@ -38,6 +40,7 @@ + #include "ip.h" + #include "qmail-verify.h" + #include "errbits.h" ++#include "scan.h" + + #define enew() { eout("qmail-verify: "); } + #define GETPW_USERLEN 32 +@@ -71,6 +74,7 @@ + void die_comms() { enew(); eout("Misc. comms problem: exiting.\n"); eflush(); _exit(1); } + void die_inuse() { enew(); eout("Port already in use: exiting.\n"); eflush(); _exit(1); } + void die_socket() { enew(); eout("Error setting up socket: exiting.\n"); eflush(); _exit(1); } ++void die_privs() { enew(); eout("Unable to drop/restore privileges: exiting.\n"); eflush(); _exit(1); } + + char *posstr(buf,status) + char *buf; int status; +@@ -207,10 +211,47 @@ + return 0; + } + ++static int stat_as(uid, gid, path, sbuf) ++const uid_t uid; ++const gid_t gid; ++const char * const path; ++struct stat * const sbuf; ++{ ++ static gid_t groups[NGROUPS_MAX + 1]; ++ int ngroups = 0; ++ const gid_t saved_egid = getegid(); ++ const uid_t saved_euid = geteuid(); ++ int ret = -1; ++ ++ if (saved_euid == 0) { ++ ngroups = getgroups(sizeof(groups) / sizeof(groups[0]), groups); ++ if (ngroups < 0 || ++ setgroups(1, &gid) != 0 || ++ setegid(gid) != 0 || ++ seteuid(uid) != 0) { ++ die_privs(); ++ } ++ } ++ ++ ret = stat(path, sbuf); ++ ++ if (saved_euid == 0) { ++ if (seteuid(saved_euid) != 0 || ++ setegid(saved_egid) != 0 || ++ setgroups(ngroups, groups) != 0) { ++ die_privs(); ++ } ++ } ++ ++ return ret; ++} ++ + int verifyaddr(addr) + char *addr; + { + char *homedir; ++ uid_t uid = -1; ++ gid_t gid = -1; + /* static since they get re-used on each call to verifyaddr(). Note + that they don't need resetting since initial use is always with + stralloc_copys() except wildchars (reset with ...len=0 below). */ +@@ -303,6 +344,7 @@ + if (r == 1) + { + char *x; ++ unsigned long u; + if (!stralloc_ready(&nughde,(unsigned int) dlen)) die_nomem(); + nughde.len = dlen; + if (cdb_bread(fd,nughde.s,nughde.len) == -1) die_cdb(); +@@ -318,10 +360,14 @@ + if (x == nughde.s + nughde.len) return allowaddr(addr,ADDR_OK|QVPOS3); + ++x; + /* skip uid */ ++ scan_ulong(x,&u); ++ uid = u; + x += byte_chr(x,nughde.s + nughde.len - x,'\0'); + if (x == nughde.s + nughde.len) return allowaddr(addr,ADDR_OK|QVPOS4); + ++x; + /* skip gid */ ++ scan_ulong(x,&u); ++ gid = u; + x += byte_chr(x,nughde.s + nughde.len - x,'\0'); + if (x == nughde.s + nughde.len) return allowaddr(addr,ADDR_OK|QVPOS5); + ++x; +@@ -360,6 +406,8 @@ + if (!stralloc_copys(&nughde,pw->pw_dir)) die_nomem(); + if (!stralloc_0(&nughde)) die_nomem(); + homedir=nughde.s; ++ uid = pw->pw_uid; ++ gid = pw->pw_gid; + + got_nughde: + +@@ -380,7 +428,7 @@ + if (!stralloc_cat(&qme,&safeext)) die_nomem(); + if (!stralloc_0(&qme)) die_nomem(); + /* e.g. homedir/.qmail-localpart */ +- if (stat(qme.s,&st) == 0) return allowaddr(addr,ADDR_OK|QVPOS10); ++ if (stat_as(uid,gid,qme.s,&st) == 0) return allowaddr(addr,ADDR_OK|QVPOS10); + if (errno != error_noent) { + return stat_error(qme.s,errno, STATERR|QVPOS11); /* Maybe not running as root so access denied */ + } +@@ -394,7 +442,7 @@ + if (!stralloc_cats(&qme,"default")) die_nomem(); + if (!stralloc_0(&qme)) die_nomem(); + /* e.g. homedir/.qmail-[xxx-]default */ +- if (stat(qme.s,&st) == 0) { ++ if (stat_as(uid,gid,qme.s,&st) == 0) { + /* if it's ~alias/.qmail-default, optionally check aliases.cdb */ + if (!i && (quser == auto_usera)) { + char *s; +@@ -423,6 +471,7 @@ + char *s; + + if (chdir(auto_qmail) == -1) die_control(); ++ if (control_init() == -1) die_control(); + + if (control_rldef(&envnoathost,"control/envnoathost",1,"envnoathost") != 1) + die_control();