Index: include/sanitizer/common_interface_defs.h =================================================================== --- include/sanitizer/common_interface_defs.h +++ include/sanitizer/common_interface_defs.h @@ -24,13 +24,28 @@ #ifdef __cplusplus extern "C" { #endif + // Arguments for __sanitizer_sandbox_on_notify() below. + typedef struct { + // Enable sandbox support in sanitizer coverage. + int coverage_sandboxed; + // File descriptor to write coverage data to. If -1 is passed, a file will + // be pre-opened by __sanitizer_sandobx_on_notify(). This field has no + // effect if coverage_sandboxed == 0. + int coverage_fd; + // If non-zero, split the coverage data into well-formed blocks. This is + // useful when coverage_fd is a socket descriptor. Each block will contain + // a header, allowing data from multiple processes to be sent over the same + // socket. + unsigned int coverage_max_block_size; + } __sanitizer_sandbox_arguments; + // Tell the tools to write their reports to "path." instead of stderr. void __sanitizer_set_report_path(const char *path); // Notify the tools that the sandbox is going to be turned on. The reserved // parameter will be used in the future to hold a structure with functions // that the tools may call to bypass the sandbox. - void __sanitizer_sandbox_on_notify(void *reserved); + void __sanitizer_sandbox_on_notify(__sanitizer_sandbox_arguments *args); // This function is called by the tool when it has just finished reporting // an error. 'error_summary' is a one-line string that summarizes Index: lib/sanitizer_common/sanitizer_common.h =================================================================== --- lib/sanitizer_common/sanitizer_common.h +++ lib/sanitizer_common/sanitizer_common.h @@ -183,7 +183,8 @@ bool StackSizeIsUnlimited(); void SetStackSizeLimitInBytes(uptr limit); void AdjustStackSize(void *attr); -void PrepareForSandboxing(); +void PrepareForSandboxing(__sanitizer_sandbox_arguments *args); +void CovPrepareForSandboxing(__sanitizer_sandbox_arguments *args); void SetSandboxingCallback(void (*f)()); void InitTlsSize(); Index: lib/sanitizer_common/sanitizer_common.cc =================================================================== --- lib/sanitizer_common/sanitizer_common.cc +++ lib/sanitizer_common/sanitizer_common.cc @@ -300,9 +300,9 @@ } } -void NOINLINE __sanitizer_sandbox_on_notify(void *reserved) { - (void)reserved; - PrepareForSandboxing(); +void NOINLINE +__sanitizer_sandbox_on_notify(__sanitizer_sandbox_arguments *args) { + PrepareForSandboxing(args); if (sandboxing_callback) sandboxing_callback(); } Index: lib/sanitizer_common/sanitizer_coverage.cc =================================================================== --- lib/sanitizer_common/sanitizer_coverage.cc +++ lib/sanitizer_common/sanitizer_coverage.cc @@ -43,7 +43,7 @@ atomic_uint32_t dump_once_guard; // Ensure that CovDump runs only once. // pc_array is the array containing the covered PCs. -// To make the pc_array thread- and AS- safe it has to be large enough. +// To make the pc_array thread- and async-signal-safe it has to be large enough. // 128M counters "ought to be enough for anybody" (4M on 32-bit). // pc_array is allocated with MmapNoReserveOrDie and so it uses only as // much RAM as it really needs. @@ -51,6 +51,10 @@ static uptr *pc_array; static atomic_uintptr_t pc_array_index; +static bool cov_sandboxed = false; +static int cov_fd = kInvalidFd; +static unsigned int cov_max_block_size = 0; + namespace __sanitizer { // Simply add the pc into the vector under lock. If the function is called more @@ -71,8 +75,56 @@ return a < b; } +// Block layout for packed file format: header, followed by module name (no +// trailing zero), followed by data blob. +struct CovHeader { + int pid; + unsigned int module_name_length; + unsigned int data_length; +}; + +static void CovWritePacked(int pid, const char *module, const void *blob, + unsigned int blob_size) { + CHECK_GE(cov_fd, 0); + unsigned module_name_length = internal_strlen(module); + CovHeader header = {pid, module_name_length, blob_size}; + + if (cov_max_block_size == 0) { + // Writing to a file. Just go ahead. + internal_write(cov_fd, &header, sizeof(header)); + internal_write(cov_fd, module, module_name_length); + internal_write(cov_fd, blob, blob_size); + } else { + // Writing to a socket. We want to split the data into appropriately sized + // blocks. + InternalScopedBuffer block(cov_max_block_size); + CHECK_EQ((uptr)block.data(), (uptr)(CovHeader *)block.data()); + uptr header_size_with_module = sizeof(header) + module_name_length; + CHECK_LT(header_size_with_module, cov_max_block_size); + unsigned int max_payload_size = + cov_max_block_size - header_size_with_module; + char *block_pos = block.data(); + internal_memcpy(block_pos, &header, sizeof(header)); + block_pos += sizeof(header); + internal_memcpy(block_pos, module, module_name_length); + block_pos += module_name_length; + char *block_data_begin = block_pos; + char *blob_pos = (char *)blob; + while (blob_size > 0) { + unsigned int payload_size = Min(blob_size, max_payload_size); + blob_size -= payload_size; + internal_memcpy(block_data_begin, blob_pos, payload_size); + blob_pos += payload_size; + ((CovHeader *)block.data())->data_length = payload_size; + internal_write(cov_fd, block.data(), + header_size_with_module + payload_size); + } + } +} + // Dump the coverage on disk. -void CovDump() { +static void CovDump() { + if (!common_flags()->coverage) return; #if !SANITIZER_WINDOWS if (atomic_fetch_add(&dump_once_guard, 1, memory_order_relaxed)) return; @@ -81,7 +133,7 @@ InternalMmapVector offsets(size); const uptr *vb = pc_array; const uptr *ve = vb + size; - MemoryMappingLayout proc_maps(/*cache_enabled*/false); + MemoryMappingLayout proc_maps(/*cache_enabled*/true); uptr mb, me, off, prot; InternalScopedBuffer module(4096); InternalScopedBuffer path(4096 * 2); @@ -102,22 +154,57 @@ offsets.push_back(static_cast(diff)); } char *module_name = StripModuleName(module.data()); - internal_snprintf((char *)path.data(), path.size(), "%s.%zd.sancov", - module_name, internal_getpid()); - InternalFree(module_name); - uptr fd = OpenFile(path.data(), true); - if (internal_iserror(fd)) { - Report(" CovDump: failed to open %s for writing\n", path.data()); + if (cov_sandboxed) { + CovWritePacked(internal_getpid(), module_name, offsets.data(), + offsets.size() * sizeof(u32)); + VReport(1, " CovDump: %zd PCs written to packed file\n", vb - old_vb); } else { - internal_write(fd, offsets.data(), offsets.size() * sizeof(u32)); - internal_close(fd); - VReport(1, " CovDump: %s: %zd PCs written\n", path.data(), vb - old_vb); + // One file per module per process. + internal_snprintf((char *)path.data(), path.size(), "%s.%zd.sancov", + module_name, internal_getpid()); + uptr fd = OpenFile(path.data(), true); + if (internal_iserror(fd)) { + Report(" CovDump: failed to open %s for writing\n", path.data()); + } else { + internal_write(fd, offsets.data(), offsets.size() * sizeof(u32)); + internal_close(fd); + VReport(1, " CovDump: %s: %zd PCs written\n", path.data(), + vb - old_vb); + } } + InternalFree(module_name); } } + if (cov_fd >= 0) + internal_close(cov_fd); #endif // !SANITIZER_WINDOWS } +static void OpenPackedFileForWriting() { + CHECK(cov_fd == kInvalidFd); + InternalScopedBuffer path(1024); + internal_snprintf((char *)path.data(), path.size(), "%zd.sancov.packed", + internal_getpid()); + uptr fd = OpenFile(path.data(), true); + if (internal_iserror(fd)) { + Report(" Coverage: failed to open %s for writing\n", path.data()); + Die(); + } + cov_fd = fd; +} + +void CovPrepareForSandboxing(__sanitizer_sandbox_arguments *args) { + if (!args) return; + if (!common_flags()->coverage) return; + cov_sandboxed = args->coverage_sandboxed; + if (!cov_sandboxed) return; + cov_fd = args->coverage_fd; + cov_max_block_size = args->coverage_max_block_size; + if (cov_fd < 0) + // Pre-open the file now. The sandbox won't allow us to do it later. + OpenPackedFileForWriting(); +} + } // namespace __sanitizer extern "C" { Index: lib/sanitizer_common/sanitizer_internal_defs.h =================================================================== --- lib/sanitizer_common/sanitizer_internal_defs.h +++ lib/sanitizer_common/sanitizer_internal_defs.h @@ -91,11 +91,15 @@ SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_set_report_path(const char *path); - // Notify the tools that the sandbox is going to be turned on. The reserved - // parameter will be used in the future to hold a structure with functions - // that the tools may call to bypass the sandbox. - SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE - void __sanitizer_sandbox_on_notify(void *reserved); + typedef struct { + int coverage_sandboxed; + int coverage_fd; + unsigned int coverage_max_block_size; + } __sanitizer_sandbox_arguments; + + // Notify the tools that the sandbox is going to be turned on. + SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE void + __sanitizer_sandbox_on_notify(__sanitizer_sandbox_arguments *args); // This function is called by the tool when it has just finished reporting // an error. 'error_summary' is a one-line string that summarizes Index: lib/sanitizer_common/sanitizer_linux.cc =================================================================== --- lib/sanitizer_common/sanitizer_linux.cc +++ lib/sanitizer_common/sanitizer_linux.cc @@ -390,7 +390,7 @@ } #endif // SANITIZER_GO -void PrepareForSandboxing() { +void PrepareForSandboxing(__sanitizer_sandbox_arguments *args) { // Some kinds of sandboxes may forbid filesystem access, so we won't be able // to read the file mappings from /proc/self/maps. Luckily, neither the // process will be able to load additional libraries, so it's fine to use the @@ -401,6 +401,7 @@ if (Symbolizer *sym = Symbolizer::GetOrNull()) sym->PrepareForSandboxing(); #endif + CovPrepareForSandboxing(args); } enum MutexState { Index: lib/sanitizer_common/sanitizer_mac.cc =================================================================== --- lib/sanitizer_common/sanitizer_mac.cc +++ lib/sanitizer_common/sanitizer_mac.cc @@ -191,7 +191,8 @@ UNIMPLEMENTED(); } -void PrepareForSandboxing() { +void PrepareForSandboxing(__sanitizer_sandbox_arguments *args) { + (void)args; // Nothing here for now. } Index: lib/sanitizer_common/sanitizer_win.cc =================================================================== --- lib/sanitizer_common/sanitizer_win.cc +++ lib/sanitizer_common/sanitizer_win.cc @@ -192,7 +192,8 @@ UNIMPLEMENTED(); } -void PrepareForSandboxing() { +void PrepareForSandboxing(__sanitizer_sandbox_arguments *args) { + (void)args; // Nothing here for now. } Index: lib/sanitizer_common/scripts/sancov.py =================================================================== --- lib/sanitizer_common/scripts/sancov.py +++ lib/sanitizer_common/scripts/sancov.py @@ -4,6 +4,7 @@ # We need to merge these integers into a set and then # either print them (as hex) or dump them into another file. import array +import struct import sys prog_name = ""; @@ -11,16 +12,16 @@ def Usage(): print >> sys.stderr, "Usage: \n" + \ " " + prog_name + " merge file1 [file2 ...] > output\n" \ - " " + prog_name + " print file1 [file2 ...]\n" + " " + prog_name + " print file1 [file2 ...]\n" \ + " " + prog_name + " unpack file1 [file2 ...]\n" exit(1) def ReadOneFile(path): - f = open(path, mode="rb") - f.seek(0, 2) - size = f.tell() - f.seek(0, 0) - s = set(array.array('I', f.read(size))) - f.close() + with open(path, mode="rb") as f: + f.seek(0, 2) + size = f.tell() + f.seek(0, 0) + s = set(array.array('I', f.read(size))) print >>sys.stderr, "%s: read %d PCs from %s" % (prog_name, size / 4, path) return s @@ -44,6 +45,36 @@ a = array.array('I', s) a.tofile(sys.stdout) + +def UnpackOneFile(path): + with open(path, mode="rb") as f: + print >> sys.stderr, "%s: unpacking %s" % (prog_name, path) + while True: + header = f.read(12) + if not header: return + if len(header) < 12: + break + pid, module_length, blob_size = struct.unpack('iII', header) + module = f.read(module_length) + blob = f.read(blob_size) + assert(len(module) == module_length) + assert(len(blob) == blob_size) + extracted_file = "%s.%d.sancov" % (module, pid) + print >> sys.stderr, "%s: extracting %s" % \ + (prog_name, extracted_file) + # The packed file may contain multiple blobs for the same pid/module + # pair. Append to the end of the file instead of overwriting. + with open(extracted_file, 'ab') as f2: + f2.write(blob) + # fail + raise Exception('Error reading file %s' % path) + + +def Unpack(files): + for f in files: + UnpackOneFile(f) + + if __name__ == '__main__': prog_name = sys.argv[0] if len(sys.argv) <= 2: @@ -52,5 +83,7 @@ PrintFiles(sys.argv[2:]) elif sys.argv[1] == "merge": MergeAndPrint(sys.argv[2:]) + elif sys.argv[1] == "unpack": + Unpack(sys.argv[2:]) else: Usage() Index: test/asan/TestCases/Linux/coverage-sandboxing.cc =================================================================== --- /dev/null +++ test/asan/TestCases/Linux/coverage-sandboxing.cc @@ -0,0 +1,84 @@ +// RUN: %clangxx_asan -mllvm -asan-coverage=2 -DSHARED %s -shared -o %T/libcoverage_sandboxing_test.so -fPIC +// RUN: %clangxx_asan -mllvm -asan-coverage=1 %s -o %t -Wl,-R,\$ORIGIN -L%T -lcoverage_sandboxing_test +// RUN: export ASAN_OPTIONS=coverage=1:verbosity=1 +// RUN: rm -rf %T/coverage_sandboxing_test +// RUN: mkdir %T/coverage_sandboxing_test && cd %T/coverage_sandboxing_test +// RUN: mkdir vanilla && cd vanilla +// RUN: %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-vanilla +// RUN: mkdir ../sandbox1 && cd ../sandbox1 +// RUN: %run %t a 2>&1 | FileCheck %s --check-prefix=CHECK-sandbox +// RUN: %sancov unpack coverage_sandboxing_test.sancov.packed +// RUN: mkdir ../sandbox2 && cd ../sandbox2 +// RUN: %run %t a b 2>&1 | FileCheck %s --check-prefix=CHECK-sandbox +// RUN: %sancov unpack coverage_sandboxing_test.sancov.packed +// RUN: cd .. +// RUN: %sancov print vanilla/libcoverage_sandboxing_test.so.*.sancov > vanilla.txt +// RUN: %sancov print sandbox1/libcoverage_sandboxing_test.so.*.sancov > sandbox1.txt +// RUN: %sancov print sandbox2/libcoverage_sandboxing_test.so.*.sancov > sandbox2.txt +// RUN: diff vanilla.txt sandbox1.txt +// RUN: diff vanilla.txt sandbox2.txt +// https://code.google.com/p/address-sanitizer/issues/detail?id=263 +// XFAIL: android + +#include +#include +#include +#include +#include + +#include + +#define bb0(n) \ + case n: \ + fprintf(stderr, "foo: %d\n", n); \ + break; + +#define bb1(n) bb0(n) bb0(n + 1) +#define bb2(n) bb1(n) bb1(n + 2) +#define bb3(n) bb2(n) bb2(n + 4) +#define bb4(n) bb3(n) bb3(n + 8) +#define bb5(n) bb4(n) bb4(n + 16) +#define bb6(n) bb5(n) bb5(n + 32) +#define bb7(n) bb6(n) bb6(n + 64) +#define bb8(n) bb7(n) bb7(n + 128) + +#ifdef SHARED +void foo(int i) { + switch(i) { + // 256 basic blocks + bb8(0) + } +} +#else +extern void foo(int i); + +int main(int argc, char **argv) { + assert(argc <= 3); + for (int i = 0; i < 256; i++) foo(i); + fprintf(stderr, "PID: %d\n", getpid()); + if (argc == 1) { + // Vanilla mode, dump to individual files. + return 0; + } + // Dump to packed file. + int fd = creat("coverage_sandboxing_test.sancov.packed", 0660); + __sanitizer_sandbox_arguments args = {0}; + args.coverage_sandboxed = 1; + args.coverage_fd = fd; + if (argc == 2) + // Write to packed file, do not split into blocks. + args.coverage_max_block_size = 0; + else if (argc == 3) + // Write to packed file, split into blocks (as if writing to a socket). + args.coverage_max_block_size = 100; + __sanitizer_sandbox_on_notify(&args); + return 0; +} +#endif + +// CHECK-vanilla: PID: [[PID:[0-9]+]] +// CHECK-vanilla: [[PID]].sancov: 1 PCs written +// CHECK-vanilla: .so.[[PID]].sancov: 258 PCs written + +// CHECK-sandbox: PID: [[PID:[0-9]+]] +// CHECK-sandbox: 258 PCs written to packed file Index: test/asan/lit.cfg =================================================================== --- test/asan/lit.cfg +++ test/asan/lit.cfg @@ -77,6 +77,14 @@ lit_config.fatal("Can't find script on path %r" % asan_symbolize) python_exec = get_required_attr(config, "python_executable") config.substitutions.append( ("%asan_symbolize", python_exec + " " + asan_symbolize + " ") ) +# Setup path to sancov.py script. +sanitizer_common_source_dir = os.path.join( + get_required_attr(config, "compiler_rt_src_root"), "lib", "sanitizer_common") +sancov = os.path.join(sanitizer_common_source_dir, "scripts", "sancov.py") +if not os.path.exists(sancov): + lit_config.fatal("Can't find script on path %r" % sancov) +python_exec = get_required_attr(config, "python_executable") +config.substitutions.append( ("%sancov", python_exec + " " + sancov + " ") ) # Determine kernel bitness if config.host_arch.find('64') != -1 and config.android != "TRUE":