Clang을 사용한 동적 분석¶
이 문서는 Clang을 사용하여 Python과 그 라이브러리를 분석하는 방법을 설명합니다.
이 문서에서는 발견 사항을 해석하는 방법을 다루지 않습니다. 결과 해석에 관한 논의는 Marshall Clow의 Testing libc++ with -fsanitize=undefined를 참조하십시오. 이 블로그 게시물에서는 Clang이 libc++에서 발견한 문제를 자세히 살펴봅니다.
이 문서는 Clang에 중점을 두지만, 대부분의 기법은 일반적으로 GCC의 새니타이저에도 적용할 수 있습니다.
이 지침은 Linux에서 테스트되었지만 macOS에서도 작동할 것입니다. Windows용 지침은 완전하지 않습니다.
Clang이란 무엇입니까?¶
Clang은 LLVM 컴파일러용 C, C++ 및 Objective C 프런트엔드입니다. 프런트엔드는 LLVM의 최적화 도구와 코드 제너레이터에 대한 접근을 제공합니다. 새니타이저 또는 검사기는 의심스러운 동작이 표시되도록 컴파일된 코드를 계측하기 위해 코드 생성 단계에 연결되는 훅입니다.
새니타이저란 무엇입니까?¶
Clang 새니타이저는 의심스럽거나 정의되지 않은 동작을 식별하는 데 사용되는 런타임 검사기입니다. 검사는 실제 런타임 매개변수를 사용하여 런타임에 수행되므로 거짓 양성이 최소화됩니다.
사용할 수 있는 새니타이저는 여러 가지이지만, 정기적으로 사용해야 하는 두 가지는 Address Sanitizer(ASan)와 Undefined Behavior Sanitizer(UBSan)입니다. ASan은 컴파일러 옵션 -fsanitize=address로 호출하고, UBSan은 -fsanitize=undefined로 호출합니다. 플래그는 CFLAGS와 CXXFLAGS를 통해 전달되며, 때로는 컴파일러와 함께 CC와 CXX를 통해서도 전달됩니다.
새니타이저의 전체 목록은 Controlling Code Generation에서 확인할 수 있습니다.
참고
새니타이저는 실제 프로그램 매개변수를 사용하여 런타임에 작동하므로, 완전한 긍정 및 부정 자체 테스트 모음을 제공하는 것이 중요합니다.
Clang과 그 새니타이저에는 강점과 약점이 있습니다. 이는 버그를 발견하고 코드 품질을 개선하기 위한 도구 중 하나일 뿐입니다. Clang은 코드 리뷰, Valgrind등을 비롯한 다른 방법을 보완하는 데 사용해야 합니다.
Clang/LLVM 설정¶
미리 빌드된 Clang 빌드는 대부분의 플랫폼에서 제공됩니다:
macOS에서는 Clang이 기본 컴파일러입니다.
주요 Linux 배포판에서는
clang패키지를 설치할 수 있습니다. 경우에 따라llvm도 별도로 설치해야 하며, 그렇지 않으면 일부 도구를 사용할 수 없습니다.Windows에서는 Visual Studio(Code가 아님) 설치 프로그램에 “C++ clang tools for windows” 기능이 포함되어 있습니다.
clang을 소스에서 빌드할 수도 있습니다. 자세한 내용은 the clang documentation을 참조하십시오.
설치 관리자는 경우에 따라 필요한 모든 구성 요소를 설치하지 않습니다. 예를 들어, scan-build를 실행하거나 scan-view로 결과를 살펴보고 싶을 수 있습니다. 이 경우 Clang을 소스에서 빌드하고 tools/clang/tools의 도구를 PATH에 있는 디렉터리로 복사할 수 있습니다.
플랫폼의 채널에서 아직 최신 버전을 제공하지 않는 경우, 최신 버전의 Clang/LLVM을 얻는 것도 소스에서 빌드하는 이유입니다. 최신 버전의 Clang/LLVM에는 새로운 새니타이저 검사가 도입됩니다.
Python 빌드 설정¶
문서의 이 부분에서는 새니타이저가 테스트 스위트에서 Python을 분석하도록 하는 데 필요한 옵션으로 Clang과 LLVM을 호출하는 방법을 다룹니다.
컴파일러가 기본적으로 Clang으로 설정되어 있지 않은 경우 Clang으로 설정하십시오:
export CC="clang"
추가 새니타이저 옵션(Clang 문서에서 확인할 수 있음)을 사용하려면 CFLAGS 변수에 추가하십시오. 예를 들어, 검사 대상 프로세스가 첫 번째 실패 후 종료되도록 할 수 있습니다:
export CFLAGS="-fno-sanitize-recover"
그런 다음 관련 플래그를 사용하여 ./configure를 실행하십시오:
ASan:
--with-address-sanitizer --without-pymallocUBsan:
--with-undefined-behavior-sanitizer
--without-pymalloc 옵션은 필요하지 않지만(이 옵션 없이도 테스트를 통과해야 함), pymalloc을 비활성화하면 ASan이 더 많은 버그를 찾아내는 데 도움이 됩니다(ASan은 pymalloc이 수행한 개별 할당을 추적하지 않음).
두 새니타이저를 모두 지정해도 됩니다.
그 후 평소와 같이 make와 make test를 실행하십시오. 빌드 후반 단계에서 방금 컴파일된 Python이 실행되므로, 새니타이저 실패로 인해 make 자체가 실패할 수도 있습니다.
모든 코드에서 새니타이저를 활성화하기 위한 빌드 설정¶
Python의 일부 구성 요소(예: _testembed, _freeze_importlib, test_cppext)는 configure에서 설정한 변수를 사용하지 않을 수 있으며, 위 설정에서는 새니타이저 검사 없이 컴파일됩니다.
이 문제를 우회하려면 새니타이저 옵션을 컴파일러, 즉 CC (C용)와 CXX (C++용)를 통해 전달할 수 있습니다. 아래에서는 이 방법을 사용합니다. LDFLAGS를 통해 옵션을 전달하는 방법도 작동하는 것으로 보고되었습니다.
ASan의 경우 다음을 사용하십시오:
# ASan
export CC="clang -fsanitize=address"
export CXX="clang++ -fsanitize=address -fno-sanitize=vptr"
UBSan의 경우에는 다음을 사용하십시오:
# UBSan
export CC="clang -fsanitize=undefined"
export CXX="clang++ -fsanitize=undefined -fno-sanitize=vptr"
두 새니타이저를 모두 지정해도 됩니다.
그 후 평소와 같이 ./configure, make, make test를 실행하십시오.
출력 분석¶
새니타이저 실패가 발생하면 프로세스가 실패하고 다음과 같은 진단 정보를 출력합니다:
Objects/longobject.c:39:42: runtime error: index -1 out of bounds
for type 'PyLongObject [262]'
Objects/tupleobject.c:188:13: runtime error: member access within
misaligned address 0x2b76be018078 for type 'PyGC_Head' (aka
'union _gc_head'), which requires 16 byte alignment
0x2b76be018078: note: pointer points here
00 00 00 00 40 53 5a b6 76 2b 00 00 60 52 5a b6 ...
^
...
주소 새니타이저를 사용하는 경우 유용한 추적 정보를 얻으려면 추가 도구가 필요합니다. 일반적으로 llvm-symbolizer 도구를 통해 자동으로 처리됩니다. 이 도구가 PATH에 설치되어 있지 않다면 ASAN_SYMBOLIZER_PATH를 도구의 위치로 설정하거나, 테스트 출력을 Clang 배포판의 asan_symbolize.py 스크립트로 파이프할 수 있습니다. 예를 들어, 다음은 컴파일 중 발생한 이슈 20953의 사례입니다(명확성을 위해 서식 추가):
$ make test 2>&1 | asan_symbolize.py
...
/usr/local/bin/clang -fsanitize=address -Xlinker -export-dynamic
-o python Modules/python.o libpython3.3m.a -ldl -lutil
/usr/local/ssl/lib/libssl.a /usr/local/ssl/lib/libcrypto.a -lm
./python -E -S -m sysconfig --generate-posix-vars
=================================================================
==24064==ERROR: AddressSanitizer: heap-buffer-overflow on address
0x619000004020 at pc 0x4ed4b2 bp 0x7fff80fff010 sp 0x7fff80fff008
READ of size 4 at 0x619000004020 thread T0
#0 0x4ed4b1 in PyObject_Free Python-3.3.5/./Objects/obmalloc.c:987
#1 0x7a2141 in code_dealloc Python-3.3.5/./Objects/codeobject.c:359
#2 0x620c00 in PyImport_ImportFrozenModuleObject
Python-3.3.5/./Python/import.c:1098
#3 0x620d5c in PyImport_ImportFrozenModule
Python-3.3.5/./Python/import.c:1114
#4 0x63fd07 in import_init Python-3.3.5/./Python/pythonrun.c:206
#5 0x63f636 in _Py_InitializeEx_Private
Python-3.3.5/./Python/pythonrun.c:369
#6 0x681d77 in Py_Main Python-3.3.5/./Modules/main.c:648
#7 0x4e6894 in main Python-3.3.5/././Modules/python.c:62
#8 0x2abf9a525eac in __libc_start_main
/home/aurel32/eglibc/eglibc-2.13/csu/libc-start.c:244
#9 0x4e664c in _start (Python-3.3.5/./python+0x4e664c)
AddressSanitizer can not describe address in more detail (wild
memory access suspected).
SUMMARY: AddressSanitizer: heap-buffer-overflow
Python-3.3.5/./Objects/obmalloc.c:987 PyObject_Free
Shadow bytes around the buggy address:
0x0c327fff87b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff87c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff87d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff87e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff87f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c327fff8800: fa fa fa fa[fa]fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff8810: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff8820: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff8830: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff8840: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff8850: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
ASan internal: fe
==24064==ABORTING
make: *** [pybuilddir.txt] Error 1
참고
asan_symbolize.py가 설치되어 있지 않으면 Clang을 소스에서 빌드한 다음, Clang/LLVM 빌드 디렉터리에서 해당 파일을 찾아 직접 사용하거나 PATH에 있는 디렉터리로 복사하십시오.
발견 사항 무시하기¶
Clang에서는 컴파일 시 특수 무시 목록 파일을 제공하여 특정 소스 수준 항목에 대한 새니타이저 도구의 동작을 변경할 수 있습니다. 관리되지 않는 라이브러리 코드에서 문제가 수만 번 보고되더라도 모든 문제 사례를 보고하므로 무시 목록이 필요합니다.
-fsanitize-ignorelist=XXX로 무시 목록을 지정합니다. 예를 들면 다음과 같습니다.:
-fsanitize-ignorelist=my_ignorelist.txt
그러면 my_ignorelist.txt에는 다음과 같은 항목이 포함됩니다. 이 항목은 libc++의 ios 포매팅 함수에 있는 버그를 무시합니다.:
fun:_Ios_Fmtflags
Python 3.4.0을 예로 들면, audioop.c에서 여러 발견 사항이 발생합니다.:
./Modules/audioop.c:422:11: runtime error: left shift of negative value -1
./Modules/audioop.c:446:19: runtime error: left shift of negative value -1
./Modules/audioop.c:476:19: runtime error: left shift of negative value -1
./Modules/audioop.c:504:16: runtime error: left shift of negative value -1
./Modules/audioop.c:533:22: runtime error: left shift of negative value -128
./Modules/audioop.c:775:19: runtime error: left shift of negative value -70
./Modules/audioop.c:831:19: runtime error: left shift of negative value -70
./Modules/audioop.c:881:19: runtime error: left shift of negative value -1
./Modules/audioop.c:920:22: runtime error: left shift of negative value -70
./Modules/audioop.c:967:23: runtime error: left shift of negative value -70
./Modules/audioop.c:968:23: runtime error: left shift of negative value -70
...
주목할 함수 중 하나는 audioop_getsample_impl(422번째 줄에서 표시됨)이며, 무시 목록 항목에는 다음이 포함됩니다.:
fun:audioop_getsample_imp
또는 다음과 같이 파일 전체를 무시할 수도 있습니다.:
src:Modules/audioop.c
안타깝게도 새니타이저를 실행하기 전에는 무시 목록에 무엇을 추가해야 하는지 알 수 없습니다.
문서는 Sanitizer special case list에서 확인할 수 있습니다.