View in English

  • Apple Developer
    • 시작하기

    시작하기 탐색

    • 개요
    • 알아보기
    • Apple Developer Program

    알림 받기

    • 최신 뉴스
    • Hello Developer
    • 플랫폼

    플랫폼 탐색

    • Apple 플랫폼
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    피처링

    • 디자인
    • 배포
    • 게임
    • 액세서리
    • 웹
    • 홈
    • CarPlay
    • 기술

    기술 탐색

    • 개요
    • Xcode
    • Swift
    • SwiftUI

    피처링

    • 손쉬운 사용
    • AI 및 머신러닝
    • 앱 인텐트
    • Apple Intelligence
    • 게임
    • 보안
    • Xcode Cloud
    • 커뮤니티

    커뮤니티 탐색

    • 개요
    • Apple과의 만남 이벤트
    • 커뮤니티 이벤트
    • 개발자 포럼
    • 오픈 소스

    피처링

    • WWDC
    • Swift Student Challenge
    • 개발자 이야기
    • App Store 어워드
    • Apple 디자인 어워드
    • 문서

    문서 탐색

    • 문서 라이브러리
    • 기술 개요
    • 샘플 코드
    • 휴먼 인터페이스 가이드라인
    • 비디오

    릴리즈 노트

    • 피처링 업데이트
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • 다운로드

    다운로드 탐색

    • 모든 다운로드
    • 운영 체제
    • 애플리케이션
    • 디자인 리소스

    피처링

    • Xcode
    • TestFlight
    • 서체
    • SF Symbols
    • Icon Composer
    • 지원

    지원 탐색

    • 개요
    • 도움말
    • 개발자 포럼
    • 피드백 지원
    • 문의하기

    피처링

    • 계정 도움말
    • 앱 심사 지침
    • App Store Connect 도움말
    • 새로 추가될 요구 사항
    • 계약 및 지침
    • 시스템 상태
  • 빠른 링크

    • 이벤트
    • 뉴스
    • 포럼
    • 샘플 코드
    • 비디오
 

비디오

메뉴 열기 메뉴 닫기
  • 컬렉션
  • 전체 비디오
  • 소개
  • 소개
  • 자막 전문
  • C 및 C++의 경계 안전성 취약점 제거하기

    Xcode의 경계 안전성 기술이 기존 C 및 C++ 코드베이스에서 경계를 벗어난 취약성의 전체 클래스를 어떻게 제거하는지 살펴보세요. __counted_by와 같은 주석을 활용한 C 경계 안전성 확장 프로그램을 사용하여 ABI 호환성을 유지하면서 경계 검사를 적용하는 방법을 알아보세요. 또한 최소 성능 오버헤드로 프로덕션 앱을 보호하기 위해 C++ 안전 버퍼와 강화된 C++ 표준 라이브러리를 활성화하는 방법을 살펴보세요.

    이 세션은 원래 Apple과의 만남 이벤트 ‘앱의 보안 강화하기: 보안 강화를 위한 필수 전략'의 일부로 진행되었습니다. 전체 비디오를 시청하여 더 많은 인사이트와 관련 세션을 확인하세요.

    리소스

      • HD 비디오
      • SD 비디오
  • 비디오 검색…

    안녕하세요. 저는 보안 도구 팀의 책임자이며, 동료인 루이스와 함께 발표를 진행할 예정입니다. 오늘 세션은 C 및 C++ 코드에서 특정 유형의 메모리 안전성 취약점을 완전히 제거해 주는 기술인 ‘바운드 세이프티(bound safety)’에 대해 다룰 예정입니다.

    이번 세션의 주요 내용은 다음과 같습니다. 먼저, 실제 취약점 사례를 바탕으로 바운드 세이프티가 왜 중요한지 설명하겠습니다.

    이어서 바운드 세이프티의 작동 원리, 도입해야 하는 이유,

    그리고 실제 적용 방법을 다룰 예정입니다.

    또한, 보다 광범위한 바운드 세이프티 생태계를 구축하기 위한 노력에 대해서도 이야기하겠습니다.

    마지막으로, C++에 대한 접근 방식을 다룰 예정입니다.

    다음은 실제 사례입니다. 최근, 널리 사용되는 오디오 라이브러리에 제로 클릭(zero-click) 취약점이 발견되었습니다. 제로 클릭이란 사용자가 아무런 조작도 하지 않아도 공격자가 은밀하게 임의의 코드를 실행할 수 있음을 의미합니다. 대부분의 플랫폼에서 이 취약점을 통해 공격자는 은밀하게 기기를 장악할 수 있습니다. 하지만 애플 플랫폼에서는 이 익스플로잇이 단순히 실패합니다. 바운드 세이프티 기술 덕분에 이 전체 범주의 버그가 더 이상 악용될 수 없게 되었기 때문입니다. 그리고 오늘날 여러분은 앱에 바로 그와 동일한 방어 장치를 구축할 수 있는 도구를 갖게 되었습니다. 메모리 안전성은 여전히 시스템 프로그래밍에서 가장 큰 보안 과제입니다. 여러분은 아마도 이미 정적 분석, 산티라이저, 코드 검토를 통해 버그를 찾아 수정하기 위해 열심히 노력하고 계실 것입니다. 하지만 문제는 다음과 같습니다. 공격자들은 끊임없이 새로운 버그를 찾아내고 있습니다. 버그 탐지 도구는 유용하지만, 그 자체만으로는 공격 패턴을 근본적으로 바꾸기에는 부족합니다. 버그가 존재하더라도 악용될 수 없도록, 취약점의 전체 범주를 근절해야 합니다.

    메모리 안전성을 위한 완벽한 해결책은 Swift와 같은 메모리 안전 언어를 사용하는 것입니다. 그리고 이는 새로운 코드를 작성할 때 절대적으로 올바른 선택입니다.

    하지만 기존 코드베이스는 사정이 다릅니다. 때로는 수억 줄에 달하는 C 및 C++ 코드가 존재하기도 합니다. 전체를 다시 작성하려면 수십 년이 걸릴 것입니다.

    하지만 사람들은 지금 당장 보호가 필요합니다.

    실용적인 해결 방안이 필요합니다. 기존 코드는 필수적입니다.

    필요한 것은 단순히 개별 버그를 찾는 것이 아니라, 취약점의 전체 범주를 근절하는 기술입니다.

    이러한 기술은 전체를 다시 작성하는 것보다 도입하기 쉬워야 합니다.

    그리고 무엇보다도, 점진적으로 적용 가능해야 합니다. 따라서 대규모 마이그레이션 프로젝트를 기다릴 필요 없이 오늘 바로 코드 보호를 시작할 수 있습니다.

    오늘 초반에 마크가 메모리 안전성 버그의 다섯 가지 범주에 대해 훌륭하게 개괄해 주었습니다. 이 모든 문제가 중요하지만, 각각 다른 해결책이 필요합니다.

    그리고 이번 발표는 수명 안전성과 함께 두 가지 가장 큰 문제 중 하나인 경계 안전성에 집중할 것입니다.

    앞서 언급한 오디오 코덱 취약점을 기억하시나요? 그것은 안전성 문제였습니다. 그리고 이는 전체 메모리 안전성 문제의 약 절반을 차지합니다.

    즉, 균형을 맞추는 것이 중요합니다. 안전성 문제를 해결하면 메모리 안전성 문제의 절반이 사라질 것입니다. 이제 Apple의 C용 안전성 기술을 활용해 앱을 보호하는 방법을 보여드리겠습니다. 앱을 위한 이미지 필터를 개발하고 있다고 상상해 보세요.

    이 필터는 버퍼와 크기를 받아 픽셀을 순회합니다.

    하지만 여기서 루프 조건을 주의 깊게 살펴보세요. ‘size 이하’ 조건을 사용하고 있습니다. 마지막 반복에서 전형적인 ‘by one’ 오류가 발생하여, 버퍼 끝을 한 요소 넘겨 쓰게 됩니다.

    비록 단순화된 예시이지만, 이는 공격자가 제어하는 입력과 결합될 때 수많은 실제 익스플로잇의 근본 원인이 되는 바로 그 종류의 범위를 벗어난 오류를 나타냅니다.

    다음은 C용 경계 안전성 확장 기능이 경계 안전성을 통해 이 문제를 해결하는 방법입니다. 인덱싱에는 경계 정보가 필요합니다. 컴파일러는 경계를 알지 못하는 포인터에 대한 인덱싱을 허용하지 않습니다.

    여기 오류 메시지를 살펴보세요. 컴파일러는 `image`에 경계 정보가 없으므로 0이 아닌 인덱스로 인덱싱할 수 없다고 알려줍니다.

    이 오류는 필요한 경계 주석을 추가하도록 안내합니다. 컴파일러에게 `image`가 가리키는 요소의 개수를 알려주는 것입니다.

    이때 `counted by`가 등장합니다. `counted by size`. 이 포인터가 `size`개의 요소를 가리킨다는 것을 컴파일러에 알립니다.

    이제 컴파일러는 경계를 파악하게 됩니다.

    컴파일러는 모든 메모리 액세스 전에 경계 검사를 자동으로 삽입합니다.

    경계 안전 버그가 발생하더라도, 범위를 벗어난 쓰기 작업이 수행되기 전에 프로그램이 중단됩니다.

    코드에 버그는 여전히 존재하지만, 더 이상 악용될 수 없습니다.

    C 프로그램은 포인터를 다양한 방식으로 사용하므로, 경계 안전성 확장 기능은 일련의 경계 주석을 제공합니다. 일반적인 포인터 패턴과 각 경우에 어떤 주석을 사용해야 하는지 보여드리겠습니다.

    먼저 단일 요소를 가리키는 포인터부터 시작하겠습니다.

    C와 C++에서 경계 안전성 버그를 분석해 보면 분명한 패턴이 나타납니다. 대부분의 문제는 배열 인덱싱과 포인터 산술에서 비롯됩니다.

    이 다이어그램에는 4개의 정수로 구성된 배열이 있습니다. 녹색 상자는 0부터 3까지의 유효한 요소 인덱스입니다. 빨간색 상자는 범위를 벗어난 영역입니다. 메모리. 배열에 대한 포인터를 선언하면 0은 첫 번째 요소를 가리킵니다. 이는 안전합니다.

    하지만 배열 인덱싱이나 포인터 산술을 사용하면 배열 범위를 벗어나 빨간색 영역에 접근하기 쉽습니다.

    흥미롭게도, C 언어의 대부분의 포인터는 실제로 포인터 산술 연산이 필요하지 않습니다. 이 메시지 T와 같이 단순히 단일 객체를 가리킬 뿐입니다. 이를 증가시키거나 배열처럼 인덱싱하여 범위를 벗어나게 하는 것은 안전하지 않을 뿐만 아니라 불필요합니다.

    멤버에 접근하기 위해 화살표 연산자를 사용하거나, 직접 참조하기 위해 별표 연산자를 사용하는 것이 바로 필요한 작업입니다.

    그리고 이것이 바로 ‘single’ 어노테이션이 포착하는 바입니다.

    'single'은 이 포인터가 정확히 하나의 요소를 가리키거나 null임을 나타내며, 컴파일러는 포인터 산술 연산과 배열 인덱싱을 차단합니다. 컴파일 시점에 이러한 오류가 감지되므로, 실수로 포인터를

    해당 단일 객체 범위를 벗어나게 할 수 없으며, 기본적으로 안전합니다.

    자, 'single'은 대부분의 포인터에 훌륭하게 작동합니다. 단일 객체만을 가리키는 포인터의 경우 말이죠. 하지만 실제로 인덱싱과 포인터 산술이 필요한 여러 요소를 가리키는 포인터의 경우, ‘바운스’ 정보가 필요합니다. 안전하게 접근할 수 있는 요소의 개수를 알아야 합니다. 유효 범위는 무엇일까요?

    이 바운스 정보는 어딘가에서 나와야 합니다.

    다행히도 대부분의 코드에는 이미 이 정보가 포함되어 있습니다.

    ‘process image’를 예로 들어 보겠습니다. 포인터는 그 크기와 함께 전달됩니다.

    아니면 메시지 T를 생각해 보세요. 데이터 포인터는 데이터 크기 바로 옆에 위치해 있습니다.

    이 패턴은 C 함수 곳곳에서 찾아볼 수 있습니다. 포인터 구조체를 사용하는 경로 크기도 이를 나란히 저장합니다.

    정보는 이미 존재합니다. 단지 이를 명시적으로 표현할 방법이 필요할 뿐입니다.

    바로 여기서 ‘Counted by’가 등장합니다. 이를 통해 ‘이 포인터의 범위는 이 다른 변수에 의해 결정된다’고 명시할 수 있습니다. 즉, 명시적으로 표현하는 것입니다. 코드에 이미 존재하던 ‘counted by’는 가장 흔한 어노테이션입니다. 하지만 다른 경계 패턴을 표현하는 어노테이션들도 있습니다.

    약간 수정된 예제를 통해 또 다른 어노테이션을 보여드리겠습니다. 이제 함수가 불투명한 직렬화 데이터를 처리하기 위해 void 포인터를 받는다고 가정해 봅시다. 타입은 알 수 없지만, 사용 가능한 바이트 수는 알려져 있습니다.

    구조체에서도 마찬가지입니다. 이제 시리얼화된 데이터, 즉 void 포인터를 받는다고 가정해 봅시다. 구조는 알 수 없지만, 데이터 크기는 포함된 바이트 수를 추적합니다.

    이때는 `size by` 어노테이션을 사용합니다.

    `counted by`처럼 요소 수를 세는 대신, 바이트 수를 세는 것입니다.

    이제 다른 예제를 보겠습니다. `allocate pixels` 함수는 이미지를 픽셀 배열로 할당합니다. 이 함수는 n개의 픽셀 요소에 대한 포인터를 반환하거나 null을 반환합니다. 할당이 실패할 경우,

    n이 0이 아닌 한 반환 유형에 `counted by n elements`를 사용하는 것은 잘못된 것입니다. null을 반환할 때 포인터는 n개의 요소를 가리키는 것이 아니라 아무것도 가리키지 않기 때문입니다.

    이때 `counted by or null`을 사용합니다. 이는 이 포인터가 n개의 요소를 가리키거나, 그렇지 않으면 null임을 나타냅니다. 카운트가 0이 아닌데도 포인터가 null일 수 있는 경우에 이 어노테이션을 사용하십시오.

    가장 일반적인 것들만 다루었지만, size Y나 null, Devi 등 다른 부모 유형에 대한 어노테이션도 더 있습니다.

    전체 목록은 경계 안전 확장(bound safety extensions) 문서를 참조하세요.

    이제 경계 안전 확장이 어떻게 작동하는지 알게 되었으니, 이를 도입해야 하는 이유를 살펴보겠습니다.

    강력한 안전성을 보장하고 도입하기 실용적이라는 두 가지 이유입니다.

    첫째, 강력한 안전성 보장은 컴파일러가 항상 필요한 균형 검사를 삽입한다는 것을 의미합니다.

    수년 동안 개발자들은 Unison이나 Fortify Source와 같은 기회주의적인 균형 검사 도구에 의존해 왔습니다.

    이러한 도구들은 유용하지만, 최선을 다해 버그를 찾아낼 뿐입니다. 마치 끝없는 ‘두더지 잡기’ 게임을 하는 것과 같습니다.

    공격자들은 우리가 놓친 빈틈을 찾아내기만 하면 됩니다.

    본드 세이프티는 이러한 상황을 완전히 바꿔 놓습니다. 본드 세이프티는 보장된 검증을 제공합니다. 컴파일러는 안전망 역할을 하여, 경계 정보가 항상 존재하고 항상 정확하도록 보장합니다. 이를 통해 해당 취약점 유형을 구조적으로 제거합니다.

    첫째, 컴파일러는 경계 정보가 항상 사용 가능하도록 보장하므로,

    경계 정보 없이 포인터에 인덱싱을 시도하면 첫 번째 함수에서

    컴파일러 오류가 발생합니다. image에 대한 바운스나 표기법이 없으므로,

    컴파일러는 해당 배열 액세스를 거부합니다.

    두 번째 함수인 `counted by`가 바운스를 제공합니다. 따라서 코드가 컴파일되면 컴파일러가 바운스 검사를 삽입합니다.

    즉, 코드가 컴파일된다면 경계 검사가 이루어진다는 뜻입니다.

    맞습니다. 이 기능은 제가 가장 좋아하는 기능 중 하나입니다. 버퍼를 업데이트하고도 크기를 업데이트하는 것을 깜빡한 적이 몇 번이나 있으신가요? 이 확장 기능을 사용하면 컴파일러가 포인터와 크기 변수 간의 관계를 실제로 이해하고, 컴파일 시점과 실행 시점에 이를 강제 적용하므로 실수로 범위를 벗어나는 일이 없습니다. 정확성입니다.

    맨 위에 있는 이 예제를 살펴보겠습니다. 데이터 포인터는 아래의 데이터 크기와 연결되어 있습니다. 함수는 데이터에 대한 메모리를 할당하지만 데이터 크기를 업데이트하는 것을 잊어버립니다. 이러한 전형적인 실수는 대개 오래된 크기 값을 남기며, 이는 범위를 벗어난 오류로 이어집니다. 하지만 이제 컴파일러가 이를 즉시 감지하고 컴파일을 거부합니다.

    이 문제를 해결하려면 포인터와 함께 크기 값도 업데이트하기만 하면 됩니다.

    컴파일 시점 검사 그 이상. 크기 필드가 100으로 고정되어 있기 때문에

    컴파일러는 런타임 검사도 삽입합니다. 여기서 컴파일러는 100이 원래 할당된 메모리 내에 실제로 들어맞는지 자동으로 보장합니다.

    이러한 안전성 보장 덕분에 경계 안전 확장 기능을 도입하는 것이 실용적입니다.

    우선, 대부분의 포인터에는 주석이 필요하지 않습니다. 스마트 기본값이 일반적인 경우를 자동으로 처리해 주며,

    ABI 호환성을 유지하면서 점진적으로 적용할 수 있습니다. 코드베이스의 나머지 부분과의 호환성을 깨지 않고 한 번에 하나의 파일씩 변환할 수 있습니다.

    스마트 기본값부터 살펴보겠습니다. 앞서 언급한 메시지 처리 예제로 돌아가 보겠습니다. 이 함수는 단일 메시지 T 객체에 대한 포인터를 받으며, 이를 표현하기 위해 `single`이 추가되었습니다.

    이 패턴은 매우 흔하기 때문에, 경계 안전성 확장 기능은 ABI 경계에서 포인터의 기본값을 `single`로 설정합니다. 함수 매개변수, 구조체 필드, 전역 변수, 중첩된 포인터 등이 모두 해당됩니다.

    따라서 이 매개변수에는 주석을 달 필요가 없습니다. 이미 기본적으로 `single`로 설정되어 있습니다. `count it by`와 같이 다른 설정을 원할 때만 주석을 달면 됩니다.

    이제 여러분은 이렇게 생각할 수도 있습니다. “수백만 줄에 달하는 코드에서 로컬 포인터 하나하나마다 정말 주석을 달아야 하나요?” 답은 절대 ‘아니오’입니다. 로컬 변수는 ABI 경계에 노출되지 않기 때문입니다. 컴파일러는 정말 기발한 방식을 사용합니다. 백그라운드에서 자동으로 이를 와이드 포인터로 승격시킵니다. 포인터 산술 연산을 모두 사용할 수 있고, 런타임 자동 검사가 이루어지며, 로컬 코드에 주석을 추가하지 않아도 이러한 모든 안전성을 확보할 수 있습니다. 그냥 작동합니다.

    예를 들어 보겠습니다. `image` 함수는 현재

    위치를 추적하고 배열의 끝을 표시하기 위해 로컬 변수를 사용합니다. 두 변수 모두 자동으로 와이드 포인터로 처리됩니다. 주석이 필요 없습니다.

    `PTR`을 인크먼트하면 컴파일러가 경계 조건을 자동으로 반영합니다.

    TR을 역참조할 때는 주석을 추가하지 않아도 자동으로 바운스 검사가 수행되어 모든 안전성이 보장됩니다.

    그리고 이것이 이 기능을 도입해야 하는 가장 실용적인 이유입니다.

    이 기능은 실제 환경에서 점진적으로 도입할 수 있도록 설계되었습니다. 이러한 주석이 ABI 경계에서 포인터 표현을 변경하지 않기 때문에,

    개발을 중단하고 하룻밤 사이에 방대한 SQL 기반을 재작성하는 것은 현실적으로 불가능합니다. 완전한 재작성은 필요하지 않습니다. 보안 수준이 매우 높은 중요한 파일 하나를 골라 오늘 바로 바운스 안전성을 활성화한 뒤, 기존 주석이 없는 프로젝트에 바로 다시 링크할 수 있습니다.

    시스템 헤더와 같이 주석이 없는 API가 필요한 실제 시나리오가 있을 수 있습니다. 기본적으로 이러한 헤더의 포인터는 안전하지 않은 것으로 취급됩니다. 바운스 정보가 없습니다. 아니요. 바운스 검사도 없습니다. 컴파일러는 이를 검증할 수 없지만,

    이를 통해 바운스 안전 코드가 여전히 컴파일되고 구형 라이브러리와 상호 운용될 수 있습니다.

    다음은 예시입니다. 파일 핸들 open은 open을 호출하여 파일 포인터를 얻습니다.

    file은 어노테이션이 적용된 시스템 헤더에서 비롯되었기 때문에, 컴파일러는 이를 안전하지 않은 포인터로 간주하여 safe 변수에 할당합니다. safe F에 할당하려면 명시적 변환이 필요합니다.

    이를 수행하려면 `unsafe for single` 매크로를 사용합니다. 이 매크로는 기본 포인터 유형과 안전하지 않은 포인터를 인수로 받습니다. 이는 “이 포인터들이 단일 파일 객체를 가리킨다는 것을 알고 있으며, 이에 대한 책임을 지겠습니다”라고 명시하는 것입니다.

    목표는 업스트림에 적절한 주석이 추가됨에 따라 시간이 지남에 따라 이러한 안전하지 않은 구문을 검토하고 제거하는 것입니다.

    자, 이론 설명은 이 정도면 충분하겠죠? 이제 재미있는 부분을 살펴보겠습니다. 프로젝트에 바운드 세이프티를 적용하는 구체적인 방법은 다음과 같습니다.

    과정은 이렇습니다. 먼저 헤더 파일에 어노테이션을 추가하는 것부터 시작합니다. 그런 다음 파일을 하나씩 검토해 나갑니다. 해당 파일에 대해 바운드 세이프티를 활성화한 후 테스트와 디버깅을 진행합니다. 모든 파일을 완료한 후에는 Xcode에서 전역적으로 바운드 세이프티를 활성화합니다.

    각 단계를 하나씩 설명해 드리겠습니다. 먼저, API 계약이 정의된 헤더 파일에 더블 바운드 어노테이션을 추가합니다.

    이 헤더를 예로 들어보겠습니다. 먼저, `include`를 사용하여 H를 확인합니다. 이렇게 하면 바운스 안전성 어노테이션과 매크로가 제공되며, 그 다음 `Abi` 또는 `single`을 확인합니다. 헤더를 수정할 때, 이 매크로는 소비자가 Abi 포인터를 unsafe가 아닌 single로 자동 처리하도록 보장합니다. 마지막으로, 필요에 따라 `counted by` 또는 기타 바운스 안전성 어노테이션을 추가합니다.

    이것으로 끝입니다. 이제 이 헤더는 바운스 안전성이 보장됩니다. 다른 바운스 안전 코드가 이 함수를 호출하면, 컴파일러는 호출 위치에 바운스 검사를 삽입합니다.

    다음으로, 하나의 소스 파일을 선택합니다. 이 기능을 활성화합니다.

    이를 위해 컴파일러 플래그를 추가합니다. 적용한 소스 파일에 대해 이 기능을 활성화하려면 빌드 단계에서 안전성을 활성화해야 합니다.

    그런 다음 컴파일하고 컴파일러 진단 메시지를 확인합니다. 컴파일러는 누락되거나 일치하지 않는 어노테이션을 즉시 표시합니다.

    프로세스에 대한 바운스 안전성을 활성화하면 이미지 예시에서 볼 수 있듯이 두 가지 진단 메시지가 나타납니다.

    첫 번째 메시지는 함수 정의가 헤더 선언의 어노테이션과 일치해야 한다고 알려줍니다.

    두 번째 메시지는 매개변수 `image`에 바운스 어노테이션이 없으므로, 해당 매개변수에 대한 인덱싱이 허용되지 않는다고 알려줍니다.

    매개변수의 크기를 기준으로 카운트하면 두 가지 진단 메시지가 모두 해결됩니다.

    이제 테스트를 실행하고 런타임 검사에서 발생하는 오류를 디버깅하세요.

    문제가 발생하면, 컴파일러는 잘못된 어노테이션을 감지했거나 다음과 같은 실제 버그를 발견한 것입니다. ‘오프 바이 원(Off by one)’ 오류입니다. Xcode는 즉시 실행을 중지하고 정확히 무슨 일이 일어났는지 알려줍니다.

    상한을 초과하여 참조한 것입니다.

    여기서 로직을 수정하면 코드가 문제없이 실행됩니다.

    테스트가 통과되면 해당 파일은 보호됩니다.

    나중에 나머지 파일들도 차례로 적용할 수 있습니다.

    프로젝트 전체가 준비되면 Xcode 빌드 설정에서 바운스 세이프티(Bounce Safety) 확장을 활성화하세요.

    이를 위해 프로젝트의 빌드 설정으로 이동하여 보안 섹션에서 ‘C 언어 확장 활성화’를 찾으세요. C 언어용 바운스 세이프티 확장을 ‘예’로 설정해 보겠습니다. 이 시점부터 타깃 내의 모든 C 코드는 보호되며, 새로운 코드는 바운스 세이프티 규칙을 따라야 합니다.

    분명히 말씀드리자면, 이는 단순한 연구 프로젝트가 아닙니다. 이 기술은 대규모 환경에서 그 효과가 입증되었습니다. Apple은 이미 이 기술을 사용하여 수백만 줄에 달하는 생산 코드를 보호하고 있습니다. 이 기술은 Apple 플랫폼을 구동하는 커널의 네트워킹 스택에 적용되어 있습니다. 내장된 오디오 및 이미지 코덱에도 포함되어 있습니다. 보안 부팅 라이브러리에도 적용되어 있습니다. N1 네트워킹 칩의 펌웨어 등에도 사용되고 있습니다.

    현재 보안에 매우 민감한 시스템에서 고객들에게 제공되고 있습니다.

    애플은 이를 통해 매일 수십억 명의 사용자를 보호하고 있습니다.

    이제 여러분의 차례입니다. 이 기술을 활용하여 사용자를 안전하게 지켜주세요.

    물론, C 언어를 사용하는 분이라면 성능에 신경 쓰실 것입니다. 아마도 ‘무슨 함정이 있을까’ 하고 궁금해하실 텐데요. 그래서 마이크로초 단위의 성능이 중요한 커널의 네트워킹 스택 전반에 걸쳐 성능을 측정했습니다. 분명히 말씀드리자면, 이 테스트 동안 모든 제어 경로에서 ‘다운 안전성(down safety)’ 기능이 완전히 활성화된 상태였습니다.

    바운드 세이프티를 활성화한 상태에서, 테스트의 93%에서 오버헤드가 측정 오차 범위 내에 완전히 포함되는 것으로 나타났습니다.

    실제로 차이가 측정 가능한 극소수의 경우에서도 그 수치는 2% 미만으로 유지됩니다. 결론적으로, 여러분은 매우 실용적인 성능을 갖춘 바운스 FC의 ‘성배’를 얻게 되는 것입니다.

    바운스 세이프티는 여러분의 코드베이스에 큰 이점이 되지만, 어디서나 사용할 수 있게 되면 완전히 판도를 바꾸는 요소가 됩니다. 다음은 우리가 더 광범위한 바운스 세이프 생태계를 구축하는 방법입니다.

    목표는 간단합니다. Apple뿐만 아니라 모든 플랫폼에서 사용자를 보호할 수 있어야 하며, 이를 실현하기 위해 노력하고 있습니다. 바운스 세이프는 이미 Clang의 Swift 포크에서 오픈 소스로 공개되어 있습니다.

    또한 누구나 사용할 수 있도록 LLVM 메인라인으로 적극적으로 업스트림되고 있지만,

    그 범위는 훨씬 더 넓어집니다.

    애플은 C 표준 위원회와 직접 협력하여 바이오세이프티를 C 언어 자체에 표준화하기 위해 노력하고 있습니다.

    궁극적으로 이는 어떤 플랫폼을 대상으로 개발하든 상관없이 모든 C 개발자에게 바이오세이프티를 제공할 것입니다.

    자, 이제 루이스에게 마이크를 넘겨 C++ 접근 방식에 대해 설명해 드리겠습니다.

    감사합니다. 안녕하세요, 여러분. 제 이름은 루이스이고, 애플에서 C++ 표준 라이브러리 작업을 담당하고 있습니다. 이 자리에 서게 되어 기쁩니다. 이제 C++ 측면에 대해 자세히 살펴보겠습니다.

    C++은 안전성을 보장하기에 충분한 정보를 갖춘 다양한 추상화 기능을 이미 제공한다는 점에서 C와 다릅니다. 예를 들어, 표준 span은 포인터와 크기를 포함하고 있으므로, 액세스 시 유효한 범위가 무엇인지 파악할 수 있습니다.

    표준 벡터(vector), 표준 문자열(string), 표준 배열(array) 등 표준 라이브러리의 다른 많은 구성 요소도 마찬가지입니다.

    하지만 C++ 표준은 이러한 API에서 안전성을 강제할 것을 요구하지 않으며, 실제로 C++ 표준 라이브러리도 역사적으로 그렇게 해오지 않았습니다.

    예를 들어, 오른쪽의 코드는 앞서 UL이 소개한 C 프로세스 이미지 함수를 C++로 직접 변환한 것입니다. 이 코드는 원시 포인터 대신 표준 span을 사용합니다. 하지만 표준 span을 사용함에도 불구하고, 이 코드는 기본적으로 바운스 안전성을 강제하지 않습니다. 이제 Xcode를 통해 이를 변경할 수 있게 되었습니다.

    이는 ‘C++ 세이브 버퍼(C++ save buffers)’라고 하는 두 가지 접근 방식을 통해 구현됩니다. 첫째, Xcode는 C++ 코드가 버퍼에 접근할 때 표준 라이브러리가 제공하는 관례적인 추상화를 사용하도록 돕는 도구를 제공합니다.

    둘째, 이제 많은 API에서 오용을 감지하도록 활성화할 수 있는 강화된 C++ 표준 라이브러리도 함께 제공됩니다.

    이제 코드에서 더 안전한 추상화를 채택할 수 있도록 돕기 위해, Xcode는 비관용적인 방식으로 버퍼에 접근하는 코드 부분을 표시해 주는 진단 기능을 제공합니다. 예를 들어, 이 코드는 원시 포인터를 사용하여 작성된 `process_image` 함수의 한 버전입니다. 이 경우, 새로운 Xcode 진단 기능은 원시 포인터를 사용하여 인덱싱하는 것이 안전하지 않으므로 이 코드를 표시할 것입니다.

    이를 통해 버퍼에 접근할 때 관례적인 구문을 사용하지 않는 코드 부분을 파악하고 문제를 해결할 수 있습니다.

    이 경우, 표준 스팬(span)을 사용하여 코드를 다시 작성하면 오류가 사라집니다.

    이제 관례적인 추상화를 사용하는 코드를 실제로 더 안전하게 만들기 위해, Xcode는 강화된 C++ 표준 라이브러리도 제공합니다.

    Xcode 설정에서 이 기능을 활성화하면, 표준 라이브러리는 이미 포함된 경계 정보를 활용하여 특정 API 사용 여부를 확인합니다.

    이 예시에서, 강화 모드가 활성화된 상태에서 표준 span에 인덱싱을 수행하면, 이미 가지고 있는 경계 정보를 사용하여 액세스가 유효한지 확인합니다.

    만약 인덱스가 유효하지 않은 경우, 강화된 표준 라이브러리는 프로그램이 종료되도록 보장합니다. 즉, 프로그램이 계속 실행되어 앱을 손상시키거나 보안에 위협을 가할 가능성이 없습니다. 또한 API의 사용법이나 계약 조건은 변경되지 않는다는 점도 주목할 만합니다.

    보안 강화된 표준 라이브러리는 여전히 ISO C++ 규격을 준수하는 라이브러리입니다.

    보안 강화된 라이브러리가 활성화되어도 ABI도 변경되지 않습니다. 이 모든 것은 추가된 안전성을 활용하기 위해 코드 변경이 필요하지 않음을 의미합니다. 따라서 강화 기능을 쉽게 도입할 수 있습니다. 자, 이제

    새로운 Xcode 진단 기능과 표준 라이브러리 강화 기능을 모두 활성화하려면 빌드 설정의 ‘보안’ 항목으로 이동하여 ‘C++에서 저장 버퍼 사용 제한 적용’을 활성화하십시오.

    경우에 따라 안전하지 않은 구문을 사용하는 기존 코드가 있을 수 있습니다. 아직 최신 구문으로 업데이트할 수 없는 상황에서도 코드에

    새로운 오류를 발생시키지 않고 강화된 표준 라이브러리의 이점을 즉시 활용할 수 있습니다.

    이 경우, 빌드 설정의 ‘Apple Clang 언어 C++’ 항목으로 이동하여 ‘C++ 표준 라이브러리 강화 활성화’를 선택함으로써 강화된 표준 라이브러리만 활성화할 수 있습니다.

    또한, 이것이 단순한 디버깅 기능이 아니라는 점도 유의해야 합니다. 디버깅 기능은 생산성을 높이는 데 매우 유용하지만, 그것만으로는 충분하지 않습니다. 코드가 실행될 때 진정으로 더 안전해지도록 하려면 말이죠. 실제 운영 환경에서 기기에서 실행될 때 말입니다. 실제로, 강화된 표준 라이브러리를 사용하는 코드는 운영 환경에 배포될 목적으로 설계되었으며, 표준 라이브러리의 강화 기능은 이를 실현할 수 있도록 매우 신중하게 설계되었습니다.

    이를 통해 코드가 사용자의 기기에서 실행되어 실제 데이터를 처리할 때, 즉 안전성이 진정으로 중요한 운영 환경에서 코드가 더 안전해질 수 있습니다.

    강화된 표준 라이브러리를 사용할 때, 많은 API가 추가적인 안전성을 제공합니다.

    일반적으로 컨테이너 액세스 API와 직접적인 크기 요구 사항이 있는 컨테이너 수정 API가 강화되었다고 생각하기 마련입니다. 실제로 이러한 API는 이미 경계를 확인하는 데 필요한 정보를 갖추고 있으며, 이러한 API에서 오용을 감지하는 것은 직접적인 보안 가치를 제공합니다.

    예를 들어, 컨테이너의 인덱싱 연산자를 들 수 있습니다. 이들은 back, pub, front 메서드입니다. 이들 모두 강화 처리되어 있습니다. 선택적 값에 대한 접근도 강화 처리되어 있습니다.

    일반적으로 ISO C++ 26 강화 구현에서 확인되는 모든 전제 조건은 확실히 검사된다고 믿어도 됩니다.

    또한 표준 라이브러리가 이미 기본적으로 검사를 제공하는 경우도 있습니다. 예를 들어, 함수가 비어 있을 때 표준 함수를 호출하면 이미 예외가 발생합니다. 이러한 API에는 강화 처리가 영향을 미치지 않습니다. 보안 강화가 쉽지만 보안상 가치가 제한적인 다른 API들도 강화되지 않습니다. 애플리케이션에 미치는 성능 영향을 최소화하기 위해, 실제로 보안상 가치를 제공하는 검사들이 우선적으로 처리됩니다.

    예를 들어, null 포인터로 문자열을 생성하는 것은 기술적으로 올바르지 않습니다. 하지만 실제로는 이미 세그멘테이션 오류가 발생하여 프로그램이 종료됩니다. 그 때문에 해당 경우에 대한 명시적인 검사를 추가하더라도 보안상 이점은 제한적입니다.

    마지막으로, 강화 조치에 있어 주목할 만한 예외는 이터레이터입니다. 이터레이터는 일반적으로 경계 검사를 수행하는 데 필요한 충분한 정보를 저장하지 않습니다. 해당 정보를 추가하려면 ABI를 변경해야 하며, 이는 도입을 훨씬 더 어렵게 만들 것입니다. 이러한 이유로, 이터레이터 액세스는 강화 대상이 아닙니다.

    이제 어떤 API가 강화 대상인지 잘 이해하셨으니, 강화된 애플리케이션을 개발할 때 예상할 수 있는 경험에 대해 설명하겠습니다.

    강화 검사가 실패하면 프로그램은 Xcode 내에서 종료됩니다. 하드닝 어설션이 실패한 위치, 즉 표준 라이브러리로 이동하게 되며, 왼쪽에 있는 스택 프레임 선택기를 사용하여 자신의 코드로 이동할 수 있습니다.

    디버깅 콘솔에는 실패에 대한 추가적인 맥락을 제공하는 오류 메시지가 표시되며, Xcode의 일반적인 디버깅 워크플로를 사용하여 문제를 파악하고 해결할 수 있습니다.

    실제 출시된 앱의 경우, 연결할 디버거가 없기 때문에 상황이 조금 다릅니다. C나 C++에서 바운드 안전 확장(bound safety extensions)이 발생할 때와 마찬가지로, 트랩(trap)에 의해 프로그램이 종료됩니다. 이 메커니즘은 어설션이 실패한 후 잠재적으로 악의적인 공격자가 프로그램의 제어 흐름을 우회할 수 있는 여지를 거의 남기지 않기 때문에, 프로그램을 종료하는 가장 빠르고 안전한 방법입니다.

    특히, 이는 콘솔에 어떤 메시지도 기록되지 않음을 의미합니다. 이를 통해 프로그램에 대한 정보 유출을 방지할 수 있을 뿐만 아니라, 실행 파일에 진단 문자열을 저장하지 않음으로써 바이너리 크기를 최대한 작게 유지할 수 있습니다.

    하지만 그게 전부가 아닙니다. Xcode는 성능상의 장단점이 서로 다른 추가적인 검사 모드도 제공합니다.

    지금까지 제가 언급해 온 강화 모드는 사실 ‘Fast 모드’라고 불립니다. 패스트 모드 외에도 Xcode는 비용은 적게 들지만 보안상 반드시 필수적인 것은 아닌 검사들을 추가하는 ‘광범위 모드’도 제공합니다.

    이 모드는 성능 저하를 크게 유발하지 않으면서도 철저한 프로그래밍을 보장하는 데 매우 유용합니다. 그리고 광범위 모드 위에 Xcode는 디버그 모드도 제공합니다. 디버그 모드는 성능에 더 큰 영향을 미치는 대신 더 철저한 검사를 수행합니다.

    예를 들어, 디버그 모드에서는 C++ 라이브러리가 표준 정렬 함수에 제공된 비교자가 정렬에 필요한 속성을 갖추고 있는지 확인할 수 있습니다. 이는 앱 내의 미묘한 의미론적 버그를 포착하는 데 매우 유용합니다.

    패스트 모드와 익스텐시브 모드는 프로덕션 환경에서 사용할 수 있는 반면, 디버그 모드는 개발 중에만 사용해야 합니다.

    또한 파일마다 서로 다른 모드를 혼합하여 사용할 수도 있습니다. 이를 통해 코드베이스에 보안 강화 기능을 점진적으로 도입하거나 성능을 정밀하게 조정할 수 있습니다. 예를 들어, 코드베이스 전체에서는 광범위하거나 빠른 검사를 활성화하되, 성능에 매우 민감한 단일 소스 파일에서는 보안 강화 기능을 비활성화할 수 있습니다.

    또한 보안 강화 기능은 ABI에 영향을 미치지 않으므로, 이 모든 과정이 원활하게 작동합니다.

    이제 Xcode에서 이러한 기능들이 정식 출시되었다는 사실에 정말 기쁩니다. 실제로 애플은 꽤 오랫동안 이 기술을 개발해 왔으며, 오늘날의 수준에 도달하기 위해 내부적으로도 이를 적용해 왔습니다.

    특히, 이 기술은 WebKit 전체와 커널의 일부를 포함한 운영 체제의 여러 부분에 적용되었습니다.

    성능에 큰 영향은 관찰되지 않았으며, 매우 포착하기 어려웠던 버그를 포함해 다수의 버그가 발견되었습니다.

    애플의 자체 경험 외에도, 업계 전반에서 이 기술이 사용된 사례가 많이 보고되고 있습니다.

    예를 들어, 구글은 자사의 전체 서버 군에 이 기술을 배포한 내용을 문서화했습니다. 이는 성능에 민감한 수백만 줄의 코드에 해당합니다.

    그들은 성능 저하가 0.3% 수준에 불과하다는 것을 확인했습니다. 약간의 조정을 거친 후, 그들은 보안에 치명적인 버그를 포함해 1,000개 이상의 버그를 발견했다고 보고했습니다. 업계 도입의 또 다른 예로는 패스트 모드를 기반으로 한 강화된 표준 라이브러리 개념을 채택한 C++ 26이 있습니다.

    따라서 전반적으로 경험은 압도적으로 긍정적이었으며, 이제 Xcode에서 이 기능이 일반에 공개된다는 점이 정말 기대됩니다.

    자, 지금까지 Xcode가 C와 C++ 모두에서 중요한 취약점 유형을 제거하는 데 도움을 주기 위해 제공하는 도구들에 대해 살펴보았습니다. 이는 이제 C 코드에서 파일 단위로 바운드 안전성 확장 기능을 도입하기 시작할 수 있음을 의미합니다. 코드의 보안에 가장 민감한 부분부터 시작하여, 준비가 되면 나머지 코드로 차근차근 확장해 나가시기 바랍니다. C++ 코드의 경우 프로젝트 전체에 이 기능을 활성화하십시오. 코드 변경 없이도 애플리케이션의 안전성을 높이려면 즉시 패스트 모드를 활성화하십시오.

    또한 테스트 및 개발 단계에서는 디버그 모드를 활성화해야 합니다. 이를 통해 표준 라이브러리의 관례적인 추상화 방식을 사용하지 않고 버퍼에 접근하는 코드 내의 부분을 포착하여, 그렇지 않으면 놓쳤을 미묘한 버그를 발견할 수 있습니다. 새로운 Xcode 진단 기능을 활성화하고... 오늘 제가 소개한 내용에 대해 더 자세히 알고 싶으시다면, 온라인에서 자세한 문서를 확인하실 수 있습니다.

    사용자들은 여러분의 코드가 안전할 것이라고 믿고 있으며, 모든 단계가 중요하다는 점을 기억하십시오. 그러니 오늘부터 바로 시작하여 이러한 기능들을 직접 사용해 보시기 바랍니다. 이것으로 제 발표를 마치고, 다시 커트에게 마이크를 넘기겠습니다.

Developer 바닥글

  • 비디오
  • Meet with Apple
  • C 및 C++의 경계 안전성 취약점 제거하기
  • 메뉴 열기 메뉴 닫기
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store
    메뉴 열기 메뉴 닫기
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    메뉴 열기 메뉴 닫기
    • 손쉬운 사용
    • 액세서리
    • AI 및 머신 러닝
    • Apple Intelligence
    • 오디오 및 비디오
    • 증강 현실
    • 비즈니스
    • 디자인
    • 배포
    • 교육
    • 게임
    • 건강 및 피트니스
    • 앱 내 구입
    • 현지화
    • 지도 및 위치
    • 보안
    • Safari 및 웹
    메뉴 열기 메뉴 닫기
    • 문서
    • 다운로드
    • 샘플 코드
    • 비디오
    • 문서 아카이브
    메뉴 열기 메뉴 닫기
    • 도움말 및 문서
    • 문의하기
    • 포럼
    • 피드백 및 버그 리포트
    • 시스템 상태
    메뉴 열기 메뉴 닫기
    • Apple Developer
    • App Store Connect
    • 인증서, ID 및 프로파일
    • 피드백 지원
    메뉴 열기 메뉴 닫기
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • Mini Apps Partner Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    메뉴 열기 메뉴 닫기
    • Apple과의 만남
    • Apple Developer Centers
    • App Store 어워드
    • Apple 디자인 어워드
    • Apple Developer Academy
    • WWDC
    최신 뉴스 읽기 Apple Developer 앱 받기 bilibili, LinkedIn, WeChat, YouTube에서 팔로우하기
    Copyright © 2026 Apple Inc. 모든 권리 보유.
    약관 개인정보 처리방침 계약 및 지침