We recommend to compile the experiments in this directory on the machine itself.
conditions.c implements the first experiment.
We execute GhostWrite on a cache line with specific states such as dirty or non-dirty.
Compile and run with:
make && sudo ./conditionsExpected output:
Virtual address: 3fdd3b2000
Physical address: 8a8a5000
[!] Initial memory cache state is uncached
===================================================
[+] Maccess (non-dirty) *before* + evict *after*
-> 10000/10000
[+] Maccess (non-dirty) *before* + flush *after*
-> 10000/10000
[+] Maccess (non-dirty) *before* + nothing *after*
-> 0/10000
===================================================
[+] Maccess (dirty) *before* + evict *after*
-> 0/10000
[+] Maccess (dirty) *before* + flush *after*
-> 0/10000
[+] Maccess (dirty) *before* + nothing *after*
-> 0/10000
===================================================
[+] Fence *before* and *after*
-> 0/10000
[+] Fence *before* and flush *after*
-> 10000/10000
===================================================
[+] Maccess + Flush *before* and M+F *after*
-> 10000/10000
[+] Maccess + IFlush *before*
-> 0/10000
This behavior undermines the hypothesis that GhostWrite interacts directly with physical memory and that the write does not travel through the CPU's cache hierarchy. For all experiments, we ensure that the attacked memory address is initially cached. The first 3 experiments show that after a GhostWrite write, the changed value is only observed when the attacked memory address is removed from the cache and thus served from main memory. The next 3 experiments show that when a dirty cache line is flushed or evicted and thus rewritten to main memory, it also overwrites the value injected by GhostWrite again. The next 2 experiments aim to show that the previous observations are not due to fencing behavior. The last 2 experiments show that the behavior requires an actual data flush and not an instruction flush.
perf.c implements the second experiment, querying performance counters.
Compile and run with:
make && sudo ./perfExpected output:
Virtual address: 3fc167f000
Physical address: 8e6f5000
===== L1d Misses =====
baseline: 0.007000
exploit: 0.000000
===== L2d Misses =====
baseline: 0.000000
exploit: 0.000000
===== dTLB Misses =====
baseline: 1.000000 (sum: 10000)
exploit: 1.000000 (sum: 10000)
This group of experiments aims to further strenghen the previous hypothesis. We analyze the performance counters for L1d and L2d misses together with the counters for dTLB misses. For none of these counters, we observe an increase when the GhostWrite primitive is executed. Thus, further hinting towards writes that completely skip the cache hierachy. As not even the dTLB is queried, which would be required for accesses to a virtual address, it hints that the GhostWrite primitive directly interacts with physical memory addresses.
The hypothesis of a memory write that directly interacts with physical memory addresses would not only explain why the cache hierachy is skipped but also why all virtual memory isolation are skipped as well.
make && sudo ./duplicate_mappingExpected output:
Mapping1: 3ff7ad2000
Mapping2: 3ff7ad1000
Physical address (mapping1): 130bd5000
Physical address (mapping2): 130bd5000
Reading from uncached mapping
cache misses: 1.000300
-> 10000/10000
Reading from cached mapping
cache misses: 0.000100
-> 0/10000
This experiment further strenghens the hypothesis. Two virtual address mappings point to the same physical address. This physical address is then updated using the GhostWrite primitive and only the uncached mapping observes the new value. The cached mapping, which is still served from the cache and not from main memory, still reads the original value.