- Browsers allocate ArrayBuffers in a page-aligned manner (at the start of a new physical page, and with the least significant 12 bits set to ‘0’)
- Browsers use mmap to allocate larger chunks of memory
- mmap is optimized to allocate 2 MB transparent huge pages (THP).
This is where the side-channel attack kicks in. The physical pages in a THP are mapped on demand (when the first access occurs). So, as you iterate over the array indices in your ArrayBuffer, when you get to the first new physical page will incur a page fault, and accessing the underlying data will take much longer (it’s not just memory access, y’see?). And so, you — or more to the point, the attacker — now knows the index at which a new page starts, a page that has the 21 least significant bits set to ‘0’.
As these physical pages are mapped on demand, i.e., as soon as the first access to the page occurs, iterating over the array indices results in page faults at the beginning of a new page. The time to resolve a page fault is significantly higher than a normal memory access. Thus, an attacker knows the index at which a new 2 MB page starts. At this array index, the underlying physical page has the 21 least significant bits set to ‘0’.
Nifty, isn’t it? And one of those things that you have to be a really sneaky bugger to have thought of in the first place 😮.
- Buffer ASLR: When you ask for an ArrayBuffer, behind the scenes, the request adds an extra 4KB. The array you get back is randomly located within that “size + 4KB” array that was actually allocated. This prevents attackers from assuming that the array starts at a new physical page.
- Preloading: When the array is allocated, the system proceeds to iterate through it, triggering all the page-faults. That way, the user can’t trigger page-faults by iterating through the array (it’s already loaded in memory!), and finding page boundaries
- Non-determinism: The array’s setter is modified so that each access also involves an access to a random location in the same array. That way, the attacker can’t know the actual index at which a page-fault was triggered (it prevents the user from just waiting for the Preloaded pages to be swapped out)
There’s more — much more — including Array Index Randomization, Timestamp manipulation, Multithreading updates, and more, but the above should serve to explain both how the attacks work, and how some of the attacks can be prevented.
Performance is remarkably good too, largely because JITs are constantly optimizing this stuff. The bottom line is that, by and large, people had no idea that this was running in their browsers.