Skip to content

Commit a857bfb

Browse files
catenacyberoliverchangjonathanmetzman
authored
SystemSan: arbitrary DNS resolution detection (#9119)
cc @oliverchang @alan32liu after #9100 and #8448 After compiling locally, I can see that `./SystemSan ./target_dns -dict=vuln.dict` crashes in a few seconds with ``` ===BUG DETECTED: Arbitrary domain name resolution=== ===Domain resolved: .f.z=== ===DNS request type: 0, class: 256=== ==315== ERROR: libFuzzer: deadly signal #0 0x539131 in __sanitizer_print_stack_trace /src/llvm-project/compiler-rt/lib/asan/asan_stack.cpp:87:3 #1 0x457c48 in fuzzer::PrintStackTrace() /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerUtil.cpp:210:5 #2 0x43c923 in fuzzer::Fuzzer::CrashCallback() /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:233:3 #3 0x7fa57940041f (/lib/x86_64-linux-gnu/libpthread.so.0+0x1441f) (BuildId: 7b4536f41cdaa5888408e82d0836e33dcf436466) #4 0x7fa5793ff7db in send (/lib/x86_64-linux-gnu/libpthread.so.0+0x137db) (BuildId: 7b4536f41cdaa5888408e82d0836e33dcf436466) #5 0x503ba4 in __interceptor_send /src/llvm-project/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:6802:17 #6 0x7fa578abf462 (/lib/x86_64-linux-gnu/libresolv.so.2+0xb462) (BuildId: 4519041bde5b859c55798ac0745b0b6199cb7d94) #7 0x7fa578abbc43 in __res_context_query (/lib/x86_64-linux-gnu/libresolv.so.2+0x7c43) (BuildId: 4519041bde5b859c55798ac0745b0b6199cb7d94) #8 0x7fa578abc8ed in __res_context_search (/lib/x86_64-linux-gnu/libresolv.so.2+0x88ed) (BuildId: 4519041bde5b859c55798ac0745b0b6199cb7d94) #9 0x7fa578ad2cc1 (/lib/x86_64-linux-gnu/libnss_dns.so.2+0x2cc1) (BuildId: 3fac4ec397ba8e8938fe298f103113f315465130) #10 0x7fa578ad2e8b in _nss_dns_gethostbyname3_r (/lib/x86_64-linux-gnu/libnss_dns.so.2+0x2e8b) (BuildId: 3fac4ec397ba8e8938fe298f103113f315465130) #11 0x7fa578ad2f41 in _nss_dns_gethostbyname2_r (/lib/x86_64-linux-gnu/libnss_dns.so.2+0x2f41) (BuildId: 3fac4ec397ba8e8938fe298f103113f315465130) #12 0x7fa5792fdc9d in gethostbyname2_r (/lib/x86_64-linux-gnu/libc.so.6+0x130c9d) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee) #13 0x7fa5792d179e (/lib/x86_64-linux-gnu/libc.so.6+0x10479e) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee) #14 0x7fa5792d2f58 in getaddrinfo (/lib/x86_64-linux-gnu/libc.so.6+0x105f58) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee) #15 0x4d93ac in getaddrinfo /src/llvm-project/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:2667:13 #16 0x56c8d9 in LLVMFuzzerTestOneInput /out/SystemSan/target_dns.cpp:35:11 #17 0x43dec3 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:611:15 #18 0x43d6aa in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool, bool*) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:514:3 #19 0x43ed79 in fuzzer::Fuzzer::MutateAndTestOne() /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:757:19 #20 0x43fa45 in fuzzer::Fuzzer::Loop(std::__Fuzzer::vector<fuzzer::SizedFile, std::__Fuzzer::allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:895:5 #21 0x42edaf in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:912:6 #22 0x458402 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10 #23 0x7fa5791f1082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee) #24 0x41f7ed in _start (/out/SystemSan/target_dns+0x41f7ed) NOTE: libFuzzer has rudimentary signal handlers. Combine libFuzzer with AddressSanitizer or similar for better crash reports. SUMMARY: libFuzzer: deadly signal MS: 2 CrossOver-ManualDict- DE: "f.z"-; base unit: ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4 0x66,0x2e,0x7a, f.z artifact_prefix='./'; Test unit written to ./crash-926813b2d6adde373f96a10594a5314951588384 Base64: Zi56 ``` You can also try ``` echo -n f.z > toto ./SystemSan ./target_dns toto ``` Co-authored-by: Oliver Chang <oliverchang@users.noreply.github.com> Co-authored-by: jonathanmetzman <31354670+jonathanmetzman@users.noreply.github.com>
1 parent bfdc5b9 commit a857bfb

File tree

8 files changed

+425
-51
lines changed

8 files changed

+425
-51
lines changed

infra/experimental/SystemSan/Makefile

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
CXX = clang++
33
CFLAGS = -std=c++17 -Wall -Wextra -O3 -g3
44

5-
all: clean SystemSan target target_file
5+
all: clean SystemSan target target_file target_dns
66

7-
SystemSan: SystemSan.cpp
7+
SystemSan: SystemSan.cpp inspect_dns.cpp inspect_utils.cpp
88
$(CXX) $(CFLAGS) -lpthread -o $@ $^
99

1010
# Needs atheris.
@@ -17,9 +17,13 @@ target: target.cpp
1717
target_file: target_file.cpp
1818
$(CXX) $(CFLAGS) -fsanitize=address,fuzzer -o $@ $^
1919

20+
target_dns: target_dns.cpp
21+
$(CXX) $(CFLAGS) -fsanitize=address,fuzzer -o $@ $^
22+
2023
test: all vuln.dict
2124
./SystemSan ./target -dict=vuln.dict
2225
./SystemSan ./target_file -dict=vuln.dict
26+
./SystemSan ./target_dns -dict=vuln.dict
2327

2428
pytorch-lightning-1.5.10:
2529
cp SystemSan.cpp PoEs/pytorch-lightning-1.5.10/; \
@@ -34,4 +38,4 @@ node-shell-quote-v1.7.3:
3438
docker run -t systemsan_node-shell-quote:latest;
3539

3640
clean:
37-
rm -f SystemSan /tmp/tripwire target target_file
41+
rm -f SystemSan /tmp/tripwire target target_file target_dns

infra/experimental/SystemSan/SystemSan.cpp

Lines changed: 5 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
#include <string>
4141
#include <vector>
4242

43+
#include "inspect_utils.h"
44+
#include "inspect_dns.h"
45+
4346
#define DEBUG_LOGS 0
4447

4548
#if DEBUG_LOGS
@@ -77,16 +80,6 @@ constexpr int kRootDirMaxLength = 16;
7780
// The PID of the root process we're fuzzing.
7881
pid_t g_root_pid;
7982

80-
// Structure to know which thread id triggered the bug.
81-
struct ThreadParent {
82-
// Parent thread ID, ie creator.
83-
pid_t parent_tid;
84-
// Current thread ID ran exec to become another process.
85-
bool ran_exec = false;
86-
87-
ThreadParent() : parent_tid(0) {}
88-
ThreadParent(pid_t tid) : parent_tid(tid) {}
89-
};
9083
// Map of a PID/TID its PID/TID creator and wether it ran exec.
9184
std::map<pid_t, ThreadParent> root_pids;
9285

@@ -162,23 +155,6 @@ pid_t run_child(char **argv) {
162155
return pid;
163156
}
164157

165-
std::vector<std::byte> read_memory(pid_t pid, unsigned long long address,
166-
size_t size) {
167-
std::vector<std::byte> memory;
168-
169-
for (size_t i = 0; i < size; i += sizeof(long)) {
170-
long word = ptrace(PTRACE_PEEKTEXT, pid, address + i, 0);
171-
if (word == -1) {
172-
return memory;
173-
}
174-
175-
std::byte *word_bytes = reinterpret_cast<std::byte *>(&word);
176-
memory.insert(memory.end(), word_bytes, word_bytes + sizeof(long));
177-
}
178-
179-
return memory;
180-
}
181-
182158
// Construct a string with the memory specified in a register.
183159
std::string read_string(pid_t pid, unsigned long reg, unsigned long length) {
184160
auto memory = read_memory(pid, reg, length);
@@ -191,27 +167,6 @@ std::string read_string(pid_t pid, unsigned long reg, unsigned long length) {
191167
return content;
192168
}
193169

194-
void report_bug(std::string bug_type, pid_t tid) {
195-
// Report the bug found based on the bug code.
196-
std::cerr << "===BUG DETECTED: " << bug_type.c_str() << "===\n";
197-
// Rely on sanitizers/libFuzzer to produce a stacktrace by sending SIGABRT
198-
// to the root process.
199-
// Note: this may not be reliable or consistent if shell injection happens
200-
// in an async way.
201-
// Find the thread group id, that is the pid.
202-
pid_t pid = tid;
203-
auto parent = root_pids[tid];
204-
while (!parent.ran_exec) {
205-
// Find the first parent which ran exec syscall.
206-
if (parent.parent_tid == g_root_pid) {
207-
break;
208-
}
209-
pid = parent.parent_tid;
210-
parent = root_pids[parent.parent_tid];
211-
}
212-
tgkill(pid, tid, SIGABRT);
213-
}
214-
215170
void inspect_for_injection(pid_t pid, const user_regs_struct &regs) {
216171
// Inspect a PID's registers for the sign of shell injection.
217172
std::string path = read_string(pid, regs.rdi, kTripWire.length());
@@ -459,6 +414,8 @@ int trace(std::map<pid_t, Tracee> pids) {
459414
}
460415
}
461416

417+
inspect_dns_syscalls(pid, regs);
418+
462419
if (regs.orig_rax == __NR_openat) {
463420
// TODO(metzman): Re-enable this once we have config/flag support.
464421
// inspect_for_arbitrary_file_open(pid, regs);
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*
2+
* Copyright 2022 Google LLC
3+
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
/* A detector that uses ptrace to identify shell injection vulnerabilities. */
17+
18+
/* POSIX */
19+
#include <sys/user.h>
20+
#include <unistd.h>
21+
22+
/* Linux */
23+
#include <arpa/inet.h>
24+
#include <syscall.h>
25+
#include <sys/ptrace.h>
26+
27+
#include <iostream>
28+
29+
#include "inspect_utils.h"
30+
31+
32+
// Arbitrary domain name resolution.
33+
const std::string kArbitraryDomainNameResolution = "Arbitrary domain name resolution";
34+
35+
// Global constant for one file descriptor about of a DNS socket
36+
int kFdDns = 0;
37+
const size_t kDnsHeaderLen = 12;
38+
39+
40+
void inspect_for_arbitrary_dns_connect(pid_t pid, const user_regs_struct &regs) {
41+
auto memory = read_memory(pid, regs.rsi, sizeof(struct sockaddr_in));
42+
if (memory.size()) {
43+
struct sockaddr_in * sa = reinterpret_cast<struct sockaddr_in *>(memory.data());
44+
if (sa->sin_family == AF_INET && htons(sa->sin_port) == 53) {
45+
// save file descriptor for later sendmmsg
46+
kFdDns = regs.rdi;
47+
}
48+
}
49+
}
50+
51+
struct DnsHeader {
52+
uint16_t tx_id;
53+
uint16_t flags;
54+
uint16_t questions;
55+
uint16_t answers;
56+
uint16_t nameservers;
57+
uint16_t additional;
58+
};
59+
60+
struct DnsHeader parse_dns_header(std::vector<std::byte> data) {
61+
struct DnsHeader h;
62+
h.tx_id = (((uint16_t) data[0]) << 8) | ((uint16_t) data[1]);
63+
h.flags = (((uint16_t) data[2]) << 8) | ((uint16_t) data[3]);
64+
h.questions = (((uint16_t) data[4]) << 8) | ((uint16_t) data[5]);
65+
h.answers = (((uint16_t) data[6]) << 8) | ((uint16_t) data[7]);
66+
h.nameservers = (((uint16_t) data[8]) << 8) | ((uint16_t) data[9]);
67+
h.additional = (((uint16_t) data[10]) << 8) | ((uint16_t) data[11]);
68+
return h;
69+
}
70+
71+
bool dns_flags_standard_query(uint16_t flags) {
72+
if ((flags & 0x8000) == 0) {
73+
// Query, not response.
74+
if (((flags & 0x7800) >> 11) == 0) {
75+
// Opcode 0 is standard query.
76+
if ((flags & 0x0200) == 0) {
77+
// Message is not truncated.
78+
if ((flags & 0x0040) == 0) {
79+
// Z-bit reserved flag is unset.
80+
return true;
81+
}
82+
}
83+
}
84+
}
85+
return false;
86+
}
87+
88+
struct DnsRequest {
89+
// Start of name in the byte vector.
90+
size_t offset;
91+
// End of name in the byte vector.
92+
size_t end;
93+
// Length of top level domain.
94+
uint8_t tld_size;
95+
// Number of levels/dots in domain name.
96+
size_t nb_levels;
97+
// DNS type like A is 1.
98+
uint16_t dns_type;
99+
// DNS class like IN is 1.
100+
uint16_t dns_class;
101+
};
102+
103+
struct DnsRequest parse_dns_request(std::vector<std::byte> data, size_t offset) {
104+
struct DnsRequest r;
105+
r.offset = offset;
106+
r.tld_size = 0;
107+
r.nb_levels = 0;
108+
while(offset < data.size()) {
109+
uint8_t rlen = uint8_t(data[offset]);
110+
if (rlen == 0) {
111+
break;
112+
}
113+
r.nb_levels++;
114+
offset += rlen+1;
115+
r.tld_size = rlen;
116+
}
117+
if (offset <= 4 + data.size()) {
118+
r.end = offset;
119+
r.dns_type = (((uint16_t) data[offset]) << 8) | ((uint16_t) data[offset+1]);
120+
r.dns_class = (((uint16_t) data[offset+2]) << 8) | ((uint16_t) data[offset+3]);
121+
} else {
122+
r.end = data.size();
123+
}
124+
return r;
125+
}
126+
127+
void log_dns_request(struct DnsRequest r, std::vector<std::byte> data) {
128+
size_t offset = r.offset;
129+
std::cerr << "===Domain resolved: ";
130+
while(offset < r.end) {
131+
uint8_t rlen = uint8_t(data[offset]);
132+
if (rlen == 0) {
133+
break;
134+
}
135+
std::cerr << '.';
136+
for (uint8_t i = 1; i < rlen+1; i++) {
137+
std::cerr << (char) data[offset + i];
138+
}
139+
offset += rlen+1;
140+
}
141+
std::cerr << "===\n";
142+
std::cerr << "===DNS request type: " << r.dns_type << ", class: " << r.dns_class << "===\n";
143+
}
144+
145+
void inspect_for_arbitrary_dns_pkt(std::vector<std::byte> data, pid_t pid) {
146+
if (data.size() < kDnsHeaderLen + 1) {
147+
return;
148+
}
149+
struct DnsHeader h = parse_dns_header(data);
150+
if (h.questions != 1) {
151+
return;
152+
}
153+
if (h.answers != 0 || h.nameservers != 0) {
154+
return;
155+
}
156+
if (!dns_flags_standard_query(h.flags)) {
157+
return;
158+
}
159+
160+
struct DnsRequest req = parse_dns_request(data, kDnsHeaderLen);
161+
// Alert if the top level domain is only one character and
162+
// if there is more than just the TLD.
163+
if (req.tld_size == 1 && req.nb_levels > 1 && req.end < data.size()) {
164+
report_bug(kArbitraryDomainNameResolution, pid);
165+
log_dns_request(req, data);
166+
}
167+
}
168+
169+
void inspect_for_arbitrary_dns_fdbuffer(pid_t pid, const user_regs_struct &regs) {
170+
if (kFdDns > 0 && kFdDns == (int) regs.rdi) {
171+
auto memory = read_memory(pid, regs.rsi, regs.rdx);
172+
if (memory.size()) {
173+
inspect_for_arbitrary_dns_pkt(memory, pid);
174+
}
175+
}
176+
}
177+
178+
void inspect_for_arbitrary_dns_iov(pid_t pid, unsigned long iov) {
179+
auto memory = read_memory(pid, iov, sizeof(struct iovec));
180+
if (memory.size()) {
181+
struct iovec * iovec = reinterpret_cast<struct iovec *>(memory.data());
182+
memory = read_memory(pid, (unsigned long) iovec->iov_base, iovec->iov_len);
183+
if (memory.size()) {
184+
inspect_for_arbitrary_dns_pkt(memory, pid);
185+
}
186+
}
187+
}
188+
189+
void inspect_for_arbitrary_dns_sendmsg(pid_t pid, const user_regs_struct &regs) {
190+
if (kFdDns > 0 && kFdDns == (int) regs.rdi) {
191+
auto memory = read_memory(pid, regs.rsi, sizeof(struct msghdr));
192+
if (memory.size()) {
193+
struct msghdr * msg = reinterpret_cast<struct msghdr *>(memory.data());
194+
if (msg->msg_iovlen == 1) {
195+
inspect_for_arbitrary_dns_iov(pid, (unsigned long) msg->msg_iov);
196+
}
197+
}
198+
}
199+
}
200+
201+
void inspect_for_arbitrary_dns_sendmmsg(pid_t pid, const user_regs_struct &regs) {
202+
if (kFdDns > 0 && kFdDns == (int) regs.rdi) {
203+
auto memory = read_memory(pid, regs.rsi, sizeof(struct mmsghdr));
204+
if (memory.size()) {
205+
struct mmsghdr * msg = reinterpret_cast<struct mmsghdr *>(memory.data());
206+
if (msg->msg_hdr.msg_iovlen == 1) {
207+
inspect_for_arbitrary_dns_iov(pid, (unsigned long) msg->msg_hdr.msg_iov);
208+
}
209+
}
210+
}
211+
}
212+
213+
void inspect_dns_syscalls(pid_t pid, const user_regs_struct &regs) {
214+
switch (regs.orig_rax) {
215+
case __NR_connect:
216+
inspect_for_arbitrary_dns_connect(pid, regs);
217+
break;
218+
case __NR_close:
219+
if (kFdDns > 0 && kFdDns == (int) regs.rdi) {
220+
// reset DNS file descriptor on close
221+
kFdDns = 0;
222+
}
223+
break;
224+
case __NR_sendmmsg:
225+
inspect_for_arbitrary_dns_sendmmsg(pid, regs);
226+
break;
227+
case __NR_sendmsg:
228+
inspect_for_arbitrary_dns_sendmsg(pid, regs);
229+
break;
230+
case __NR_sendto:
231+
// fallthrough
232+
case __NR_write:
233+
inspect_for_arbitrary_dns_fdbuffer(pid, regs);
234+
}
235+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2022 Google LLC
3+
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
/* A detector that uses ptrace to identify DNS arbitrary resolutions. */
17+
18+
19+
/* POSIX */
20+
#include <unistd.h>
21+
22+
/* Linux */
23+
#include <sys/ptrace.h>
24+
25+
26+
void inspect_dns_syscalls(pid_t pid, const user_regs_struct &regs);

0 commit comments

Comments
 (0)