← 목록으로
보안·개인정보
#메모리안전성#버퍼오버플로#Use-after-free#Rust#MTE
최종 업데이트 · 2026-10-23

메모리 안전성(Memory Safety)과 메모리 안전 언어

1. 개요

정의: 메모리 안전성(Memory Safety)은 프로그램이 실행 중 할당받은 메모리 영역의 공간적 경계(spatial)와 시간적 유효기간(temporal)을 벗어나 메모리에 접근하지 못하도록 보장하는 성질이다. 메모리 안전한 프로그램은 유효하게 할당되고 아직 해제되지 않은, 그리고 타입에 부합하는 영역만 읽고 쓴다.

C와 C++은 포인터 산술과 수동 메모리 관리로 고성능과 하드웨어 직접 제어를 제공하는 대신, 프로그래머가 경계 검사와 수명 관리를 전적으로 책임진다. 문제는 사람이 이 책임을 완벽히 이행하기가 사실상 불가능하다는 점이다. 버퍼의 끝을 한 바이트 넘기거나(off-by-one), 이미 해제한 포인터를 다시 참조하는(use-after-free) 실수는 코드 리뷰와 테스트를 통과해 운영 환경까지 살아남는다. 이렇게 남은 결함은 단순한 버그에 그치지 않고, 공격자가 프로그램의 제어 흐름을 탈취하는 원격 코드 실행(RCE)의 출발점이 된다.

이 문제가 산업 전체의 의제로 격상된 이유는 통계가 압도적이기 때문이다. Microsoft는 자사 제품에서 매년 보고되는 보안 취약점(CVE)의 약 70%가 메모리 안전성 문제라고 밝혔고, Google도 Chromium과 Android에서 유사한 비율을 반복적으로 확인했다. 즉 메모리 안전성은 수많은 취약점 유형 중 하나가 아니라, 심각도 높은 취약점의 구조적 원천이다. 단일 결함을 패치하는 방식으로는 끝없이 재발하므로, "취약점을 근원에서 제거(eliminate at the source)"하자는 접근으로 전환되고 있다.

메모리 안전성을 논할 때 전제로 둘 특징은 세 가지다. 첫째, 그것은 이분법적 보장이다 — 한 곳이라도 안전하지 않으면 그 틈으로 전체 프로세스 메모리가 노출될 수 있어, "90% 안전"은 사실상 안전하지 않은 것에 가깝다. 둘째, 성능과의 본원적 긴장이 있다 — 경계·수명 검사는 어떤 형태로든 비용을 요구하므로 해법마다 그 비용을 어디(런타임·컴파일타임·하드웨어)로 옮기느냐가 갈린다. 셋째, 레거시 종속성이 크다 — 세상의 핵심 인프라가 C/C++로 쓰여 있어, 해법은 "새로 만드는 법"만이 아니라 "기존을 보강하는 법"까지 포괄해야 현실성을 가진다.

그 전환은 2022~2024년 사이 정부·표준 수준의 권고로 공식화되었다. 미국 NSA의 「Software Memory Safety」 정보지(2022), CISA·NSA·FBI와 국제 파트너의 「The Case for Memory Safe Roadmaps」(2023), 백악관 국가사이버국장실(ONCD)의 「Back to the Building Blocks」(2024)가 연이어 메모리 안전 언어로의 전환을 권고했다. 정보관리기술사 관점에서 이 주제의 본질은 "버그를 더 잘 잡는 법"이 아니라, 언어·런타임·하드웨어·조직 정책을 아우르는 소프트웨어 공급망 전반의 설계 패러다임 전환에 있다.

메모리 안전성은 기능 요구사항이 아니라 비기능적 보안 품질 속성이라는 점에서 다루기가 특히 까다롭다. 사용자가 보는 기능에는 드러나지 않아 우선순위에서 밀리기 쉽지만, 한 번 뚫리면 전체 시스템의 신뢰성을 무너뜨린다. 메모리 안전성이 유독 무거운 과제인 이유는 그 결함이 조용히 잠복하기 때문이다. 기능적 버그는 잘못된 화면이나 예외로 즉시 드러나지만, 메모리 안전성 위반은 평소에는 멀쩡히 동작하다가 특정 입력·타이밍에서만 깨진다. 경계를 한 바이트 넘긴 쓰기는 인접 변수를 미세하게 오염시키고, 그 결과는 전혀 다른 코드 경로에서 뒤늦게 터진다. 이런 비국소성(non-locality) 때문에 디버깅이 극도로 어렵고, 테스트 커버리지가 높아도 놓치기 쉽다. 바로 이 특성이 "사람의 주의력에 기대는" 접근의 한계를 규정한다.

2. 메모리 안전성 위반의 구조와 유형

메모리 안전성 위반을 이해하려면 먼저 두 축으로 나누어 보아야 한다. 공간적 안전성은 "허용된 범위 안을 접근하는가"의 문제이고, 시간적 안전성은 "아직 유효한 시점에 접근하는가"의 문제다. 버퍼 오버플로가 전자의 대표라면, use-after-free는 후자의 대표다. 두 축이 교차하면서 실제 취약점 유형이 파생된다.

이 구분이 중요한 이유는 두 안전성이 서로 독립적이어서 한쪽만 지켜서는 완전하지 않기 때문이다. 경계 검사(공간적)를 아무리 철저히 해도 이미 해제된 포인터를 쓰는 시간적 위반은 막지 못하고, 반대로 수명을 완벽히 관리해도 인덱스 범위를 넘으면 공간적 위반이 발생한다. 따라서 "메모리 안전"이라는 보장은 두 축을 동시에 충족할 때만 성립하며, 어떤 해법이든 두 측면을 어떻게 함께 다루는지로 평가해야 한다.

flowchart TD
    MS["메모리 안전성 위반"]
    MS --> SP["공간적 위반(경계 초과)"]
    MS --> TP["시간적 위반(수명 위반)"]
    SP --> OF["버퍼 오버/언더플로"]
    SP --> OOB["범위 밖 읽기/쓰기(OOB)"]
    SP --> TC["타입 혼동(type confusion)"]
    TP --> UAF["해제 후 사용(use-after-free)"]
    TP --> DF["이중 해제(double free)"]
    TP --> UI["미초기화 사용(uninitialized read)"]
    OF --> EXP["제어흐름 탈취·정보유출"]
    OOB --> EXP
    TC --> EXP
    UAF --> EXP
    DF --> EXP
    UI --> EXP

위 구조도에서 핵심은 겉보기에 다른 여러 취약점 유형이 결국 하나의 결과(공격자의 메모리 임의 읽기·쓰기)로 수렴한다는 점이다. 바꾸어 말하면, 공격자는 이 중 어느 한 유형이라도 성립시키면 동일한 공격력을 손에 넣는다. 방어가 한 유형만 막아서는 소용이 없고, 두 안전성 축을 모두 봉쇄하는 포괄적 보장이 필요한 구조적 이유가 여기에 있다. 공간적 위반은 배열이나 버퍼의 경계를 지키지 못할 때 생긴다. 예컨대 길이 검사 없이 strcpy로 긴 문자열을 짧은 스택 버퍼에 복사하면, 인접한 반환 주소를 덮어써 함수 복귀 시점에 공격자가 지정한 코드로 점프시킬 수 있다. 이것이 1988년 모리스 웜 이래 반세기 가까이 반복되는 고전적 스택 오버플로다.

공간적 위반이 특히 위험한 것은 데이터와 제어정보가 같은 메모리 공간에 뒤섞여 있기 때문이다. 스택에는 지역 변수뿐 아니라 함수의 반환 주소와 저장된 레지스터가 함께 놓이고, 힙에는 사용자 데이터와 할당자의 메타데이터가 인접한다. 경계를 넘는 쓰기가 이 제어정보를 건드리는 순간, 단순한 데이터 손상이 곧 프로그램 흐름의 탈취로 비약한다. 메모리 레이아웃이 공격면을 결정한다는 이 사실이, 레이아웃을 무작위화하거나 분리하려는 여러 완화 기법의 출발점이다.

시간적 위반은 수명(lifetime) 관리의 실패에서 온다. use-after-free는 free()로 반환한 메모리를 가리키는 포인터(dangling pointer)를 그대로 다시 사용하는 경우다. 해제된 청크가 다른 객체로 재할당된 뒤 과거 포인터로 접근하면, 공격자는 한 객체의 포인터로 다른 객체의 내용을 조작할 수 있다. 웹 브라우저처럼 객체 수명이 복잡하게 얽힌 대규모 프로그램에서 use-after-free가 특히 치명적인 이유가 여기에 있다. 다음 표는 주요 유형과 전형적 원인, 영향을 정리한 것이다.

유형 분류 전형적 원인 주된 영향
스택/힙 버퍼 오버플로 공간적 길이 검사 누락, off-by-one 제어흐름 탈취, RCE
범위 밖 읽기(OOB read) 공간적 인덱스 검증 누락 정보 유출(예: Heartbleed)
해제 후 사용(UAF) 시간적 dangling pointer 재참조 객체 조작, RCE
이중 해제(double free) 시간적 중복 free, 소유권 혼동 힙 메타데이터 오염
타입 혼동 공간/타입 잘못된 캐스팅 임의 읽기·쓰기
미초기화 메모리 사용 시간적 초기화 누락 정보 유출, 비결정적 동작

표에서 보듯 영향의 양상은 다르지만 뿌리는 같다. 모두 "메모리를 바라보는 포인터가 실제 객체의 경계·수명과 어긋나는" 데서 비롯된다. 따라서 근본 대책도 유형별 패치가 아니라, 포인터와 객체의 정합을 언어나 하드웨어가 강제하는 방향으로 모인다.

한편 미초기화 메모리 사용은 눈에 덜 띄지만 정보 유출의 조용한 통로다. 할당만 하고 초기화하지 않은 버퍼를 그대로 외부로 내보내면, 이전에 그 자리에 있던 다른 데이터(예: 암호키 잔재)가 함께 새어 나갈 수 있다. 결정적 결과가 아니어서 테스트로 재현이 어려운 것도 특징이다.

이 유형들이 추상적 위험이 아님은 실제 대형 사고가 입증한다. 2014년 OpenSSL의 Heartbleed(CVE-2014-0160)는 길이 필드를 검증하지 않은 범위 밖 읽기(OOB read) 하나로 서버 메모리의 개인키·세션까지 유출시켜, 전 세계 HTTPS 인프라의 재발급을 촉발했다. 2017년 Windows SMB의 EternalBlue는 버퍼 오버플로 계열 결함을 악용해 WannaCry·NotPetya 랜섬웨어 대유행의 통로가 되었다. 두 사건 모두 "메모리 한 줄의 경계 실수"가 국가·산업 단위 피해로 증폭된 사례로, 메모리 안전성이 왜 개별 버그가 아니라 시스템 리스크로 다뤄져야 하는지를 보여준다.

3. 익스플로잇 메커니즘과 완화 기술

메모리 결함이 곧바로 공격 성공을 뜻하지는 않는다. 결함(vulnerability)과 실제 악용(exploit) 사이에는 여러 단계가 있고, 방어는 그 사이사이에 장벽을 세운다. 공격자는 결함을 이용해 (1) 코드나 포인터를 덮어쓰고, (2) 방어 기법을 우회하며, (3) 실제 악성 동작을 실행하는 단계를 밟는다. 방어 측은 각 단계에 완화 기법을 배치해 공격 비용을 높인다. 이 "비용 상승" 관점은 왜 완화만으로 충분치 않은지를 설명하는 핵심 틀이다. 아래는 전형적 익스플로잇 체인과 그에 대응하는 완화 계층을 보여주는 세부 아키텍처다.

flowchart LR
    subgraph ATK["익스플로잇 체인"]
        V["메모리 결함 발견"] --> OW["메모리 임의 쓰기 확보"]
        OW --> HJ["제어흐름 탈취"]
        HJ --> PAY["페이로드 실행(ROP·쉘코드)"]
    end
    subgraph DEF["완화 계층(심층 방어)"]
        C1["스택 카나리(Canary)"]
        C2["DEP·NX(실행 방지)"]
        C3["ASLR(주소 무작위화)"]
        C4["CFI(제어흐름 무결성)"]
    end
    OW -. "탐지" .-> C1
    PAY -. "차단" .-> C2
    HJ -. "교란" .-> C3
    HJ -. "검증" .-> C4

예를 들어 전형적 스택 오버플로에서는, 길이 검사 없는 복사가 64바이트 버퍼에 128바이트를 써 넣어 인접한 저장된 반환 주소를 덮어쓰고, 함수가 복귀하는 순간 공격자가 심은 주소로 분기한다. 완화 기법은 이 체인의 각 고리를 끊으려 한다. 이 체인에서 완화 기법들은 공격을 원천 차단하는 것이 아니라 비용을 높이는 확률적 방벽이다. 스택 카나리는 반환 주소 앞에 임의값을 두어 오버플로로 그 값이 바뀌면 함수 복귀 전에 중단시킨다. DEP/NX는 데이터 영역을 실행 불가로 표시해 스택에 주입한 쉘코드의 직접 실행을 막는다. 그러자 공격자는 기존 코드 조각(gadget)을 이어붙이는 ROP(Return-Oriented Programming)로 우회했고, 이에 다시 ASLR이 주소를 무작위화해 가젯 위치 예측을 어렵게 만들었다.

문제는 이 공방이 끝나지 않는다는 데 있다. ASLR은 정보 유출(OOB read)로 주소가 새면 무력화되고, CFI는 성능 비용과 우회 기법 때문에 완전하지 않다. 즉 완화 기술은 사후적·확률적이어서, 공격자가 하나의 강력한 임의 읽기·쓰기 원시연산(primitive)을 확보하면 순차적으로 무너질 수 있다. 실제로 Pwn2Own 같은 해킹 대회에서는 이런 완화를 모두 갖춘 최신 브라우저도 메모리 결함 하나에서 출발한 익스플로잇 체인으로 반복적으로 뚫린다. 이 한계가 "결함을 애초에 만들지 않는" 언어·하드웨어 수준 접근으로의 전환을 정당화한다.

한편 결함을 사전에 발견하는 테스트 기법도 완화 체계의 한 축이다. 커버리지 기반 퍼징(fuzzing)은 무작위·변이 입력을 대량 주입해 비정상 경로를 탐색하고, 여기에 AddressSanitizer(ASan) 같은 동적 분석기를 결합하면 오버플로·UAF가 실제 크래시로 즉시 드러난다. Google의 OSS-Fuzz는 이 조합으로 주요 오픈소스에서 수만 건의 결함을 자동 발견했다. 다만 퍼징은 도달한 경로의 결함만 찾으므로 완전성을 보장하지 못한다 — 테스트로 결함을 줄일 수는 있어도 "없음"을 증명할 수는 없다는 점이 안전 언어로의 전환을 보완재가 아닌 근본책으로 만드는 이유다.

4. 메모리 안전 확보 접근법 비교

앞의 완화 기법이 공격을 "어렵게" 만든다면, 안전성 확보 접근은 결함 자체를 "불가능하게" 만드는 것을 지향한다. 메모리 안전성을 확보하는 길은 크게 세 갈래다. 첫째는 런타임 검사형 안전 언어로, Java·C#·Go·Python처럼 가비지 컬렉터(GC)가 수명을 자동 관리하고 모든 배열 접근에 경계 검사를 넣는다. 생산성과 안전성이 높지만, GC로 인한 지연(latency)과 메모리 오버헤드가 있어 운영체제 커널이나 실시간 시스템에는 부적합할 수 있다. GC는 "언제 메모리를 회수할지"를 런타임이 추적해 dangling pointer를 원천적으로 없애지만, 그 추적 비용과 예측 불가능한 수거 멈춤(stop-the-world)이 트레이드오프다. 금융 거래나 게임 서버처럼 꼬리 지연(tail latency)이 중요한 영역에서 GC 튜닝이 상시 과제가 되는 이유가 여기에 있다.

둘째는 컴파일타임 검증형 안전 언어인 Rust다. Rust는 GC 없이 소유권(ownership)·대여(borrowing)·수명(lifetime) 규칙을 컴파일러가 정적으로 검증하는 빌림 검사기(borrow checker)로, 값마다 소유자는 하나이며 가변 참조와 불변 참조가 동시에 존재할 수 없다는 규칙을 강제한다. 이로써 use-after-free와 데이터 경쟁(data race)을 런타임 비용 없이 원천 차단한다. 다만 소유권 모델을 벗어나야 하는 저수준 코드는 unsafe 블록으로 명시적으로 분리해 검증 책임을 좁힌다.

Rust의 안전성은 "불가능을 컴파일 에러로 바꾸는" 데 있다. 예컨대 컬렉션을 순회하는 동안 같은 컬렉션을 수정하려 하면, 다른 언어에서는 런타임에 반복자 무효화(iterator invalidation)로 이어질 코드가 Rust에서는 가변·불변 참조 공존 금지 규칙에 걸려 컴파일 자체가 실패한다. 이처럼 결함이 생산에 도달하기 전에 개발 단계에서 걸러지므로, 안전성의 책임이 사람의 주의력에서 도구의 보장으로 옮겨간다. 이것이 Rust가 "zero-cost abstraction"이라 불리는 이유다 — 안전성의 대가를 런타임 성능이 아니라 컴파일 시간과 설계 제약으로 치르기 때문이다.

셋째는 하드웨어 지원 안전성이다. 기존 C/C++ 자산을 다시 쓰지 않고 안전성을 보강하려는 접근으로, Arm의 메모리 태깅 확장(MTE)과 CHERI/Morello가 대표적이다. 다음 표는 세 접근을 기준별로 비교한다.

기준 런타임 검사(Java·Go) 컴파일타임(Rust) 하드웨어 지원(MTE·CHERI)
안전성 보장 시점 실행 중 컴파일 시 실행 중(HW 강제)
성능 오버헤드 GC·검사 비용 거의 없음 소~중간(설계별 상이)
기존 C/C++ 재사용 어려움(재작성) 어려움(재작성) 용이(재컴파일·보강)
학습·전환 비용 낮음~중간 높음(소유권) 중간(툴체인·HW)
적용 영역 앱·서버 시스템·커널·앱 레거시 보강

표의 비교에서 중요한 것은 차이가 생기는 이유다. Rust가 성능 오버헤드가 거의 없는 것은 안전성 검사를 런타임이 아니라 컴파일 시점으로 옮겼기 때문이고, 그 대가로 개발자가 소유권 규칙을 익혀야 하는 학습 비용이 높다. 반대로 하드웨어 지원 방식이 레거시 보강에 유리한 것은 소스 재작성 없이 재컴파일만으로 포인터에 태그를 붙여 검증할 수 있기 때문이며, 그 대가로 전용 CPU 기능과 메모리 태그 저장 오버헤드가 필요하다. 어느 하나가 절대 우위인 것이 아니라, 대상 시스템의 성능 예산·레거시 규모·팀 역량에 따라 전략적으로 조합하는 것이 현실적 해법이다.

실무에서는 한 시스템 안에서도 접근이 혼합된다. 예컨대 성능이 극한인 암호·네트워크 코어는 Rust로 재작성하고, 방대한 레거시 C 라이브러리는 MTE로 보강하며, 상위 비즈니스 로직은 GC 언어로 두는 계층별 차등 적용이 가능하다. 이때 관건은 서로 다른 안전성 보장을 가진 모듈이 맞닿는 경계(FFI, foreign function interface)다. 안전 언어가 비안전 코드를 호출하는 순간 보장이 끊기므로, 경계를 최소화하고 그 지점을 집중 검증·문서화하는 것이 전체 안전성을 좌우한다.

5. 심화: 정책·하드웨어·산업 적용 동향

메모리 안전성은 더 이상 개발팀 내부의 품질 이슈가 아니라, 정책·하드웨어·산업이 함께 움직이는 생태계 전환의 축이 되었다. 최근 3년간 메모리 안전성은 기술 담론을 넘어 국가 정책과 규제의 영역으로 들어왔다. 2024년 2월 백악관 ONCD가 발표한 「Back to the Building Blocks」 보고서는 소프트웨어 제조사가 메모리 안전 언어를 채택해 "취약점 유형 전체를 제거"할 것을 권고하며, 메모리 비안전 언어의 예로 C·C++을, 안전 언어의 예로 Rust를 제시했다. 이는 그보다 앞선 CISA·NSA·FBI와 국제 파트너의 「The Case for Memory Safe Roadmaps」(2023)가 제조사에 기존 제품의 메모리 안전 로드맵 수립을 요구한 흐름과 연결된다. 2024년 6월 CISA 등이 발표한 「Exploring Memory Safety in Critical Open Source Projects」는 핵심 오픈소스 172개를 분석해 과반이 여전히 C/C++로 작성되었고, 안전 언어로 작성된 프로젝트도 비안전 의존성을 통해 노출될 수 있음을 지적했다. (구체적 시행·강제 수준은 변동 가능하므로 원문 확인이 필요하다.)

하드웨어 측면의 흐름도 뚜렷하다. Arm의 메모리 태깅 확장(MTE)은 물리 메모리를 16바이트 단위(granule)로 나누어 각 단위에 4비트 태그를 부여하고, 포인터에도 같은 태그를 실어 접근 시 태그 일치를 하드웨어가 검사하는 "자물쇠-열쇠" 방식이다. 태그가 어긋나면 공간·시간 위반을 즉시 탐지한다. Google은 Android에서 MTE를 활용해 출시 전 상당수 메모리 버그를 잡았다고 보고했다(벤더 측 수치이므로 해석에 유의). 보다 근본적인 접근인 CHERI는 Arm의 실험 플랫폼 Morello에서 평가되는데, 일반 포인터를 위조 불가능한 하드웨어 능력(capability)으로 대체해 경계·권한을 포인터 자체에 각인한다.

이러한 하드웨어 보강은 소프트웨어 전환의 공백을 메우는 역할을 한다. 안전 언어로의 재작성은 수년이 걸리지만, 그 사이에도 레거시는 공격에 노출되어 있다. MTE 같은 하드웨어 기능은 이 "전환기 리스크"를 기존 바이너리에 바로 적용해 완화한다는 점에서, 소프트웨어 접근과 경쟁이 아니라 시간 축에서 상호 보완한다.

두 하드웨어 접근의 철학 차이도 음미할 가치가 있다. MTE는 확률적·점진적이다 — 4비트 태그는 16가지뿐이라 우연히 일치할 확률이 남지만, 소스 수정 없이 재컴파일만으로 광범위하게 적용할 수 있어 "레거시를 지금 보강한다"는 실용적 목표에 맞다. 반면 CHERI는 결정론적·근본적이다 — 능력 기반으로 경계를 원천 봉쇄하지만, ABI와 포인터 표현을 바꾸므로 생태계 전반의 재설계가 필요하다. 이 대비는 보안이 늘 "완결성"과 "채택 가능성" 사이의 균형임을 보여주며, 당분간 두 접근이 상호 보완적으로 공존할 가능성을 시사한다.

산업 적용 사례는 "전부 다시 쓰기"가 아니라 "새 코드부터 안전하게"라는 전략을 보여준다. Google은 Android에서 새로 작성하는 코드에 Rust를 우선 적용하는 정책으로 전환한 뒤, 새 코드의 메모리 안전 취약점 비중이 크게 줄었다고 발표했다. 핵심 통찰은 취약점이 대부분 새 코드와 최근 변경분에 집중되므로, 수십 년 된 레거시를 전부 포팅하지 않아도 신규 코드의 언어만 바꾸면 전체 취약점 곡선을 꺾을 수 있다는 점이다. Microsoft(Windows 커널 일부의 Rust 도입), AWS(Firecracker 등), Linux 커널(Rust for Linux)도 같은 점진적 전환 경로를 따르고 있다.

이 전략이 효과적인 데는 정량적 근거가 있다. 결함의 발생 밀도는 코드의 "나이"에 반비례하는 경향이 있어, 오래되고 안정된 코드는 이미 수많은 버그가 걸러진 반면 새로 추가된 코드에 결함이 몰린다. 따라서 신규 코드에만 안전 언어를 적용해도 시간이 지날수록 전체 코드베이스에서 안전하지 않은 코드의 비중과 신규 취약점이 함께 감소한다. 전면 재작성이라는 비현실적 목표 없이도 리스크 곡선을 꺾을 수 있다는 이 통찰이, 실용적 전환 전략의 이론적 토대다.

국내(한국) 맥락에서도 이 흐름은 점차 반영되고 있다. 공공·금융의 핵심 시스템 상당수가 C/C++ 기반 레거시와 임베디드 펌웨어로 구성되어 있어, 소프트웨어 공급망 보안(SBOM 의무화, 보안약점 진단 등)의 강화와 맞물려 메모리 안전성은 중장기 품질·보안 지표로 부상하고 있다. 다만 안전 언어 전환은 인력 숙련도와 검증 체계가 뒷받침되어야 하므로, 국내 조직은 전면 전환보다 신규 모듈·고위험 구성요소의 선별 적용과 정적분석·퍼징의 내재화를 우선하는 것이 합리적이다.

6. 고려사항 및 시사점

메모리 안전성은 단일 도구 도입으로 끝나는 과제가 아니라, 기술사 관점에서 다년에 걸친 전환 로드맵과 트레이드오프 관리가 필요한 전략적 의제다. 아래 고려사항은 기술 선택을 넘어 조직의 투자·거버넌스·공급망 전략으로 확장된다.

  • 점진적 전환 전략(전면 재작성 지양): 레거시 수백만 줄을 한 번에 포팅하는 것은 비용·리스크가 과도하다. 취약점이 집중되는 신규·고변경 모듈부터 Rust 등 안전 언어로 작성하고, 경계 지점(FFI)을 명확히 관리하는 스트랭글러(strangler) 방식이 현실적이다. 조직은 제품별 메모리 안전 로드맵과 KPI(신규 코드의 안전 언어 비중 등)를 설정해야 한다.
  • 성능·생산성 트레이드오프의 정량 관리: GC 언어의 지연, Rust의 학습 곡선, 하드웨어 방식의 태그 오버헤드는 각각 다른 비용을 부과한다. 지연에 민감한 커널·실시간 영역, 생산성이 중요한 업무 앱, 레거시 보강이 급한 영역을 구분해 접근법을 차등 적용하고, 벤치마크로 비용을 수치화해 의사결정해야 한다.
  • 공급망·의존성 관점의 확장: 자사 코드를 안전 언어로 바꿔도 C/C++로 된 라이브러리·의존성에 결함이 있으면 노출된다. SBOM(소프트웨어 자재명세서)으로 의존성의 언어·안전성을 가시화하고, 안전 언어 바인딩이나 샌드박싱으로 비안전 의존성을 격리하는 전략이 병행되어야 한다.
  • 심층 방어와의 병행(완전 전환 이전): 전환에는 시간이 걸리므로, 그 기간 동안 ASLR·CFI·스택 카나리·MTE 같은 완화 기술과 퍼징(fuzzing)·정적분석(SAST)·AddressSanitizer 기반 테스트를 병행해 잔존 위험을 관리해야 한다. 안전 언어 도입은 완화 기법을 대체하는 것이 아니라 그 부담을 근원에서 줄이는 것이다.
  • 인력·조직 역량과 규제 대응: 소유권 모델 등 새 패러다임은 교육·채용 비용을 수반한다. 동시에 정부 조달·보안 인증이 메모리 안전 로드맵을 요구하는 추세이므로, 규제 대응과 역량 내재화를 동시 과제로 묶어 중장기 계획에 반영해야 한다.
  • 측정 가능한 목표와 거버넌스: "메모리 안전을 지향한다"는 선언만으로는 전환이 진행되지 않는다. ONCD 보고서가 강조하듯 측정 가능성(measurability)이 핵심이며, 신규 코드의 안전 언어 비중, 메모리 안전 CVE 추이, 안전·비안전 경계(FFI) 수 같은 지표를 정의하고 경영 보고 체계에 포함해야 전환이 조직 차원에서 추적·관리된다.
  • AI 코드 생성 시대의 유의점: 생성형 AI가 대량의 코드를 쏟아내는 환경에서는, 학습 데이터에 포함된 방대한 C/C++ 관용구 때문에 비안전 패턴이 재생산될 위험이 있다. AI가 생성한 코드도 동일한 정적분석·퍼징·언어 정책의 통제 아래 두어, 생산성 향상이 안전성 후퇴로 이어지지 않도록 가드레일을 설계해야 한다.

참고자료


한 줄 요약: 메모리 안전성은 포인터가 객체의 경계·수명과 어긋나지 않도록 보장하는 성질로, 심각한 취약점의 약 70%를 차지하는 근원 문제이며, 런타임 검사(Java·Go)·컴파일타임 검증(Rust)·하드웨어 지원(MTE·CHERI)을 상황에 맞게 조합한 점진적 전환이 핵심 전략이다.