Skip to content

Commit 44be77a

Browse files
committed
SystemSan: detect argument injection vulnerabilities
1 parent 9d74012 commit 44be77a

File tree

6 files changed

+114
-24
lines changed

6 files changed

+114
-24
lines changed

infra/experimental/SystemSan/Makefile

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

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

77
SystemSan: SystemSan.cpp inspect_dns.cpp inspect_utils.cpp
88
$(CXX) $(CFLAGS) -lpthread -o $@ $^
@@ -17,13 +17,17 @@ target: target.cpp
1717
target_file: target_file.cpp
1818
$(CXX) $(CFLAGS) -fsanitize=address,fuzzer -o $@ $^
1919

20+
target_argument: target_argument.cpp
21+
$(CXX) $(CFLAGS) -fsanitize=address,fuzzer -o $@ $^
22+
2023
target_dns: target_dns.cpp
2124
$(CXX) $(CFLAGS) -fsanitize=address,fuzzer -o $@ $^
2225

2326
test: all vuln.dict
2427
./SystemSan ./target -dict=vuln.dict
2528
./SystemSan ./target_file -dict=vuln.dict
2629
./SystemSan ./target_dns -dict=vuln.dict
30+
./SystemSan ./target_argument -dict=vuln.dict
2731

2832
pytorch-lightning-1.5.10:
2933
cp SystemSan.cpp PoEs/pytorch-lightning-1.5.10/; \
@@ -38,4 +42,4 @@ node-shell-quote-v1.7.3:
3842
docker run -t systemsan_node-shell-quote:latest;
3943

4044
clean:
41-
rm -f SystemSan /tmp/tripwire target target_file target_dns
45+
rm -f SystemSan /tmp/tripwire target target_file target_dns target_argument

infra/experimental/SystemSan/SystemSan.cpp

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,31 +43,15 @@
4343
#include "inspect_utils.h"
4444
#include "inspect_dns.h"
4545

46-
#define DEBUG_LOGS 0
47-
48-
#if DEBUG_LOGS
49-
#define debug_log(...) \
50-
do { \
51-
fprintf(stderr, __VA_ARGS__); \
52-
fflush(stdout); \
53-
fputc('\n', stderr); \
54-
} while (0)
55-
#else
56-
#define debug_log(...)
57-
#endif
58-
59-
#define fatal_log(...) \
60-
do { \
61-
fprintf(stderr, __VA_ARGS__); \
62-
fputc('\n', stderr); \
63-
exit(EXIT_FAILURE); \
64-
} while (0)
65-
6646
// The magic string that we'll use to detect full control over the command
6747
// executed.
6848
const std::string kTripWire = "/tmp/tripwire";
6949
// Shell injection bug confirmed with /tmp/tripwire.
7050
const std::string kInjectionError = "Shell injection";
51+
// Argument injection bug confirmed with --tripwire.
52+
const std::string kArgumentInjectionError = "Argument injection";
53+
// The magic string we'll use to detect argument injection
54+
const std::string kArgumentTripWire = "--tripwire";
7155
// Shell corruption bug detected based on syntax error.
7256
const std::string kCorruptionError = "Shell corruption";
7357
// The magic string that we'll use to detect arbitrary file open
@@ -169,14 +153,24 @@ std::string read_string(pid_t pid, unsigned long reg, unsigned long length) {
169153

170154
void inspect_for_injection(pid_t pid, const user_regs_struct &regs) {
171155
// Inspect a PID's registers for the sign of shell injection.
172-
std::string path = read_string(pid, regs.rdi, kTripWire.length());
156+
std::string path = read_null_terminated(pid, regs.rdi);
173157
if (!path.length()) {
174158
return;
175159
}
176160
debug_log("inspecting");
177161
if (path == kTripWire) {
178162
report_bug(kInjectionError, pid);
179163
}
164+
165+
// Inspect a PID's argv for signs of argument injection
166+
for (auto i: read_argv(pid, regs.rsi)) {
167+
if (i == "--") {
168+
break;
169+
}
170+
else if (i.find(kArgumentTripWire) == 0) {
171+
report_bug(kArgumentInjectionError, pid);
172+
}
173+
}
180174
}
181175

182176
std::string get_pathname(pid_t pid, const user_regs_struct &regs) {

infra/experimental/SystemSan/inspect_utils.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
/* POSIX */
2222
#include <unistd.h>
23+
#include <limits.h>
2324

2425
/* Linux */
2526
#include <sys/ptrace.h>
@@ -51,6 +52,43 @@ std::vector<std::byte> read_memory(pid_t pid, unsigned long long address,
5152
return memory;
5253
}
5354

55+
std::string read_null_terminated(pid_t pid, unsigned long long address) {
56+
std::string str;
57+
while (1) {
58+
long word = ptrace(PTRACE_PEEKDATA, pid, address, 0);
59+
if (word == -1) {
60+
return str;
61+
}
62+
address += sizeof(long);
63+
const char *word_bytes = reinterpret_cast<const char*>(&word);
64+
for (size_t i = 0; i < sizeof(long); i++) {
65+
if (word_bytes[i] == 0) {
66+
debug_log("read_null_terminated() read %s (%lu bytes)", str.c_str(), str.length());
67+
return str;
68+
}
69+
str.push_back(word_bytes[i]);
70+
}
71+
}
72+
}
73+
74+
std::vector<std::string> read_argv(pid_t pid, unsigned long long address) {
75+
std::vector<std::string> argv;
76+
for (size_t i = 0; _POSIX_ARG_MAX; i++) {
77+
long p = ptrace(PTRACE_PEEKDATA, pid, address, 0);
78+
debug_log("argv[%lu] @ 0x%llx = 0x%lx", i, address, p);
79+
if (p == -1) {
80+
break;
81+
}
82+
address += sizeof(long);
83+
std::string arg = read_null_terminated(pid, p);
84+
argv.push_back(arg);
85+
if (p == 0) {
86+
break;
87+
}
88+
}
89+
return argv;
90+
}
91+
5492
void report_bug(std::string bug_type, pid_t tid) {
5593
// Report the bug found based on the bug code.
5694
std::cerr << "===BUG DETECTED: " << bug_type.c_str() << "===\n";

infra/experimental/SystemSan/inspect_utils.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,26 @@
2222
#include <string>
2323
#include <vector>
2424

25+
#define DEBUG_LOGS 0
26+
27+
#if DEBUG_LOGS
28+
#define debug_log(...) \
29+
do { \
30+
fprintf(stderr, __VA_ARGS__); \
31+
fflush(stdout); \
32+
fputc('\n', stderr); \
33+
} while (0)
34+
#else
35+
#define debug_log(...)
36+
#endif
37+
38+
#define fatal_log(...) \
39+
do { \
40+
fprintf(stderr, __VA_ARGS__); \
41+
fputc('\n', stderr); \
42+
exit(EXIT_FAILURE); \
43+
} while (0)
44+
2545
// Structure to know which thread id triggered the bug.
2646
struct ThreadParent {
2747
// Parent thread ID, ie creator.
@@ -36,4 +56,8 @@ struct ThreadParent {
3656
std::vector<std::byte> read_memory(pid_t pid, unsigned long long address,
3757
size_t size);
3858

59+
std::vector<std::string> read_argv(pid_t pid, unsigned long long address);
60+
61+
std::string read_null_terminated(pid_t pid, unsigned long long address);
62+
3963
void report_bug(std::string bug_type, pid_t tid);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2023 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+
#include <unistd.h>
17+
#include <iostream>
18+
19+
void vulnerable(const char *str) {
20+
char *const argv[] = { (char *const) "id", (char *const) str, NULL };
21+
execve("id", argv, NULL);
22+
}
23+
24+
extern "C" int LLVMFuzzerTestOneInput(char* data, size_t size) {
25+
std::string str(data, size);
26+
std::cout << "INPUT" << str << std::endl;
27+
vulnerable(str.c_str());
28+
return 0;
29+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
"/tmp/tripwire"
22
"/fz/"
3-
"f.z"
3+
"f.z"
4+
"--tripwire"

0 commit comments

Comments
 (0)