cpp

3 개의 포스트

대규모 환경의 Rust (새 탭에서 열림)

WhatsApp은 최근 30억 명 이상의 사용자들을 멀웨어 위협으로부터 보호하기 위해 미디어 처리 라이브러리를 Rust 언어로 재구축하여 성공적으로 배포했습니다. 이는 글로벌 규모의 서비스에서 Rust가 프로덕션 환경에 적합함을 증명한 사례로, 특히 메모리 안전성이 취약한 C/C++ 기반의 미디어 파싱 라이브러리에서 발생할 수 있는 보안 취약점을 근본적으로 해결하는 데 중점을 두었습니다. 결과적으로 WhatsApp은 성능과 메모리 효율성을 동시에 개선하면서도 사용자 보안을 한층 더 강화하는 성과를 거두었습니다. **미디어 보안의 취약점과 대응의 역사** - 이미지나 영상처럼 무해해 보이는 파일도 운영체제의 취약점을 공격하는 악성 코드를 포함할 수 있으며, 2015년 안드로이드의 'Stagefright' 취약점이 대표적인 사례입니다. - 당시 WhatsApp은 OS 라이브러리의 패치를 기다리는 대신, 자체 개발한 C++ 기반의 미디어 일관성 검사 라이브러리인 'wamedia'를 통해 표준을 준수하지 않는 파일을 사전에 차단하는 방식을 택했습니다. - 하지만 미디어 체크 로직 자체가 신뢰할 수 없는 입력을 자동으로 처리하기 때문에, 이 라이브러리 자체의 메모리 안전성을 확보하는 것이 보안의 핵심 과제로 떠올랐습니다. **Rust를 통한 대규모 현대화 및 성능 개선** - WhatsApp은 점진적인 수정 대신 기존 C++ 버전과 병행하여 Rust 버전의 라이브러리를 새롭게 개발했습니다. - 두 언어 간의 호환성을 보장하기 위해 '디퍼런셜 퍼징(Differential Fuzzing)'과 광범위한 통합 테스트를 거쳐 안전성을 검증했습니다. - 기존 160,000줄의 C++ 코드를 90,000줄의 Rust 코드로 대체했으며, 결과적으로 이전보다 더 우수한 성능과 낮은 런타임 메모리 사용량을 기록했습니다. - 안드로이드, iOS, 웹, 웨어러블 등 다양한 플랫폼 지원을 위한 빌드 시스템 구축과 바이너리 크기 최적화라는 기술적 난관을 극복하고 글로벌 배포를 완료했습니다. **다층 방어 체계 'Kaleidoscope'의 구축** - Rust로 작성된 이 라이브러리들은 'Kaleidoscope'라 불리는 종합 보안 체크 시스템의 핵심 구성 요소입니다. - 단순히 파일 구조의 결함을 찾는 것을 넘어, PDF 내의 스크립트 요소나 임베디드 파일, 확장자를 위조한 MIME 타입 변조 등을 감지합니다. - 실행 파일이나 앱 설치 파일과 같은 위험한 파일 형식을 식별하여 사용자 인터페이스(UX) 차원에서 특별 관리함으로써 비공식 클라이언트나 악성 첨부파일로부터 사용자를 보호합니다. **메모리 안전 언어 중심의 보안 로드맵** - WhatsApp의 분석에 따르면 심각도가 높은 취약점의 대부분은 C/C++의 메모리 관리 문제에서 발생하며, 이를 해결하기 위해 새로운 코드 작성 시 메모리 안전 언어(Memory Safe Language)를 기본으로 선택하고 있습니다. - 불필요한 공격 표면을 최소화하고, 기존 C/C++ 코드에 대해서는 강화된 메모리 할당자와 보안 버퍼 API를 적용하는 등 보안 보증 투자를 병행하고 있습니다. - 이번 Rust 도입의 성공을 바탕으로 향후 더 많은 영역에 Rust 채택을 가속화하여 내부 방어 체계를 지속적으로 강화할 계획입니다. **결론 및 제언** WhatsApp의 사례는 보안이 중요한 클라이언트 사이드 애플리케이션에서 Rust가 단순한 대안을 넘어 최고의 선택지가 될 수 있음을 보여줍니다. 특히 외부에서 유입되는 미가공 데이터를 파싱해야 하는 시스템이라면, 메모리 안전성이 보장되는 Rust로의 전환을 통해 보안 사고의 근본 원인을 제거하고 운영 효율성을 높이는 전략을 적극 검토할 필요가 있습니다.

코드 품질 개선 기법 19편: 차일드 록 (새 탭에서 열림)

상속 구조에서 자식 클래스가 부모 클래스의 함수를 오버라이딩할 때 발생할 수 있는 결함을 방지하기 위해, 오버라이딩 가능한 범위를 최대한 제한해야 합니다. 부모 클래스의 공통 로직과 자식 클래스의 확장 로직을 분리하지 않으면 `super` 호출 누락이나 책임 범위의 혼선과 같은 버그가 발생하기 쉽습니다. 따라서 부모 클래스에서 전체적인 흐름을 제어하고 자식 클래스는 특정 지점의 로직만 구현하도록 설계하는 '차일드 록(child lock)' 기법이 필요합니다. **기존 오버라이딩 방식의 문제점** * **super 호출 누락의 위험:** 자식 클래스에서 부모의 기능을 실행하기 위해 `super.bind()`를 명시적으로 호출해야 하는 구조는 실수를 유발하기 쉽습니다. 호출을 잊더라도 컴파일 에러가 발생하지 않아 헤더나 푸터가 업데이트되지 않는 등의 버그가 방치될 수 있습니다. * **구현 강제성 부족:** 오버라이딩이 필수적인 상황임에도 불구하고 주석으로만 안내되어 있다면, 개발자가 실수로 구현을 누락할 가능성이 큽니다. * **책임 범위의 모호함:** 하나의 함수(`bind`)가 공통 로직과 개별 로직을 모두 포함하고 있으면 오버라이딩의 책임 범위를 오해하기 쉽고, 결과적으로 자식 클래스에 부적절한 코드가 포함될 수 있습니다. **차일드 록을 통한 구조 개선** * **공통 흐름의 고정:** 부모 클래스의 메인 함수(예: `bind`)에서 `open` 키워드를 제거하여 자식 클래스가 전체 흐름을 수정할 수 없도록 '록'을 겁니다. * **추상 메서드 분리:** 자식 클래스마다 달라져야 하는 로직만 별도의 `abstract` 메서드(예: `updateMessageList`)로 추출합니다. * **템플릿 메서드 패턴 적용:** 부모 클래스의 `bind` 함수에서 공통 로직(헤더/푸터 업데이트)을 실행한 후, 자식 클래스가 구현한 추상 메서드를 호출하는 방식으로 설계합니다. 이를 통해 자식 클래스는 부모의 로직을 신경 쓰지 않고 자신의 역할에만 집중할 수 있습니다. **견고한 상속 설계를 위한 가이드라인** * **super 호출 지양:** 라이프사이클 관리나 플랫폼 API의 제약이 있는 특수한 경우를 제외하고는, 자식 클래스에서 `super`를 호출해야만 기능이 완성되는 구조를 피해야 합니다. * **제어 흐름의 중앙 집중화:** 자식 클래스들이 공통으로 사용하는 함수의 실행 순서나 흐름은 반드시 부모 클래스에서 정의하고 관리해야 합니다. * **캡슐화 강화:** C++의 `private virtual` 기법처럼, 부모 클래스에서만 호출 가능하면서 자식 클래스에서 동작을 재정의할 수 있는 구조를 활용하여 오버라이딩 범위를 엄격하게 제한하는 것이 좋습니다. 상속을 설계할 때는 자식 클래스에게 과도한 자유를 주기보다, 필요한 부분만 안전하게 확장할 수 있도록 제약 장치를 마련하는 것이 시스템의 안정성을 높이는 핵심입니다. 이는 코드 리뷰 과정에서 발견하기 어려운 논리적 오류를 컴파일 단계나 구조적 제약으로 사전에 차단하는 효과를 줍니다.

소개: Figma to React | 피 (새 탭에서 열림)

Figma는 비대해진 C++ 코드베이스로 인한 긴 빌드 시간을 단축하여 개발자의 피드백 루프를 개선하고 생산성을 극대화하는 성과를 거두었습니다. 단순히 고사양 하드웨어를 도입하는 것에 그치지 않고, 빌드 과정의 병목을 정밀하게 측정하고 헤더 구조 최적화 및 분산 빌드 시스템을 도입하여 빌드 속도를 획기적으로 개선했습니다. 이러한 노력은 결과적으로 대규모 코드 수정 시에도 개발자가 신속하게 결과를 확인할 수 있는 쾌적한 엔지니어링 환경을 구축하는 결론으로 이어졌습니다. **빌드 데이터 시각화와 병목 지점 파악** - Clang의 `-ftime-trace` 플래그를 활용하여 각 소스 파일의 컴파일 과정과 템플릿 인스턴스화에 소요되는 시간을 상세하게 데이터화했습니다. - 수집된 JSON 데이터를 Chrome 브라우저의 `about:tracing` 도구로 시각화하여, 어떤 헤더 파일이 반복적으로 파싱되고 있는지 또는 특정 템플릿이 얼마나 많은 시간을 잡아먹는지 직관적으로 분석했습니다. - 이를 통해 막연한 추측이 아닌, 실제 수치에 기반하여 최적화 우선순위를 결정할 수 있는 기틀을 마련했습니다. **헤더 의존성 관리 및 전방 선언 활용** - 헤더 파일 내에 불필요하게 포함된 다른 헤더들을 제거하고, 실제 구현 파일(.cpp)에서만 포함하도록 구조를 변경했습니다. - '전방 선언(Forward Declaration)'을 적극적으로 사용하여 헤더 간의 복잡한 의존성 연결 고리를 끊어냄으로써, 특정 헤더 변경 시 재컴파일되는 파일의 숫자를 최소화했습니다. - 'Include What You Use(IWYU)' 원칙을 적용하여 각 파일이 컴파일에 꼭 필요한 최소한의 심볼만 참조하도록 정돈했습니다. **Unity Build와 Precompiled Headers (PCH)** - 여러 개의 소스 파일을 하나의 큰 컴파일 단위로 묶는 'Unity Build' 기법을 도입하여, 동일한 헤더가 수천 번 반복해서 파싱되는 오버헤드를 줄였습니다. - 표준 라이브러리(STL)나 외부 라이브러리처럼 변경이 거의 없는 무거운 헤더들을 'Precompiled Headers(PCH)'로 구성하여 빌드 초기 단계에서 한 번만 처리되도록 최적화했습니다. **분산 빌드 인프라 및 캐싱 적용** - 로컬 머신의 CPU 코어 수 한계를 극복하기 위해 Google의 `Goma`나 `Remote Build Execution(RBE)`와 같은 분산 컴파일 시스템을 구축했습니다. - 수백 대의 원격 서버 노드에 컴파일 작업을 분산시켜 수천 개의 파일을 동시에 처리함으로써 전체 빌드 시간을 물리적으로 단축했습니다. - `ccache`와 같은 컴파일러 캐시 도구를 병행 사용하여, 코드가 변하지 않은 부분에 대해서는 이전 빌드 결과를 재사용함으로써 불필요한 연산을 제거했습니다. **지속적인 빌드 성능 모니터링** - 빌드 시간 최적화는 일회성 작업으로 끝나지 않도록, CI(지속적 통합) 단계에서 빌드 시간을 모니터링하고 성능 저하가 발생할 경우 이를 추적하는 체계를 구축했습니다. - 템플릿 메타프로그래밍의 과도한 사용을 경계하고, 복잡한 템플릿 구조가 빌드 성능에 미치는 영향을 코드 리뷰 과정에서 검토하는 문화를 형성했습니다. C++ 프로젝트의 규모가 커질수록 빌드 시간은 기하급수적으로 늘어날 수 있으므로, 초기부터 `-ftime-trace`를 통한 시각화 습관을 들이는 것이 중요합니다. 특히 헤더 의존성을 느슨하게 유지하고 분산 빌드 환경을 구축하는 것은 대규모 팀의 개발 속도를 유지하기 위한 필수적인 투자입니다.