PostgreSQL AddressSanitizer로 버그 찾기: ASan 설정과 회귀 테스트 방법
```html
작성자: Mark Wong 날짜: 2026년 2월 8일
AddressSanitizer(ASan)는 메모리 오염(Memory Corruption) 버그를 찾기 위해 설계된 프로그래밍 도구입니다. 이 글에서는 AddressSanitizer가 PostgreSQL 개발 과정에서 실제 버그를 발견한 사례를 살펴봅니다. 또한 AddressSanitizer를 처음 사용하는 개발자도 로컬 환경에서 PostgreSQL 회귀 테스트(Regression Tests)와 함께 실행할 수 있도록 설정 방법을 단계별로 소개합니다.
AddressSanitizer가 PostgreSQL 버그를 발견한 사례
최근 저는 몇몇 시스템 카탈로그 함수를 최신 코딩 스타일에 맞춰 옵션 파라미터를 가질 수 있도록 리팩토링하는 #PatchPositivity 패치 시리즈를 제출했습니다.
로컬에서 PostgreSQL 회귀 테스트를 수행했을 때는 문제가 없었고, Cirrus CI 테스트도 대부분 통과했습니다. 하지만 AddressSanitizer를 사용하는 테스트 단계에서만 실패가 발생했습니다.
출력 결과를 검토하는 방법은 여러 가지가 있지만, 핵심은 다음과 같습니다. 빨간색으로 표시된 Run test_world 섹션은 회귀 테스트 실행 중 실패가 발생했음을 나타냈고, Run Cores 섹션을 확장하자 코어 덤프(Core Dump)가 생성된 것을 확인할 수 있었습니다. 코어의 백트레이스(Backtrace) 최상단에 표시된 AddressSanitizer 설명에는 8-byte-read-stack-buffer-overflow가 발생했다고 명시되어 있었습니다.
간결한 설명을 위해 AddressSanitizer 리포트 바로 다음에 나오는, 코어 덤프를 유발한 첫 번째 라인을 살펴보겠습니다.
[23:58:34.391] #9 0x000056200585ccf2 in pg_get_expr (fcinfo=0x7ffecac436a0) at ruleutils.c:2565
[23:58:34.391] expr = <optimized out>
[23:58:34.391] relid = <optimized out>
[23:58:34.391] pretty = <optimized out>
[23:58:34.391] result = <optimized out>
[23:58:34.391] prettyFlags = <optimized out>이 결과는 그리 놀랍지 않았습니다. pg_get_expr() 함수는 제가 리팩토링 중이었던 함수 중 하나였고, 반환된 표현식을 사람이 읽기 쉬운 형태로 만들 것인지 결정하는 세 번째 옵션 인자인 pretty를 추가했기 때문입니다.
2561 pg_get_expr(PG_FUNCTION_ARGS)
2562 {
2563 text *expr = PG_GETARG_TEXT_PP(0);
2564 Oid relid = PG_GETARG_OID(1);
2565 bool pretty = PG_GETARG_BOOL(2);그렇다면 2565번 라인, 즉 세 번째 인자를 가져오는 부분에서 무엇이 문제를 일으켰을까요? 백트레이스의 다음 라인이 그 답을 보여줍니다.
[23:58:34.391] #10 0x000056200594baff in DirectFunctionCall2Coll (func=0x56200585cc76 <pg_get_expr>, collation=collation@entry=0, arg1=139953427822388, arg2=<optimized out>) at fmgr.c:825
[23:58:34.391] fcinfodata = <optimized out>
[23:58:34.391] fcinfo = 0x7ffecac436a0
[23:58:34.391] result = <optimized out>
[23:58:34.391] __func__ = "DirectFunctionCall2Coll"문제의 원인은 DirectFunctionCall2Coll()이 세 개의 인자를 기대하는 pg_get_expr()에 인자를 두 개만 전달하고 있었기 때문입니다. 해결 방법은 간단합니다. 예상대로 세 개의 인자를 전달할 수 있도록 DirectFunctionCall3Coll()을 사용하면 됩니다.
이 사례는 런타임 인스트루멘테이션(Runtime Instrumentation) 도구가 없었다면 발견하기 어려웠던 버퍼 오버플로우 상황을 AddressSanitizer가 어떻게 찾아냈는지 보여줍니다.
PostgreSQL에서 AddressSanitizer 사용하는 방법
이제 AddressSanitizer를 로컬 PostgreSQL 환경에서 사용하는 방법을 살펴보겠습니다. 관련 설정 정보는 Cirrus CI 웹 출력 결과보다 PostgreSQL 소스 코드의 .cirrus.tasks.yml 파일에서 더 쉽게 확인할 수 있습니다.
1. AddressSanitizer를 활성화해 PostgreSQL 설정
AddressSanitizer를 활성화하고 컴파일러 최적화를 비활성화하여 PostgreSQL을 구성합니다.
./configure --enable-cassert --enable-injection-points --enable-debug --enable-tap-tests --with-segsize-blocks=6 CLANG=clang CFLAGS="-Og -ggdb -fno-sanitize-recover=all -fsanitize=address"2. AddressSanitizer 환경 변수 설정
환경 변수를 사용하여 AddressSanitizer의 동작 규칙을 정의합니다.
export UBSAN_OPTIONS="print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2"
export ASAN_OPTIONS="print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detct_stack_use_after_return=0"3. PostgreSQL 회귀 테스트 실행
설정이 완료되면 다음 명령어로 PostgreSQL 회귀 테스트를 실행합니다.
make check이 과정을 통해 로컬 PostgreSQL 개발 환경에서도 AddressSanitizer를 활성화한 상태로 회귀 테스트를 실행할 수 있습니다. 이 짧은 사례가 다른 개발자분들에게도 도움이 되기를 바랍니다.
PostgreSQL AddressSanitizer FAQ
AddressSanitizer(ASan)란 무엇인가요?
AddressSanitizer는 메모리 오염 버그를 찾기 위해 설계된 프로그래밍 도구입니다. 이 사례에서는 PostgreSQL 회귀 테스트 과정에서 발생한 스택 버퍼 오버플로우를 탐지하는 데 사용되었습니다.
PostgreSQL에서 AddressSanitizer는 어떤 버그를 발견했나요?
이 사례에서 AddressSanitizer는 8-byte-read-stack-buffer-overflow를 발견했습니다. 백트레이스를 분석한 결과 DirectFunctionCall2Coll()이 세 개의 인자를 기대하는 pg_get_expr()에 두 개의 인자만 전달하고 있었습니다.
pg_get_expr() 관련 버그의 원인은 무엇이었나요?
리팩토링 과정에서 pg_get_expr()에 세 번째 옵션 인자인 pretty가 추가됐지만, DirectFunctionCall2Coll()에서는 두 개의 인자만 전달되고 있었습니다. 이를 세 개의 인자를 전달할 수 있는 DirectFunctionCall3Coll()로 변경하는 것이 해결 방법이었습니다.
PostgreSQL에서 AddressSanitizer를 사용하려면 어떻게 설정하나요?
이 글의 설정에서는 PostgreSQL 구성 시 -fsanitize=address를 포함한 CFLAGS를 지정해 AddressSanitizer를 활성화합니다. 이후 UBSAN_OPTIONS와 ASAN_OPTIONS 환경 변수를 설정한 뒤 회귀 테스트를 실행합니다.
AddressSanitizer를 설정한 뒤 PostgreSQL 회귀 테스트는 어떻게 실행하나요?
PostgreSQL 설정과 환경 변수 구성이 완료된 후 make check 명령어를 실행합니다. 이 글에서는 해당 방법을 사용해 로컬 환경에서 PostgreSQL 회귀 테스트를 실행합니다.