How MemorySanitizer Detects Uninitialized Reads in Chromium
This article explains how MemorySanitizer (MSan) detects the use of uninitialized memory in specialized test builds of the Chromium web browser. It covers the core mechanism of bit-to-bit shadow memory tracking, how MSan propagates uninitialized states across memory operations, the specific conditions that trigger a crash report, and why Chromium requires fully instrumented system libraries to prevent false positives during testing.
Bit-to-Bit Shadow Memory
MemorySanitizer is a compiler-based tool integrated into Clang/LLVM. It operates by mapping every single bit of application memory to a corresponding bit in "shadow memory."
In a specialized Chromium MSan build, the compiler inserts
instrumentation instructions around every memory allocation and
deallocation. When memory is allocated on the stack or the heap, MSan
automatically marks the corresponding shadow bits as 1,
which means the memory is "poisoned" (uninitialized). When the browser
writes valid data to that memory address, the instrumentation updates
the shadow bits to 0, marking the memory as "unpoisoned"
(initialized).
Silent State Propagation
Simply reading or copying uninitialized memory does not immediately trigger an MSan error. C++ code frequently copies uninitialized padding bytes inside structures or moves unused variables around safely. Flagging every read would result in massive amounts of false positives.
Instead of crashing on a simple read, MSan propagates the uninitialized state. When data is copied from one memory address to another, the compiler instrumentation copies the corresponding shadow bits alongside the actual data. If an uninitialized variable is copied to a new location, the new location's shadow memory is also marked as uninitialized. Arithmetic and bitwise operations also compute and propagate shadow states based on the validity of their inputs.
Triggering the Error Report
MSan only halts the Chromium test execution and reports a bug when uninitialized memory is used in a way that affects the program's observable behavior. The compiler inserts verification checks directly before critical instructions to inspect the shadow bits. An error is triggered under three main conditions:
- Conditional Branches: An uninitialized value is
evaluated in an
ifstatement, ternary operator, loop condition, orswitchcase. Because the value is random garbage, the execution path becomes unpredictable. - Pointer Dereferencing: An uninitialized value is used as a memory address to read or write data, which could lead to memory corruption or segmentation faults.
- System Calls: Uninitialized data is passed as an argument into an operating system kernel call.
Origin Tracking
Because uninitialized memory can be copied multiple times before it is finally used in a branch or system call, knowing where the crash happened is often not enough to fix the bug.
To solve this, Chromium MSan builds enable origin tracking. This feature uses an additional section of shadow memory to store a 32-bit Origin ID for every four bytes of application memory. When uninitialized memory is created on the stack or heap, MSan records the allocation stack trace and assigns it an Origin ID. Every time the uninitialized data is copied, the Origin ID is updated to track the intermediate steps. When an error is finally triggered, MSan prints the exact line of code where the uninitialized value was used, alongside the historical stack traces showing where the uninitialized memory was originally created.
Full-System Instrumentation
MSan requires that every single piece of code executed by the process
be compiled with MSan instrumentation. If Chromium calls an external,
uninstrumented system library (such as the standard C library) to write
data into a buffer, MSan has no way of knowing that the memory was
updated. The shadow bits will remain marked as 1
(uninitialized). When Chromium later reads that buffer, MSan will
trigger a false positive crash.
To prevent this, Chromium's MSan build configuration does not use the
host machine's default system libraries. Instead, the build system
downloads and compiles a specialized, fully instrumented collection of
third-party dependencies and standard libraries (like
libc++). This ensures that every memory write—from deep
within the system libraries up to the browser's user interface—is
accurately tracked in shadow memory.