AddressSanitizer와 PostgreSQL: 스택 버퍼 오버플로 해결 사례

작성자: Mark Wong 작성일: 2026년 2월 8일

개발자에게 메모리 오염(Memory Corruption) 버그는 가장 까다로운 숙제 중 하나입니다. 이번 포스팅에서는 메모리 오류 탐지 도구인 **AddressSanitizer(ASan)**가 어떻게 실제 버그를 찾아냈는지, 그리고 PostgreSQL 회귀 테스트(Regression Tests) 환경에서 이를 처음 접하는 분들이 로컬에서 어떻게 실행할 수 있는지 간단한 가이드를 소개합니다.

1. 사건의 발단: “내 로컬에선 잘 되는데?”

최근 저는 최신 코딩 스타일을 적용하기 위해 몇 가지 시스템 카탈로그 함수를 리팩토링(Refactoring)하는 패치를 제출했습니다. 선택적 매개변수(Optional parameters)를 추가하는 작업이었죠.

제 로컬 환경에서 수행한 회귀 테스트는 모두 통과했습니다. 심지어 Cirrus CI의 대부분의 테스트 항목도 모두 통과했죠. 하지만 딱 하나, AddressSanitizer를 사용하는 테스트 세션에서만 빨간불이 들어왔습니다.

테스트 결과를 분석하는 방법은 여러 가지가 있지만, 간단히 말씀드리면 빨간색으로 표시된 Run test_world 섹션에서 회귀 테스트 실패가 확인되었고, Run Cores 섹션을 확장해 보니 무언가가 코어 파일(Core file)을 덤프했다는 것을 알 수 있었습니다. 코어의 백트레이스 상단에 적힌 AddressSanitizer의 설명은 명확했습니다. **’8-byte-read-stack-buffer-overflow(8바이트 읽기 스택 버퍼 오버플로)’**가 발생했다는 것이었죠.

2. 원인 분석: 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. 로컬 환경에서 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의 동작 규칙을 정의합니다.

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단계: 회귀 테스트 실행

설정이 완료되면 평소처럼 테스트를 실행합니다.

Bash

make check

이 짧은 경험담이 다른 개발자분들께도 도움이 되기를 바랍니다.

메일: salesinquiry@enterprisedb.com

Visited 7 times, 2 visit(s) today