굉장히 저명한 C++ 전문가이자 ISO C++ 표준 위원회의 의장이신 Herb Sutter님의 글을 번역해서 올려봅니다.

제목을 번역하는데 가장 애를 먹었네요. 원문이 이래요. Crate-training Tiamat, un-calling Cthulhu:Taming the UB monsters in C++. 그니까 티아마트한테 켄넬 훈련을 시키고 크툴루를 소환 해제한다는 뜻인데요. 티아마트를 켄넬 훈련을 시켰다는건 길들였다는 의미니까 길들인다고 번역을 했고요. 크툴루를 un-calling한다는 건 ‘돌려보낸다’고 번역하면 어린 아이를 집으로 돌려보내는게 연상되면서 웃길 것 같아서 그렇게 번역해봤습니다.

참고로 티아마트는 신화에서 외형에 대한 묘사가 여러가지로 갈리는 여신인데, 여기서는 머리가 5개 달린 용으로 생각하면 됩니다. 크툴루는 흔히 아는 그 문어같이 생긴 크툴루가 맞고요.


이 글은 C++ 표준과 UB에 대한 이해가 있어야 이해할 수 있기 때문에, 해당 내용이 생소하시다면 이 글을 참고하세요.

원문은 2025년 3월 30일에 게시되었으므로, 시간에 대한 상대적인 표현은 이 날짜를 기준으로 생각해주시면 됩니다.

원문 링크: https://herbsutter.com/2025/03/30/crate-training-tiamat-un-calling-cthulhutaming-the-ub-monsters-in-c/


이 글은 현재 진행 중인 우리의 C++ 소프트웨어 강화1및 안전성(securing) 확보 개선 작업에 대한 현황 보고입니다.

C++ 커뮤니티는 강화에 대한 작업을 잘 이어나가고 있습니다. 이러한 작업들은 산업 전반에 걸쳐, 개별적인 공급자2에 의해 완료되며 해당 공급자들은 C++ 프로그래머들이 이식성있게 사용할 수 있도록 표준화 작업에 기여합니다. 그동안 우리가 해 온 작업들(UB-free constexpr 컴파일 타임 코드)부터, 최근에 완료한 작업들(C++ 26 초안에서: erroneous behavior, bounds-hardened standard library, contracts for functional safety)과 현재 논의하고 있는 제안서들(진행중: 비야네 스트롭스트룹의 프로파일, 울파르 엘링슨의 원격 코드 실행 강화)까지 모두가 표준에 포함됩니다.3

이 작업들에서 가장 중요한 공통점은 각각의 작업들이 C++의 정의되지 않은 동작(UB)를 점점 더 많이 다룬다는 것입니다. 그 중에서도 공격자들에게 가장 많이 악용되는 UB를 다루죠. 우리는 UB를 체계적으로 다루고 있으며, 코드의 보안을 가장 많이 강화할 수 있는 일반적이고 중요한 사례들부터 다룹니다. 여기에는 초기화되지 않은 변수, 범위 외 접근, 포인터 오용, 원격 코드 실행을 위해 필요로하는 주요 UB 사례들이 포함됩니다. 이들은 공격자들이 악용하는 취약점이며, 우리는 이를 봉쇄하여 그 공격자들을 차단하고 있습니다.

흔한 (잘못된) 믿음: “UB는 C++의 핵심이기 때문에 유의미한 개선을 기대하는 것은 가망이 없다”

Safety Update
논의를 위해, 이 케이지는 용의 숨결이나 염력에도 끄떡없다고 가정합니다. 그냥 은유일 뿐이니까요.

기술 전문가들은 여전히 UB가 C++의 스펙과 프로그램에 얽혀있어 C++가 UB를 유의미하게 해결할 수 없을 것이라고 가정하는 것 같습니다. 그리고 그것은 사실입니다. 지금은 고요한 UB의 마수가 우리의 C++ 코드로 침투하는 것을 우연하게 놓치기가 너무나도 쉽기 때문입니다.

배경 지식을 조금 요약해서 말씀드려보죠. C++에서 (대개 우연히) UB를 유발시키는 코드는 우리의 메모리 안전성과 보안 취약성을 발생시키는 주요한 근본적 원인입니다. 프로그램이 UB를 포함할 경우, 어떤 일이든 일어날 수 있습니다. 흔히 이 모든 일들을 “UB 드래곤”이라고 부르면서 “UB는 당신의 하드 드라이브를 포맷시키거나 코에서 악마가 나오게 할 수도 있습니다”라고 말하기도 합니다. 여기서 티아마트와 크툴루에 대한 은유를 따왔죠. 그러나 이런 것들보다 더 최악인 것은, UB가 일반적으로 악용 가능한 보안 취약점 또는 고치는데 비용이 많이 드는 버그로 이어진다는 것입니다(UB에 대해 더 자세한 정보는 부록을 참고하세요).

그러니 이렇게 물어 볼만도 하겠죠: C++는 UB에 대해 주요한 변화를 맞이할 수 있나요? 그리고 그렇게 할 건가요?

요약 및 스포일러

이 글을 통해 C++ UB에 대한 길들이기 작업이 진행 중이라는 것을 보고할 수 있게 되어 기쁘군요…

  1. 2011년 제정된 C++11부터, 더욱 많은 C++ 코드들이 이미 UB로부터 자유로워졌습니다. 단지 대다수 사람들이 눈치채지 못했을 뿐이죠.
    • 스포일러: 모든 컴파일 타임 코드 constexpr / consteval 는 UB로부터 자유롭습니다. C++26을 기점으로 거의 모든 언어 그리고 많은 표준 라이브러리가 컴파일 타임에 사용가능하고, 컴파일 타임에 실행될 때 UB로부터 자유롭습니다(그러나 런타임에서는 그렇지 않습니다. 그러므로 이어지는 추가적인 작업은 모두 런타임 실행에 관한 것입니다).
  2. 2024년 3월부터, C++26 표준 초안은 이미 해결하기 쉬운 주요 런타임 UB 사례들을 제거했습니다. 이들은 중요한 보안 취약점 범주의 근본 원인이었습니다.
    • 스포일러: C++26 초안에서, 초기화되지 않은 지역 변수는 더 이상 UB가 아니며, string, vector, string_view, span과 같이 강화된 표준 라이브러리에서 발생하는 일반적인 범위 오류(이터레이터 제외) 대부분은 “강화된” 구현에서는 더 이상 UB가 아닙니다. (그리고 C++26은 일반적인 버그 감소를 위한 방어적 프로그래밍의 기능 안전성을 위한 언어 계약 기능도 포함하고 있습니다.)
  3. 이제, 우리는 더 많은 도구를 추가하고 C++ 언어에서 런타임 UB를 체계적으로 분류 및 해결하는 작업에 착수하고 있습니다.

만약 이러한 단계들이 성공적으로 완료된다면, 이를 통하여 C++는 보안 취약점 수를 기준으로 다른 현대적인 메모리 안전 언어와 동등해질 것이며 이는 C++를 안전성과 관련된 이유로 사용하지 않을 이유를 제거하게 될 것입니다. 다만 경쟁의 장을 다른 언어들과 나란히 하는 것은 해결해야 할 또 다른 보안 문제들이 여전히 남아있음을 의미한다는 것을 알아두시길 바랍니다. 예를 들면 기능성 안전성과 관련된 논리적 버그 같은 것들 말입니다(이 경우에는 C++26의 계약 기능이 도움이 될 것입니다). 우리는 먼저 다른 현대적인 언어들과 동등한 수준을 도달하기 위해 가장 가치있는 목표를 해결하고 있고, 그리고 나서 더 많은 일들을 할 것입니다.

중요한 것은, 이러한 C++의 강화에 대한 접근은  C++의 가치 제안4을 바꾸지 않는다는 점입니다. C++은 여전히 C++이며, C++을 “다른 무언가”로 바꾸려고 하는 것이 아닙니다. 예를 들면 필연적으로 성능적인 오버헤드가 따르는 일을 요구하지 않습니다. 이상의 모든 작업들은 현존하는 C++ 코드들과 “제로 오버헤드, 사용하지 않으면 지불하지도 않는다”라는 핵심 가치를 포용하며, 그저 메모리 안전성을 기본값으로 편리하게 만들 뿐입니다. 언제나 이들을 선택적으로 제외할 수 있으므로, 당신이 티아마트와 크툴루의 힘을 당신의 통제하에서 선한 목적으로 사용하고 싶다면 완전한 성능과 제어는 항상 이용 가능합니다.

그리고 이는 기존 코드를 새로운 표준으로 끌어올리기 위해 매우 쉽게 적용할 수 있도록 설계되었습니다.

  • 다수의 개선사항들은 아무런 코드 변경도 없이 적용 가능합니다(정말로요!). 단순히 여러분의 프로젝트를 C++26 컴파일러로 다시 컴파일하기만 하면 여러분의 코드는 더 안전해집니다. 이것은 매우 중요한데, 여러분이 코드를 짜면 버그가 나오고 그 버그를 고치려고 코드를 짜도 또 다른 버그가 나오기 때문입니다. 코드 수정을 요구하면 이러한 비용이 발생하므로 우리는 이것을 최소화하고자 합니다.
  • 설령 안전하지 않은 코드를 기본적으로 거부하는 프로파일 언어 서브셋을 선택하더라도, 여러분은 여전히 명시적이고, 검색grep 가능하며, 감사audit 가능한 “여기서는 안전 규칙을 무시합니다”라는 어노테이션(다른 언어의 “unsafe”와 유사)를 사용하여 안전하지 않은 코드를 작성하도록 선택적으로 제외할 수 있습니다.

이게 다예요. 만약 당신이 여기까지만 읽더라도 모든 이야기를 다 읽은 셈입니다.

하지만 제가 생각하기엔 디테일한 부분들도 꽤나 흥미롭거든요. 그러니 관심이 있으시다면 위에서 언급한 1, 2, 3 항목을 자세히 살펴보시죠…

(1) 2011년부터: constexpr 코드

C++11부터, C++의 컴파일타임 constexpr 세상은 이미 UB로부터 자유로운 샌드박스가 되었습니다. 강력한 컴파일 타임 연산을 가능케함은 물론 안전성까지 보장하여 C++의 고요한 혁신을 이루었죠. constexpr 평가가 진행되는 동안, 언어는 잘 정의된 동작well-defined behavior을 강제합니다. 와일드 포인터도, 초기화되지 않은 읽기도, 놀랄 만한 일도 없죠. 만약 이러한 연산이 정의되지 않은 동작을 발생시킬 수 있다면, 컴파일러는 단순히 컴파일 타임에 constexpr 평가하는 것을 거부합니다. 이는 실행시간 이전에 정확성을 보장하여 개발자들이 더 빠르고 안전하며 표현력 있는 코드를 작성할 수 있도록 힘을 보태줍니다.

C++의 모든 릴리즈는 언어와 표준 라이브러리가 컴파일 타임에 constexpr 코드에서 더 많이 사용할 수 있도록 해왔습니다. 그리하여 C++26에서는 거의 모든 언어와 표준 라이브러리의 많은 부분들이 constexpr 코드에서 사용할 수 있게 됐습니다.

이것은 모던 C++의 최고의 모습입니다. 컴파일 타임의 힘을 해방하는 것은 물론 정확성까지 강제하죠.

이는 실제 제품에서 사용 중입니다. 베이퍼웨어가 아니라요. 모든 주요 컴파일러들은 UB로부터 자유로운 constexpr 컴파일 타임 코드를 10년 넘게 지원해왔고 업계에서 널리 사용중에 있습니다. 아마도 오늘날의 거의 모든 복잡한 C++ 프로젝트들은 이미 적어도 UB로부터 자유로운 constexpr 코드를 일부 사용 중일것입니다. 매우 오래된 컴파일러로 컴파일된 매우 오래된 코드가 아닌 이상 말입니다.

(2) 2024년부터: C++26에 채택된 언어 안전성 및 소프트웨어 보안 개선 사항들

지난 한 해 동안, C++26은 언어 안전성과 소프트웨어 보안 분야에 있어 견고한 진전을 달성했습니다. 간략하게, C++26이 이미 채택한 내용들은 다음과 같습니다(일부 내용은 제가 이전에 작성한 출장 보고서와 중복될 수 있습니다. 자세한 내용은 링크를 참고하세요):

  • 2024년 3월에(제 2024년 3월 출장 보고서를 참고하세요), C++26 초안은 초기화 되지 않은 변수에 대한 UB를 제거하는 대신 erroneous behavior(오류 동작, EB)새로운 종류의 동작으로 변경하였습니다. 이는 여전히 “잘못된 코드”로 간주되지만(그래서 컴파일러가 여전히 경고해야 합니다), 코드가 규칙을 위반하더라도 UB 드래곤의 먹이가 되지 않도록 잘 정의(well-defined)되었습니다. 이는 심각한 보안 취약점 유형의 근본 원인 하나를 제거합니다.
  • 지난달(제 2025년 2월 출장 보고서를 참고하세요), C++26 초안은 추가적으로 강화된 표준 라이브러리 명세를 추가했습니다. 단순히 강화된 라이브러리를 재컴파일 하는 것만으로 우리 프로그램은 string, string_view, span, mdspan, vector, array, optional, expected, bitset, valarray와 같이 매우 인기있는 표준 타입에 대해 다수의 일반적인 (반복자를 제외한) 경계 안전성을 보장받습니다. (같은 회의에서, 우리는 일반적인 버그 감소를 위한 방어적 프로그래밍의 기능 안전성 개선을 돕는 언어 계약도 채택했습니다.)

가장 중요한 점은, 이 두가지 모두 채택의 성배를 달성했다는 것입니다:5: “당신의 기존 코드를 C++26 컴파일러와 강화된 라이브러리로 재컴파일하기만 하면 더 안전해집니다.” 참으로 놀라운 채택 사례입니다. 최근 제 강연을 보셨다면 이것이 제게 얼마나 중요한 것인지 잘 아실겁니다… 특히 11월에 폴란드에서 열린 제 강연의 짧은 클립C++를 개선하는 것의 사회적 가치에 대한 Q&A의 짧은 클립을 참고하세요. 물론 완전한 안전성 향상을 얻기 위해서는 코드 수정이 필요할 때도 있습니다. 여기에 이의를 제기할 사람은 없습니다. 예를 들어, 당신이 소유권에 대한 혼동으로 댕글링 포인터를 작성했다면, 이를 고치는 것은 물론 코드의 구조를 변경해야할 것입니다. 하지만 기존 코드를 재컴파일 하는 것만으로 안전성을 일부라도 개선할 수 있다는 것은 여전히 꽤 대단한 일이죠!

다시 한 번, 이것은 베이퍼웨어가 아니라 실제 제품에서 사용중입니다. 초기화되지 않은 변수에 대한 지원과 강화된 표준 라이브러리는 C++26 표준의 초안에는 새로울 수 있지만, 기존 컴파일러에서 이미 잘 지원되고 있습니다. 초기화되지 않은 변수의 경우, 이미 비표준 컴파일러 스위치인 -ftrivial-auto-var-init=pattern (GCC, Clang)과 /RTC1 (MSVC)을 사용할 수 있습니다. 강화된 표준 라이브러리의 경우, P3471의 저자들이 언급한 바와 같이 이미 주요 상업 환경에 배포되었습니다(지금 당장에도 libc++에서 사용할 수 있습니다. 이 문서를 참고하세요. MS-STL와 libstdc++은 유사한 옵션들을 가지고 있습니다.)6

“우리는 이미 존재하는 몇몇 코드베이스에서 Apple 플랫폼에 강화 기능을 배포한 경험이 있다.

Google은 수 억 줄의 코드에 이 기술을 배포한 경험을 담은 기사를 게시하였다. 그들은 성능에 대한 영향이 0.3% 정도로 낮았으며, 보안상 치명적인 버그를 포함하여 1000개 이상의 버그를 발견했다고 보고하였다.

Google 안드로메다팀은 약 1년 전에 강화 기능을 성공적으로 활성화한 경험에 대한 기사를 게시하였다.

libc++ 관리자들은 강화 기능을 활성화하는 것이 코드베이스에서 버그를 찾는데 도움이 되었다는 내용의 수 많은 비공식 보고를 받았다.

전반적으로, 표준 라이브러리 강화는 커다란 성공을 이루었으며, 사실 우리는 이 정도의 성공을 전혀 기대하지 않았다. 반응은 압도적으로 긍정적이다…”

이는 해결하기 쉬운 일을 해결하는 것의 가치, 그리고 파레토 법칙(일명 80/20 법칙)을 잘 보여줍니다. 즉, 투자한 것의 20% 내에서 80%의 이익을 얻는 경우가 많다는 것이죠.

(3) 지난 달 부터: C++26 시기 동안 진행 중인 더 많은 작업

약 1년 전부터, 여러 C++ 위원회 전문가들은 C++의 UB를 체계적으로 분류하거나 해결(혹은 둘다)하기 위한 제안을 독립적으로 해왔습니다:

  • 2023년 12월: 샤픽 야그무르의 제안 P3075R0은 C++의 언어 UB를 분류하고 이를 표준의 부속서로 문서화하는 것입니다.(이는 팬데믹 이전의 초기 문서 P1705R1을 기반으로 합니다.) 이는 2024년 3월 회의에서 Core Language Working Group(일명 CWG)의 지지7를 받았습니다.
  • 2024년 10월: 저의 제안 P3436R0는 UB를 분류하고 비야네 스트롭스트룹과 가브리엘 도스 레이스의 언어 프로파일 제안을 선택적으로 수용하는 메커니즘을 통해 체계적으로 해결하는 것입니다. 이 프로파일은 안전성을 기본값으로 만들기 위해 쉽게 선택할 수 있는 명명된 그룹 형태의 컴파일 타임 제약 및 런타임 검사를 지정할 수 있습니다. 더 자세한 정보는 제 2024년 11월 출장 보고서를 참고하세요. 이는 2024년 11월 회의에서 Safety and Security subgroup(일명 SG23)의 만장일치 지지를 받았습니다.
  • 2024년 10월: 티무르 도이믈러, 가슈페르 아즈만, 조슈아 번의 제안 P3100R1은 UB를 분류하고, C++26의 새로운 기능인 contract_assert를 통해 문제가 있는 언어 기능에 대해 런타임 검사를 수행함으로써 계약 위반을 체계적으로 해결하는 것입니다. 관련된 제안인 P3400은 안전성을 기본값으로 만들기 위해 쉽게 선택할 수 있는 명명된 그룹 형태의 런타임 검사로서 계약 레이블을 지정하는 것입니다. P3100은 2024년 11월 회의에서 Contracts subgroup(일명 SG21)의 만장일치 지지를 받았습니다.

아마 패턴이 보이실 겁니다. 제안자 및 봉사자들은 아래와 같은 일을 추진하려고 합니다.

  • 체계적으로 언어 UB를 분류하는 것,
  • UB를 제거하는 방식을 명시하는 것(이를 불법으로 만들거나, 혹은 필요한 경우 경계 검사와 같은 런타임 검사를 포함하여 잘 정의되도록 만드는 것),
  • 만약 그러한 작업이 (C++26에서 초기화되지 않은 지역 변수에 대해 수행하는 것처럼) 충분히 효율적인 경우 항상 일어나도록 하거나 쉽게 선택할 수 있도록 명명된 그룹 안에서 일어나도록 하는 것(프로파일 이름 또는 계약 레이블 이름),
  • 서로 다른 UB 사례들은 서로 다른 방법으로 해결되어야함을 인식하는 것, 우리는 그 노력을 감수할 것입니다… 마법 지팡이는 필요하지 않습니다. 그저 엔지니어링만 있으면 됩니다.

2025년 2월 회의에서, 모든 언어의 발전을 책임지는 메인 서브그룹(일명 EWG)은 이러한 제안들을 모아 다음을 추구하는 계획을 승인했습니다.

“…C++26 시기 동안의 언어 안전성 백서에는 C++의 코어 언어 UB에 대한 체계적인 처리가 담기며, 오류 동작(EB), 프로파일, 계약을 다룬다.”

이것은 C++26과 별개라는 점을 주목해주시길 바랍니다. C++26은 현재 기능 동결 단계를 거치고 있으며, 다음 1년은 검토와 마무리 작업에 전념할 것이기 때문에 우리는 현재 새로운 내용(UB 완화 조치 등)을 추가할 수 없습니다. 다만 우리는 이러한 움직임을 그대로 가져가길 원하며, 이 중요한 임무가 C++29까지 기다리는 것을 원하지 않습니다. 따라서 “C++26 기간 동안” C++26과 동시에 C++언어 UB를 분류하고 해결하기 위한 백서 작업을 진행할 예정이며, 이것이 C++26이 발행될 무렵게 발표되기를 희망합니다.

참고: 백서는 ISO의 간행물 중 기술 명세(Technical Specification, TS)의 일종입니다. 백서나 TS를 “피처 브랜치”라고 생각하면 좋습니다. C++ 위원회는 2012년부터 Concepts, Modules TS를 포함한 다수의 TS를 발표했으며 이들 대부분은 이미 국제 표준(IS)의 정식 버전으로 병합되었습니다. 백서와 TS는 C++ 위원회 내에서 동일한 프로세스를 사용하지만, 백서는 최종 단계에서 TS에 비해 ISO의 정식 절차가 간결하여 더 빠르게 발표될 수 있습니다.

따라서 지금부터 1~2년 동안, 우리는 C++ 언어의 UB 사례들을 체계적으로 분류하여 각 송곳니와 촉수에 눈에 보이는 라벨을 붙이는 작업을 수행할 것입니다. 그리고 나서, 가장 중요한 고가치의 목표부터 시작하여 해당 작업에서 언급된 3가지 도구를 사용하여 가장 적절한 방식으로 각각의 사례를 해결할 것인지에 대한 여부, 그리고 그 방법을 결정하기 시작할 것입니다:

  1. C++26 오류 동작. C++26 표준 초안이 이미 초기화되지 않은 지역 변수를 처리하는데 사용하고 있는걸 기억하실 겁니다.
  2. 비야네 스트롭스트룹의 프로파일가브리엘 도스 레이스의 P3589 프로파일 프레임워크. 이는 규칙과 검사에 대해 명명된 그룹을 만들 수 있도록 하여 프로그램 코드가 기본적으로 완전한 안전성을 쉽게 선택하고 필요한 곳에서 다시 전략적으로 제외할 수 있도록 합니다. 현재 진행 중인 노력들은 C++ 생태계 전반에 걸친 실험을 위해 프로파일 프레임워크와 몇 가지 핵심 프로파일의 구현 및 배포에 집중하고 있습니다.
  3. C++26 계약 어서트(contract assertion). 언어 기능을 검사하기 위해 P3400 레이블로 확장되어 명명된 검사 그룹을 생성할 수 있게 합니다.

거짓말은 안하겠습니다. 이는 어마어마한 양의 작업이 될 겁니다. 그리고 이는 일부 사람들이 C++이 절대 해낼 수 없을 것이라고 생각하는 작업들이죠. 하지만 저는 이러한 작업들이 충분히 해낼 수 있고, 그럴 가치가 있다고 생각하며, 이미 도움을 주겠다고 자원할 의향이 있음을 밝혀준 위원회 구성원들에게 감사의 말씀을 전하고 싶습니다. 감사합니다!

지난 주 소식: P3656이 강력하게 지지됨8

가슈페르 아즈만과 저는 이 작업을 조직하기 위해 임명되었습니다. 이 작업을 시작하기 위해 가슈페르와 저는 제안된 절차와 계획을 설명하는 문서 P3656을 작성했습니다. 3월 19일, EWG는 이 문서를 전화회의를 통해 검토하고 다음과 같은 지지를 투표로 결정했습니다.

“코어 언어 UB(및 IF-NDR9)에 대한 백서를 제작하기 위한 전략으로서 P3656은 ‘올바른 방향’에 있다.”

따라서 C++26과 같은 기간인 향후 1~2년간 우리가 목표로 하는 작업에 대한 간략한 개요는 아래와 같습니다…

첫째, 사례 목록 작성: 언어 UB를 열거

이 부분의 목표는 표준의 LaTeX10 소스에 모든 언어 UB 사례를 직접 태그하고, 최소한 간단한 설명과 코드 예제를 붙이는 것입니다. 표준이 이미 문법 등에 대해 하고 있듯이, 표준 소스 내에 LaTeX 태그를 사용하면 UB 목록을 한 곳에 자동적으로 구축할 수 있습니다. 추가적인 자세한 논의와 선정된 완화조치들은 백서에 포함될 것입니다.

또한, 우리는 각 UB에 대하여 몇 가지 기본적인 속성을 태그할 것입니다.

  • 보안 전문가들이 직접 악용 가능한지 태그하여 보안상 치명적이고 해결하기 쉬운 것부터 우선순위를 정할 수 있도록 합니다.
  • 이미 사용 가능한 정보로 로컬에서 검사하기 쉬운지(예: ptr != nullptr과 같이 로컬에서 적은 비용으로 검사할 수 있는 널 포인터 역참조) 또는 더 많은 정보가 필요한지(널 이외의 댕글링 포인터 역참조와 같이 더 어려운 일부 UB는 완전히 제거하기에 너무 비용이 클 수 있습니다.) 태그합니다.

이는 새로운 UB 제안에 대해 논의 및 문서화를 요구함으로써 미래에 새로운 UB를 추가하는 것을 억제하는 역할도 합니다.

둘째, 도구 목록 작성: “도구에 대한 비포괄적인 초기 메뉴”를 만들기

이 아이디어는 각 UB 사례에 적용할 수 있는 도구들의 초기 목록을 만드는 것입니다.

EWG의 임무에는 이미 오류 동작(EB), 프로파일, 계약이 주요 예상 도구로 포함되어 있으므로 조금 더 자세한 후보들은 다음과 같을 것입니다.

  • UB를 잘 정의well-defined되도록 만들기(선택적 적용 없이 항상 해결하며, 이는 런타임 검사가 될 수도 있음)
  • UB를 컴파일되지 않도록 하기(예: UB 경로를 피하기 위해 다른 대체 경로를 사용할 수 있는 SFINAE11 코드의 의미를 변경할 수 있는 ill-formed로 만들거나, 의미 변경 없이 직접 거부하는 것), 이는 항상 또는 프로파일/레이블이 강제될 때 수행됨
  • UB를 사용 중단deprecated 처리하기, 항상 또는 프로파일/레이블이 강제될 때 수행됨(다음 항목과 동시에 적용될 수 있음)
  • UB 대신 EB로 대체하기, 이는 항상(초기화되지 않았던 지역변수에 대해 수행했던 것 처럼) 또는 프로파일/레이블이 강제될 때 수행됨

이 목록이 모든 것을 총망라한 것은 아닙니다. 우리는 다른 기술을 사용하여 우리가 처리하고자 하는 UB를 찾을 수도 있지만, 저는 대부분의 UB 사례들은 이 도구들로 잘 처리될 수 있을 것으로 예상합니다.

또한 우리는 성능 고려사항, 채택 허들(해당 UB의 빈도, 충돌 결과 등), 기타 고려사항들을 포함하여 각 도구를 언제 사용해야하는지에 대한 초기 가이드라인을 작성하여 EWG의 검토와 승인을 받을 예정입니다.

셋째, 적용: 각 UB 사례에 대해 해결 계획을 명시

많은 경우, 이는 성능이나 배포 가능성 대한 리스크가 있을 때 견고한 구현 경험을 포함하는 신중한 문서를 필요로 할 것입니다. 저는 유사한 UB들을 묶어 하나의 문서에서 처리할 수 있을 것이라고 예상하지만, 핵심은 우리가 이 일을 체계적으로 진행하고 싶다는 것입니다… 우리는 신속하게 움직이는 것을 목표로 하지만, 주된 목표는 실제로 망가진 것을 확실하게 고치는 것입니다.

넷째, 그룹화: UB 사례들을 응집성 있는 그룹으로 묶기 (프로파일 이름 / 계약 레이블)

마지막으로, 프로그램이 하나의 단위로 쉽게 선택할 수 있도록, 함께 해결하기를 원하는 UB 사례들의 응집성 있는 그룹을 구분할 것입니다.

며칠 전 소식: 악성 코드가 의존하는 특정 UB를 봉쇄하려는 노력이 진행 중

이와 관련하여, Google의 클라우드 보안 부문 DE인 울파르 엘링슨이 2월 ISO C++회의에 매우 흥미로운 제안인 P3627R0(슬라이드)를 가져왔습니다: “기존 C++ 코드에서 RCE(원격 코드 실행)를 방지하기 위해 채택하기 쉬운 보안 프로파일”

울파르의 전제를 요약하자면:

  • 우리는 코드 변경 없이 기존 C++ 코드를 효과적으로 강화할 수 있는 충분한 강화 구현 기술을 이미 개발했습니다. 이는 광범위한 언어 메모리 안전 보장을 목표로하는 것이 아니라, 원격 코드 실행(RCE)를 가능하게 하는 핵심 UB를 외과적으로 타격함으로써 이루어집니다. 구체적으로는 스택 무결성(Stack integrity), 제어-흐름 무결성(control-flow integrity, CFI), 힙 데이터 무결성(heap data integrity), 그리고 포인터 무결성 및 위조불가능성(unforgeability)입니다. (참고: 울파르는 원래 CCured에서 이를 설계한 조지 네큘라와 협력해 강력한 보장을 갖춘 스택 무결성을 효율적으로 구현한 최초의 인물이며 그와 협력자들은 CFI를 최초로 제안하고 구현했습니다.)
  • 만약 우리가 RCE의 구성 요소로 사용될 수 있는 UB를 제거하는 것만 제대로 해도, 공격자들bad actors</sub>은 실행에 대한 제어권을 얻고 악성 코드를 실행하는데 사용하는 대부분의 도구를 잃게 될 것이며, 우리는 전세계의 코드를 극적으로 강화하게 될것입니다.
  • 핵심 문제는 현재 이러한 기술들이 별개의 기능으로 존재한다는 점이며, 진정한 이점은 이들을 함께 활성화할 때 발생하므로 우리는 프로그래머들이 컴파일러에 이들을 함께 활성화하도록 지시하는 프로파일을 표준화할 필요가 있습니다.

목요일에 울파르는 이러한 아이디어들을 자세히 설명하는 논문을 발표했습니다: “메모리 안전 없이 기존 C 및 C++ 소프트웨어를 보호하는 법”은 이러한 기술들이 어떻게 대부분의 RCE를 방지할 뿐만 아니라 일반적으로 공격자로부터 실행 제어권을 다시 가져올 수 있는지 설명합니다.

충분히 읽어볼 가치가 있습니다. 이 내용을 C++ 표준화에 제안하는 업데이트된 문서가 곧 C++ 위원회에 나올 것으로 예상됩니다. 울파르가 언급했듯이(볼드는 필자에 의한 것입니다12): “이것은 큰 변화이며 팀의 노력이 필요할 것입니다: 연구자들과 표준 기구들은 함께 협력하여 기존 소프트웨어를 안전하게 하기 위해 적용할 수 있는 보호 프로파일 집합을 정의해야합니다. 새로운 위험이나 어려움 없이, 플래그 하나를 뒤집는 것만으로 쉽게 말이죠…”

참고: 일주일 전에 업데이트된 관련된 새로운 간행물은 OpenSSF의 “C 및 C++를 위한 컴파일러 옵션 강화 가이드”입니다. 이는 알아두면 유용하고 오늘날의 컴파일러에서 사용할 수 있는 기존 보안 옵션에 대한 좋은 가이드입니다. 이러한 옵션들은 울파르의 제안에 사용된 일부(CFI 및 주소 공간 랜덤화address space layout randomization, ASLR)를 포함하여 보안에 도움이 될 다양한 경고와 메커니즘을 추가합니다. 그러나 이러한 옵션들은 모두 “최선의 노력”일 뿐이고, 소스코드 변경이 필요한 옵션이나 눈에 띄는 오버헤드가 있는 옵션을 포함하여 모두 함께 사용하더라도 어떠한 보장도 약속하지 않습니다. 울파르의 접근이 다른 점은 함수들의 중첩된 실행과 힙 객체 및 포인터의 사용에 대한 보장을 확립할 수 있도록 서로를 보완하도록 설계된 네 가지 특정 기술을 주의깊에 선택했다는 것입니다. 그러한 보장들은 악성코드 작성자들이 의존하는 거의 모든 특정 UB를 제거하며, 메모리를 오염시키는 것과 같이 남은 UB가 발생하더라도 유효하게 유지될 것입니다.

만약 언어 UB 백서가 UB를 광범위하고 체계적으로 분류하고 완화(프로그래머가 켤 수 있는 프로파일/레이블 이름으로 그룹화됨)하려는 첫 번째 목표에 그치지 않고, 특히 “controlled_execution_security” 프로파일까지 달성할 수 있다면, 아주 훌륭한 결과가 될 것입니다. 그리고 C++ 소프트웨어 보안 취약점 노출을 다른 현대적인 언어들과 대등한 수준으로 극적으로 줄여줄 것입니다.

요약, 그리고 다음 단계

어떤 현명한 분께서 이런 말을 남겼죠: “결정하지 않기를 선택했다면, 그것 또한 선택이다.”

수 년동안, 소프트웨어 보안은 C++ 표준화 전반에 걸쳐 최우선 순위로 삼을만큼 충분히 긴박해보이지 않았을 수 있습니다. 비록 점진적인 개선은 계속해서 일어나고 있었지만 말이죠. 하지만 시대가 변했습니다. 우리는 우리 문명을 유지하기 위해 의존하는 시스템들에 심각한 위협을 가하는 사이버 공격과 사이버 전쟁의 급증에 직면했고, 가혹한 선택 앞에 놓였습니다: 단호하게 대응할 것인가? 어떻게? 아니면 하지 않을 것인가? 현자가 지적한 것과 같이 선택choice을 하는 것은 선택사항optional이 아니었습니다.

우리는 선택했습니다: C++언어의 안전성을 개선하는 것을 최우선으로 하기로 했고, 다른 현대적 언어들과 (보안 취약점 수로 보았을 때) 대등한 수준을 달성하는 것을 목표로 합니다.

우리는 이미 커다란 성과를 냈습니다. 컴파일-타임 C++은 이미 UB로부터 자유로우며, 이는 오늘날 실세계 C++의 거대한 영역이 이미 UB로부터 자유롭다는 것을 의미합니다. C++26에서 우리는 이미 여러 빈번한 취약점의 UB 근본 원인들을 제거하고 있습니다. 언어 차원에서는 초기화되지 않은 변수가 UB가 아니며 표준 라이브러리에서는 vector, string, span, string_view와 같이 널리 사용되는 타입들에 대한 많은 일반적인 작업들이 C++26의 강화된 구현hardened implementation에서 경계 안전bounds-safe해지고 있습니다. 비록 이것들이 표준에서는 새로 도입된 것이지만, 모두 현장에서 대규모로 배포되어 왔고 이를 표준으로 만드는 것은 훨씬 더 널리 채택되는 것을 더 쉽게 만들 것입니다. (C++26에서 우리는 안전의 또 다른 측면, 즉 일반적인 버그를 줄이기 위한 방어적 프로그래밍을 위한 기능적 안정성인 언어계약도 실을 예정입니다.)

효과가 나타나고 있습니다: 제로데이 공격의 가격은 이미 상승했습니다. 이제 우리는 C++에서 UB를 길들이기 위한 나머지 길을 갈 수 있는 경로를 확보했습니다. 맞습니다, 여전히 많은 작업이 남아있습니다만, 우리가 향후 1, 2년 동안 강력하게 추진할 수 있다면 우리는 거의 원격 코드 실행(RCE) 공격을 제거하는 것을 포함하여 C++의 UB를 체계적으로 해결할 수 있는 실질적인 기회를 갖게 될 것입니다. 만약 이 괴물들을 가두려는 노력이 우리가 바라는 것의 절반만큼이라도 잘 풀린다면, 많은 사람들이 상당히(그리고 제 생각헤는 기쁜 마음으로) 놀랄 것입니다.

몇몇 또 다른 현명한 분께서는 이런 말도 남겼습니다: “아싸”13

만약 당신이 이미 달성된 성과나 위의 다음 단계들을 돕고 있는 분들 중 한 명이라면, 다시 한 번 이 말씀을 전하고 싶네요. “감사합니다!” 당신의 도움에 감사드리며, 정말로 중요합니다.

정말로 감사합니다.


부록: UB에 대한 짧은 설명

역사적으로, UB는 C와 C++에서 컴파일러 최적화를 위해 허용되어 왔습니다. 컴파일러는 UB가 절대 일어나지 않는다고 가정하고 해당 가정에 기반해서 프로그램을 최적화합니다. 실제로는 컴파일러마다 이를 얼마나 적극적으로 가정하는지가 다릅니다. 주요 컴파일러들이 최적화 수준에 따라 일반적인 예제들을 어떻게 최적화하는지 조사한 내용은 제가 2020년에 작성한 문서 P2064R0의 3.4절을 보시면 됩니다.

우리는 이제 두 가지 이유에 따라 UB에 대해 다시 고려하고 있습니다. UB 드래곤이 여러개의 머리를 가진 이유죠.

  • UB는 종종 직접적인 안전성 및 보안 문제를 가집니다. 예를 들어, 프로그램이 메모리 범위 바깥을 접근하려고 할 때, 악의적인 행위자는 이 취약성을 이용해 암호화폐를 탈취하는 멀웨어를 설치하는 공격 코드를 작성할 수도 있습니다.
  • 또한 UB는 간접적인 안전성 및 보안 문제도 가집니다. 예를 들어, 컴파일러가 if/else 분기를 만났고 한 쪽 분기가 항상 UB를 만난다는 것을 알게 될 경우 컴파일러는 해당 분기에서 확인하는 조건이 항상 참(또는 거짓)이라고 가정하고는 전혀 확인하지 않을 수도 있습니다. 만약 이 분기가 보안과 관련된 내용을 확인하기 위해 의도적으로 활성화된 것이고, 컴파일러가 이를 조용히 최적화 해버렸다면 컴파일된 프로그램은 이러한 내용을 전혀 확인할 수 없게 됩니다. 이런 경우에는 문제가 되겠죠.
  • UB 최적화는 미스테리하고 평범한 버그들을 만들어냅니다. 예를 들어 동시에 참이면서 거짓인 변수, 실행될 수 없는 코드인데 실행되어버리기, UB가 발생할 수 있는 지점보다 앞선 코드를 변경하는 “시간 여행” 최적화 등이 있죠. (따라서, UB가 ‘과거를 수정하기 위해 되돌아간다’는 개념이 생겨납니다.)

제발 이런 것들 좀 그만 봅시다. 지난 10년간 C++는 우리의 영광스러운 최적화는 그대로 유지하면서도 불을 뿜고 정신을 지배하는 UB가 아닌 다른 방식으로 최적화 가능성을 명세하는 방법들을 추구해왔습니다.

참고:
    • C++의 UB를 해결하는 것은 C보다는 쉽습니다. C는 좋은 언어이지만, 더 저수준의 언어로서 표준화된 추상화가 적어서 추천할 수 있는 범용적인 대안이 적고, 표준이 직접적으로 강화할 수 있는 표준 라이브러리가 더 적기 때문입니다.
    • UB는 C++ 표준의 또 다른 기술적 개념인 “ill formed, no diagnostic required”(IF-NDR)과 연관이 깊습니다. 편의를 위해서, UB와 IF-NDR을 묶어서 UB로 표기했습니다.


각주 (역자 주)

  1. hardening, 보안과 안정성을 높이고자 하는 일련의 기술 및 노력―역자 주 

  2. 컴파일러, 라이브러리, 대형 플랫폼 개발사 등―역자 주 

  3. 프로그래머들은 C++ 표준 위원회에에 수정 또는 개선사항을 담은 제안서를 제출할 수 있다―역자 주 

  4. 고객이 자사의 상품을 선택하도록 설득하기 위한 유무형의 수단, 여기서는 프로그래머가 C++을 선택할 이유―역자 주 

  5. 이상적이거나 궁극적인 목표를 달성했다는 관용적 표현―역자 주 

  6. 원문에서 이 링크는 깨져있는데, 원래 의도했던 것으로 추정되는 링크로 대체하였다.―역자 주 

  7. encourage, 최종 승인은 아니지만 핵심적인 방향성에 대한 위원회의 동의를 얻었다는 의미―역자 주 

  8. 여기서 ‘지지(encourage)’라는 표현은 윗 문단에서와 마찬가지로 최종 승인은 아니지만 핵심적인 방향성에 대한 위원회의 동의를 얻었다는 의미로 사용된다.―역자 주 

  9. ill-formed, no diagnosis required, 하단 부록 참고 ―역자 주 

  10. 수식과 그래프가 포함된 문서에 특화된 편집기―역자 주 

  11. C++에서 템플릿 인자 치환 시 발생하는 문법적 오류를 컴파일 에러로 처리하지 않고 단순히 후보군에서 제외하는 규칙―역자 주 

  12. 원문: emphasis added, 강조 표시는 원문의 저자가 아니라 이 글의 필자가 넣었다는 의미로 인용구에 대한 오해를 방지하고자 하는 관용적 표현―역자 주 

  13. 원문: Let the good times roll(좋은 일이나 분위기를 이어나가자는 관용구). ‘현자’의 말을 빌리는 형식으로 관용구를 사용하는 유머이다―역자 주 

카테고리:

업데이트:

댓글남기기