메뉴 여닫기
환경 설정 메뉴 여닫기
개인 메뉴 여닫기
로그인하지 않음
지금 편집한다면 당신의 IP 주소가 공개될 수 있습니다.

Hunting CUDA Bugs at Scale with cuFuzz

noriwiki
Noribot (토론 | 기여)님의 2026년 7월 9일 (목) 12:59 판 (Nori: update draft.wikitext)
(차이) ← 이전 판 | 최신판 (차이) | 다음 판 → (차이)
Hunting CUDA Bugs at Scale with cuFuzz
AuthorMohamed Tarek Ibn Ziad, Christos Kozyrakis
ConferenceOOPSLA1
Year2026



개요

이 논문은 CUDA 프로그램에서 Coverage-guided fuzzing이 왜 기존 CPU fuzzing 방식처럼 잘 작동하지 않는지를 분석하고, whole-program fuzzing, device-side coverage, sanitizer 분리를 결합한 cuFuzz로 실제 GPU 메모리 안전성 및 동시성 버그를 찾을 수 있음을 보인다.

연구 성격은 GPU 프로그램을 위한 tooling/debugging paper이자, Memory safety, Data race, closed-source CUDA library 테스트를 다루는 systems/security-oriented testing paper에 가깝다.

Motivation

GPU는 image processing, machine learning, LLM, HPC 등에서 핵심 실행 장치가 되었지만, CUDA 프로그램은 CPU host code와 GPU device kernel이 함께 동작하는 heterogeneous execution model을 가진다. 이 구조에서는 입력 파싱, host-side validation, CUDA API error handling, device kernel launch, GPU memory access가 서로 얽혀 버그가 발생한다.

기존 정적 분석 도구는 false positive가 많고, 동적 검사 도구는 주어진 입력에서 실제로 실행된 오류만 찾는다. 따라서 자동으로 입력을 탐색하는 fuzzing이 자연스러운 해법이지만, CPU용 AFL++ 같은 fuzzer를 CUDA 프로그램에 그대로 적용하기에는 다음 문제가 있다.

첫째, kernel-level fuzzing은 개별 GPU kernel 인자를 독립적으로 변형하므로 host code가 보장하는 invariant를 깨뜨린다. 그 결과 실제 full program에서는 불가능한 오류를 보고하거나, 반대로 host-device interaction에서 생기는 오류를 놓친다.

둘째, host-side coverage만으로는 device-side branch와 kernel-internal behavior를 충분히 관찰할 수 없다. 특히 nvTIFF, nvJPEG, nvJPEG2000, cuDNN, cuBLAS 같은 closed-source CUDA library는 recompile 기반 coverage instrumentation이 불가능하다.

셋째, CUDA 환경에서는 coverage 수집 도구와 sanitizer가 같은 GPU runtime API에 의존하여 같은 process 안에서 함께 동작하기 어렵다. NVIDIA Compute Sanitizer의 memcheck, racecheck, initcheck는 device-side bug detection에 필요하지만, NVBit 기반 coverage와 동시에 붙이기 어렵다.

Importance

이 논문의 중요성은 GPU fuzzing을 "개별 kernel을 얼마나 공격적으로 흔들 것인가"가 아니라 "host-device context를 보존하면서 실제 실행 가능한 입력 공간을 어떻게 탐색할 것인가"라는 문제로 재정의했다는 점에 있다. 이 관점은 GPU testing에서 false positive를 줄이는 것뿐 아니라 closed-source production library까지 fuzzing 대상으로 포함시키는 데 중요하다.

또한 cuFuzz는 GPU fuzzing에서 coverage, sanitization, throughput이 서로 긴장 관계에 있음을 정량적으로 보여준다. device-side coverage는 closed-source library에서 탐색력을 크게 높이지만 NVBit overhead를 만든다. sanitizer는 더 많은 bug를 찾지만 fuzzing throughput을 낮춘다. persistent mode는 CUDA runtime initialization cost를 줄일 수 있지만 harness 구조에 따라 효과가 달라진다. 따라서 이 논문은 GPU fuzzing tool 자체뿐 아니라 이후 연구가 어떤 tradeoff를 다뤄야 하는지 보여주는 reference point 역할을 한다.

Challenge

  1. Fuzzing granularity: kernel-level fuzzing은 host-side validation, CUDA API error handling, inter-kernel dependency를 무시한다. 이 때문에 실제 프로그램에서는 발생하지 않는 parameter combination을 만들거나, host-side root cause가 device-side symptom으로 나타나는 버그를 놓친다.
  2. Device-side coverage: CUDA host code와 device code는 compilation path와 execution model이 다르다. host coverage만 쓰면 GPU 내부 branch를 feedback으로 사용할 수 없고, closed-source library는 source-level instrumentation을 적용할 수 없다.
  3. Tool incompatibility: AddressSanitizer는 host-side memory bug에 강하지만 device memory bug를 보지 못한다. 반대로 Compute Sanitizer는 device-side bug를 잘 잡지만 NVBit, cuda-gdb 등 다른 GPU instrumentation tool과 같은 process에서 충돌한다.
  4. Throughput: CUDA runtime initialization, GPU context setup, dynamic binary instrumentation, device sanitizer 실행은 fuzzing iteration cost를 크게 만든다. GPU fuzzing은 CPU fuzzing보다 input/sec가 낮아 coverage와 sanitizer를 무작정 모두 켜기 어렵다.

Main Idea

핵심 아이디어는 CUDA bug를 개별 kernel의 local property가 아니라 whole program execution에서 나타나는 host-device interaction의 결과로 보고, fuzzer feedback과 error checking을 이 전체 실행 경로에 맞게 재구성하는 것이다.

cuFuzz는 application main function이나 library sample program을 fuzzing harness로 삼아 실제 host-side invariant를 보존한다. 동시에 NVBit로 device binary를 runtime instrumentation하여 GPU edge coverage를 수집하고, 이를 AFL++ host coverage bitmap과 합친다. sanitizer는 coverage collection process와 분리하여 별도 process에서 실행함으로써 도구 충돌을 피한다.

따라서 cuFuzz의 design thesis는 "whole-program fuzzing으로 입력의 현실성을 보존하고, device-side coverage로 GPU 내부 탐색을 guide하며, decoupled sanitization으로 silent device bug를 관찰한다"로 요약할 수 있다.

Design

  1. Whole-program harness
    cuFuzz는 개별 kernel wrapper가 아니라 CUDA application의 main function 또는 CUDALibrarySamples의 library example을 fuzzing harness로 사용한다. 이 방식은 host-side input validation, CUDA API 호출 순서, buffer allocation, kernel launch dependency를 보존한다. closed-source library의 경우에도 공개 sample이 API call sequence를 제공하므로 library 내부 source 없이 fuzzing할 수 있다.
  2. Host+device coverage integration
    Host-side coverage는 AFL++ compile-time instrumentation을 그대로 사용한다. Device-side coverage는 NVBit로 kernel load 시점에 device instruction을 patch하여 basic block edge를 추적한다. cuFuzz는 64KB AFL++ coverage bitmap을 host/device 영역으로 나누어 collision을 줄이고, device edge는 bitmap 후반부에 기록한다.
    GPU에서는 수천 개 thread가 같은 coverage entry를 동시에 갱신할 수 있으므로, cuFuzz는 warp-aware atomic update를 사용한다. 또한 AFL++의 edge hit count bucket과 유사하게 device-side에서는 thread count를 coarse bucket으로 압축하여 input이 새 GPU execution behavior를 만들었는지 판단한다.
  3. Decoupled sanitization
    cuFuzz는 coverage collection process와 sanitizer process를 분리한다. 기본 fuzzing run은 host/device coverage를 수집하고, interesting input은 sanitizer-enabled program으로 전달된다. Host-side bug는 AddressSanitizer로, device-side illegal access, race, uninitialized read는 Compute Sanitizer의 memcheck, racecheck, initcheck로 검사한다.
    이 구조는 process 수를 늘려 overhead를 만들지만, NVBit와 Compute Sanitizer의 runtime incompatibility를 우회한다. 논문은 SAND에서 영감을 받은 selective sanitization을 사용해 모든 input이 아니라 unique execution path를 만드는 input subset에 sanitizer를 적용한다. 기본 전략은 simple-trace이다.
  4. Persistent mode
    AFL++ 기본 실행은 input 하나마다 process를 새로 띄우므로 CUDA runtime initialization cost가 크다. cuFuzz는 AFL++ persistent mode를 지원하여 한 process가 여러 input을 loop 안에서 처리하도록 한다.
    Device-side coverage를 input별로 분리하기 위해 harness는 persistent loop의 시작과 끝에 빈 notification kernel을 호출한다. NVBit tool은 이 kernel launch를 intercept하여 iteration 시작 시 device bitmap을 reset하고, 끝날 때 host bitmap과 merge한다. 별도 sanitizer process가 input을 읽을 수 있도록 persistent loop 안에서 mutated buffer를 file로 기록한다.

Result

Evaluation setup
논문은 HeCBench와 CUDALibrarySamples에서 가져온 14개 CUDA program/library를 평가했다. 대상은 attention, boxfilter, crs, dxtc2, lud, medianfilter, recursiveGaussian, seam-carving, urng, nvTIFF, nvJPEG, nvJPEG2000, cuDNN, cuBLAS이며, input format은 ppm, jpg, tif, jp2/j2k, bmp, integer tuple 등으로 다양하다. 실험은 두 대의 A40 GPU server에서 실행되었고, 주요 configuration은 24시간 fuzzing을 세 번 반복했다. 전체 실험 규모는 8,064 GPU hours이다.
Bug finding
cuFuzz는 43개의 previously unknown bug를 발견했다. 이 중 19개는 commercial library에서 발견되었고, 논문 작성 시점에 40개는 maintainer가 fixed 상태로 처리했다. bug type은 host heap buffer overflow 8개, host stack buffer overflow 1개, floating-point exception 2개, segmentation fault 2개, device out-of-bounds access 13개, shared memory data race 5개, uninitialized device read 12개다.
이 결과는 decoupled sanitization의 필요성을 직접 보여준다. AFL++와 cuFuzz-noSanitizer는 각각 11개와 9개 bug만 찾았고 주로 host-side crash에 제한되었다. Device-side sanitization을 켠 cuFuzz-noDeviceCoverage는 30개를 찾았다. 기본 cuFuzz simple-trace 전략은 36/43개, 즉 83%를 찾았다.
Coverage
Device-side coverage는 closed-source library에서 특히 중요했다. cuFuzz는 closed-source benchmark에서 cuFuzz-noDeviceCoverage보다 device-side edge를 더 많이 발견했고, improvement는 nvTIFF의 9%부터 blas-gemm의 289%까지 나타났다. 반면 open-source benchmark에서는 host-side instrumentation만으로도 device execution이 충분히 guide되는 경우가 많아 AFL++가 더 빠르게 같은 coverage에 도달하거나 더 높은 coverage를 보이기도 했다.
이 결과는 device-side coverage가 항상 무료 benefit은 아니며, source를 recompile할 수 있는 단순 benchmark와 closed-source production library 사이에서 가치가 달라진다는 점을 보여준다.
Performance
전체 input 기준으로 NVBit device-side coverage는 throughput을 평균 55% 낮췄고, Compute Sanitizer의 memcheck와 racecheck는 각각 71%, 82% 낮췄다. 실제 device kernel을 실행하는 input만 보면 NVBit overhead는 67%로 측정되었고, device-side sanitizer overhead는 memcheck 39%, racecheck 66%, initcheck 32%로 요약된다.
Host-side coverage와 AddressSanitizer의 overhead는 상대적으로 작았다. 따라서 cuFuzz의 주 병목은 host instrumentation이 아니라 device binary instrumentation과 device sanitizer initialization/runtime cost이다.
Persistent mode
cuFuzz-persistent는 14개 program 중 7개에서 regular cuFuzz보다 높은 edge coverage를 얻었고, 4개에서는 같은 coverage를 얻었다. 즉 11/14개 workload에서 equal-or-better coverage를 보였다. 다만 seam-carving, urng, nvTIFF에서는 loop 내부 reinitialization이나 매우 짧은 kernel execution 때문에 persistent mode benefit이 사라졌다.
Bug finding 측면에서 persistent mode는 35/43개 bug를 찾았고, 16개 bug에 대해서는 모든 configuration 중 가장 짧은 time to exposure를 보였다. 이는 CUDA runtime initialization cost가 GPU fuzzing throughput의 중요한 병목임을 뒷받침한다.
Kernel-level fuzzing comparison
논문은 9개 open-source benchmark의 22개 kernel에 대해 kernel-level harness를 별도로 만들고 cuFuzz와 비교했다. Kernel-level fuzzing은 적용 가능한 14개 device-side bug 중 6개만 찾았다. 나머지 8개는 host-side file parsing, allocation failure handling, host-device interface mismatch 같은 whole-program context가 필요해 놓쳤다.
더 중요한 차이는 false positive다. Kernel-level fuzzing은 host-enforced invariant를 깨뜨려 16개의 false positive를 만들었다. 반면 cuFuzz whole-program approach는 14개 device-side bug와 10개 host-side bug를 찾으면서 false positive를 만들지 않았다.

Contribution

  1. CUDA fuzzing에서 kernel-level fuzzing, host-only coverage, sanitizer incompatibility가 왜 실제 bug finding을 방해하는지 체계적으로 정리했다.
  2. Whole-program CUDA fuzzing과 host+device edge coverage를 결합하여 closed-source CUDA library까지 coverage-guided fuzzing 대상으로 만드는 cuFuzz를 제안했다.
  3. NVBit 기반 device-side coverage를 AFL++ coverage feedback과 통합하고, GPU thread concurrency, bitmap collision, thread-count bucket 문제를 다루는 구현 방식을 제시했다.
  4. Coverage collection과 host/device sanitization을 process-level로 decouple하여 AddressSanitizer와 Compute Sanitizer를 fuzzing loop에 함께 사용할 수 있게 했다.
  5. 14개 CUDA program/library에서 43개 previously unknown bug를 발견하고, coverage, throughput, persistent mode, sanitization strategy, kernel-level fuzzing 대비 효과를 정량적으로 평가했다.

Criticisms

cuFuzz의 evaluation은 NVIDIA CUDA stack, NVBit, Compute Sanitizer, A40 GPU 환경에 강하게 의존한다. 논문의 문제 정의는 GPU fuzzing 일반에 의미가 있지만, AMD GPU, OpenCL, SYCL, 다른 driver/runtime stack으로 일반화하려면 추가 검증이 필요하다.

Device-side coverage는 control-flow edge와 thread-count bucket에 기반한다. 논문도 인정하듯이 이 방식은 thread interleaving 차이를 구분하지 못한다. 따라서 racecheck가 발견한 5개 race는 비교적 deterministic하고 surface-level인 race일 가능성이 높으며, scheduling timing에 민감한 race condition은 놓칠 수 있다.

Throughput 제약은 여전히 크다. 논문은 persistent mode와 selective sanitization으로 완화하지만, GPU fuzzing은 CPU fuzzing처럼 많은 input/sec를 얻기 어렵다. Seed count도 benchmark별 1-5개로 제한되어 있고, 24시간 campaign 안에서 깊은 path까지 충분히 수렴했는지는 workload마다 다를 수 있다.

Coverage result는 configuration별 세 번의 24시간 run 중 best result를 보고한다. 모든 configuration에 같은 규칙을 적용했으므로 비교 자체는 일관적이지만, typical run behavior나 variance를 이해하려면 median, spread, failed run 분석이 더 있으면 좋다.

Kernel-level fuzzing 비교는 closed-source library를 제외한 open-source benchmark에 대해서만 가능했다. 이는 closed-source library의 kernel argument와 internal structure가 숨겨져 있기 때문이므로 타당한 제한이지만, kernel-level approach와의 비교 범위가 cuFuzz가 특히 강점을 보이는 production library 전체를 포함하지는 않는다.

Conclusion

이 연구는 CUDA bug finding을 device kernel 단위의 isolated testing이 아니라 host-device whole-program execution을 보존해야 하는 coverage-guided fuzzing 문제로 바라보게 만든다. cuFuzz는 whole-program harness, NVBit 기반 device-side coverage, decoupled sanitization, persistent mode를 결합하여 closed-source production CUDA library에서도 실제 bug를 찾을 수 있음을 보였다.

따라서 이 논문은 GPU software stack의 reliability/security testing을 다룰 때, 특히 CUDA, GPU, Fuzzing, Dynamic binary instrumentation, Sanitizer 관련 연구를 비교할 때 중요한 기준점이다. 핵심 takeaway는 GPU fuzzing에서 coverage의 위치와 sanitizer의 위치를 분리하고, host-device context를 보존해야 false positive 없이 실제 버그를 찾을 수 있다는 것이다.