[PATCH v2] nslookup: fix out-of-bounds reads in TXT and SOA record parsing

Ali Ahmet Memis via busybox <[email protected]>
Newsgroups gmane.linux.busybox
Message-ID <[email protected]>
The TXT record handler reads the character-string length byte and
copies that many bytes without checking it against the record's
RDLENGTH. A reply with rdlen==1 and a length byte of up to 255 makes
memcpy() read past the record (and past the reply buffer); the data is
then printed, disclosing adjacent memory.

The SOA handler checks that RDLENGTH is at least 20, the size of the
five fixed 32-bit fields, but reads those fields at an offset that
depends on the two domain names preceding them. ns_name_uncompress()
bounds a name by the end of the message, not by the end of RDATA, so
the names can consume the space the check accounted for. A record with
rdlen==20 whose names are two compression pointers already reads four
bytes past RDATA; with longer names the read runs past the end of the
512-byte reply buffer. All five fields are printed.

What leaks is the stack around that buffer in send_queries(), so this
is not limited to data disclosure. In testing, a libc code pointer was
recovered at the same offset in 5 of 7 runs, and PIE text pointers turn
up as well, which gives a remote attacker an ASLR bypass rather than
just a read past the end of a record.

Clamp the TXT length to the bytes actually present in RDATA, and
recheck the remaining RDLENGTH in SOA once both names are consumed.

   text    data     bss     dec     hex filename
  20376    3936       0   24312    5ef8 networking/nslookup.o before
  20490    3936       0   24426    5f6a networking/nslookup.o after
                           +114

Signed-off-by: Ali Ahmet Memis <[email protected]>
---
v1 -> v2:
- also bound the SOA fixed fields. The existing rdlen >= 20 check does
  not survive the two variable-length names that precede them, so v1
  left that read unbounded.
- describe what the leak actually exposes.

The TXT read was also reported earlier by RZP <[email protected]>
(https://github.com/mirror/busybox/issues/122), with a fix posted to the
list by Roberto A. Foglietta. Happy to defer to that patch if it is the
one being picked up.

 networking/nslookup.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/networking/nslookup.c b/networking/nslookup.c
index b67d354f7..0dd92f443 100644
--- a/networking/nslookup.c
+++ b/networking/nslookup.c
@@ -808,6 +808,9 @@ static NOINLINE int parse_reply(const unsigned char *msg, size_t len)
 				return -1;
 			}
 			n = *(unsigned char *)ns_rr_rdata(rr);
+			/* Length byte can lie, clamp it to RDATA */
+			if (n > rdlen - 1)
+				n = rdlen - 1;
 			if (n > 0) {
 				memset(dname, 0, sizeof(dname));
 				memcpy(dname, ns_rr_rdata(rr) + 1, n);
@@ -852,6 +855,7 @@ static NOINLINE int parse_reply(const unsigned char *msg, size_t len)
 
 			printf("\torigin = %s\n", dname);
 			cp += n;
+			rdlen -= n;
 
 			n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle),
 			                       cp, dname, sizeof(dname));
@@ -862,6 +866,13 @@ static NOINLINE int parse_reply(const unsigned char *msg, size_t len)
 
 			printf("\tmail addr = %s\n", dname);
 			cp += n;
+			rdlen -= n;
+
+			/* Names are variable-length, recheck the fixed fields */
+			if (rdlen < 20) {
+				dbg("SOA record too short:%d\n", rdlen);
+				return -1;
+			}
 
 			printf("\tserial = %lu\n", ns_get32(cp));
 			cp += 4;
-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.