Source-linked AI summary
SoK: Sanitizing for Security
Dokyung Song, Julian Lettner, Prabhu Rajasekaran, Yeoul Na, Stijn Volckaert, Per Larsen, Michael Franz
TL;DR
C and C++ remain indispensable but expose developers to memory-safety and undefined-behavior risks, motivating dynamic bug-finding tools. This paper systematically surveys sanitizers, their vulnerability coverage, performance, compatibility, and trade-offs. It identifies both widely adopted tools and important coverage and deployment gaps.
Problem
C and C++ are widely used despite security risks, while sanitizers are widespread but insufficiently understood for further adoption and development.
Method
The paper taxonomizes sanitizers and covered vulnerabilities, and analyzes their performance, compatibility, metadata schemes, and design trade-offs.
Results
AddressSanitizer is the most widely adopted sanitizer, while MSan and UBSan face higher false-positive and deployment burdens.
Takeaways & Limitations
Developers can readily adopt ASan or Memcheck, but uncovered vulnerabilities and scaling problems leave important gaps requiring further research.
Abstract
from arXiv · showhide
The C and C++ programming languages are notoriously insecure yet remain indispensable. Developers therefore resort to a multi-pronged approach to find security issues before adversaries. These include manual, static, and dynamic program analysis. Dynamic bug finding tools --- henceforth "sanitizers" --- can find bugs that elude other types of analysis because they observe the actual execution of a program, and can therefore directly observe incorrect program behavior as it happens. A vast number of sanitizers have been prototyped by academics and refined by practitioners. We provide a systematic overview of sanitizers with an emphasis on their role in finding security issues. Specifically, we taxonomize the available tools and the security vulnerabilities they cover, describe their performance and compatibility properties, and highlight various trade-offs.
I. INTRODUCTION
C and C++ remain indispensable for low-level systems software, but their flexibility leaves programmers responsible for preventing memory errors and undefined behavior. This paper surveys sanitizers as dynamic bug-finding tools and organizes their coverage, properties, and trade-offs.
- Motivation: C and C++ power kernels, runtime libraries, and browsers because they are efficient and provide direct hardware control.Those benefits also require programmers to ensure valid memory accesses and avoid undefined behavior.
- Motivation: Memory corruption exploits increasingly bypass mitigations such as ASLR and DEP through code-reuse and data-only attacks.ROP hijacks control flow by corrupting control data, while DOP corrupts non-control data along legal control-flow paths.
- Sanitizers: Static analysis is conservatively correct across executions, whereas sanitizers analyze one execution and produce a precise result for that run.Sanitizers therefore observe incorrect behavior as it occurs during execution.
- Scope and contributions: The paper taxonomizes sanitizers and covered vulnerabilities, then compares their performance, compatibility, and trade-offs.It also proposes deployment directions for developers and research directions for researchers.
- Sanitizers versus exploit mitigations: Sanitizers pinpoint buggy statements, unlike exploit mitigations that detect or prevent attacks after deviations from legal control or data flows.Sanitizers can tolerate some false alerts because they are primarily used for testing, while production mitigations require stricter runtime and false-positive constraints.
- Vulnerability scope: The survey covers undefined behavior and well-defined C/C++ behaviors with security implications, including bugs exploitable for information leaks, privilege escalation, or arbitrary code execution.Memory safety violations include spatial and temporal violations involving intended referents and their validity.
B. Use of Uninitialized Variables
The section describes uninitialized values, unsafe type conversions, variadic misuse, integer overflows, and optimization-related undefined behavior as security-relevant C/C++ problems. These issues can propagate unintended data, corrupt memory, or cause compilers to remove security checks.
- Uninitialized values: Uninitialized values may propagate in limited C++14 cases, but other uses produce undefined behavior whose effects depend on the compiler and compilation flags.Partially initialized data can become vulnerable when sent across a trust boundary.
- Type errors: Unsafe casts and type punning can make accesses interpret referents incorrectly, producing type- and memory-unsafe behavior.A bad downcast can write beyond the actual object and overwrite security-critical data such as a vtable pointer.
- Variadic functions: Variadic functions do not specify the number or types of variadic arguments, leaving va_arg-dependent expectations difficult to verify statically.User-controlled format strings can expose stack contents or overwrite memory through format specifiers.
- Integer overflow: Integer overflow can produce undersized allocations or bypass array-index checks when attacker-controlled values determine buffer sizes or indices.Signed overflow is undefined, while unsigned wrap-around is defined but often unintended and potentially dangerous.
- Compiler interaction: Compiler optimizations may omit security checks when they assume undefined behavior is unreachable.The paper cites a Linux kernel privilege-escalation vulnerability in which GCC omitted a null-pointer check.
III. BUG FINDING TECHNIQUES
Memory-safety sanitizers use location-based or identity-based metadata checks to detect invalid accesses. The main design trade-offs involve precision, compatibility, runtime cost, and memory overhead.
- Location-based access checkers: Location-based checkers track validity for address-space regions and detect accesses to invalid memory using red zones or guard pages.They can combine these mechanisms with delayed memory reuse to detect some temporal violations.
- Location-based access checkers: Location-based checkers offer low runtime overhead and compatibility with non-instrumented code, but they are imprecise and generally memory-intensive.They detect whether an address is valid, not whether it belongs to the intended referent.
- Identity-based access checkers: Identity-based checkers maintain bounds or allocation metadata for objects or pointers and determine whether accesses target their intended referents.Per-pointer tracking propagates metadata through pointer creation, arithmetic, and assignment.
- Location-based access checkers: Red-zone insertion has low runtime overhead but misses illegal accesses into valid objects and generally cannot detect intra-object overflows.Adding subobject red zones would create excessive memory overhead.
- Identity-based access checkers: Per-pointer bounds tracking can detect complete spatial violations, including intra-object overflows, but typically has poor compatibility and high runtime overhead.Non-instrumented libraries generally cannot propagate or update the required bounds information correctly.
- Identity-based access checkers: Per-object bounds tracking improves compatibility with partially instrumented programs but cannot reliably link pointers to their intended referents.Some schemes encode out-of-bounds relationships or metadata in specialized representations, which can affect compatibility with external code.
2) Temporal Memory Safety Violations:
Temporal-safety sanitizers detect dangling-pointer use through delayed memory reuse, allocation keys, pointer tagging, or object-to-pointer tracking. These techniques trade detection coverage and precision against memory, performance, and compatibility costs.
- Memory-reuse delay: Memory-reuse delay marks freed objects invalid and detects dangling dereferences while the memory remains unreused.Longer delays increase memory overhead but also increase the chance of detecting dangling-pointer dereferences.
- Memory-reuse delay: Static analysis can determine when freed memory is safe to reuse while using virtual-page remapping and guard pages for temporal checking.Dhurjati and Adve’s design shares physical pages while separating virtual pages into heap pools.
- Identity-based temporal checking: Lock-and-key checking assigns allocation identifiers to objects and pointers, then detects dereferences when the pointer’s key no longer matches its object’s lock.The lock’s key is revoked when the associated object is deallocated.
- Dangling-pointer tagging: Dangling-pointer tagging can invalidate the pointer passed to free, but straightforward tagging does not affect copies of that pointer.More comprehensive tools maintain auxiliary object-to-pointer maps or use taint tracking to find copied dangling pointers.
- Dangling-pointer tagging: Compile-time pointer-map approaches invalidate tracked pointers after deallocation so later dereferences trigger hardware traps.They include DangNull, FreeSentry, and DangSan.
- Dangling-pointer tagging: Non-taint-tracking tagging tools require source and precise type information, mishandle type-unsafe pointer copies, and cannot track pointers stored only in registers.These constraints limit their coverage of dangling pointers.
B. Use of Uninitialized Variables
This section surveys sanitizers for uninitialized values, unsafe pointer types, variadic misuse, and other undefined behavior. It emphasizes how detection mechanisms balance precision, coverage, and language compatibility.
- Uninitialized values: Uninitialized-memory detectors mark newly allocated regions as uninitialized and warn when instrumented reads access them.Writes clear the corresponding uninitialized flags.
- Uninitialized values: Memcheck instead reports uses of undefined values, reducing false positives from permitted propagation such as copying partially uninitialized structures.It limits reporting to dereferences and other uses rather than every read.
- Pointer type errors: Pointer casting monitors detect illegal C++ static_cast downcasts by comparing the target type with run-time type information.RTTI-based tools cannot verify casts between non-polymorphic types lacking RTTI.
- Pointer type errors: Custom type metadata extends casting checks to non-polymorphic types, while pointer-use monitors check run-time type tags on memory loads and stores.Type-use monitoring can avoid false positives from casts that are never dereferenced, but type-tag policies must respect language-permitted accesses.
- Other misuse and undefined behavior: Sanitizers also detect indirect function-call type mismatches, variadic argument errors, and diverse undefined behaviors such as integer overflow or null dereferences.FormatGuard counts retrieved variadic arguments, while HexVASAN generalizes argument counting and adds type checking.
IV. PROGRAM INSTRUMENTATION
Sanitizers can be inserted at language, IR, binary, run-time, or library-call levels, each trading semantic information, compatibility, coverage, and overhead. Metadata representation likewise affects lookup cost, memory use, and practicality.
- Instrumentation levels: Inlined reference monitors mediate instructions relevant to vulnerabilities, including memory accesses, allocation operations, calls, and system calls.They can be embedded by a compiler, linker, or instrumentation framework.
- Compiler instrumentation: Language-level instrumentation preserves type information and intended semantics, whereas IR-level instrumentation is more generic and can reuse compiler analyses and optimizations.IR-level instrumentation has limited support for closed-source libraries and inline assembly.
- Binary instrumentation: Dynamic binary translation supports closed-source programs and complete user-mode coverage, but incurs much higher run-time overhead than static instrumentation.Binary-level tools also lack type information and language syntax, preventing pointer-type checks and fully precise spatial safety instrumentation.
- Library interposition: Library interposition works with COTS binaries and incurs virtually no overhead, but only intercepts inter-library calls and is platform- and target-specific.A malloc interposer does not work when a program uses its own allocator.
- Metadata storage: Direct-mapped shadow metadata enables one-read lookups, while multi-level object metadata requires multiple memory accesses and can significantly affect performance.Shadow memory can waste allocation space and worsen fragmentation; multi-level storage fits tools with infrequent lookups and constant-sized metadata.
B. Pointer Metadata
Pointer metadata can be stored inside pointers or in disjoint structures. The trade-off is between metadata capacity and cache behavior on one side, and instrumentation, calling-convention, and propagation compatibility on the other.
- In-pointer metadata: Fat pointers pair the original pointer with metadata such as the referent base and size.They can store arbitrary metadata without much additional cache pressure compared with regular pointers.
- In-pointer metadata: Fat pointers require extensive instrumentation, alter pointer-argument calling conventions, and cannot interact directly with non-instrumented code.These compatibility costs constrain their use across instrumented and non-instrumented components.
- In-pointer metadata: Tagged pointers embed metadata without changing pointer size, improving compatibility because standard calling conventions remain unchanged.They provide a less invasive alternative to fat pointers.
- Disjoint metadata: Disjoint metadata improves compatibility over in-pointer representations but requires explicit metadata propagation whenever pointers are copied.Copies through memcpy require updating metadata for pointers in the destination structure.
- Metadata organization: Two-level structures can maintain per-pointer bounds or allocation identifiers, while compile-time static metadata supports run-time checks such as type-cast validation.The latter embeds information discarded during compilation into the generated program.
VI. DRIVING A SANITIZER
Driving a sanitizer requires executing relevant code paths, so testing strategy determines bug-finding opportunities. The section compares tests, fuzzing, and beta testing while documenting false-positive and false-negative limits.
- Driving execution: Dynamic analysis detects bugs only on executed paths, making path coverage central to sanitizer effectiveness.Execution can be driven by unit or integration tests, fuzzers, or alpha and beta testers.
- Testing strategies: Hand-written tests often emphasize valid inputs and leave code paths uncovered, while automated generators can help when full source code is available.Security bugs are typically exploited through invalid inputs.
- Testing strategies: Fuzzers automate negative testing with generated inputs and can quickly find bugs on easily accessible code paths.Their advantages are automatic operation after integration and frequent use of invalid inputs.
- Testing strategies: Beta testing distributes the testing load, but users tend to focus on main scenarios and sanitizer overhead can reduce thorough testing.Consumer-grade machines may become unusable when sanitization slows programs substantially.
- Analysis quality: Sanitizer practicality depends strongly on minimizing false positives, because developers must review reported bugs; false negatives are a secondary concern.The paper identifies policy and mechanism mismatches as recurring sources of both false positives and false negatives.
- False positives: Sanitizers can report false positives when stricter policies reject language- or practice-permitted behavior, including temporary out-of-bounds pointers or uninitialized reads.Examples include placement-new type reuse and aliasing conversions between nonidentical types.
- False negatives: Red-zone and guard-page mechanisms miss accesses beyond the protected boundary and intra-object overflows between subobjects.These mechanisms detect only accesses targeting the adjacent red-zone or guard page.
C. Incomplete Instrumentation
Incomplete instrumentation limits static sanitizers when programs generate code at runtime, use uninstrumentable external libraries, or contain unsupported inline assembly. Runtime binary instrumentation can address coverage gaps but sacrifices accurate type information.
- C. Incomplete Instrumentation: Static instrumentation cannot fully support just-in-time code generation or external libraries whose source code cannot be instrumented.Compiler-IR instrumentation may also fail to support inline assembly that is not translated into compiler IR.
- C. Incomplete Instrumentation: Uninstrumented libraries can prevent sanitizers from propagating pointer metadata, producing false negatives or false positives.Missing metadata may be absent from a store or outdated.
- C. Incomplete Instrumentation: Dynamic binary instrumentation can overcome these coverage problems, but it cannot provide accurate type information and therefore excludes some sanitizer types.Pointer-casting monitors are an example of the unsupported types.
D. Thread Safety
Metadata-based sanitizers face thread-safety challenges when metadata access is unsafe or metadata updates are not atomic with program updates. These conditions can create both false positives and false negatives.
- D. Thread Safety: Metadata-based sanitizers can produce false positives and false negatives in multithreaded programs.The causes include thread-unsafe metadata access and failure to update metadata in the same transaction as associated atomic program updates.
- D. Thread Safety: FreeSentry cannot support multithreaded programs because of thread-safety problems in its metadata handling.The passage identifies unsafe metadata access as affecting FreeSentry.
- D. Thread Safety: Atomic program updates to pointers or objects may become inconsistent with sanitizer metadata when both are not updated in the same transaction.This inconsistency is one reported source of inaccurate detections.
E. Performance Overhead
Sanitizer overhead depends on acceptable testing costs, instrumentation strategy, metadata design, checking frequency, and memory footprint. Deployment evidence shows that compatibility and false-positive behavior often matter more than maximum speed.
- E. Performance Overhead: Less than 3x overhead is common among widely used sanitizers, while up to 20x can be acceptable when source is unavailable or code is generated on the fly.Exploit mitigations typically require overhead below 5%, whereas sanitizers are used for testing.
- E. Performance Overhead: 25.7x overhead on SPEC2000 benchmarks illustrates the high instrumentation cost of dynamic binary instrumentation in Valgrind Memcheck.Most statically instrumented sanitizers have zero runtime instrumentation cost; dynamic instrumentation can be very high.
- E. Performance Overhead: Embedded metadata and tagged or fat pointers generally reduce cache pressure compared with disjoint or shadow metadata schemes.Tagged or fat pointers also propagate metadata automatically when pointers or objects are copied, but can be incompatible with incompletely instrumented programs.
- E. Performance Overhead: Checking cost rises with monitoring frequency, so memory error detectors generally cost more than type-casting checkers.Memory error detectors often monitor all memory accesses or pointer-arithmetic operations, although selective instrumentation is possible.
- E. Performance Overhead: 3.37x average memory usage on SPEC2006 benchmarks is reported for ASan because it adds red zones and shadow addressability metadata.Larger allocations and disjoint or shadow metadata can create sizable footprints, especially on 32-bit platforms.
- E. Performance Overhead: ASan is used in 24 top C and 19 top C++ GitHub projects, making it the most widely adopted sanitizer in the study.The paper attributes adoption to memory-safety coverage, compatibility, low false-positive rates, compiler integration, and scalability.
- E. Performance Overhead: Memcheck and Dr. Memory can instrument complete programs without source code, but their real-world adoption trails ASan.Memcheck was popular before ASan entered LLVM and GCC, while Dr. Memory never reached comparable adoption.
- E. Performance Overhead: MSan and UBSan have lower adoption partly because users report high false-positive rates and substantial effort to instrument dependencies or maintain suppressions.Chromium requires whole-program instrumentation for MSan and extensive suppressions for UBSan.
C. Deployment Directions
Deployment favors sanitizers that are easy to enable, compatible with partially instrumented programs, and unlikely to produce false positives. The paper therefore points toward broader composition, hardware support, and lower-footprint sanitization for uncovered software and bug classes.
- C. Deployment Directions: Deployed sanitizers are easy to use because they can be enabled by compiler flag or applied directly to any binary.Clang sanitizers use compiler flags, while Memcheck can operate on binaries.
- C. Deployment Directions: Fewer false positives correlate with higher adoption, while performance overhead is tolerated when no faster alternative is available.The paper contrasts ASan and Memcheck with MSan and UBSan, and Memcheck with faster ASan.
- C. Deployment Directions: ASan and Memcheck can be adopted with little effort, but they do not detect every class of memory-safety violation.MSan and UBSan require extensive recompilation, blacklisting, or annotation to control false positives.
- C. Deployment Directions: Intra-object overflows and type errors caused by type punning remain without viable sanitizer options because existing research prototypes do not scale to real-world code bases.Pointer-use monitoring is described as a way to detect illegal dereferences that casting monitors can miss.
- C. Deployment Directions: ASan is more deployed than more precise memory-vulnerability sanitizers, which the paper attributes primarily to compatibility with language standards and partially instrumented programs.The paper encourages making other sanitizers equally compatible.
- C. Deployment Directions: Incompatible metadata schemes currently prevent embedding multiple sanitizers together, forcing repeated testing runs and additional developer effort.The paper encourages generic metadata schemes and parallel multi-variant execution as alternatives.
- C. Deployment Directions: Hardware support may improve sanitizer compatibility and precision while reducing performance and memory costs.The paper describes ARM address tagging and hardware-assisted ASan as examples for spatial and temporal memory safety.
- C. Deployment Directions: Kernels, device drivers, and hypervisors lack traditional sanitizer benefits, while their memory constraints make user-space footprints of 3x or more difficult to accept.Efforts are underway to bring ASan and MSan to the Linux kernel.
APPENDIX A
The appendix evaluates sanitizer performance and false positives using SPEC CPU2006 benchmarks, standardized baselines, and tool-specific configurations. It reports normalized overheads and false positives while documenting inclusion and exclusion decisions.
- Evaluation setup: 10 sanitizers were evaluated using automated SPEC CPU2006 benchmarking procedures.The experiments covered all 19 C/C++ benchmarks, or all 7 C++ benchmarks for type-casting sanitizers.
- Evaluation setup: Sanitizers were included when actively maintained or published within the preceding decade, while unavailable, nonfunctional, or insufficiently supported tools were excluded.Some tools were excluded because their authors did not provide source access, they failed to compile or run more than half the benchmarks, or they did not support baseline binaries.
- Experimental platform: The experiments used an Intel Xeon E5-2660 host with 64GB RAM and 64-bit Ubuntu 14.04.5 LTS.Unless stated otherwise, system-default distribution libraries were used.
- Measurement procedure: Each benchmark was run three times with and without sanitization, and median results were normalized to the median baseline.Table III summarizes the resulting overheads and false positives.
- Tool configurations: Tool-specific measurement choices included compiler versions, sanitizer flags, allocator settings, patches, and disabled checks.For example, AddressSanitizer binaries used -fsanitize=address, while leak and allocation-mismatch detection were disabled.
4) DangSan:
This section describes how the evaluated sanitizers were compiled and configured, including DangSan, MemorySanitizer, type-casting tools, CFI, HexVASAN, and UBSan. The configurations adapt baselines and instrumentation to each tool's compiler, allocator, and known false-positive conditions.
- DangSan: DangSan required LLVM/Clang 3.8.0, the GNU gold linker, link-time optimization, and tcmalloc for both instrumented and baseline binaries.The baseline omitted SafeStack because it incurs overhead.
- DangSan: Known false-positive conditions were handled with pointer-unmasking patches and metadata-related annotations for selected benchmarks.The setup applied the authors' pointer-unmasking patch for 450.soplex and marked 400.perlbench as having false positives.
- MemorySanitizer: MemorySanitizer used LLVM/Clang 6.0.0, instrumented libcxx and libcxxabi, and disabled early termination for 403.gcc.Instrumented C++ libraries addressed a false positive in 450.soplex.
- Type-casting sanitizers: TypeSan and HexType used tool-specific LLVM/Clang versions, sanitizer flags, and allocator or coverage configurations.TypeSan matched tcmalloc in the baseline, while HexType enabled all supported type-casting coverage and optimization features.
- Clang CFI: Clang CFI checked C++ casts and indirect or member-function calls, with diagnostic output enabled through -fno-sanitize-trap.The checks covered virtual and non-virtual C++ member-function calls.
- HexVASAN and UBSan: HexVASAN and UBSan used patched or official LLVM/Clang builds with tool-specific flags, while HexVASAN continued after known check failures for one benchmark.UBSan enabled a selected set of sanitizers through -fsanitize=undefined.