Skip to main content
Software-Based Defenses

Software-Based Defenses

657 words·4 mins
Memory Safety - This article is part of a series.
Part 2: This Article

The Defense-in-Depth Approach
#

Because no single technique eliminates all memory safety risks, modern systems employ multiple layers of defense. This page surveys the major software-based approaches — their strengths, overheads, and fundamental limitations.

Why this matters for AGIACC: software mitigations remain essential, but their cost and bypassability explain why hardware-rooted assurance is becoming commercially important.


Compile-Time and Language-Based Defenses
#

Memory-Safe Languages
#

Languages like Rust, Go, Java, Swift, and C# prevent memory safety violations through type systems, garbage collection, or ownership models.

LanguageSafety MechanismTrade-Off
RustOwnership + borrow checker (compile-time)Steep learning curve; FFI boundaries are unsafe
GoGarbage collection + bounds checksGC pauses; lower-level control sacrificed
Java/C#Managed runtime + GCJIT overhead; JNI/P/Invoke boundaries are unsafe
SwiftARC + bounds checksRuntime reference counting overhead

Limitation: All of these languages must interoperate with C/C++ libraries at their boundaries, inheriting memory-unsafety at FFI call sites.

Static Analysis
#

Tools like Coverity, Infer (Meta), and Clang Static Analyzer find potential memory errors at compile time by modelling program state.

  • Strengths: Zero runtime overhead; catches bugs before deployment.
  • Limitations: High false positive rates; cannot reason about all execution paths; misses bugs that depend on runtime input.

Runtime Sanitizers
#

AddressSanitizer (ASan)
#

ASan instruments memory accesses at compile time to detect spatial and temporal violations at runtime.

  • How it works: Maintains a shadow memory map. Every allocation is surrounded by “red zones.” Every access is checked against the shadow map.
  • Detects: Heap/stack/global buffer overflows, use-after-free, use-after-return, double-free.
  • Overhead: ~2× slowdown, 2–3× memory increase.
  • Use case: Testing and CI pipelines — not suitable for production deployment due to overhead.

MemorySanitizer (MSan)
#

Detects reads of uninitialised memory. Similar shadow-memory approach to ASan, with comparable overhead.

ThreadSanitizer (TSan)
#

Detects data races in multithreaded programs. Overhead of ~5–15× makes it testing-only.

UndefinedBehaviorSanitizer (UBSan)
#

Catches various forms of undefined behaviour (signed integer overflow, null pointer dereference, type mismatch). Low overhead (~20%); increasingly used in production.


Fuzzing
#

Fuzzing generates random or mutated inputs to trigger unexpected program behaviour, including memory safety bugs.

ToolApproachNotable Finds
AFL++Coverage-guided mutationThousands of CVEs across open-source projects
libFuzzerIn-process, LLVM-integratedIntegrated into Chrome, OpenSSL, etc.
OSS-FuzzGoogle’s continuous fuzzing service40,000+ bugs found in 1000+ open-source projects
HonggfuzzCoverage + hardware feedbackKernel and driver fuzzing

Limitation: Fuzzing is inherently incomplete — it cannot prove the absence of bugs, only find bugs that happen to be triggered by test inputs.


Runtime Exploit Mitigations
#

Address Space Layout Randomization (ASLR)
#

Randomises the base addresses of stack, heap, libraries, and executable segments. Forces attackers to guess memory layout.

  • Bypass: Information leaks (e.g., format string bugs) can reveal addresses. On 32-bit systems, the address space is small enough to brute-force.

Stack Canaries
#

Places a random “canary” value before the return address on the stack. If a buffer overflow corrupts the canary, the program aborts.

  • Bypass: Canary values can be leaked or brute-forced. Does not protect heap-based attacks.

Data Execution Prevention (DEP / W^X / NX)
#

Marks memory pages as either writable or executable, never both. Prevents classic code injection.

  • Bypass: Return-Oriented Programming (ROP) and Jump-Oriented Programming (JOP) reuse existing executable code gadgets.

Control Flow Integrity (CFI)
#

Restricts indirect control flow transfers (function pointers, virtual calls, returns) to a set of valid targets determined at compile time.

  • Fine-grained CFI (Clang CFI): validates the type of the target function at each indirect call site.
  • Coarse-grained CFI (Intel CET IBT): ensures indirect branches land on ENDBRANCH instructions — a weaker but hardware-accelerated variant.

The Fundamental Limitation
#

All software-based defenses share a common constraint: they run in the same address space and privilege level as the code they protect. An attacker who achieves arbitrary code execution can, in principle, disable or bypass any software-only defense.

This is why hardware-assisted approaches — which enforce safety properties below the software layer — offer fundamentally stronger guarantees.


Next: Hardware-Assisted Solutions Overview →

Memory Safety - This article is part of a series.
Part 2: This Article