Rowhammer.js: Breaking Hardware Security Guarantees from Your Browser
Hook
In 2015, researchers proved that rapidly accessing memory could physically corrupt bits in adjacent DRAM rows. Then they made it worse: they did it from JavaScript, turning every website into a potential attack vector.
Context
The Rowhammer vulnerability fundamentally challenges our assumptions about hardware reliability. Discovered by Carnegie Mellon researchers in 2014, it exploits a physical property of modern DRAM: cells are packed so densely that repeatedly accessing one row can induce electromagnetic interference in adjacent rows, causing bits to flip. Originally demonstrated with native code hammering memory at maximum speed, it represented a serious security concern but required software installation or compromised systems.
Rowhammer.js escalates this threat dramatically. Developed by researchers at Graz University of Technology, it proves that Rowhammer attacks can execute entirely within JavaScript running in a web browser. This transforms Rowhammer from a local privilege escalation technique into a remote attack that requires nothing more than visiting a malicious webpage. The implications are staggering: JavaScript's managed memory model and browser sandboxing—designed specifically to prevent memory manipulation—can still be weaponized to corrupt physical hardware. The project bridges three distinct security domains: hardware vulnerabilities, browser security, and cache timing side-channels.
Technical Insight
The brilliance of Rowhammer.js lies in overcoming JavaScript's fundamental constraint: you cannot directly control physical memory addresses. The attack requires a multi-stage approach combining native reconnaissance tools with pure JavaScript exploitation code.
The first stage uses C++ programs to identify vulnerable memory regions. The rowhammer_test tool systematically allocates memory, hammers adjacent rows using double-sided attacks, and records which physical addresses produce bit flips. This preprocessing identifies the "victim" addresses where corruption occurs and the "aggressor" row pairs that trigger it:
// Simplified concept from native tooling
for (int i = 0; i < NUM_READS; i++) {
*aggressor_row1;
*aggressor_row2;
clflush(aggressor_row1); // Evict from cache
clflush(aggressor_row2);
}
if (victim_row_changed) {
// Record physical addresses for JS mapping
}
The critical innovation comes in the second stage: mapping physical addresses to JavaScript array indices. The watch_firefox tool monitors Firefox's memory allocations to correlate physical address ranges with JavaScript-visible TypedArray indices. When JavaScript allocates a large ArrayBuffer, the browser requests physically contiguous memory from the OS (typically 2MB transparent huge pages). By tracking these allocations, the tool builds a translation table that tells the JavaScript code "index 524288 in your array corresponds to physical address 0x12340000."
The pure JavaScript attack then implements cache eviction without native instructions. Since CPU caches prevent DRAM access (no access, no hammer), the code must evict target addresses from cache using only JavaScript. It does this through eviction sets—groups of memory locations that map to the same cache set due to cache associativity:
// Evict a target address from cache using eviction set
function evict(target_idx, eviction_set) {
for (var i = 0; i < eviction_set.length; i++) {
var dummy = array[eviction_set[i]];
}
}
// Rowhammer attack loop
function hammer(aggressor1_idx, aggressor2_idx, eviction1, eviction2) {
for (var i = 0; i < iterations; i++) {
array[aggressor1_idx] = value; // Access first row
evict(aggressor1_idx, eviction1);
array[aggressor2_idx] = value; // Access second row
evict(aggressor2_idx, eviction2);
}
}
The eviction set discovery itself is computationally expensive. The code must find sets of array indices that compete for the same L3 cache sets, which depends on the physical address mapping and cache organization. On Haswell CPUs with 16-way set-associative L3 cache, an eviction set requires at least 16 addresses that hash to the same cache set. The JavaScript variant includes an algorithm to discover these sets through timing measurements—repeatedly accessing candidate sets and measuring whether they successfully evict a known target based on subsequent access latency.
The attack's effectiveness depends on JavaScript engine optimizations not breaking the memory access patterns. Modern JIT compilers can eliminate seemingly redundant memory accesses. The code includes specific patterns to prevent optimization: writing values instead of just reading, mixing operation types, and maintaining dependencies between accesses. The timing must be tight enough to generate sufficient DRAM activations (typically millions of accesses within the refresh window) but not so tight that the browser's JavaScript engine throttles execution.
Finally, the project includes utilities for validating bit flips. Once hammering completes, the code checks victim row contents for unexpected changes. A single bit flip at a predictable location can be exploited to escape browser sandboxes, gain elevated privileges, or leak sensitive data. The researchers demonstrated practical attacks including page table manipulation and browser security bypass.
Gotcha
Rowhammer.js is impressively fragile. It works on a narrow slice of hardware configurations: specifically Haswell-generation CPUs with 16-way L3 cache, no L4 cache, and single-channel memory. DDR4 memory includes Target Row Refresh (TRR) mitigations that detect and prevent many Rowhammer patterns, rendering the attack ineffective on newer systems. Even on vulnerable hardware, success depends on OS behavior—you need transparent huge pages enabled for predictable 2MB contiguous allocations, which varies across Linux configurations and is even less reliable on Windows or macOS.
The timing requirements create reliability problems. JavaScript execution is non-deterministic; garbage collection, JIT compilation, browser background tasks, and system scheduling all introduce timing jitter that disrupts the precise memory access patterns needed. A GC pause during hammering can allow DRAM refresh cycles to restore corrupted bits, erasing progress. The pure JavaScript eviction strategy discovery can take hours or fail entirely if the memory allocation doesn't provide suitable address patterns. In practice, you'd need to run reconnaissance on each target system individually, making it impractical for wide-scale attacks. The project acknowledges this: it's a proof-of-concept demonstrating feasibility, not a weaponized exploit kit. Modern browsers have also implemented mitigations including reduced timer precision (making cache timing harder) and memory allocation randomization.
Verdict
Use if: You're a security researcher studying hardware vulnerabilities, browser security models, or cache side-channels; you need to demonstrate Rowhammer risk to stakeholders managing legacy Haswell-era systems; you're conducting academic research on JavaScript sandbox escapes or writing a paper on hardware-software security boundaries. This is an invaluable reference implementation showing how high-level languages can trigger low-level hardware vulnerabilities. Skip if: You need practical memory testing tools (use Google's rowhammer-test instead for reliable, cross-platform DRAM vulnerability assessment); you're working with modern hardware where TRR mitigations exist; you want production-ready exploit code (this requires extensive per-system tuning and fails on most configurations); you're looking for general-purpose browser security testing. Rowhammer.js is a landmark research artifact that changed threat models, but it's not a turnkey solution for any real-world security testing scenario.