From 8fde7e92fa219a268e98d8c54973cbcc1d830e8d Mon Sep 17 00:00:00 2001 From: Guido Vranken Date: Wed, 15 Jul 2020 01:29:36 +0200 Subject: [PATCH] Add project for OpenWRT mdnsd CVE-2020-11750 --- openwrt-mdnsd-cve-2020-11750/Dockerfile | 22 ++ openwrt-mdnsd-cve-2020-11750/README.md | 47 +++ .../mayhem/mdnsd/Mayhemfile | 8 + .../mayhem/mdnsd/poc/parse_answer_crash | Bin 0 -> 78 bytes .../mayhem/mdnsd/poc/parse_question_crash | Bin 0 -> 156 bytes .../mayhem/mdnsd/poc/scan_name_crash | Bin 0 -> 19 bytes openwrt-mdnsd-cve-2020-11750/src/Makefile | 6 + openwrt-mdnsd-cve-2020-11750/src/fuzzer.c | 369 ++++++++++++++++++ 8 files changed, 452 insertions(+) create mode 100644 openwrt-mdnsd-cve-2020-11750/Dockerfile create mode 100644 openwrt-mdnsd-cve-2020-11750/README.md create mode 100644 openwrt-mdnsd-cve-2020-11750/mayhem/mdnsd/Mayhemfile create mode 100644 openwrt-mdnsd-cve-2020-11750/mayhem/mdnsd/poc/parse_answer_crash create mode 100644 openwrt-mdnsd-cve-2020-11750/mayhem/mdnsd/poc/parse_question_crash create mode 100644 openwrt-mdnsd-cve-2020-11750/mayhem/mdnsd/poc/scan_name_crash create mode 100644 openwrt-mdnsd-cve-2020-11750/src/Makefile create mode 100644 openwrt-mdnsd-cve-2020-11750/src/fuzzer.c diff --git a/openwrt-mdnsd-cve-2020-11750/Dockerfile b/openwrt-mdnsd-cve-2020-11750/Dockerfile new file mode 100644 index 0000000..c70f56b --- /dev/null +++ b/openwrt-mdnsd-cve-2020-11750/Dockerfile @@ -0,0 +1,22 @@ +FROM debian:buster-slim +LABEL maintainer="guido@guidovranken.com" + +RUN apt-get update --allow-releaseinfo-change && \ + apt-get install --no-install-recommends -y build-essential \ + ca-certificates clang gcc-multilib \ + g++-multilib subversion libc6-dbg + +WORKDIR /src + +# Get and build the latest libFuzzer +RUN svn co https://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/fuzzer Fuzzer +RUN cd /src/Fuzzer && ./build.sh + +COPY /src/* /src/ + +# Compile the harness +RUN CC=clang \ + CXX=clang++ \ + LIBFUZZER_A_PATH="/src/Fuzzer/libFuzzer.a" \ + CFLAGS="-fsanitize=address,undefined,fuzzer-no-link -g -O1" \ + CXXFLAGS="-fsanitize=address,undefined,fuzzer-no-link -g -O1" make diff --git a/openwrt-mdnsd-cve-2020-11750/README.md b/openwrt-mdnsd-cve-2020-11750/README.md new file mode 100644 index 0000000..218acea --- /dev/null +++ b/openwrt-mdnsd-cve-2020-11750/README.md @@ -0,0 +1,47 @@ +# OpenWRT mdns CVE-2020-11750 + +Three out-of-bounds access issues were found in OpenWRT's mdns. They were reported to the OpenWRT security address on April 9 2020, and a fix ([1](https://git.openwrt.org/?p=project/mdnsd.git;a=commit;h=e74a3f9883199e9db7220d52b78e5fbdb4441ca3), [2](https://git.openwrt.org/?p=project/mdnsd.git;a=commit;h=cdac0460ba50dc45735f0be2e19a5a8efc3dafe1)) was released soon after. + +The code included in this project is an edited version of the original OpenWRT mdns code and `__dn_expand` from musl libc's DNS resolution code. + +## To build + +Assuming you just want to build the docker image, run: + +```bash +docker build -t forallsecure/openwrt-mdnsd-cve-2020-11750 . +``` + +## Get from Dockerhub + +If you don't want to build locally, you can pull a pre-built image +directly from dockerhub: + +```bash +docker pull forallsecure/openwrt-mdnsd-cve-2020-11750 +``` + + +## Run under Mayhem + +Change to the `openwrt-mdnsd-cve-2020-11750` folder and run: + +```bash +mayhem run mayhem/mdnsd +``` + +and watch Mayhem replicate the bugs! These bugs take seconds! + +## Run locally + +You can run this locally by running the libfuzzer harness or standalone harness +with qemu-afl inside the docker container. + +## POC + +We have included a proof of concept output under the `poc` +directory. + +> Note: Fuzzing has some degree of non-determinism, so when you run +yourself you may not get exactly this file. This is expected; your +output should still trigger the Oniguruma regex bug. diff --git a/openwrt-mdnsd-cve-2020-11750/mayhem/mdnsd/Mayhemfile b/openwrt-mdnsd-cve-2020-11750/mayhem/mdnsd/Mayhemfile new file mode 100644 index 0000000..04e3c6c --- /dev/null +++ b/openwrt-mdnsd-cve-2020-11750/mayhem/mdnsd/Mayhemfile @@ -0,0 +1,8 @@ +version: '1.5' +project: openwrt-mdnsd-cve-2020-11750 +target: mdnsd +baseimage: forallsecure/openwrt-mdnsd-cve-2020-11750 +duration: 60 +cmds: +- cmd: /src/fuzzer + libfuzzer: true diff --git a/openwrt-mdnsd-cve-2020-11750/mayhem/mdnsd/poc/parse_answer_crash b/openwrt-mdnsd-cve-2020-11750/mayhem/mdnsd/poc/parse_answer_crash new file mode 100644 index 0000000000000000000000000000000000000000..3289f24ba8ad1252d92ebca3213a06177dfde855 GIT binary patch literal 78 icmX?b;J_0G1{OI7k^ut{Fexx804Xj;AR)z|cmM!6K@kxE literal 0 HcmV?d00001 diff --git a/openwrt-mdnsd-cve-2020-11750/mayhem/mdnsd/poc/parse_question_crash b/openwrt-mdnsd-cve-2020-11750/mayhem/mdnsd/poc/parse_question_crash new file mode 100644 index 0000000000000000000000000000000000000000..670dc865d83cf984b41caac4b25888e9ea324a6e GIT binary patch literal 156 zcmZQzU?} +#include +#include +#include +#include +#include +#include +#include +#include + +struct interface { + int dummy; +}; + +#define DBG(...) +#define fprintf(...) +#define perror(...) + +#define MAX_NAME_LEN 128 +#define HOSTNAME_LEN 256 + +#define IS_COMPRESSED(x) ((x & 0xc0) == 0xc0) +#define CLASS_FLUSH 0x8000 +#define CLASS_UNICAST 0x8000 +#define CLASS_IN 0x0001 +#define FLAG_RESPONSE 0x8000 + +#define C_DNS_SD "_services._dns-sd._udp.local" +#define TYPE_A 0x0001 +#define TYPE_PTR 0x000C +#define TYPE_TXT 0x0010 +#define TYPE_AAAA 0x001c +#define TYPE_SRV 0x0021 +#define TYPE_ANY 0x00ff + +static char name_buffer[MAX_NAME_LEN + 1]; + +struct dns_answer { + uint16_t type; + uint16_t class; + uint32_t ttl; + uint16_t rdlength; +} __attribute__((packed)); + +struct dns_question { + uint16_t type; + uint16_t class; +} __attribute__((packed)); + +char umdns_host_label[HOSTNAME_LEN] = "X"; +char mdns_hostname_local[HOSTNAME_LEN + 6] = "Y"; + +struct dns_header { + uint16_t id; + uint16_t flags; + uint16_t questions; + uint16_t answers; + uint16_t authority; + uint16_t additional; +}; + +static int +scan_name(const uint8_t *buffer, int len) +{ + int offset = 0; + + while (len && (*buffer != '\0')) { + int l = *buffer; + + if (IS_COMPRESSED(l)) + return offset + 2; + + len -= l + 1; + offset += l + 1; + buffer += l + 1; + } + + if (!len || !offset || (*buffer != '\0')) + return -1; + + return offset + 1; +} + +static struct dns_answer* +dns_consume_answer(uint8_t **data, int *len) +{ + struct dns_answer *a = (struct dns_answer *) *data; + + if (*len < sizeof(struct dns_answer)) + return NULL; + +#if 0 + a->type = be16_to_cpu(a->type); + a->class = be16_to_cpu(a->class); + a->ttl = be32_to_cpu(a->ttl); + a->rdlength = be16_to_cpu(a->rdlength); +#endif + + *len -= sizeof(struct dns_answer); + *data += sizeof(struct dns_answer); + + return a; +} + +/* This function was taken from musl libc */ +int x__dn_expand(const unsigned char *base, const unsigned char *end, const unsigned char *src, char *dest, int space) +{ + const unsigned char *p = src; + char *dend, *dbegin = dest; + int len = -1, i, j; + if (p==end || space <= 0) return -1; + dend = dest + (space > 254 ? 254 : space); + /* detect reference loop using an iteration counter */ + for (i=0; i < end-base; i+=2) { + /* loop invariants: p= end-base) return -1; + p = base+j; + } else if (*p) { + if (dest != dbegin) *dest++ = '.'; + j = *p++; + if (j >= end-p || j >= dend-dest) return -1; + while (j--) *dest++ = *p++; + } else { + *dest = 0; + if (len < 0) len = p+1-src; + return len; + } + } + return -1; +} + +static char * +dns_consume_name(const uint8_t *base, int blen, uint8_t **data, int *len) +{ + int nlen = scan_name(*data, *len); + + if (nlen < 1) + return NULL; + + if (x__dn_expand(base, base + blen, *data, name_buffer, MAX_NAME_LEN) < 0) { + perror("dns_consume_name/dn_expand"); + return NULL; + } + + *len -= nlen; + *data += nlen; + + return name_buffer; +} + +static int parse_answer(struct interface *iface, struct sockaddr *from, + uint8_t *buffer, int len, uint8_t **b, int *rlen, + int cache) +{ + char *name = dns_consume_name(buffer, len, b, rlen); + struct dns_answer *a; + uint8_t *rdata; + + if (!name) { + fprintf(stderr, "dropping: bad question\n"); + return -1; + } + + a = dns_consume_answer(b, rlen); + if (!a) { + fprintf(stderr, "dropping: bad question\n"); + return -1; + } + + if ((a->class & ~CLASS_FLUSH) != CLASS_IN) + return -1; + + rdata = *b; + if (a->rdlength > *rlen) { + fprintf(stderr, "dropping: bad question\n"); + return -1; + } + + *rlen -= a->rdlength; + *b += a->rdlength; + +#if 0 + if (cache) + cache_answer(iface, from, buffer, len, name, a, rdata, a->class & CLASS_FLUSH); +#endif + + return 0; +} + +static struct dns_header* +dns_consume_header(uint8_t **data, int *len) +{ + struct dns_header *h = (struct dns_header *) *data; + + if (*len < sizeof(struct dns_header)) + return NULL; + +#if 0 + h->id = be16_to_cpu(h->id); + h->flags = be16_to_cpu(h->flags); + h->questions = be16_to_cpu(h->questions); + h->answers = be16_to_cpu(h->answers); + h->authority = be16_to_cpu(h->authority); + h->additional = be16_to_cpu(h->additional); +#endif + + *len -= sizeof(struct dns_header); + *data += sizeof(struct dns_header); + + return h; +} + +static struct dns_question* +dns_consume_question(uint8_t **data, int *len) +{ + struct dns_question *q = (struct dns_question *) *data; + + if (*len < sizeof(struct dns_question)) + return NULL; + +#if 0 + q->type = be16_to_cpu(q->type); + q->class = be16_to_cpu(q->class); +#endif + + *len -= sizeof(struct dns_question); + *data += sizeof(struct dns_question); + + return q; +} + +static void +parse_question(struct interface *iface, struct sockaddr *from, char *name, struct dns_question *q) +{ + struct sockaddr *to = NULL; + char *host; + + /* TODO: Multicast if more than one quarter of TTL has passed */ + if (q->class & CLASS_UNICAST) { + to = from; +#if 0 + if (iface->multicast) + iface = iface->peer; +#endif + } + + DBG(1, "Q -> %s %s\n", dns_type_string(q->type), name); + + switch (q->type) { + case TYPE_ANY: + if (!strcmp(name, mdns_hostname_local)) { +#if 0 + dns_reply_a(iface, to, announce_ttl); + service_reply(iface, to, NULL, NULL, announce_ttl); +#endif + } + break; + + case TYPE_PTR: + if (!strcmp(name, C_DNS_SD)) { +#if 0 + dns_reply_a(iface, to, announce_ttl); + service_announce_services(iface, to, announce_ttl); +#endif + } else { + if (name[0] == '_') { +#if 0 + service_reply(iface, to, NULL, name, announce_ttl); +#endif + } else { + /* First dot separates instance name from the rest */ + char *dot = strchr(name, '.'); + + if (dot) { + *dot = '\0'; +#if 0 + service_reply(iface, to, name, dot + 1, announce_ttl); +#endif + *dot = '.'; + } + } + } + break; + + case TYPE_AAAA: + case TYPE_A: + host = strstr(name, ".local"); + if (host) + *host = '\0'; + if (!strcmp(umdns_host_label, name)) + { +#if 0 + dns_reply_a(iface, to, announce_ttl); +#endif + } + break; + }; +} + +void +dns_handle_packet(struct interface *iface, struct sockaddr *from, uint16_t port, uint8_t *buffer, int len) +{ + struct dns_header *h; + uint8_t *b = buffer; + int rlen = len; + + h = dns_consume_header(&b, &rlen); + if (!h) { + fprintf(stderr, "dropping: bad header\n"); + return; + } + +#if 0 + if (h->questions && !iface->multicast && port != MCAST_PORT) + /* silently drop unicast questions that dont originate from port 5353 */ + return; +#endif + + while (h->questions-- > 0) { + char *name = dns_consume_name(buffer, len, &b, &rlen); + struct dns_question *q; + + if (!name) { + fprintf(stderr, "dropping: bad name\n"); + return; + } + + q = dns_consume_question(&b, &rlen); + if (!q) { + fprintf(stderr, "dropping: bad question\n"); + return; + } + + if (!(h->flags & FLAG_RESPONSE)) + parse_question(iface, from, name, q); + } + + if (!(h->flags & FLAG_RESPONSE)) + return; + + while (h->answers-- > 0) + if (parse_answer(iface, from, buffer, len, &b, &rlen, 1)) + return; + + while (h->authority-- > 0) + if (parse_answer(iface, from, buffer, len, &b, &rlen, 1)) + return; + + while (h->additional-- > 0) + if (parse_answer(iface, from, buffer, len, &b, &rlen, 1)) + return; + +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + uint8_t* buffer = malloc(size); + memcpy(buffer, data, size); + dns_handle_packet(NULL, NULL, 1234, buffer, size); + free(buffer); + return 0; +}