PostgreSQL AddressSanitizer 사용법: 메모리 오류 탐지와 회귀 테스트
작성자: Mark Wong 작성일: 2026년 2월 8일
PostgreSQL 개발 과정에서 메모리 오염(Memory Corruption) 버그는 원인을 추적하기 까다로운 문제 중 하나입니다. 특히 일반적인 로컬 테스트에서는 문제가 나타나지 않다가 특정 테스트 환경에서만 오류가 발생하기도 합니다.
이번 포스팅에서는 메모리 오류 탐지 도구인 AddressSanitizer(ASan)가 PostgreSQL 코드에서 실제 버그를 찾아낸 사례를 살펴봅니다. 또한 PostgreSQL 회귀 테스트(Regression Tests) 환경에서 AddressSanitizer를 처음 사용하는 개발자를 위해 로컬에서 ASan을 구성하고 테스트를 실행하는 방법을 단계별로 소개합니다.
1. 사건의 발단: 로컬에서는 통과한 PostgreSQL 테스트
최근 저는 최신 코딩 스타일을 적용하기 위해 몇 가지 시스템 카탈로그 함수를 리팩토링(Refactoring)하는 패치를 제출했습니다. 선택적 매개변수(Optional parameters)를 추가하는 작업이었죠.
제 로컬 환경에서 수행한 회귀 테스트는 모두 통과했습니다. 심지어 Cirrus CI의 대부분의 테스트 항목도 모두 통과했죠. 하지만 딱 하나, AddressSanitizer를 사용하는 테스트 세션에서만 빨간불이 들어왔습니다.
테스트 결과를 분석하는 방법은 여러 가지가 있지만, 간단히 말씀드리면 빨간색으로 표시된 Run test_world 섹션에서 회귀 테스트 실패가 확인되었고, Run Cores 섹션을 확장해 보니 무언가가 코어 파일(Core file)을 덤프했다는 것을 알 수 있었습니다.
코어의 백트레이스 상단에 적힌 AddressSanitizer의 설명은 명확했습니다. ‘8-byte-read-stack-buffer-overflow(8바이트 읽기 스택 버퍼 오버플로)’가 발생했다는 것이었죠.
2. 원인 분석: AddressSanitizer가 탐지한 8바이트 스택 버퍼 오버플로
AddressSanitizer 코어 덤프를 유발한 수많은 리포트를 건너뛰고, 가장 핵심이 되는 첫 번째 줄을 확인해 보았습니다.
Plaintext
[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를 추가했기 때문입니다.
C
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행에서 대체 무슨 문제가 발생한 것일까요? 백트레이스의 다음 줄이 그 해답을 알려줍니다.
Plaintext
[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가 어떻게 식별해 냈는지 잘 보여줍니다.
3. 로컬 환경에서 PostgreSQL AddressSanitizer 사용하기
마지막으로 로컬에서 AddressSanitizer를 사용하는 간단한 가이드를 정리해 드립니다. 이 설정 방법은 Cirrus CI 웹 출력 화면보다는 PostgreSQL 소스 코드의 .cirrus.tasks.yml 파일에서 더 쉽게 확인할 수 있습니다.
1단계: PostgreSQL 구성(Configure)
컴파일러 최적화를 비활성화하고 AddressSanitizer를 활성화하여 구성합니다.
Bash
./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의 동작 규칙을 정의합니다.
Bash
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:detect_stack_use_after_return=0"3단계: PostgreSQL 회귀 테스트 실행
설정이 완료되면 평소처럼 테스트를 실행합니다.
Bash
make check이 짧은 경험담이 다른 개발자분들께도 도움이 되기를 바랍니다.
PostgreSQL AddressSanitizer FAQ
PostgreSQL에서 AddressSanitizer는 무엇을 탐지하나요?
AddressSanitizer(ASan)는 메모리 오류를 탐지하기 위한 런타임 계측 도구입니다. 이 사례에서는 일반적인 로컬 회귀 테스트에서는 발견되지 않았던 8-byte-read-stack-buffer-overflow를 탐지해 문제의 원인을 추적하는 데 도움을 주었습니다.
PostgreSQL에서 AddressSanitizer를 어떻게 활성화하나요?
이 글에서 소개한 방법은 PostgreSQL 구성 단계에서 CFLAGS에 -fsanitize=address를 지정하는 방식입니다. 이후 ASAN_OPTIONS 등의 환경 변수를 설정한 뒤 회귀 테스트를 실행합니다.
PostgreSQL에서 AddressSanitizer 회귀 테스트는 어떻게 실행하나요?
PostgreSQL을 AddressSanitizer가 활성화된 설정으로 구성하고 필요한 환경 변수를 설정한 뒤 make check를 실행합니다. 이 글에서는 PostgreSQL 소스 코드의 .cirrus.tasks.yml 파일에서도 관련 설정 방법을 확인할 수 있다고 설명합니다.
이번 사례에서 스택 버퍼 오버플로가 발생한 원인은 무엇인가요?
리팩토링된 pg_get_expr()은 세 개의 인자를 기대했지만 호출부의 DirectFunctionCall2Coll()에서는 두 개의 인자만 전달하고 있었습니다. 이 문제는 세 개의 인자를 전달하는 DirectFunctionCall3Coll()을 사용하도록 수정해 해결할 수 있었습니다.
로컬 회귀 테스트가 통과해도 AddressSanitizer 테스트가 필요한 이유는 무엇인가요?
이 사례에서는 로컬 회귀 테스트와 Cirrus CI의 대부분 테스트가 통과했지만 AddressSanitizer를 사용하는 테스트 세션에서만 오류가 확인되었습니다. 따라서 일반적인 테스트에서 드러나지 않는 메모리 관련 문제를 확인하는 데 AddressSanitizer가 도움이 될 수 있음을 보여줍니다.