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 도움말
    • 새로 추가될 요구 사항
    • 계약 및 지침
    • 시스템 상태
  • 빠른 링크

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

비디오

메뉴 열기 메뉴 닫기
  • 컬렉션
  • 전체 비디오
  • 소개
  • 소개
  • 자막 전문
  • Sanitizer로 보안 버그 찾기

    Address Sanitizer와 Thread Sanitizer가 숨겨진 메모리 손상 및 동시성 문제를 찾아내는 데 어떻게 도움이 될 수 있는지 살펴보세요. Address Sanitizer가 어떻게 바이트 수준 정밀도로 Swift, C, C++, Objective-C 전반에서 버퍼 오버플로 및 UAF(Use-After-Free) 오류를 감지하는지 알아보세요. Thread Sanitizer가 미묘한 데이터 경합을 어떻게 발견하는지 살펴보고, 자동화된 테스트 계획에서 두 도구를 모두 구성하는 방법을 알아보세요.

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

    리소스

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

    안녕하세요, 저는 댄이라고 합니다. Apple에서 소프트웨어 엔지니어로 일하고 있습니다. 저는 보안 도구 팀 소속으로, 산티라이저 관련 업무를 담당하고 있습니다. 오늘은 산티라이저를 활용해 기존 코드에서 보안 버그를 찾는 방법에 대해 이야기해 드리려고 합니다. 이번 발표에서는 산티라이저의 개념을 소개해 드리겠습니다. 그리고 가장 강력한 두 가지 산티라이저인 ‘주소 산티라이저(Address Sanitizer)’와 ‘스레드 산티라이저(Thread Sanitizer)’에 대해 설명하겠습니다. 먼저 일반적인 개념을 소개하겠습니다.

    산티라이저는 버그 탐지 도구입니다. 엄격한 런타임 검사를 수행하기 위해 코드에 추가적인 관리 로직과 런타임 검사 코드를 삽입합니다. 이를 통해 고객에게 영향을 미치기 전에, 개발자가 인지하지 못했던 버그를 발견하는 데 도움을 줄 수 있습니다. 또한 겉보기에는 원인을 파악하기 어려운 크래시 보고서의 근본 원인을 찾아내는 데에도 매우 유용합니다.

    이제 산티라이저가 무엇인지 정의했으니, 주소 산티라이저에 대해 개괄적으로 설명하겠습니다. 주소 산티라이저는 프로그램이 메모리를 부적절하게 사용할 때 이를 감지하고 오류를 발생시킵니다.

    힙 메모리의 문제만 감지하는 guard malloc과 같은 도구와 달리, 주소 산티라이저는 스택 및 전역 변수 영역의 문제도 감지할 수 있습니다. 또한 바이트 단위의 정밀도로 검사를 수행하므로, 아주 미세한 범위를 벗어난 액세스라도 포착할 수 있습니다. 이제 주소 산티라이저가 찾아낼 수 있는 버그 유형 몇 가지를 살펴보겠습니다.

    이 코드 스니펫에서 저는 original이라는 Nsstring 변수를 그대로 복사하려고 합니다. 눈에 잘 띄지 않을 수 있지만, 여기에는 범위를 벗어난 메모리 액세스 버그가 있습니다.

    그 이유를 설명해 드리겠습니다. 여기서 오른쪽에 표시된 original의 기본 바이트에 대한 포인터를 가져옵니다.

    그리고 복사를 위해 original.length 바이트를 할당합니다.

    바로 여기서 문제가 발생하기 시작합니다.

    original.length는 바이트 수를 반환하지 않습니다. 실제로는 UTF-16 코드 유닛의 수를 반환하는데, 손을 흔드는 이모티콘은 이 코드 유닛 두 개로 구성되어 있습니다. 따라서 ‘raw copy’는 이제 5바이트 할당된 영역을 가리키게 됩니다.

    다음으로, ‘raw original’의 바이트를 ‘raw copy’로 복사하지만, 마지막 두 바이트는 할당된 영역의 범위를 벗어나게 됩니다. 이것이 바로 간과하기 쉽고 악용될 수 있는 교묘한 버그의 전형적인 예입니다.

    다행히 Address Sanitizer가 이 버퍼 오버플로우를 감지하여 경고해 줍니다.

    Address Sanitizer가 감지하는 또 다른 버그 유형은 ‘use after free’입니다. 이 C++ 예제에서는 탭을 담는 표준 벡터를 유지하고, 현재 활성화된 탭에 대한 포인터를 보관합니다.

    오른쪽에서 볼 수 있듯이, 벡터 내에 요소 하나를 위한 공간이 예약되어 있지만 아직 데이터가 채워지지 않았습니다.

    첫 번째 탭인 ‘메일’을 열고 이를 활성 탭으로 설정했습니다.

    그런 다음 다른 탭을 열었습니다. ‘뉴스’ 탭입니다. 이로 인해 벡터의 용량이 확장되는데,

    이는 새로운 메모리 할당을 생성하고 요소를 그곳으로 이동해야 함을 의미합니다.

    안타깝게도 이 과정에서 `active` 변수가 업데이트되지 않아, 이제 `active`는 할당 해제된 메모리를 가리키고 있습니다. `active`가 가리키는 탭 포인터를 다시 불러오려고 하면, Address Sanitizer가 할당 해제된 메모리 사용을 알리는 오류를 발생시킵니다.

    예제에서 볼 수 있듯이. C, C++, Objective-C는 모두 Objective-C에서 지원됩니다. 여기에는 수동 및 Automatic Reference Counting이 포함됩니다. 마지막으로, Swift도 마찬가지입니다. 비록 순수 Swift에서는 메모리를 부적절하게 사용하는 경우가 거의 없겠지만요. 하지만 중요한 점은, Address Sanitizer가 여러 언어에 걸쳐 작동한다는 것입니다. 여기에는 Swift 배열 numbers가 있습니다.

    C 함수에서 이 배열을 사용하기 위해, unsafe 버퍼 포인터를 가져올 것입니다. nums 포인터가 배열의 첫 번째 요소를 가리키고 있다는 점에 유의하세요.

    이제 sum 함수를 호출하겠습니다. 이를 위해 배열의 시작 지점을 가리키는 포인터와 배열의 길이를 전달해야 합니다.

    sum 함수는 배열의 각 요소를 순회하며 합계를 구할 것입니다. 하지만 여기서 전형적인 실수를 저질렀습니다. Len보다 작을 때까지 반복해야 하는데, ‘작거나 같을 때까지’를 사용했습니다. 그 결과, 마지막 반복에서 배열 범위를 벗어날 것입니다.

    어드레스 샌티라이저가 이를 감지하고 버퍼 오버플로우에 대한 오류를 발생시킵니다. 이는 C용 밴드 세이프티(band safety) 확장을 사용하면 해결할 수 있는 보안 버그라는 점을 주목할 필요가 있습니다.

    주소 살균기에 대해 알아야 할 사항은 다음과 같습니다. 첫째, 이 도구는 개발 단계에서만 사용되며, 런타임 보안 강화용이 아닙니다.

    이처럼 강력한 도구인 만큼, 메모리 및 런타임 오버헤드가 각각 약 2~3배 정도 발생합니다.

    테스트 중에 발생하는 버그를 고객에게 전달되기 전에 찾아내는 데 매우 효과적이므로, 가능한 한 많은 테스트를 수행하는 것이 중요합니다. 이 기능을 활성화할

    때는 유닛 테스트나 UI 테스트와 같은 자동화된 테스트에서 활성화하고, 개발 중 수동 테스트를 수행할 때도 활성화하여 도구의 효과를 극대화해야 합니다. 경계 사례와 드문 경로를 유발하는 것이 정말 중요합니다.

    개발 중에 주소 산티라이저를 활성화하려면, 먼저 제품 메뉴를 열고, 스킴 하위 메뉴를 연 다음 ‘스킴 편집(Edit Scheme)’을 선택하여 스킴 편집기로 이동합니다.

    여기서 ‘실행 스킴(Run Scheme)’으로 이동합니다. 그런 다음 ‘진단(Diagnostics)’ 탭에서 ‘Address Sanitizer’ 확인란을 선택하여 테스트 계획에 대해 Address Sanitizer를 활성화합니다. 제품 메뉴를 열고, ‘테스트 계획(Test Plan)’을 선택한 뒤 ‘테스트 계획 편집(Edit Test Plan)’을 선택합니다. ‘구성(Configurations)’ 탭을 열고 ‘런타임 살균(Runtime Sanitization)’ 섹션까지 스크롤합니다. 그런 다음 ‘Address Sanitizer’ 행을 선택하고 ‘켜기(On)’로 설정합니다. 이제 테스트에 Address Sanitizer를 사용하는 방법을 시연해 보겠습니다.

    자, 여기 혼합 언어 예제 슬라이드에 나온 제 코드가 있습니다. Swift 배열 numbers를 가져옵니다. 그런 다음 이 배열에 대한 unsafe 버퍼 포인터를 생성하고, 그 안에서 C 함수 sum을 호출하겠습니다. 각 요소를 순회하며 합계를 구한 뒤, 결과를 Swift로 반환하겠습니다.

    그리고 배열과 결과를 출력해 보겠습니다. 자, 이제 이 코드를 실행해 봅시다. 우선, 프로그램이 성공적으로 실행된 것을 확인할 수 있습니다.

    하지만 여기 있는 이 값은 분명히 올바르지 않습니다.

    그래서 이제 ‘Product Scheme’의 ‘Edit Scheme’에서 Address Sanitizer를 활성화하겠습니다. ‘Address Sanitizer’ 확인란을 체크하면 됩니다.

    실행 버튼을 클릭하면 산티라이저가 적용된 상태로 재빌드되며, 바로 이 줄에서 힙 버퍼 오버플로우 오류가 감지된 것을 확인할 수 있습니다.

    왼쪽에는 이 오류로 이어진 호출 스택을 볼 수 있으며, 64바이트 힙 할당 직후에 발생한 것임을 확인할 수 있습니다.

    이 사이드 메뉴에서는 할당이 어디서 이루어졌는지 확인할 수 있는데, 예상대로 Swift 배열을 생성할 때였습니다.

    스택 프레임으로 돌아가 보면, 현재 라이브 디버깅 세션 중이므로 변수의 값을 확인할 수 있습니다. 현재 `i`가 `Len`과 동일한 값을 가지는 것을 볼 수 있는데, 이는 제 슬라이드에서 설명한 대로 루프 경계 조건에 문제가 있음을 의미합니다. 그 이유는 여기에 불필요한 등호가 하나 더 있기 때문입니다. 그래서 이를 제거하고 애플리케이션을 다시 빌드해 보겠습니다.

    이제 성공적으로 실행된 것을 확인할 수 있습니다. 게다가 이제 sum에서 올바른 값을 얻고 있습니다.

    자, 이제 슬라이드로 돌아가겠습니다.

    다음으로 살펴볼 산티라이저는 스레드 산티라이저입니다.

    스레드 산티라이저는 데이터 경합 및 기타 동시성 문제를 감지합니다.

    데이터 경합은 한 스레드가 메모리 위치에 쓰기를 수행하는 동안, 다른 스레드가 동기화 없이 동일한 메모리 위치에 접근할 때 발생합니다.

    오른쪽의 코드 예제에서, 저는 큐 ‘pending’에서 head를 가져와 전송하고, 해제(free)한 다음 head의 값을 1 증가시킵니다. 다른 스레드가 동일한 작업을 수행하기 전까지는 이 코드가 정상적으로 작동합니다. 이제 두 개의 워커 스레드가 있는데, 다음과 같은 작업의 교차 실행이 발생할 수 있습니다.

    스레드 1이 값이 0인 head를 읽습니다.

    그 다음 프레드 2도 head의 값이 0인 것을 읽습니다.

    프레드 1은 pending 메시지를 전송하고 객체를 해제합니다.

    그런 다음 head의 값을 1로 증가시킵니다.

    여기서 데이터 경합이 발생합니다. 프레드 2의 읽기 작업과 프레드 1의 쓰기 작업이 어떤 순서로 발생하는지에 따라, 이 경우 Fred 2의 인덱스 값이 달라질 수 있습니다. Fred 2가 head의 오래된 값을 가지고 있으면, Fred 1이 방금 해제해 놓은 'pending 0'을 읽게 됩니다. 주소 살균기(Address Sanitizer)는 이러한 '해제 후 사용(use after free)'이 발생할 때 이를 감지하며, 이번 실행에서는 실제로 '해제 후 사용'이 발생했습니다. 이것은 동일한 두 스레드에서 실행되는 동일한 코드이지만, 이번에는 두 스레드 간에 작업이 교차하지 않습니다. 이 실행에서는 주소 산티라이저가 문제를 감지하지 못합니다.

    하지만 좋은 소식이 있습니다. 예상된 결과가 나타났음에도 불구하고, 스레드 산티라이저는 여전히 여기에 문제가 있음을 감지하고 경고를 표시합니다. 스레드 2에서의 이 읽기 작업은 데이터 경합(data race)의 일부이며, 스레드 산티라이저는 경합을 일으키는 메모리 액세스 위치도 보여줍니다. 바로 스레드 1에서 발생하는 문제입니다. 이것이 바로 스레드 산티라이저가 겉보기에는 재현 불가능해 보이는 버그 보고서의 근본 원인을 찾는 데 매우 유용할 수 있는 이유입니다.

    이 문제를 해결하기 위해, 저는 시리얼 디스패치 큐를 사용하여 각 섹션이 원자적으로 실행되도록 하고, 앞서 수행된 읽기 및 쓰기 작업이 올바르게 동기화되도록 했습니다. 이 코드들을 `dispatch async`로 감쌌으며, 전용 시리얼 큐에서 실행될 것입니다.

    Swift 동시성 기능을 사용하면 모든 데이터 경합을 방지할 수 있습니다. 스레드 산티라이저는 주소 산티라이저와 동일한 방식으로 활성화됩니다.

    두 기능은 상호 배타적이므로, 한 번에 하나만 활성화하여 실행할 수 있다는 점에 유의하세요.

    자동화된 테스트의 경우, 테스트 계획에 두 가지 서로 다른 구성을 만들 수 있습니다. 하나는 주소 산티라이저가 활성화된 구성이고, 다른 하나는 스레드 산티라이저가 활성화된 구성입니다.

    산티라이저는 메모리 무결성 강제 적용을 도입하는 데 중요한 역할을 합니다.

    주소 산티라이저는 잘못된 메모리 액세스를 찾아내고 수정하는 데 도움을 줍니다. 이러한 오류는 Emmy가 활성화된 상태에서 앱이 강제 종료되는 원인이 됩니다. 스레드 산티라이저는 데이터 경합을 찾아내는 데 도움을 주며, 이는 종종 ‘use after free’ 오류로 이어집니다. 다행히도, ‘use after free’ 오류가 발생하면 Emmy가 앱을 강제 종료시켜 악용을 방지하지만, 이는 사용자에게 불편을 초래할 수 있습니다.

    스레드 산티라이저를 사용하여 버그를 찾아 수정하면 고객의 안전을 보장하고 만족도를 높일 수 있습니다.

    다음은 여러분이 취해야 할 조치입니다. 먼저, 특히 코드베이스에 C, C++ 또는 Objective-C를 사용하고 있다면 테스트 계획의 구성에 주소 산티라이저를 추가하세요.

    그런 다음, C나 사전 동시성 기능을 사용하고 있다면 테스트 계획에 Fred Sanitizer를 추가하세요. Swift.

    Swift 코드에서 데이터 경합을 방지하려면 Swift 동시성 기능을 도입하세요.

    마지막으로, 원인을 파악하거나 재현할 수 없는 크래시 보고서를 받게 되면, 샌티라이저를 활용해 문제가 감지되는지 확인해 보세요. 어쩌면 그 문제가 발생한 경위에 대한 단서를 제공해 줄 수도 있습니다.

    감사합니다. 이제 Kurt에게 마이크를 넘기겠습니다.

Developer 바닥글

  • 비디오
  • Meet with Apple
  • Sanitizer로 보안 버그 찾기
  • 메뉴 열기 메뉴 닫기
    • 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. 모든 권리 보유.
    약관 개인정보 처리방침 계약 및 지침