00 Kernel fuzzing neden önemli
Linux kernel, kullanıcı alanından gelen her syscall'ı işler; hatalı bir sürücü tek bir syscall'la kernel panik üretebilir, ayrıcalık yükseltme sağlayabilir ya da bellek bozulmasına neden olabilir. Fuzzing, bu yüzeyi otomatik olarak tarar.
Saldırı yüzeyi neden bu kadar geniş
Linux kernel, yaklaşık 400 syscall ve binlerce ioctl koduyla kullanıcı alanına devasa bir arayüz sunar. Her ioctl handler, her read/write implementasyonu bir girdi ayrıştırma noktasıdır. Gömülü sistemlerde ise durum daha kritiktir: üçüncü taraf BSP sürücüleri genellikle minimum test görür.
Manuel test neden yetersiz kalır
Kernel sürücü testleri genellikle "mutlu yol" senaryolarını kapsar. Aşırı büyük uzunluk değerleri, NULL göstericiler, eş zamanlı kapatma ile okuma gibi kenar durumları elle yazılmış testlerle nadiren kapsanır. Fuzzing, milyarlarca rastgele ama yapısal olarak geçerli giriş kombinasyonunu otomatik dener.
Tarihsel başarılar
syzkaller 2016'dan bu yana 5000'den fazla kernel hatasını ortaya çıkarmıştır. Bunların büyük bölümü CVE numarası almış, kritik güvenlik yamalarına yol açmıştır. BlueZ, net subsystem, btrfs ve V4L2 sürücülerinde çok sayıda yüksek önem dereceli güvenlik açığı syzkaller tarafından keşfedilmiştir.
| Fuzzer | Yaklaşım | Kernel Uyumu | Coverage |
|---|---|---|---|
| syzkaller | Coverage-guided, syscall-aware | Linux, FreeBSD, NetBSD, Fuchsia | KCOV bitmap |
| Trinity | Rastgele syscall, tip farkındalığı yok | Yalnızca Linux | Yok |
| AFL++ (kernel modu) | Mutasyon bazlı, user-space odaklı | Sınırlı | lcov |
| kAFL | Hypervisor tabanlı, donanım PT | Linux, Windows | Intel PT |
01 syzkaller mimarisi
syzkaller üç ana bileşenden oluşur: merkezi koordinatör syz-manager, her VM'de çalışan syz-fuzzer ve syscall'ları doğrudan çalıştıran syz-executor.
┌─────────────────────────────────────────────────────────┐
│ HOST MAKİNE │
│ ┌──────────────┐ ┌───────────────────────────┐ │
│ │ syz-manager │─────▶│ HTTP Dashboard :56741 │ │
│ │ (koordinatör)│ │ crash/, corpus/ dizini │ │
│ └──────┬───────┘ └───────────────────────────┘ │
│ │ SSH / gRPC │
│ ┌──────▼───────────────────────────┐ │
│ │ QEMU VM Havuzu (N instance) │ │
│ │ ┌───────────┐ ┌───────────┐ │ │
│ │ │syz-fuzzer │ │syz-fuzzer │ │ │
│ │ │ + │ │ + │ │ │
│ │ │syz-executor│ │syz-executor│ │ │
│ │ └───────────┘ └───────────┘ │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
syz-manager
Tüm fuzzing oturumunu yönetir. VM'leri başlatır ve izler; crash tespit ettiğinde VM'i durdurur, crash log'unu kaydeder ve reproducer üretme sürecini başlatır. HTTP üzerinden canlı durum paneli sunar.
syz-fuzzer
Her VM içinde çalışır. syz-manager'dan syscall programları alır, mutasyona uğratır ve syz-executor aracılığıyla çalıştırır. KCOV'dan gelen coverage bilgisini okuyarak hangi mutasyonların yeni dal kapsama açtığını ölçer.
syz-executor
Minimal C programı; gerçek syscall'ları çalıştırır. Güvenlik için ayrı bir süreç olarak fork'lanır. Sandboxing (CLONE_NEWUSER, CLONE_NEWNET) ile izole edilmiş ortamda çalışabilir. syz-fuzzer ile shared memory üzerinden iletişim kurar.
İletişim protokolü
syz-manager ile syz-fuzzer arasındaki iletişim RPC tabanlıdır. syz-fuzzer, TCP üzerinden syz-manager'a bağlanır; yeni programlar, coverage deltas ve crash raporları bu kanal üzerinden iletilir.
02 QEMU ile syzkaller kurulumu
syzkaller'ın çalışması için KCOV ve KASAN etkin bir kernel, Debian tabanlı minimal bir VM imajı ve Go ile derlenmiş syzkaller binary'leri gerekir.
Adım 1 — Go ve syzkaller derleme
# Go kurulumu (1.21+)
wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
# syzkaller kaynak kodu
git clone https://github.com/google/syzkaller.git
cd syzkaller
make -j$(nproc)
# Üretilen binary'ler: bin/ altında
ls bin/
# syz-manager syz-fuzzer syz-executor syz-repro syz-db ...
Adım 2 — Kernel yapılandırması
Fuzzing kernel'i için minimum gereken CONFIG seçenekleri:
# .config minimum fuzzing seçenekleri
CONFIG_KCOV=y # Coverage toplama — zorunlu
CONFIG_KCOV_INSTRUMENT_ALL=y # Tüm alt sistemleri enstrümante et
CONFIG_DEBUG_FS=y # /sys/kernel/debug/kcov için
CONFIG_KASAN=y # Kernel Address Sanitizer
CONFIG_KASAN_INLINE=y # Satır içi enstrümanasyon (hız için)
CONFIG_UBSAN=y # Undefined Behavior Sanitizer
CONFIG_UBSAN_TRAP=n # Raporla ama durdurma
CONFIG_FAULT_INJECTION=y
CONFIG_FAULT_INJECTION_DEBUG_FS=y
CONFIG_FAILSLAB=y
CONFIG_FAIL_PAGE_ALLOC=y
CONFIG_FAIL_FUTEX=y
CONFIG_NAMESPACES=y
CONFIG_UTS_NS=y
CONFIG_IPC_NS=y
CONFIG_PID_NS=y
CONFIG_NET_NS=y
CONFIG_USER_NS=y # Unprivileged fuzzing için
CONFIG_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_INFO_DWARF4=y # debuginfo — reproducer için
CONFIG_RANDOMIZE_BASE=n # KASLR kapalı — stable crash addr için
CONFIG_LOCKDEP=y
CONFIG_PROVE_LOCKING=y
# Kernel derleme (x86_64 hedef)
make defconfig
scripts/config --enable KCOV --enable KASAN --enable UBSAN \
--enable NAMESPACES --enable USER_NS
make -j$(nproc) 2>&1 | tee build.log
ls arch/x86/boot/bzImage
Adım 3 — Debian VM imajı oluşturma
# syzkaller'ın create-image.sh scripti
cd syzkaller
sudo apt-get install debootstrap
./tools/create-image.sh -d bullseye -s 4096
# Üretilen dosyalar:
# bullseye.img — ext4 disk imajı
# bullseye.id_rsa — SSH anahtarı
# Manuel test: QEMU ile boot
qemu-system-x86_64 \
-kernel /path/to/linux/arch/x86/boot/bzImage \
-drive file=bullseye.img,format=raw \
-append "root=/dev/sda rw console=ttyS0 earlyprintk=serial" \
-net nic,model=e1000 -net user,host=10.0.2.10,hostfwd=tcp::10022-:22 \
-nographic -m 2G
Adım 4 — SSH bağlantısını doğrula
ssh -i bullseye.id_rsa -p 10022 -o StrictHostKeyChecking=no root@localhost
# KCOV kontrolü
ls /sys/kernel/debug/kcov # dosya mevcutsa kernel hazır
03 syz-manager yapılandırması
syz-manager, JSON formatında bir yapılandırma dosyası okur. Bu dosya VM türünü, kernel yolunu, SSH bilgilerini ve hangi syscall'ların fuzz edileceğini belirler.
Temel config.cfg
{
"target": "linux/amd64",
"http": "127.0.0.1:56741",
"workdir": "/home/user/syzkaller-workdir",
"kernel_obj": "/home/user/linux",
"kernel_src": "/home/user/linux",
"image": "/home/user/syzkaller/bullseye.img",
"sshkey": "/home/user/syzkaller/bullseye.id_rsa",
"syzkaller": "/home/user/syzkaller",
"procs": 8,
"type": "qemu",
"vm": {
"count": 4,
"kernel": "/home/user/linux/arch/x86/boot/bzImage",
"cpu": 2,
"mem": 2048
}
}
Gelişmiş yapılandırma parametreleri
Belirli syscall kümesiyle hedefli fuzzing
{
"enable_syscalls": [
"socket$nl_generic",
"sendmsg$nl_generic",
"bind$nl_generic",
"ioctl$sock",
"setsockopt$sock_int"
],
"sandbox": "namespace",
"cover": true,
"reproduce": true,
"crash_retry_count": 3
}
syz-manager başlatma ve dashboard
# Fuzzing başlat
./bin/syz-manager -config config.cfg
# HTTP dashboard
# http://127.0.0.1:56741
# Gösterilen bilgiler:
# corpus boyutu, coverage bitmap doluluk oranı,
# crash sayısı, her VM'in durumu, çalıştırılan program/sn
Workdir yapısı
| Dizin / Dosya | İçerik |
|---|---|
| workdir/corpus/ | Coverage artıran syscall programları (syz formatı) |
| workdir/crashes/ | Her benzersiz crash için alt dizin; log, report, repro |
| workdir/coverage/ | HTML coverage raporları |
| workdir/syz-manager.log | Koordinatör log dosyası |
04 Coverage-guided fuzzing
syzkaller'ı diğer fuzzers'tan ayıran temel özellik, KCOV enstrümantasyonundan gelen dallanma coverage bilgisini kullanarak corpus evrimini yönlendirmesidir.
KCOV nasıl çalışır
KCOV, Linux kernel'e GCC/LLVM enstrümantasyon geçişiyle eklenir. Her temel blok başında kernel, coverage array'indeki sayacı artırır. Kullanıcı alanı bu diziyi /sys/kernel/debug/kcov üzerinden mmap ile okur.
/* Basit KCOV kullanım örneği (C) */
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long)
#define KCOV_ENABLE _IO('c', 100)
#define KCOV_DISABLE _IO('c', 101)
#define COVER_SIZE (64 * 1024)
int main(void) {
int fd = open("/sys/kernel/debug/kcov", O_RDWR);
ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE);
uint64_t *cover = mmap(NULL, COVER_SIZE * sizeof(uint64_t),
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
ioctl(fd, KCOV_ENABLE, 0);
/* İzlemek istediğimiz syscall */
read(0, NULL, 0);
ioctl(fd, KCOV_DISABLE, 0);
uint64_t count = cover[0];
printf("Kapsanan adres sayisi: %lu\n", count);
for (uint64_t i = 0; i < count; i++)
printf(" 0x%lx\n", cover[i + 1]);
return 0;
}
Coverage bitmap ve corpus seçimi
syz-fuzzer her programı çalıştırdıktan sonra yeni keşfedilen PC adreslerini bitmap'te işaretler. Daha önce görülmemiş adresler içeren bir program "ilginç" sayılır ve corpus'a eklenir. Bu evrimsel yaklaşım, fuzzer'ın derinlere inen kod yollarını keşfetmesini sağlar.
Başlangıç corpus (minimal programlar)
│
▼
Program seç ──▶ Mutasyon uygula ──▶ VM'de çalıştır
▲ │
│ ▼
Corpus'a ekle ◀── Yeni coverage var mı? ─── Hayır ──▶ Atla
│ (KCOV bitmap)
│ Evet
▼
Crash var mı? ──▶ Evet ──▶ Crash kaydı + triage
Mutasyon stratejileri
Comparisons coverage (KCOV_MODE_TRACE_CMP)
# syz-manager config'e ekle
"cover": true,
"cover_filter_bitmap": "",
"enable_cgroups": false
KCOV_MODE_TRACE_CMP modu, karşılaştırma operandlarını da kaydeder. Bu özellik sihirli sayı karşılaştırmalarını (örn. if (magic == 0xDEADBEEF)) geçmek için gereken değerleri fuzzer'a sağlar.
05 Syscall tanımlama dili (syzlang)
syzlang, syscall imzalarını, argüman tiplerini ve aralarındaki bağımlılıkları tanımlayan syzkaller'a özgü bir DSL'dir. Doğru tanımlar daha yüksek kaliteli fuzzing programları üretir.
Temel sözdizimi
# sys/linux/socket.txt içinden örnek
socket(domain flags[socket_domain], type flags[socket_type], proto int32) sock
bind(fd sock, addr ptr[in, sockaddr], addrlen len[addr]) (fails)
connect(fd sock, addr ptr[in, sockaddr], addrlen len[addr]) (fails)
sendmsg(fd sock, msg ptr[in, msghdr], f flags[send_flags]) (fails)
recvmsg(fd sock, msg ptr[out, msghdr], f flags[recv_flags]) (fails)
socket_domain = AF_UNIX, AF_INET, AF_INET6, AF_NETLINK, AF_PACKET
socket_type = SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_NONBLOCK, SOCK_CLOEXEC
Tip sistemi
| Tip | Açıklama | Örnek |
|---|---|---|
| int8, int16, int32, int64 | Tam sayı; işaretli veya işaretsiz | int32[0:255] |
| flags | Sabit listesinden biri | flags[socket_type] |
| ptr[in, T] | T tipine girdi göstericisi | ptr[in, ifreq] |
| ptr[out, T] | T tipine çıktı göstericisi | ptr[out, stat] |
| len[field] | field boyutunu otomatik hesapla | len[buf] |
| bytesize[field] | bayt cinsinden boyut | bytesize[data] |
| array[T, N] | N elemanlı dizi | array[int8, 16] |
| string | Null-terminated C string | string[filename] |
| sock | Socket fd kaynağı | fd sock |
| fd | Genel dosya tanımlayıcı | fd[opt] |
Özel ioctl tanımı — gömülü sürücü örneği
# sys/linux/dev_my_sensor.txt
# Varsayımsal /dev/mysensor sürücüsü için tanım
include <linux/ioctl.h>
include <uapi/linux/mysensor.h>
# Sürücüye özel veri yapıları
mysensor_config {
sample_rate int32[1:1000]
gain int32[0:7]
mode flags[mysensor_mode]
reserved array[int8, 4]
}
mysensor_data {
value int32
status int16
flags int16
ts_ns int64
}
mysensor_mode = MYSENSOR_MODE_CONT, MYSENSOR_MODE_ONESHOT, MYSENSOR_MODE_SLEEP
# ioctl tanımları
ioctl$MYSENSOR_SET_CONFIG(fd fd_mysensor, cmd const[MYSENSOR_SET_CONFIG],
arg ptr[in, mysensor_config])
ioctl$MYSENSOR_GET_DATA(fd fd_mysensor, cmd const[MYSENSOR_GET_DATA],
arg ptr[out, mysensor_data])
ioctl$MYSENSOR_RESET(fd fd_mysensor, cmd const[MYSENSOR_RESET])
# Fd kaynağı
openat$mysensor(fd const[AT_FDCWD], file ptr[in, string["/dev/mysensor"]],
flags flags[open_flags], mode const[0]) fd_mysensor
Tanımı derleme ve doğrulama
# Sözdizimi kontrolü
go run ./tools/syz-check -input sys/linux/dev_my_sensor.txt
# Yeni tanımları dahil ederek syzkaller yeniden derle
make generate # proto ve tanım dosyalarını günceller
make -j$(nproc)
06 Crash triage
Fuzzing sırasında üretilen crash log'ları ham kernel çıktısı içerir. Triage süreci bu log'ları sınıflandırır, tekilleştirir ve önceliklendirir.
Crash dizini yapısı
workdir/crashes/
├── KASAN_use-after-free_in_skb_free_datagram/
│ ├── description # Kısa başlık
│ ├── log0 # Ham kernel log (ilk oluşum)
│ ├── report0 # Ayrıştırılmış crash raporu
│ ├── repro.prog # syz dili reproducer
│ └── repro.cprog # C reproducer (varsa)
└── BUG_unable_to_handle_page_fault/
├── description
├── log0
└── report0
Kernel oops anatomisi
[ 1234.567890] BUG: KASAN: use-after-free in skb_free_datagram+0x45/0x80
[ 1234.567891] Read of size 8 at addr ffff8881234abc00 by task syz-executor/1234
[ 1234.567892]
[ 1234.567893] CPU: 1 PID: 1234 Comm: syz-executor Not tainted 6.7.0-syzkaller #1
[ 1234.567894] Call Trace:
[ 1234.567895] <TASK>
[ 1234.567896] dump_stack_lvl+0x4c/0x70
[ 1234.567897] print_report+0xd4/0x620
[ 1234.567898] kasan_report+0xb8/0x100
[ 1234.567899] skb_free_datagram+0x45/0x80
[ 1234.567900] unix_dgram_recvmsg+0x3c1/0xa80
[ 1234.567901] sock_recvmsg+0xc8/0x100
[ 1234.567902] __sys_recvmsg+0x1b4/0x360
[ 1234.567903] do_syscall_64+0x3c/0x80
[ 1234.567904] entry_SYSCALL_64_after_hwframe+0x46/0xb0
KASAN raporu yorumlama
UBSAN raporları
[ 5678.901234] UBSAN: signed-integer-overflow in drivers/net/ethernet/mydrv.c:234:15
[ 5678.901235] -2147483648 - 1 cannot be represented in type 'int'
[ 5678.901236] CPU: 0 PID: 5678 Comm: syz-executor
[ 5678.901237] Call Trace:
[ 5678.901238] ubsan_epilogue+0x9/0x40
[ 5678.901239] handle_overflow+0xd2/0xe0
[ 5678.901240] mydrv_calculate_offset+0x78/0xa0
addr2line ile kaynak konumu bulma
# Crash adresini kaynak satırına dönüştür
addr2line -e vmlinux -i ffff8881234abc00
# veya
scripts/faddr2line vmlinux skb_free_datagram+0x45/0x80
# Alternatif: decode_stacktrace.sh
cat crash_log | scripts/decode_stacktrace.sh vmlinux
07 Reproducer üretimi
syzkaller bir crash tespit ettiğinde otomatik olarak minimal bir reproducer üretmeye çalışır — önce syz dilinde, ardından C dilinde. Bu reproducer, yamayı doğrulamak ve bisect yapmak için kullanılır.
Otomatik reproducer üretimi
syz-manager, reproduce=true olduğunda şu adımları izler:
Crash tespit edildi
│
▼
Crash'e yol açan program corpus'ta mı?
│
▼
Program minimize et (crash hâlâ tetikleniyor mu?)
│
▼
Syscall sayısını azalt (ikili arama)
│
▼
Bireysel argümanları minimize et
│
▼
syz reproducer kaydet (repro.prog)
│
▼
C reproducer üret (repro.cprog)
syz-repro ile manuel reproducer
# Mevcut crash log'undan reproducer üret
./bin/syz-repro \
-config config.cfg \
-crash workdir/crashes/KASAN_use-after-free/log0
# Belirli bir programı test et
./bin/syz-repro \
-config config.cfg \
workdir/crashes/KASAN_use-after-free/repro.prog
Örnek syz reproducer
# Üretilmiş minimal syz reproducer (repro.prog)
r0 = socket$nl_generic(0x10, 0x3, 0x10)
r1 = socket$nl_generic(0x10, 0x3, 0x10)
sendmsg$nl_generic(r0, &(0x7f0000000000)={
&(0x7f0000000040)={0x1c, 0x20, 0x5, 0x0, 0x0,
{0x8, 0x1, 0x0, 0x0}}, 0x1, 0x0, 0x0, 0x0}, 0x0)
close(r1)
C reproducer derleme ve çalıştırma
# Üretilmiş C reproducer
gcc -o repro repro.cprog -lpthread
./repro
# Çalıştırıldığında kernel crash yeniden tetiklenmeli
git bisect entegrasyonu
# config.cfg'ye ekle
"bisect_bin": "/home/user/syzkaller/bin/syz-bisect",
"bisect_compilers": [{
"cc": "gcc",
"cxx": "g++"
}]
# Manuel bisect
./bin/syz-bisect \
-config config.cfg \
-crash workdir/crashes/KASAN_use-after-free/
# Çıktı: ilk hatalı commit SHA + yamayı kim yazdı
08 Gerçek CVE örnekleri ve gömülü sürücü fuzz
syzkaller'ın keşfettiği gerçek güvenlik açıkları incelenerek hem aracın gücü hem de gömülü sürücülere uygulanabilirlik gösterilmektedir.
CVE-2022-0435 — TIPC yığıt overflow
syzkaller, TIPC (Transparent Inter-Process Communication) ağ protokolünde yığıt tabanlı buffer overflow keşfetti. Uzak saldırgan, özel hazırlanmış TIPC mesajıyla kernel yığıtını taşırarak ayrıcalık yükseltebiliyordu.
# syzkaller'ın ürettiği reproducer benzeri
r0 = socket(0x1e, 0x2, 0x0) # AF_TIPC, SOCK_DGRAM
bind(r0, &(0x7f0000000000)=@tipc={0x1e, 0x1, {0x1, 0x0, 0x0}, 0x0}, 0x10)
sendto(r0, &(0x7f0000000100)="oversized_payload...", 0x200, 0x0,
&(0x7f0000000200)=@tipc={0x1e, 0x1, {0x1, 0x1, 0x0}, 0x0}, 0x10)
CVE-2021-3490 — eBPF verifier bypass
eBPF verifier'da işaret aralığı takibindeki hata, sınır dışı bellek erişimine izin veriyordu. syzkaller, BPF syscall kombinasyonlarını fuzz ederken bu açığı ortaya çıkardı.
Gömülü sürücü fuzz senaryosu: V4L2 kamera sürücüsü
# sys/linux/dev_v4l2_custom.txt — BSP kamera sürücüsü için ek tanım
include <linux/videodev2.h>
# Özel format tanımları
my_cam_fmt = V4L2_PIX_FMT_MJPEG, V4L2_PIX_FMT_H264, V4L2_PIX_FMT_YUYV
v4l2_pix_format_custom {
width int32[1:4096]
height int32[1:4096]
pixelformat flags[my_cam_fmt]
field int32[0:8]
bytesperline int32[0:16384]
sizeimage int32[0:67108864]
colorspace int32[0:12]
priv const[0x00feca8349] # V4L2_PIX_FMT_PRIV_MAGIC
}
ioctl$VIDIOC_S_FMT_custom(fd fd_video, cmd const[VIDIOC_S_FMT],
arg ptr[in, v4l2_format_custom])
ARM64 cross-fuzzing konfigürasyonu
># ARM64 fiziksel donanım veya QEMU için config
{
"target": "linux/arm64",
"type": "qemu",
"vm": {
"count": 2,
"kernel": "/path/to/arm64/Image",
"cpu": 4,
"mem": 4096,
"machine": "virt",
"cpu_model": "cortex-a57"
},
"enable_syscalls": [
"ioctl$v4l2_camera",
"mmap",
"read",
"write"
]
}
Sürekli fuzzing CI/CD entegrasyonu
#!/bin/bash
# ci-fuzz.sh — günlük 8 saatlik fuzzing oturumu
TIMEOUT=28800 # 8 saat
./bin/syz-manager -config config.cfg &
SYZ_PID=$!
sleep $TIMEOUT
kill $SYZ_PID
# Yeni crash var mı kontrol et
CRASH_COUNT=$(ls workdir/crashes/ 2>/dev/null | wc -l)
if [ "$CRASH_COUNT" -gt "0" ]; then
echo "UYARI: $CRASH_COUNT yeni crash tespit edildi" | \
mail -s "[syzkaller] Crash tespit edildi" ekip@sirket.com
exit 1
fi
echo "Fuzzing temiz tamamlandi"