diff --git a/infra/experimental/SystemSan/Makefile b/infra/experimental/SystemSan/Makefile index 04db9976eac1..b1d0e5287bdc 100644 --- a/infra/experimental/SystemSan/Makefile +++ b/infra/experimental/SystemSan/Makefile @@ -2,7 +2,7 @@ CXX = clang++ CFLAGS = -std=c++17 -Wall -Wextra -O3 -g3 -Werror -all: SystemSan target target_file target_dns +all: SystemSan target target_file target_dns target_argument SystemSan: SystemSan.cpp inspect_dns.cpp inspect_utils.cpp $(CXX) $(CFLAGS) -lpthread -o $@ $^ @@ -17,6 +17,9 @@ target: target.cpp target_file: target_file.cpp $(CXX) $(CFLAGS) -fsanitize=address,fuzzer -o $@ $^ +target_argument: target_argument.cpp + $(CXX) $(CFLAGS) -fsanitize=address,fuzzer -o $@ $^ + target_dns: target_dns.cpp $(CXX) $(CFLAGS) -fsanitize=address,fuzzer -o $@ $^ @@ -24,6 +27,7 @@ test: all vuln.dict ./SystemSan ./target -dict=vuln.dict ./SystemSan ./target_file -dict=vuln.dict ./SystemSan ./target_dns -dict=vuln.dict + ./SystemSan ./target_argument -dict=vuln.dict pytorch-lightning-1.5.10: cp SystemSan.cpp PoEs/pytorch-lightning-1.5.10/; \ @@ -38,4 +42,4 @@ node-shell-quote-v1.7.3: docker run -t systemsan_node-shell-quote:latest; clean: - rm -f SystemSan /tmp/tripwire target target_file target_dns + rm -f SystemSan /tmp/tripwire target target_file target_dns target_argument diff --git a/infra/experimental/SystemSan/SystemSan.cpp b/infra/experimental/SystemSan/SystemSan.cpp index 27e8795c55df..241b9137dfd6 100644 --- a/infra/experimental/SystemSan/SystemSan.cpp +++ b/infra/experimental/SystemSan/SystemSan.cpp @@ -43,31 +43,15 @@ #include "inspect_utils.h" #include "inspect_dns.h" -#define DEBUG_LOGS 0 - -#if DEBUG_LOGS -#define debug_log(...) \ - do { \ - fprintf(stderr, __VA_ARGS__); \ - fflush(stdout); \ - fputc('\n', stderr); \ - } while (0) -#else -#define debug_log(...) -#endif - -#define fatal_log(...) \ - do { \ - fprintf(stderr, __VA_ARGS__); \ - fputc('\n', stderr); \ - exit(EXIT_FAILURE); \ - } while (0) - // The magic string that we'll use to detect full control over the command // executed. const std::string kTripWire = "/tmp/tripwire"; // Shell injection bug confirmed with /tmp/tripwire. const std::string kInjectionError = "Shell injection"; +// Argument injection bug confirmed with --tripwire. +const std::string kArgumentInjectionError = "Argument injection"; +// The magic string we'll use to detect argument injection +const std::string kArgumentTripWire = "--tripwire"; // Shell corruption bug detected based on syntax error. const std::string kCorruptionError = "Shell corruption"; // The magic string that we'll use to detect arbitrary file open @@ -169,7 +153,7 @@ std::string read_string(pid_t pid, unsigned long reg, unsigned long length) { 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()); + std::string path = read_null_terminated(pid, regs.rdi); if (!path.length()) { return; } @@ -177,6 +161,16 @@ void inspect_for_injection(pid_t pid, const user_regs_struct ®s) { if (path == kTripWire) { report_bug(kInjectionError, pid); } + + // Inspect a PID's argv for signs of argument injection + for (auto i: read_argv(pid, regs.rsi)) { + if (i == "--") { + break; + } + else if (i.find(kArgumentTripWire) == 0) { + report_bug(kArgumentInjectionError, pid); + } + } } std::string get_pathname(pid_t pid, const user_regs_struct ®s) { diff --git a/infra/experimental/SystemSan/inspect_utils.cpp b/infra/experimental/SystemSan/inspect_utils.cpp index 713d61d757c0..4e58fa1b3c4b 100644 --- a/infra/experimental/SystemSan/inspect_utils.cpp +++ b/infra/experimental/SystemSan/inspect_utils.cpp @@ -20,6 +20,7 @@ /* POSIX */ #include +#include /* Linux */ #include @@ -51,6 +52,43 @@ std::vector read_memory(pid_t pid, unsigned long long address, return memory; } +std::string read_null_terminated(pid_t pid, unsigned long long address) { + std::string str; + while (1) { + long word = ptrace(PTRACE_PEEKDATA, pid, address, 0); + if (word == -1) { + return str; + } + address += sizeof(long); + const char *word_bytes = reinterpret_cast(&word); + for (size_t i = 0; i < sizeof(long); i++) { + if (word_bytes[i] == 0) { + debug_log("read_null_terminated() read %s (%lu bytes)", str.c_str(), str.length()); + return str; + } + str.push_back(word_bytes[i]); + } + } +} + +std::vector read_argv(pid_t pid, unsigned long long address) { + std::vector argv; + for (size_t i = 0; _POSIX_ARG_MAX; i++) { + long p = ptrace(PTRACE_PEEKDATA, pid, address, 0); + debug_log("argv[%lu] @ 0x%llx = 0x%lx", i, address, p); + if (p == -1) { + break; + } + address += sizeof(long); + std::string arg = read_null_terminated(pid, p); + argv.push_back(arg); + if (p == 0) { + break; + } + } + return argv; +} + 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"; diff --git a/infra/experimental/SystemSan/inspect_utils.h b/infra/experimental/SystemSan/inspect_utils.h index a0737f28b1ae..608417034154 100644 --- a/infra/experimental/SystemSan/inspect_utils.h +++ b/infra/experimental/SystemSan/inspect_utils.h @@ -22,6 +22,26 @@ #include #include +#define DEBUG_LOGS 0 + +#if DEBUG_LOGS +#define debug_log(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + fflush(stdout); \ + fputc('\n', stderr); \ + } while (0) +#else +#define debug_log(...) +#endif + +#define fatal_log(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + fputc('\n', stderr); \ + exit(EXIT_FAILURE); \ + } while (0) + // Structure to know which thread id triggered the bug. struct ThreadParent { // Parent thread ID, ie creator. @@ -36,4 +56,8 @@ struct ThreadParent { std::vector read_memory(pid_t pid, unsigned long long address, size_t size); +std::vector read_argv(pid_t pid, unsigned long long address); + +std::string read_null_terminated(pid_t pid, unsigned long long address); + void report_bug(std::string bug_type, pid_t tid); diff --git a/infra/experimental/SystemSan/target_argument.cpp b/infra/experimental/SystemSan/target_argument.cpp new file mode 100644 index 000000000000..68d29c85ae68 --- /dev/null +++ b/infra/experimental/SystemSan/target_argument.cpp @@ -0,0 +1,29 @@ +/* + * Copyright 2023 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. +*/ +#include +#include + +void vulnerable(const char *str) { + char *const argv[] = { (char *const) "id", (char *const) str, NULL }; + execve("id", argv, NULL); +} + +extern "C" int LLVMFuzzerTestOneInput(char* data, size_t size) { + std::string str(data, size); + std::cout << "INPUT" << str << std::endl; + vulnerable(str.c_str()); + return 0; +} \ No newline at end of file diff --git a/infra/experimental/SystemSan/vuln.dict b/infra/experimental/SystemSan/vuln.dict index bf066ea4829f..aef523cb2e00 100644 --- a/infra/experimental/SystemSan/vuln.dict +++ b/infra/experimental/SystemSan/vuln.dict @@ -1,3 +1,4 @@ "/tmp/tripwire" "/fz/" -"f.z" \ No newline at end of file +"f.z" +"--tripwire" \ No newline at end of file