From 707c532b40a79309ae1ba667aace37ade7b8cd01 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 2 Dec 2022 08:43:49 +0100 Subject: [PATCH] SystemSan: arbitrary DNS resolution detection Right version rebased --- infra/experimental/SystemSan/Makefile | 10 +- infra/experimental/SystemSan/SystemSan.cpp | 53 +--- infra/experimental/SystemSan/inspect_dns.cpp | 235 ++++++++++++++++++ infra/experimental/SystemSan/inspect_dns.h | 26 ++ .../experimental/SystemSan/inspect_utils.cpp | 73 ++++++ infra/experimental/SystemSan/inspect_utils.h | 39 +++ infra/experimental/SystemSan/target_dns.cpp | 41 +++ infra/experimental/SystemSan/vuln.dict | 1 + 8 files changed, 427 insertions(+), 51 deletions(-) create mode 100644 infra/experimental/SystemSan/inspect_dns.cpp create mode 100644 infra/experimental/SystemSan/inspect_dns.h create mode 100644 infra/experimental/SystemSan/inspect_utils.cpp create mode 100644 infra/experimental/SystemSan/inspect_utils.h create mode 100644 infra/experimental/SystemSan/target_dns.cpp diff --git a/infra/experimental/SystemSan/Makefile b/infra/experimental/SystemSan/Makefile index d842c19f2fd4..7ca34f1dcc6a 100644 --- a/infra/experimental/SystemSan/Makefile +++ b/infra/experimental/SystemSan/Makefile @@ -2,9 +2,9 @@ CXX = clang++ CFLAGS = -std=c++17 -Wall -Wextra -O3 -g3 -all: clean SystemSan target target_file +all: clean SystemSan target target_file target_dns -SystemSan: SystemSan.cpp +SystemSan: SystemSan.cpp inspect_dns.cpp inspect_utils.cpp $(CXX) $(CFLAGS) -lpthread -o $@ $^ # Needs atheris. @@ -17,9 +17,13 @@ target: target.cpp target_file: target_file.cpp $(CXX) $(CFLAGS) -fsanitize=address,fuzzer -o $@ $^ +target_dns: target_dns.cpp + $(CXX) $(CFLAGS) -fsanitize=address,fuzzer -o $@ $^ + test: all vuln.dict ./SystemSan ./target -dict=vuln.dict ./SystemSan ./target_file -dict=vuln.dict + ./SystemSan ./target_dns -dict=vuln.dict pytorch-lightning-1.5.10: cp SystemSan.cpp PoEs/pytorch-lightning-1.5.10/; \ @@ -34,4 +38,4 @@ node-shell-quote-v1.7.3: docker run -t systemsan_node-shell-quote:latest; clean: - rm -f SystemSan /tmp/tripwire target target_file + rm -f SystemSan /tmp/tripwire target target_file target_dns diff --git a/infra/experimental/SystemSan/SystemSan.cpp b/infra/experimental/SystemSan/SystemSan.cpp index 3689fd88d2d5..18fd52258cbd 100644 --- a/infra/experimental/SystemSan/SystemSan.cpp +++ b/infra/experimental/SystemSan/SystemSan.cpp @@ -40,6 +40,9 @@ #include #include +#include "inspect_utils.h" +#include "inspect_dns.h" + #define DEBUG_LOGS 0 #if DEBUG_LOGS @@ -77,16 +80,6 @@ constexpr int kRootDirMaxLength = 16; // The PID of the root process we're fuzzing. pid_t g_root_pid; -// Structure to know which thread id triggered the bug. -struct ThreadParent { - // Parent thread ID, ie creator. - pid_t parent_tid; - // Current thread ID ran exec to become another process. - bool ran_exec = false; - - ThreadParent() : parent_tid(0) {} - ThreadParent(pid_t tid) : parent_tid(tid) {} -}; // Map of a PID/TID its PID/TID creator and wether it ran exec. std::map root_pids; @@ -162,23 +155,6 @@ pid_t run_child(char **argv) { return pid; } -std::vector read_memory(pid_t pid, unsigned long long address, - size_t size) { - std::vector memory; - - for (size_t i = 0; i < size; i += sizeof(long)) { - long word = ptrace(PTRACE_PEEKTEXT, pid, address + i, 0); - if (word == -1) { - return memory; - } - - std::byte *word_bytes = reinterpret_cast(&word); - memory.insert(memory.end(), word_bytes, word_bytes + sizeof(long)); - } - - return memory; -} - // Construct a string with the memory specified in a register. std::string read_string(pid_t pid, unsigned long reg, unsigned long length) { auto memory = read_memory(pid, reg, length); @@ -191,27 +167,6 @@ std::string read_string(pid_t pid, unsigned long reg, unsigned long length) { return content; } -void report_bug(std::string bug_type, pid_t tid) { - // Report the bug found based on the bug code. - std::cerr << "===BUG DETECTED: " << bug_type.c_str() << "===\n"; - // Rely on sanitizers/libFuzzer to produce a stacktrace by sending SIGABRT - // to the root process. - // Note: this may not be reliable or consistent if shell injection happens - // in an async way. - // Find the thread group id, that is the pid. - pid_t pid = tid; - auto parent = root_pids[tid]; - while (!parent.ran_exec) { - // Find the first parent which ran exec syscall. - if (parent.parent_tid == g_root_pid) { - break; - } - pid = parent.parent_tid; - parent = root_pids[parent.parent_tid]; - } - tgkill(pid, tid, SIGABRT); -} - void inspect_for_injection(pid_t pid, const user_regs_struct ®s) { // Inspect a PID's registers for the sign of shell injection. std::string path = read_string(pid, regs.rdi, kTripWire.length()); @@ -459,6 +414,8 @@ int trace(std::map pids) { } } + inspect_dns_syscalls(pid, regs); + if (regs.orig_rax == __NR_openat) { inspect_for_arbitrary_file_open(pid, regs); } diff --git a/infra/experimental/SystemSan/inspect_dns.cpp b/infra/experimental/SystemSan/inspect_dns.cpp new file mode 100644 index 000000000000..8e5af920c1dc --- /dev/null +++ b/infra/experimental/SystemSan/inspect_dns.cpp @@ -0,0 +1,235 @@ +/* + * Copyright 2022 Google LLC + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* A detector that uses ptrace to identify shell injection vulnerabilities. */ + +/* POSIX */ +#include +#include + +/* Linux */ +#include +#include +#include + +#include + +#include "inspect_utils.h" + +// Arbitrary domain name resolution +const std::string kArbitraryDomainNameResolution = "Arbitrary domain name resolution"; + +// Global constant for one file descriptor about of a DNS socket +int kFdDns = 0; + +#define DNS_HEADER_LEN 12 + + +void inspect_for_arbitrary_dns_connect(pid_t pid, const user_regs_struct ®s) { + auto memory = read_memory(pid, regs.rsi, sizeof(struct sockaddr_in)); + if (memory.size()) { + struct sockaddr_in * sa = reinterpret_cast(memory.data()); + if (sa->sin_family == AF_INET && htons(sa->sin_port) == 53) { + // save file descriptor for later sendmmsg + kFdDns = regs.rdi; + } + } +} + +struct DnsHeader { + uint16_t tx_id; + uint16_t flags; + uint16_t questions; + uint16_t answers; + uint16_t nameservers; + uint16_t additional; +}; + +struct DnsHeader parse_dns_header(std::vector data) { + struct DnsHeader h; + h.tx_id = (((uint16_t) data[0]) << 8) | ((uint16_t) data[1]); + h.flags = (((uint16_t) data[2]) << 8) | ((uint16_t) data[3]); + h.questions = (((uint16_t) data[4]) << 8) | ((uint16_t) data[5]); + h.answers = (((uint16_t) data[6]) << 8) | ((uint16_t) data[7]); + h.nameservers = (((uint16_t) data[8]) << 8) | ((uint16_t) data[9]); + h.additional = (((uint16_t) data[10]) << 8) | ((uint16_t) data[11]); + return h; +} + +bool dns_flags_standard_query(uint16_t flags) { + if ((flags & 0x8000) == 0) { + // Query, not response. + if (((flags & 0x7800) >> 11) == 0) { + // Opcode 0 is standard query. + if ((flags & 0x0200) == 0) { + // Message is not truncated. + if ((flags & 0x0040) == 0) { + // Z-bit reserved flag is unset. + return true; + } + } + } + } + return false; +} + +struct DnsRequest { + // Start of name in the byte vector. + size_t offset; + // End of name in the byte vector. + size_t end; + // Length of top level domain. + uint8_t tld_size; + // Number of levels/dots in domain name. + size_t nb_levels; + // DNS type like A is 1. + uint16_t dns_type; + // DNS class like IN is 1. + uint16_t dns_class; +}; + +struct DnsRequest parse_dns_request(std::vector data, size_t offset) { + struct DnsRequest r; + r.offset = offset; + r.tld_size = 0; + r.nb_levels = 0; + while(offset < data.size()) { + uint8_t rlen = uint8_t(data[offset]); + if (rlen == 0) { + break; + } + r.nb_levels++; + offset += rlen+1; + r.tld_size = rlen; + } + if (offset <= 4 + data.size()) { + r.end = offset; + r.dns_type = (((uint16_t) data[offset]) << 8) | ((uint16_t) data[offset+1]); + r.dns_class = (((uint16_t) data[offset+2]) << 8) | ((uint16_t) data[offset+3]); + } else { + r.end = data.size(); + } + return r; +} + +void log_dns_request(struct DnsRequest r, std::vector data) { + size_t offset = r.offset; + std::cerr << "===Domain resolved: "; + while(offset < r.end) { + uint8_t rlen = uint8_t(data[offset]); + if (rlen == 0) { + break; + } + std::cerr << '.'; + for (uint8_t i = 1; i < rlen+1; i++) { + std::cerr << (char) data[offset + i]; + } + offset += rlen+1; + } + std::cerr << "===\n"; + std::cerr << "===DNS request type: " << r.dns_type << ", class: " << r.dns_class << "===\n"; +} + +void inspect_for_arbitrary_dns_pkt(std::vector data, pid_t pid) { + if (data.size() < DNS_HEADER_LEN + 1) { + return; + } + struct DnsHeader h = parse_dns_header(data); + if (h.questions != 1) { + return; + } + if (h.answers != 0 || h.nameservers != 0 || h.additional != 0) { + return; + } + if (!dns_flags_standard_query(h.flags)) { + return; + } + + struct DnsRequest req = parse_dns_request(data, DNS_HEADER_LEN); + // Alert if the top level domain is only one character and + // if there is more than just the TLD. + if (req.tld_size == 1 && req.nb_levels > 1 && req.end < data.size()) { + report_bug(kArbitraryDomainNameResolution, pid); + log_dns_request(req, data); + } +} + +void inspect_for_arbitrary_dns_fdbuffer(pid_t pid, const user_regs_struct ®s) { + if (kFdDns > 0 && kFdDns == (int) regs.rdi) { + auto memory = read_memory(pid, regs.rsi, regs.rdx); + if (memory.size()) { + inspect_for_arbitrary_dns_pkt(memory, pid); + } + } +} + +void inspect_for_arbitrary_dns_iov(pid_t pid, unsigned long iov) { + auto memory = read_memory(pid, iov, sizeof(struct iovec)); + if (memory.size()) { + struct iovec * iovec = reinterpret_cast(memory.data()); + memory = read_memory(pid, (unsigned long) iovec->iov_base, iovec->iov_len); + if (memory.size()) { + inspect_for_arbitrary_dns_pkt(memory, pid); + } + } +} + +void inspect_for_arbitrary_dns_sendmsg(pid_t pid, const user_regs_struct ®s) { + if (kFdDns > 0 && kFdDns == (int) regs.rdi) { + auto memory = read_memory(pid, regs.rsi, sizeof(struct msghdr)); + if (memory.size()) { + struct msghdr * msg = reinterpret_cast(memory.data()); + if (msg->msg_iovlen == 1) { + inspect_for_arbitrary_dns_iov(pid, (unsigned long) msg->msg_iov); + } + } + } +} + +void inspect_for_arbitrary_dns_sendmmsg(pid_t pid, const user_regs_struct ®s) { + if (kFdDns > 0 && kFdDns == (int) regs.rdi) { + auto memory = read_memory(pid, regs.rsi, sizeof(struct mmsghdr)); + if (memory.size()) { + struct mmsghdr * msg = reinterpret_cast(memory.data()); + if (msg->msg_hdr.msg_iovlen == 1) { + inspect_for_arbitrary_dns_iov(pid, (unsigned long) msg->msg_hdr.msg_iov); + } + } + } +} + +void inspect_dns_syscalls(pid_t pid, const user_regs_struct ®s) { + switch (regs.orig_rax) { + case __NR_connect: + inspect_for_arbitrary_dns_connect(pid, regs); + break; + case __NR_close: + if (kFdDns > 0 && kFdDns == (int) regs.rdi) { + // reset DNS file descriptor on close + kFdDns = 0; + } + break; + case __NR_sendmmsg: + inspect_for_arbitrary_dns_sendmmsg(pid, regs); + break; + case __NR_sendmsg: + inspect_for_arbitrary_dns_sendmsg(pid, regs); + break; + case __NR_sendto: + // fallthrough + case __NR_write: + inspect_for_arbitrary_dns_fdbuffer(pid, regs); + } +} diff --git a/infra/experimental/SystemSan/inspect_dns.h b/infra/experimental/SystemSan/inspect_dns.h new file mode 100644 index 000000000000..849af4e98067 --- /dev/null +++ b/infra/experimental/SystemSan/inspect_dns.h @@ -0,0 +1,26 @@ +/* + * Copyright 2022 Google LLC + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* A detector that uses ptrace to identify DNS arbitrary resolutions. */ + + +/* POSIX */ +#include + +/* Linux */ +#include + + +void inspect_dns_syscalls(pid_t pid, const user_regs_struct ®s); diff --git a/infra/experimental/SystemSan/inspect_utils.cpp b/infra/experimental/SystemSan/inspect_utils.cpp new file mode 100644 index 000000000000..713d61d757c0 --- /dev/null +++ b/infra/experimental/SystemSan/inspect_utils.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2022 Google LLC + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* A detector that uses ptrace to identify DNS arbitrary resolutions. */ + +/* C standard library */ +#include + +/* POSIX */ +#include + +/* Linux */ +#include + +#include +#include +#include +#include + +#include "inspect_utils.h" + +extern pid_t g_root_pid; +extern std::map root_pids; + +std::vector read_memory(pid_t pid, unsigned long long address, + size_t size) { + std::vector memory; + + for (size_t i = 0; i < size; i += sizeof(long)) { + long word = ptrace(PTRACE_PEEKTEXT, pid, address + i, 0); + if (word == -1) { + return memory; + } + + std::byte *word_bytes = reinterpret_cast(&word); + memory.insert(memory.end(), word_bytes, word_bytes + sizeof(long)); + } + + return memory; +} + +void report_bug(std::string bug_type, pid_t tid) { + // Report the bug found based on the bug code. + std::cerr << "===BUG DETECTED: " << bug_type.c_str() << "===\n"; + // Rely on sanitizers/libFuzzer to produce a stacktrace by sending SIGABRT + // to the root process. + // Note: this may not be reliable or consistent if shell injection happens + // in an async way. + // Find the thread group id, that is the pid. + pid_t pid = tid; + auto parent = root_pids[tid]; + while (!parent.ran_exec) { + // Find the first parent which ran exec syscall. + if (parent.parent_tid == g_root_pid) { + break; + } + pid = parent.parent_tid; + parent = root_pids[parent.parent_tid]; + } + tgkill(pid, tid, SIGABRT); +} diff --git a/infra/experimental/SystemSan/inspect_utils.h b/infra/experimental/SystemSan/inspect_utils.h new file mode 100644 index 000000000000..a0737f28b1ae --- /dev/null +++ b/infra/experimental/SystemSan/inspect_utils.h @@ -0,0 +1,39 @@ +/* + * Copyright 2022 Google LLC + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* A detector that uses ptrace to identify DNS arbitrary resolutions. */ + + +/* POSIX */ +#include + +#include +#include + +// Structure to know which thread id triggered the bug. +struct ThreadParent { + // Parent thread ID, ie creator. + pid_t parent_tid; + // Current thread ID ran exec to become another process. + bool ran_exec = false; + + ThreadParent() : parent_tid(0) {} + ThreadParent(pid_t tid) : parent_tid(tid) {} +}; + +std::vector read_memory(pid_t pid, unsigned long long address, + size_t size); + +void report_bug(std::string bug_type, pid_t tid); diff --git a/infra/experimental/SystemSan/target_dns.cpp b/infra/experimental/SystemSan/target_dns.cpp new file mode 100644 index 000000000000..6452ce60f1d3 --- /dev/null +++ b/infra/experimental/SystemSan/target_dns.cpp @@ -0,0 +1,41 @@ +/* + * Copyright 2022 Google LLC + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ +/* A sample target program under test, + * /tmp/tripwire or other commands will be injected into its shell command */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(char* data, size_t size) { + std::string str(data, size); + std::cout << "INPUT" << str << std::endl; + + struct addrinfo *result; + + int s = getaddrinfo(str.c_str(), NULL, NULL, &result); + if (s == 0) { + freeaddrinfo(result); + } + + return 0; +} diff --git a/infra/experimental/SystemSan/vuln.dict b/infra/experimental/SystemSan/vuln.dict index 6414a13cc8fa..bf066ea4829f 100644 --- a/infra/experimental/SystemSan/vuln.dict +++ b/infra/experimental/SystemSan/vuln.dict @@ -1,2 +1,3 @@ "/tmp/tripwire" "/fz/" +"f.z" \ No newline at end of file