1000sj
SJ CODE
1000sj
전체 방문자
오늘
어제
  • 분류 전체보기 N
    • Security N
      • 네트워크
      • 보안
      • CTF
      • Exploit
      • Fuzzing N
    • System Programming N
      • Kernel
      • Compiler
      • Device Driver
      • Emulator
      • Parrelel Processing
      • Assembly
      • Memory Management N
    • Architecture N
      • ARM N
      • RISC-V N
    • Cloud Computing
      • Cloud Native
      • Public Cloud
      • Infrastructure
      • Database
      • DevOps
    • TroubleShooting
    • ETC
      • 문화 생활
      • 커뮤니티

인기 글

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
1000sj

SJ CODE

Security/Fuzzing

syzkaller를 이용한 커널 퍼징 #3 syz-manager 설정 및 syz-executor 실행

2025. 12. 10. 22:48

syz-manager란

syz-manager는 syzkaller 의 메인 프론트엔드 프로그램이다. 전체 퍼징 인프라를 관리하는 중앙 컨트롤러 역할을 한다.

syz-manager는 다음과 같은 주요 역할을 한다.

VM 인스턴스 관리

시작시 다음과 같은 절차를 거친다.

  1. 설정 파일 읽기
  2. VM 템플릿 이미지의 스냅샷 생성 (bhyve + ZFS 사용 시)
  3. 설정된 수만큼 VM 생성
  4. 각 VM에 SSH로 접속하여 syz-fuzzer 설치 및 시작

Crash 감지 및 처리

vm console 을 모니터링하여 crash 감지 시 다음과 같은 절차를 거친다.

  1. Crash DB에 추가
  2. 패닉 메시지로 분류
  3. 일부 VM을 재현 시도에 할당
  4. 최소 재현 프로그램 찾기 시도
  5. C 프로그램으로 변환
  6. VM 재생성

Corpus Rotation

  • VM을 주기적으로 재시작 (crash가 없어도)
  • 일부 시스템콜과 corpus 프로그램을 개별 fuzzer에게 숨김
  • 목적: 동일한 커버리지를 달성하는 다른 특성의 프로그램 발견
  • Local maxima에 갇히는 것을 방지

syz-manager config 파일 분석

{
  "target": "linux/amd64",
  "http": "0.0.0.0:8080",
  "workdir": "/home/user/syzkaller/workdir",
  "image": "/home/user/syzkaller/bullseye.img",
  "kernel_obj": "/home/user/linux/",
  "kernel_src": "/home/user/linux/",
  "syzkaller": "/home/user/go/src/github.com/google/syzkaller",
  "procs": 4,
  "type": "qemu",
  "sshkey": "/home/user/syzkaller/bullseye.id_rsa",
  "vm": {
    "count": 8,
    "cpu": 2,
    "mem": 2048,
    "kernel": "/home/user/linux/arch/x86/boot/bzImage",
    "cmdline": "console=ttyS0 root=/dev/sda earlyprintk=serial net.ifnames=0"
  }
}
필드 값 설명
target "freebsd/amd64" 타겟 OS와 아키텍처
http "0.0.0.0:8080" 웹 대시보드 바인드 주소
workdir "/data/syzkaller" 작업 디렉토리 (코퍼스, 크래시 리포트 저장)
image "/data/syzkaller/bullseye.img" VM 루트 파일시스템 이미지
syzkaller "/home/markj/go/src/..." syzkaller 소스 디렉토리
kernel_obj "/home/user/linux/" 커널 오브젝트 (심볼 정보용)
kernel_src "/home/user/linux/" 커널 소스 경로 (커버리지 리포트용)
procs 4 각 VM 내 퍼저 프로세스 수
type "qemu" 하이퍼바이저 종류
ssh_user "root" VM 접속용 SSH 사용자
sshkey "/data/syzkaller/id_rsa" SSH 개인키 경로
kernel_obj "/usr/obj/.../sys/SYZKALLER" 커널 오브젝트 파일 (심볼 정보용)
kernel_src "/" 커널 소스 경로 (커버리지 리포트용)

vm 설정 상세

필드 값 설명
bridge "bridge0" 네트워크 브릿지 이름
count 32 생성할 VM 개수
cpu 2 VM당 가상 CPU 수
hostip "169.254.0.1" 호스트 IP (VM과 통신용)
dataset "data/syzkaller" ZFS 데이터셋 (bhyve용)

QEMU 스냅샷 동작 방식

VM 시작 시:

┌────────────────────────┐
│  bullseye.img (원본)   │
└───────────┬────────────┘
            │ qcow2 copy-on-write
            ▼
┌────────────────────────┐
│   backing file 설정    │
└───────────┬────────────┘
            │ 각 VM마다 오버레이 생성
            ▼
┌────┬────┬────┬────┐
│VM1 │VM2 │VM3 │... │  ← 독립적인 오버레이 이미지
└────┴────┴────┴────┘
     (원본은 읽기 전용, 변경사항만 오버레이에 저장)

VM 재시작 시:

  • 오버레이 이미지 삭제
  • 새 오버레이 생성
  • 깨끗한 상태로 재시작

VM image 준비

syz-manager를 실행하기 전에 VM image 를 준비해야한다.

커널 설정에 필요한 옵션은 다음과 같다.

CONFIG_KCOV=y                  # 커버리지 수집 (필수)   
CONFIG_KCOV_INSTRUMENT_ALL=y   # 전체 커널 계측        
CONFIG_KCOV_ENABLE_COMPARISONS=y # 비교 연산 추적      
CONFIG_DEBUG_INFO=y            # 디버그 심볼           
CONFIG_KASAN=y                 # 메모리 오류 감지 (권장)
CONFIG_KMSAN=y                 # 초기화 안 된 메모리 감지 (권장)   
CONFIG_UBSAN=y                 # 정의되지 않은 동작  감지 (권장)

VM image에 필요한 것은 다음과 같다.

  • kcov(4) 활성화된 커널 설치
  • root 사용자용 SSH 공개키 설치
    • /root/.ssh/authorized_keys
  • SSH 서버 활성화
  • 네트워크 설정 (DHCP 또는 고정 IP)

커널 빌드 (kcov 활성화)

다음과 같이 커널설정을 해주고 빌드하여 bzImage를 얻는다.

git clone https://github.com/torvalds/linux.git
cd linux

# 커널 설정
make defconfig
make kvm_guest.config

# 필수 옵션 활성화
cat >> .config << EOF
CONFIG_KCOV=y
CONFIG_KCOV_INSTRUMENT_ALL=y
CONFIG_KCOV_ENABLE_COMPARISONS=y
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_INFO_DWARF4=y
CONFIG_KASAN=y
CONFIG_KASAN_INLINE=y
EOF

# 빌드
make olddefconfig
make -j$(nproc)

루트 파일시스템 이미지를 생성한다. syzkaller는 create-image.sh 스크립트를 제공한다.

wget https://raw.githubusercontent.com/google/syzkaller/master/tools/create-image.sh
chmod +x create-image.sh
./create-image.sh

결과물

drwxr-xr-x. 1 root root        132 12월 10일  15:05 bullseye             # 루트 파일시스템 디렉토리 (마운트/추출용)
-rw-------. 1 root root       2590 12월 10일  15:05 bullseye.id_rsa      # SSH 개인키 (syz-manager가 VM 접속에 사용)
-rw-r--r--. 1 root root        565 12월 10일  15:05 bullseye.id_rsa.pub  # SSH 공개키 (이미지 내 authorized_keys에 등록됨)
-rw-r--r--. 1 root root 2147483648 12월 10일  15:05 bullseye.img         # 루트 파일시스템 이미지 (2GB)

리소스 배분 가이드라인

VM 개수 (count)

더 많은 vm 설정은 더 많은 병렬 테스트를 진행한다. 곧 더 빠르게 버그를 찾을 수 있다. (단 호스트 리소스 한계 내에서)

CPU 배분 권장사항

호스트 CPU 수: N 으로 할 때 권장 설정은 다음과 같다.

vm.count × vm.cpu ≤ N × 1~2

예: 16코어 호스트                                
    → count=8, cpu=2   (총 16 vCPU) ✓          
    → count=16, cpu=1  (총 16 vCPU) ✓          
    → count=16, cpu=2  (총 32 vCPU) ← 약간 과다

메모리 배분

vm.mem × vm.count ≤ 호스트 RAM - 시스템 예약분

예: 64GB RAM 호스트
    → count=8, mem=4096  (총 32GB) ✓
    → count=16, mem=2048 (총 32GB) ✓

procs (VM당 퍼저 프로세스 수)

procs = VM당 동시 실행 퍼저 수

다중 CPU VM에서:
- procs를 높이면 레이스 컨디션 발견 확률 증가
- 권장: procs ≈ vm.cpu 또는 vm.cpu × 2

실제 퍼징 과정

Corpus 의 개념

  • syzkaller의 핵심 영속 상태(persistent state)
  • 커널의 다양한 코드 경로를 실행하는 "대표 프로그램들의 집합"
  • 퍼저의 시드(seed) 역할

동작 방식

초기 상태: 코퍼스 = ∅ (비어있음)

[코퍼스 성장 과정]
1. 코퍼스에서 프로그램 P 선택
2. P를 약간 변형 → P'
3. P' 실행 후 커버리지 측정
4. 새로운 코드가 실행되었나?
   - Yes → P'를 코퍼스에 추가 가능
   - No → P' 버림
5. 반복

알고리즘적으로 퍼저가 하는 일은 오직 코퍼스 크기를 늘리는 것이다. 버그 찾기는 이 과정의 부산물이다.

프로그램 변형(Mutation) 종류

퍼저가 새 테스트 프로그램을 생성하는 4가지 방법은 다음과 같다.

mutation type description example
Splice 여러 프로그램 결합 P1 + P2 → P3
Insert 새 시스템콜 삽입 [open, read] → [open, mmap, read]
Remove 기존 시스템콜 제거 [open, read, close] → [open, close]
Mutate 파라미터 값 수정 read(fd, buf, 100) → read(fd, buf, 4096)

corpus가 비어있을 때

  • 랜덤한 시스템콜 리스트와 랜덤 인자로 새 프로그램 생성
  • 코퍼스가 있어도 주기적으로 완전히 새로운 프로그램 생성 (다양성 유지)

VM 내부 구조

┌─────────────────────────────────────────────────────────────┐
│                         VM                                   │
│  ┌────────────────────────────────────────────────────────┐ │
│  │                     syz-fuzzer                          │ │
│  │                                                          │ │
│  │   ┌──────────┐   ┌──────────┐   ┌──────────┐           │ │
│  │   │ worker 0 │   │ worker 1 │   │ worker 2 │   ...     │ │
│  │   │ goroutine│   │ goroutine│   │ goroutine│           │ │
│  │   └────┬─────┘   └────┬─────┘   └────┬─────┘           │ │
│  │        │              │              │                   │ │
│  │        │   shared memory interface   │                   │ │
│  │        ▼              ▼              ▼                   │ │
│  │   ┌──────────┐   ┌──────────┐   ┌──────────┐           │ │
│  │   │syz-exec 0│   │syz-exec 1│   │syz-exec 2│           │ │
│  │   └────┬─────┘   └────┬─────┘   └────┬─────┘           │ │
│  └────────│──────────────│──────────────│──────────────────┘ │
│           │              │              │                     │
│           ▼              ▼              ▼                     │
│  ┌────────────────────────────────────────────────────────┐ │
│  │                      KERNEL                             │ │
│  │                    /dev/kcov                            │ │
│  └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                              │
                             RPC (SSH)
                              │
                              ▼
                    ┌─────────────────┐
                    │   syz-manager   │
                    │  (호스트에서 실행) │
                    │                 │
                    │ - 코퍼스 저장    │
                    │ - 크래시 DB     │
                    │ - 웹 대시보드   │
                    └─────────────────┘

syz-fuzzer 시작 과정

[syz-manager가 SSH로 syz-fuzzer 시작]
              │
              ▼
┌─────────────────────────────────────┐
│ syz-fuzzer 초기화                    │
│                                      │
│ 1. syz-manager와 RPC 연결 수립       │
│ 2. 작업 큐(work queue) 생성          │
│ 3. Worker goroutine들 생성           │
│ 4. 각 Worker가 syz-executor spawn    │
│ 5. 즉시 퍼징 시작                    │
└─────────────────────────────────────┘

Worker 스레드의 3가지 특수 작업

(1) Triage (분류/정제)

목적: 새 커버리지를 생성하는 것처럼 보이는 프로그램을 검증하고 정제

[Triage 큐에 프로그램 도착]
              │
              ▼
┌─────────────────────────────────────┐
│ 1. 일관성 검증                       │
│    - 같은 프로그램을 여러 번 실행    │
│    - 매번 동일한 커버리지가 나오나?  │
│    - No → 버림 (불안정한 커버리지)   │
│                                      │
│ 2. 프로그램 최소화                   │
│    - 커버리지를 유지하면서           │
│    - 시스템콜을 하나씩 제거 시도     │
│    - 최소한의 프로그램으로 축소      │
└─────────────────────────────────────┘
              │
              ▼
        [Smashing 큐로 이동]

왜 최소화가 중요한가?

  • 버그 재현 시 불필요한 노이즈 제거
  • 개발자가 버그 원인 파악하기 쉬움
  • 예: 100개 시스템콜 → 3개 시스템콜로 축소

(2) Smashing (집중 변형)

목적: Triage를 통과한 "유망한" 프로그램에 집중 투자

[Triage 완료된 프로그램]
              │
              ▼
┌─────────────────────────────────────┐
│ Smashing 단계                        │
│                                      │
│ - 해당 프로그램에 "추가 시간" 투자   │
│ - 집중적으로 다양한 변형 시도        │
│ - 새로운 커버리지 발견 가능성 높음   │
│                                      │
│ 이유: 이 프로그램이 이미 흥미로운    │
│ 코드 영역에 도달했으므로, 조금만     │
│ 변형해도 더 깊은 코드 탐색 가능      │
└─────────────────────────────────────┘
              │
              ▼
        [코퍼스 추가 후보]

(3) Candidate Processing (후보 처리)

목적: syz-manager가 보낸 특별 프로그램 처리

[syz-manager에서 후보 프로그램 도착]
              │
              ▼
┌─────────────────────────────────────┐
│ 후보 프로그램 실행                   │
│                                      │
│ - 실행 후 커버리지 확인              │
│ - 필요시 Triage/Smash 작업 생성      │
│                                      │
│ 발생 상황:                           │
│ - syzkaller 재시작 후 기존 코퍼스    │
│   재평가 필요                        │
│ - 커널 업데이트 후 동일 프로그램이   │
│   다른 결과를 낼 수 있음            │
└─────────────────────────────────────┘

RPC 통신 상세

(1) Poll RPC

syz-fuzzer                              syz-manager
    │                                        │
    │────────── Poll() ─────────────────────>│
    │                                        │
    │<─────── 응답 ──────────────────────────│
    │  - 최신 코퍼스 스냅샷                   │
    │  - 글로벌 커버리지 정보                 │
    │  - 처리할 후보 프로그램들               │
    │                                        │

Poll이 특히 중요한 상황

  • syzkaller 재시작 시
  • 커널 업데이트 후
  • 기존 코퍼스를 새 환경에서 재평가 필요

(2) NewInput RPC

syz-fuzzer                              syz-manager
    │                                        │
    │────── NewInput(triaged_program) ──────>│
    │                                        │
    │                    ┌───────────────────┤
    │                    │ 검증:             │
    │                    │ - 코퍼스 크기 제한│
    │                    │ - 중복 프로그램?  │
    │                    │ - 다른 퍼저가     │
    │                    │   이미 발견?      │
    │                    └───────────────────┤
    │                                        │
    │<─────── Accept/Reject ─────────────────│
    │                                        │

거부되는 경우

  • 코퍼스 크기가 너무 커지는 것 방지
  • 다른 VM의 퍼저가 이미 유사한 프로그램 발견
  • 중복 커버리지

Corpus Rotation

코드 커버리지는 완벽한 지표가 아니다. 100% 코드 커버리지가 곧 버그 없음은 아니다.

특히 멀티스레드 코드에서

  • 동일 코드라도 실행 순서에 따라 버그 발생
  • 레이스 컨디션은 커버리지로 감지 불가

corpus rotation을 사용하면 다음과 같은 효과를 볼 수 있다.

  • 각 퍼저가 다른 경로로 동일 커버리지 달성 시도
  • 중복 노력이 발생하지만, 다양한 특성의 프로그램 발견
  • 로컬 최대값 탈출 가능

전체 퍼징 루프 플로우차트

┌─────────────────┐
                    │     시작        │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │ 코퍼스 비어있음? │
                    └────────┬────────┘
                             │
              ┌──────────────┴──────────────┐
              │ Yes                         │ No
              ▼                             ▼
     ┌─────────────────┐          ┌─────────────────┐
     │ 완전히 랜덤한    │          │ 코퍼스에서      │
     │ 프로그램 생성    │          │ 프로그램 선택   │
     └────────┬────────┘          └────────┬────────┘
              │                             │
              └──────────────┬──────────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │  변형(Mutation)  │
                    │  - splice       │
                    │  - insert       │
                    │  - remove       │
                    │  - mutate param │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │ syz-executor로  │
                    │ 프로그램 실행   │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │ kcov로 커버리지 │
                    │ 정보 수집       │
                    └────────┬────────┘
                             │
              ┌──────────────┴──────────────┐
              │                             │
              ▼                             ▼
     ┌─────────────────┐          ┌─────────────────┐
     │  크래시 발생?   │          │ 새 커버리지?    │
     └────────┬────────┘          └────────┬────────┘
              │                             │
        ┌─────┴─────┐                 ┌─────┴─────┐
        │Yes        │No               │Yes        │No
        ▼           │                 ▼           │
┌──────────────┐    │        ┌──────────────┐    │
│ 크래시 DB에  │    │        │ Triage 큐에  │    │
│ 기록         │    │        │ 추가         │    │
│              │    │        └──────┬───────┘    │
│ 재현 시도    │    │               │            │
│              │    │               ▼            │
│ 최소화       │    │        ┌──────────────┐    │
│              │    │        │ 일관성 검증  │    │
│ C 코드 생성  │    │        │ 최소화       │    │
└──────────────┘    │        └──────┬───────┘    │
                    │               │            │
                    │               ▼            │
                    │        ┌──────────────┐    │
                    │        │ Smashing     │    │
                    │        │ (집중 변형)  │    │
                    │        └──────┬───────┘    │
                    │               │            │
                    │               ▼            │
                    │        ┌──────────────┐    │
                    │        │ NewInput RPC │    │
                    │        │ 코퍼스 추가  │    │
                    │        │ 요청         │    │
                    │        └──────────────┘    │
                    │                            │
                    └──────────────┬─────────────┘
                                   │
                                   ▼
                          ┌──────────────┐
                          │   반복...    │
                          └──────────────┘​

syz-executor 프로그램 실행

syz-executor의 역할과 특징

  • syzkaller에서 실제로 시스템콜을 실행하는 컴포넌트
  • C++로 작성됨 (syzkaller의 다른 부분은 Go로 작성)
  • syz-fuzzer의 worker 스레드가 spawn

왜 C++인가?

  • 시스템콜을 직접 호출해야 함
  • 저수준 메모리 조작 필요
  • 커널과 직접 상호작용
  • 성능이 중요한 부분
┌─────────────────────────────────────────────────────────┐
│                      syz-fuzzer (Go)                     │
│                                                          │
│   ┌──────────┐   ┌──────────┐   ┌──────────┐           │
│   │ worker 0 │   │ worker 1 │   │ worker 2 │           │
│   └────┬─────┘   └────┬─────┘   └────┬─────┘           │
│        │              │              │                   │
│        │ spawn        │ spawn        │ spawn            │
│        ▼              ▼              ▼                   │
│   ┌──────────┐   ┌──────────┐   ┌──────────┐           │
│   │syz-exec  │   │syz-exec  │   │syz-exec  │  (C++)    │
│   │    0     │   │    1     │   │    2     │           │
│   └──────────┘   └──────────┘   └──────────┘           │
└─────────────────────────────────────────────────────────┘

통신 방식: 공유 메모리 인터페이스

왜 공유 메모리인가?

  • RPC나 소켓보다 훨씬 빠름
  • 대량의 데이터 교환에 적합
  • 프로그램 데이터와 커버리지 결과 전달
┌─────────────────┐                    ┌─────────────────┐
│   syz-fuzzer    │                    │  syz-executor   │
│    (worker)     │                    │                 │
│                 │                    │                 │
│  ┌───────────┐  │    shared memory   │  ┌───────────┐  │
│  │ 프로그램   │──┼───────────────────>│  │ 프로그램   │  │
│  │ (입력)    │  │                    │  │ 수신       │  │
│  └───────────┘  │                    │  └───────────┘  │
│                 │                    │        │        │
│                 │                    │        ▼        │
│                 │                    │  ┌───────────┐  │
│                 │                    │  │ 시스템콜   │  │
│                 │                    │  │ 실행       │  │
│                 │                    │  └───────────┘  │
│                 │                    │        │        │
│  ┌───────────┐  │                    │        ▼        │
│  │ 커버리지  │<─┼────────────────────│  ┌───────────┐  │
│  │ (출력)    │  │                    │  │ 커버리지   │  │
│  └───────────┘  │                    │  │ 결과       │  │
│                 │                    │  └───────────┘  │
└─────────────────┘                    └─────────────────┘

소프트웨어 샌드박스

┌─────────────────────────────────────────────────────────┐
│                    syz-executor                          │
│                                                          │
│   ┌─────────────────────────────────────────────────┐   │
│   │              Sandbox (격리 환경)                 │   │
│   │                                                  │   │
│   │   ┌─────────────────────────────────────────┐   │   │
│   │   │          테스트 프로그램 실행            │   │   │
│   │   │                                          │   │   │
│   │   │  kill(-1, SIGKILL)                      │   │   │
│   │   │         │                                │   │   │
│   │   │         ▼                                │   │   │
│   │   │  샌드박스 내부만 영향                    │   │   │
│   │   │  syz-fuzzer는 안전!                     │   │   │
│   │   └─────────────────────────────────────────┘   │   │
│   │                                                  │   │
│   └─────────────────────────────────────────────────┘   │
│                                                          │
└─────────────────────────────────────────────────────────┘

샌드박스가 제한하는 것들

  • 다른 프로세스에 시그널 보내기
  • 파일시스템의 중요 부분 접근
  • 네트워크 남용
  • 시스템 리소스 고갈

시스템콜 실행 과정

기본 실행 흐름

[프로그램 예시]
───────────────
call[0]: open("/dev/pf", O_RDWR)
call[1]: ioctl(fd0, DIOCRADDTABLES, arg)
call[2]: read(fd0, buf, 100)
call[3]: close(fd0)

1차 실행 (순차 모드)

┌─────────────────────────────────────────────────────────┐
│                    Main Thread                           │
│                                                          │
│   call[0] ──> Thread Pool ──> open() 실행               │
│      │                              │                    │
│      │ 짧은 대기                    │                    │
│      ▼                              ▼                    │
│   call[1] ──> Thread Pool ──> ioctl() 실행              │
│      │                              │                    │
│      │ 짧은 대기                    │                    │
│      ▼                              ▼                    │
│   call[2] ──> Thread Pool ──> read() 실행               │
│      │                              │                    │
│      │ 짧은 대기                    │                    │
│      ▼                              ▼                    │
│   call[3] ──> Thread Pool ──> close() 실행              │
│                                                          │
└─────────────────────────────────────────────────────────┘

시간 ────────────────────────────────────────────────────>
      [call0][wait][call1][wait][call2][wait][call3]

왜 대기 시간이 있는가?

  • 각 시스템콜이 완료될 시간 확보
  • 순차적 실행 보장
  • 커버리지 수집의 정확성

Collision Mode (충돌 모드)

레이스 컨디션 버그 발견하기 위한 목적으로 사용한다.

1차 실행 vs Collision Mode:

[1차 실행 - 순차적]
───────────────────
시간 ──────────────────────────────────────>

Thread1: [call0]----[call1]----[call2]----[call3]
Thread2:        idle       idle       idle

커버리지는 측정되지만, 
레이스 컨디션은 발생 안 함


[2차 실행 - Collision Mode]
────────────────────────────
시간 ──────────────────────────────────────>

Thread1: [call0]─────[call2]─────
Thread2:      [call1]─────[call3]
                  │
                  ▼
         동시 실행으로 레이스 컨디션 유발 가능!

발견할 수 있는 버그 유형

  • 락(Lock) 누락
  • 이중 해제(Double Free)
  • Use-After-Free
  • 데이터 손상
  • 데드락

syscall(2)을 통한 실행

syscall(2)이란?

  • 범용 시스템콜 호출 함수
  • 시스템콜 번호와 가변 인자를 받음
// 일반적인 방법
int fd = open("/dev/pf", O_RDWR);

// syscall(2)을 사용하는 방법
int fd = syscall(SYS_open, "/dev/pf", O_RDWR);
//              ^^^^^^^^
//              시스템콜 번호

syz-executor의 실행 방식

┌─────────────────────────────────────────────────────────┐
│                    syz-executor                          │
│                                                          │
│   syzkaller 내부 표현:                                   │
│   ┌─────────────────────────────────────────────────┐   │
│   │ {                                                │   │
│   │   syscall_num: 5,        // SYS_open            │   │
│   │   args: ["/dev/pf", 2]   // path, O_RDWR        │   │
│   │ }                                                │   │
│   └─────────────────────────────────────────────────┘   │
│                         │                                │
│                         ▼                                │
│   ┌─────────────────────────────────────────────────┐   │
│   │ result = syscall(5, "/dev/pf", 2);              │   │
│   └─────────────────────────────────────────────────┘   │
│                         │                                │
│                         ▼                                │
│   ┌─────────────────────────────────────────────────┐   │
│   │                   KERNEL                         │   │
│   │                                                  │   │
│   │   시스템콜 번호 5 → open() 핸들러로 라우팅      │   │
│   │                                                  │   │
│   └─────────────────────────────────────────────────┘   │
│                                                          │
└─────────────────────────────────────────────────────────┘

결과 기록 및 가중치

시스템콜 결과 처리

┌─────────────────────────────────────────────────────────┐
│                    결과 기록                             │
│                                                          │
│   syscall() 호출                                        │
│        │                                                 │
│        ▼                                                 │
│   ┌─────────────┐                                       │
│   │ 반환값 확인 │                                       │
│   └──────┬──────┘                                       │
│          │                                               │
│    ┌─────┴─────┐                                        │
│    │           │                                         │
│    ▼           ▼                                         │
│ 성공 (≥0)   실패 (<0)                                   │
│    │           │                                         │
│    ▼           ▼                                         │
│ 높은 가중치  낮은 가중치                                │
│                                                          │
└─────────────────────────────────────────────────────────┘

가중치의 의미

결과 가중치 이유
성공 높음 커널 깊숙이 실행됨, 더 많은 코드 경로 탐색
실패 낮음 초기 검증에서 거부됨, 탐색 깊이가 얕음

 

Triage와 Prioritization에 사용

[프로그램 A]
call[0]: open() → 성공
call[1]: ioctl() → 성공
call[2]: read() → 성공
총 가중치: 높음 ★★★

[프로그램 B]
call[0]: open() → 실패 (ENOENT)
call[1]: ioctl() → 실패 (EBADF)
call[2]: read() → 실패 (EBADF)
총 가중치: 낮음 ★

→ 프로그램 A가 더 "흥미로운" 프로그램으로 판단
→ 변형 시 A를 우선적으로 선택

 

 

전체 실행 흐름 요약

┌─────────────────────────────────────────────────────────────┐
│                     syz-executor 전체 흐름                   │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │  프로세스 시작   │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │ 스레드 풀 생성   │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │ /dev/kcov 열기  │
                    │ 트레이싱 활성화  │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │ (선택) 샌드박스  │
                    │ 진입            │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │ (선택) 디바이스/ │
                    │ 네트워크 초기화  │
                    └────────┬────────┘
                             │
                             ▼
         ┌───────────────────────────────────────┐
         │          프로그램 실행 루프            │
         │                                        │
         │  ┌──────────────────────────────────┐ │
         │  │     1차 실행 (순차 모드)          │ │
         │  │                                   │ │
         │  │  for each call in program:       │ │
         │  │      thread = get_idle_thread()  │ │
         │  │      thread.execute(call)        │ │
         │  │      wait(short_delay)           │ │
         │  │      record_result(call)         │ │
         │  │                                   │ │
         │  └──────────────────────────────────┘ │
         │                   │                    │
         │                   ▼                    │
         │  ┌──────────────────────────────────┐ │
         │  │     2차 실행 (Collision Mode)    │ │
         │  │                                   │ │
         │  │  for each pair in program:       │ │
         │  │      thread1.execute(call_a)     │ │
         │  │      thread2.execute(call_b)     │ │
         │  │      // 대기 없이 즉시 다음      │ │
         │  │                                   │ │
         │  └──────────────────────────────────┘ │
         │                                        │
         └───────────────────────────────────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │ 커버리지 데이터  │
                    │ 공유 메모리에    │
                    │ 기록            │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │ syz-fuzzer가    │
                    │ 결과 수집       │
                    └─────────────────┘

 

실제 예시: 버그 발견 시나리오

[테스트 프로그램]
─────────────────
call[0]: fd = open("/dev/pf", O_RDWR)
call[1]: ioctl(fd, DIOCRADDTABLES, malformed_arg)
call[2]: close(fd)


[1차 실행 - 순차 모드]
─────────────────────
Thread1: open() → 성공, fd=3
         wait...
Thread1: ioctl(3, ...) → 성공 (버그 있는 코드 실행됨)
         wait...
Thread1: close(3) → 성공

커버리지: pf 드라이버의 DIOCRADDTABLES 핸들러 실행됨
결과: 크래시 없음 (아직)


[2차 실행 - Collision Mode]
───────────────────────────
Thread1: open() ──────────┐
Thread2: ioctl() ─────────┤ 거의 동시에!
                          │
                          ▼
                   레이스 컨디션 발생!
                   
Thread1이 fd를 아직 설정 중인데
Thread2가 ioctl 실행 시도

         ┌─────────────────────────────────┐
         │         KERNEL PANIC!           │
         │                                 │
         │  Use-after-free in pf driver   │
         │  at pf_ioctl+0x1234            │
         └─────────────────────────────────┘

[크래시 처리]
─────────────
1. syz-manager가 VM 콘솔에서 패닉 감지
2. 크래시 정보를 DB에 기록
3. VM 자동 재생성
4. 재현 시도 시작
5. 최소 재현 프로그램 도출
6. C 코드로 변환하여 개발자에게 제공

 

트러블 슈팅

다음과 같이 호스트의 GLIBC 버전과 게스트 VM의 GLIBC 버전이 맞지 않아 syzkaller가 구동하지 않을 수 있다.

$ syz-manager -debug -config ./x86.cfg
/syz-executor: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found (required by /syz-executor)
/syz-executor: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.33' not found (required by /syz-executor)
/syz-executor: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by /syz-executor)

syz-manager의 경우 호스트에서 빌드해서 호스트에서 쓰지만 syz-executor, syz-fuzzer는 호스트에서 빌드하여 게스트 vm에서 copy하여 실행되기 때문에 이미지를 만들 때 동일한 GLIBC를 사용하는 이미지로 vm을 생성해야한다.

나같은 경우 호스트에서 fedora 43을 사용하고 있어 glibc 2.38+ 을 맞춰 Debian Trixie 이미지를 다운받아서 테스트 했다.

create-image.sh # default Debian Bullseye GLIBC 2.31
create-image.sh -d bookworm # GLIBC 2.36
create-image.sh -d trixie # GLIBC 2.38

'Security > Fuzzing' 카테고리의 다른 글

syzbot 버그 패치 리뷰 (2025.12.18)  (0) 2025.12.18
syzbot, qemu, gdb를 사용하여 linux kernel의 버그 수정  (0) 2025.12.12
syzkaller를 이용한 커널 퍼징 #4 실제 커널 퍼징  (0) 2025.12.11
syzkaller를 이용한 커널 퍼징 #2 Syzkaller의 동작 구조  (0) 2025.12.10
syzkaller를 이용한 커널 퍼징 #1 Fuzzing 이란  (1) 2025.12.10
    'Security/Fuzzing' 카테고리의 다른 글
    • syzbot, qemu, gdb를 사용하여 linux kernel의 버그 수정
    • syzkaller를 이용한 커널 퍼징 #4 실제 커널 퍼징
    • syzkaller를 이용한 커널 퍼징 #2 Syzkaller의 동작 구조
    • syzkaller를 이용한 커널 퍼징 #1 Fuzzing 이란
    1000sj
    1000sj

    티스토리툴바