회고록 블로그
[C언어 공부] sanitizer 공부하기 본문
※ 필자는 초초초초초보자입니다. 틀린 내용은 언제든지 피드백 부탁드립니다.
개요
리눅스에서 C 소스코드를 컴파일 하기 위해서 필요한 것 중 하나가 바로 gcc(다른 컴파일러도 있다)이다.
GCC는 GNU Compiler Collection의 약자로, GNU 컴파일러 모음이라고 한다.
https://gcc.gnu.org/ 에서 말하기를 GCC는 C, C++, Objective-C, Fortran, Ada, Go, D과 같은 언어의 라이브러리가 포함되어 있다고 한다.
GCC는 정말 감사하게도 여러 유용한 옵션을 제공하는데,
오늘은 그 중 하나인 sanitizer에 대해서 공부를 해보려고 한다.
Sanitizer?
한글로 그대로 발음해보면 새니타이저 정도가 될 것 같다.
일단 sanitizer에 대해서 공부를 해보자.
GCC에는 Program Instrumentation 옵션들이 있다.
위키백과에 의하면 컴퓨터 프로그래밍에서 instrumentation은 오류를 진단하거나, 추적 정보 를 쓰기 위해 제품의 성능 정보를 모니터하거나 측정하는 기능을 가리킨다고 한다.
GCC의 Program Instrumentation 옵션은 런타임 시 instrumentation를 제어하는 옵션이다.
이 옵션을 사용하는 이유는 1) 프로그램의 핫스팟(다른 것들보다 더 많이 실행되는 프로그램 명령)을 찾거나, 코드의 커버리지를 분석하거나, 프로필 기반 최적화에 사용하기 위해서 프로파일링 통계를 수집할 때 사용한다.
혹은 2) 런타임 체크를 추가해서 프로그래밍 에러를 감지하기도 한다. (유효하지 않은 포인터 역참조, 배열의 범위를 넘어가는 접근, 스택 스매싱과 같은 용납할 수 없는(?) 공격 등)
위키백과에 의하면 컴퓨터에서의 프로파일링(profiling) 또는 성능 분석은 프로그램의 시간 복잡도, 공간 복잡도, 함수 호출의 주기와 빈도 등을 측정하는 동적 프로그램 분석의 한 형태라고 한다.
참고자료 : https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
그리고 바로 이 Program Instrumentation 옵션 중 하나가 sanitizer이다.
Microsoft의 C++, C 및 어셈블러 공식 문서에 의하면 sanitizer는 결함 보고, 분석 및 방지를 위해 사용한다고 한다.
즉, Program Instrumentation의 옵션 중 하나 답게
런타임에서 발생하는 결함을 찾기 위해 사용하는 옵션인 것이다.
❖ gcc의 옵션으로 sanitizer가 있는건 맞지만, 반드시 gcc에서만 사용할 수 있는 것은 아니다.
clang 등에서도 사용할 수 있다.
아래에서 sanitizer 사용법을 읽다보면 알겠지만,
[sanitizer] 옵션을 사용한다는 것은 AddressSanitizer, UndefinedBehaviorSanitizer 등을 활성화 한다는 의미이다.
또한 여기에서 말하는 AddressSanitizer, UndefinedBehaviorSanitizer 등은 구글에서 개발한 도구이다.
따라서, gcc에만 국한되어 사용할 수 있는 도구라고 오해해서는 안된다.
https://www.jetbrains.com/help/clion/google-sanitizers.html
Sanitizer의 세부 내용
위의 내용만 봐도 sanitizer는 막강한 옵션이라는 것을 알 수 있다.
그래서 그런지 이 옵션은 다양하게 사용될 수 있다.
아래는 gcc.gnu.org 에서 확인한 일부 옵션이다.
-fsanitizer=address
-fsanitizer=kernel-address
-fsanitize=hwaddress
-fsanitizer=kernel-hwaddress
-fsanitizer=pointer-compare
-fsanitizer=pointer-subtract
-fsanitizer=shadow-call-stack
-fsanitizer=thread
-fsanitizer=leak
-fsanitizer=undefined
...
출처 : https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
모든 내용을 다 공부하기는 어렵다. 시간적으로 여유가 많지 않으니까 말이다.
그래서 몇 가지 배워보고 싶은 내용만 집어서 매뉴얼을 보며 공부해보기로 했다.
-fsanitizer=address
이 옵션은 AddressSanitizer를 활성화시킨다.
Microsoft C++, C 및 어셈블러 공식 문서(Docs)에 의하면 AddressSanitizer는
아래와 같은 가양성 0으로 찾기 어려운 버그들을 노출한다고 한다.
- alloc/dealloc 불일치 및 new/delete 형식 불일치
- 힙에 대한 할당이 너무 클 때 (allocation-size-too-big)
- calloc 오버플로 및 alloca 오버플로
- double-free (double 'free') 및 free 후 사용
- 힙 버퍼 오버플로
- 스택 버퍼 오버플로 및 언더플로
- etc...
출처 : https://docs.microsoft.com/ko-kr/cpp/sanitizers/asan?view=msvc-170
❖ 가양성 : 실제 농도는 정해진 허용치 미만이지만, 측정 농도가 그 허용치를 초과하는 성질
물론 Microsoft 공식 문서라서 리눅스/MacOS 등 환경에서는 조금 다를 수 있지만,
이런 것들을 탐지할 때 사용하면 좋을 것 같다는 느낌만 가져가도 될 것 같다.
-fsanitizer=thread
이 옵션은 ThreadSanitizer를 활성화한다.
ThreadSanitizer는 아래 글에 의하면 data race를 감지하는 감지기라고 한다.
https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual
❖ 데이터 레이스?
멀티 쓰레드/프로세스 환경에서 발생하는 오류 중 하나인데, 여러 쓰레드가 공유자원에 동시에 접근할 때 일어나는 경쟁 상황을 말한다.
예를 들어서, 변수 num의 값을 읽고 거기에 1을 5번 더한다고 했을 때
쓰레드0이 변수 num의 값을 읽어 +1을 하는 동시에
쓰레드1도 변수 num의 값을 읽어 +1을 하고,
또 동시에 쓰레드2가 변수 num의 값을 읽어 +1을 하는 상황이라면
(모두 동시에 접근해서 값을 더하고 있는 상황)
생각하기에는 10+1+1+1 = 13이 될 것 같지만 실제로는 10+1 = 11이 된다고 한다.
이 때문에 공유자원에 대해서 동기화 기법을 사용한다고 한다.
참고자료 : https://jungwoong.tistory.com/6
예전에 보안 공부하면서 레이스 컨디션 공격에 대해서 잠깐 공부했었는데, 그것과 비슷한 느낌의 개념인 것 같다.
-fsanitizer=leak
이 옵션은 LeakSanitizer를 활성화한다.
LeakSanitizer는 런타임 메모리 누수 감지기이다.
clang (gcc 컴파일은 아니지만 아마 비슷한 개념이지 않을까 싶다) 에서 말하기를 AddressSanitizer와 결합하여 메모리 오류와 메모리 누수를 모두 확인할 수 있다고 한다.
출처 : https://clang.llvm.org/docs/LeakSanitizer.html
-fsanitizer=undefined
사실 이 옵션을 공부하기 위해서 이 긴 공부들을 시작한 것이다.
이 옵션은 UndefinedBehaviorSanitizer(a.k.a. UBSan)를 활성화 시킨다.
즉, 런타임 시 정의되지 않은 동작을 감지하는 감지기를 활성화하는 것이다.
예를 들어서 0으로 나누기(1/0, 2/0, 3/0, 10/0, 11/0 등), 배열의 범위를 벗어나 액세스하기, 오버플로우, 언더플로우 등등이 있다.
개인적인 생각으로는 위에서 공부했던 -fsanitizer=address와 비슷한 것 같다.
왜 UBSan을 쓰면 좋은지 아직 감이 잡히지 않는다면, 아래의 예시를 생각해보자.
지금 우리 앞에 이런 코드가 있다.
#include <stdio.h>
int main(void)
{
int num = 0x7FFFFFFF; //int 범위의 최댓값
num += 1;
printf("%d", num);
return (0);
}
이 코드를 실행해보면 아래와 같이 "런타임 에러 없이" 실행이 된다.
다만, 출력된 값은 우리가 원하는 값이 아니다.
여기에서는 코드가 짧아서 한번에 원하는 값이 아니라는 것을 눈치챌 수 있고, 쉽게 찾을 수 있지만
코드가 길어질수록 찾기가 힘들어진다.
더구나 "에러"가 발생하는 것도 아니기 때문에 문제가 없다고 생각하고 배포하다가 큰 일이 터질 수도 있다.
이 상황에서 UBSan을 이용해보자.
참 감사하게도, 정수 오버플로우를 잡아서 알려준다.
이러한 정의되지 않은 문제를 잡아내기 위해서 UBSan을 사용한다.
그리고 UBSan은 하위 옵션이 있다.
가령 NULL 포인터를 역참조하는 경우만 검사하고 싶다면?
-fsanitize=null 옵션을 사용하면 된다.
정수 오버플로우만 검사하고 싶다면?
-fsanitize=signed-integer-overflow 옵션을 사용하면 된다.
UBSan을 잘 사용하면 프로그램을 코딩할 때 미처 고려/예상하지 못한 상황들을 점검할 수 있고,
그로 인해서 프로그램의 안정성이 떨어지는 위험 인자를 미리 제거할 수 있다. (오버플로우 등)
더 자세한 사항은 매뉴얼을 보자.
gcc 매뉴얼은 정말 친절하게 다 알려준다.
https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
코드를 모두 작성하고, 잘 컴파일 된다고 방심하면 안될 것 같다.
앞으로 sanitizer를 잘 사용해서 안정성 있는 코드를 작성하도록 노력해야겠다.
'3. Computer Science 공부 > 리눅스 및 유닉스' 카테고리의 다른 글
tar 명령어에서 f 옵션이 없을 때 에러가 발생하는 이유 (0) | 2021.12.29 |
---|