| src | ||
| .envrc | ||
| .gitignore | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
Flush+Reload Covert Channel
Primitive
in ./src/lib.c sind folgende Primitive:
time_maccess misst wie lange der Zugriff auf die Speicheradresse addr dauert.
size_t time_maccess(void (* addr)(void))
{
uint64_t start, end, delta;
uint64_t lo, hi;
asm volatile ("LFENCE");
asm volatile ("RDTSC": "=a" (lo), "=d" (hi));
start = (hi<<32) | lo;
asm volatile ("LFENCE");
asm volatile ("movq (%0), %%rax\n"
:
: "c" (addr)
: "rax");
asm volatile ("LFENCE");
asm volatile ("RDTSC": "=a" (lo), "=d" (hi));
end = (hi<<32) | lo;
asm volatile ("LFENCE");
delta = end - start;
return delta;
}
maccess greift auf diese Speicheradresse addr zu.
void maccess(void* addr)
{
asm volatile ("movq (%0), %%rax\n"
:
: "c" (addr)
: "rax");
}
flush verdrängt die Speicheradresse addr aus dem Cache.
void flush(void* addr)
{
asm volatile ("clflush 0(%0)\n"
:
: "c" (addr)
: "rax");
}
Block 1: Messen der Timing-Differenzen + Threshold bestimmen
Tips
- am besten kompiliert man ohne Compileroptimierungen (
-O0):gcc -O0 cache_test.c primitive.c -o cache_test - CMake ist praktisch! siehe
./src/CMakeList.txt. Verwenden durchcmake .und dannmake.
Muster: ./src/cache_test.c
Block 2: Signale über den Cache
sharedlib.c sieht so aus:
void function(void)
{ asm volatile (
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
...
Die Adresse von function kann also zum Flushen und Reloaded verwendet werden.
Da immer eine gesamte Cacheline geladen wird, sollte sich nichts anderes in dem selben 64Byte Block befinden.
Die Funktionen padding_before und padding_after sind beide 64 Byte groß, also eine Cacheline.
Damit kann dann auch gar nichts mehr schief gehen :)
sharedlib.c kann man so kompilieren und linken:
# sharedlib kompilieren
gcc -fPIC -shared -O0 -o libsharedlib.so sharedlib.c
# sharedlib bei Sender und Empfänger linken
gcc -O0 sender.c primitive.c -L. -lsharedlib -o sender
gcc -O0 empfaenger.c primitive.c -L. -lsharedlib -o empfaenger
# ggfs. muss man noch den PATH exportieren
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
dann kann man function verwenden:
# in e.g. sender.c
maccess((void *) function)
...
Tips
tasksetzum pinnen der Programme auf einen bestimmten CPU-Kern.- Verwendet man blockierenden Sleep um zu Takten, z.B.
clock_nanosleep, dann sollte man unbedingt darauf achten nicht eine Periodendauer zu schlafen, sondern nur bis zum nächsten Zeitabschnitt. Einfacher geht es mit nicht-blockierendem Sleep, z.B.ualarm. - Der Zeitabstand von Flush zu Reload sollte MAXIMIERT werden! Dies ist das Zeitfenster, in welchem der Sender die Adresse(n) zurück in den Cache laden kann.
- (Der Sender kann das Flushen übernehmen)
- (Der Sender kann im gesamten Zeitabschnitt Flushen oder Laden, nicht nur einmal)
Block 3: Pakete über den Cache als Medium
- Kann man irgendwie mehr als ein Bit gleichzeitig senden? ;)
Muster: ./src/sender.c und ./src/receiver.c