Using Heartbleed as a starting point

Salvatore Sanfilippo

Heartbleed를 출발점으로

최근 OpenSSL 버그에 대한 강한 반응은 이해할 만합니다. 인터넷 전체가 갑자기 패치를 해야 한다는 것은 유쾌한 일이 아니니까요. 게다가 개인적으로 이 버그가 그토록 사소하다는 점이 충격적입니다. 저는 OpenSSL 개발자들을 비난하려는 것이 아닙니다. 다만 OpenSSL과 같은 소프트웨어에서는 보통 그런 종류의 문제가 좀 더 미묘할 것이라고 생각하기 마련입니다. 보통은 정합성 검사를 *제대로* 수행하지 못하는 경우가 많습니다. 하지만 이 버그는 memcpy() 호출에 경계 검사가 *전혀 없다*는 점에서 완전히 다릅니다.

하지만 때때로 아침에 지난밤에 쓴 코드를 읽다 보면 몹시 부끄러울 때가 있습니다. 프로그래머는 때때로 실수를 합니다. 저도 확실히 자주 실수를 합니다. 그래서 제 생각에는 필요한 것은 다른 OpenSSL 팀이 아니라 다른 프로세스라고 봅니다.

어떤 이들은 C보다 안전한 다른 언어를 제안하고, 어떤 이들은 명세가 너무 복잡해서 잘못되었다고 제안합니다. 아마도 두 주장 모두 일리가 있습니다. 하지만 가까운 시일 내에 다른 명세나 시스템 언어로 전환할 가능성은 낮습니다. 따라서 진짜 문제는 지금 시스템 소프트웨어 보안을 개선하기 위해 무엇을 할 수 있느냐는 것입니다.

1) 돈을 투자하라. 시스템 코드를 더 안전하게 만드는 것은 투자가 있다면 간단합니다. 여러 회사들이 보안 전문가를 고용하여 OpenSSL 코드베이스에 대한 코드 감사를 수행한다면, Heartbleed와 같은 버그를 발견할 확률이 높아질 것입니다.

저는 심각한 코드 감사 노력을 통해 복잡한 조건들이 얽힌 매우 복잡한 버그들이 발견되는 것을 보아 왔습니다. 경계 검사가 없는 memcpy()는 코드를 보안 관점에서 분석한다면 처음 읽을 때 눈에 띄는 것입니다. 그리고 Heartbleed가 어떻게 발견되었는지 아십니까? Google에서 수행한 보안 감사를 통해서였습니다.

아마도 우리가 오픈소스를 주로 가져다 쓰는 대상으로 여기던 시대는 지난 것 같습니다. 많은 기업들이 Google과 다른 기업들의 사례를 따라 OSS 소프트웨어 개발과 보안에 인력을 투입해야 합니다.

2) 정적 및 동적 검사. 정적 코드 분석은 부수적으로 코드 감사를 반자동으로 수행하는 방법입니다. OpenSSL과 같은 중요한 시스템 코드에서는 소스 코드에 주석을 달거나 정적 분석을 더 효과적으로 만들기 위한 일련의 규칙을 사용하는 것도 확실히 허용될 만합니다.

오늘날 정적 도구는 완벽한 해결책은 아니지만, 전문 프로그래머가 정적 분석 결과를 주의 깊게 검토한다면 어느 정도 가치를 제공할 수 있습니다.

또 다른 큰 도움은 Valgrind와 같은 동적 검사에서 나옵니다. C로 작성된 모든 시스템 소프트웨어는 모든 새 커밋에서 Valgrind를 사용하여 자동으로 테스트되어야 합니다.

3) 라이브러리로 C를 추상화하자. C는 저수준 언어이며 언어 자체에 내장된 안전성이 없습니다. 그러나 C의 좋은 점은 그 투박함 위에 계층을 구축할 수 있는 언어라는 것입니다.

제대로 된 동적 문자열 라이브러리는 많은 버퍼 오버플로 문제를 방지하며, 오늘날 거의 모든 괜찮은 프로젝트가 하나쯤은 사용합니다. 하지만 더 할 수 있는 일이 있습니다. 예를 들어 메모리에 개인 키와 같은 것이 포함될 수 있는 보안에 중요한 코드에서는 암시적 검사를 수행하면서 한 버퍼에서 다른 버퍼로만 복사하는 메모리 복사 프리미티브를 동적 문자열 라이브러리에 추가할 수 있습니다.

또한 버퍼에 중요한 데이터가 포함된 경우, 이 영역에서 복사하려고 하면 프로그램이 중단되도록 논리적 권한을 설정할 수 있습니다. 메모리 관리를 사용하여 중요한 메모리 페이지를 훨씬 더 효과적으로 보호하는 다른 이식성이 낮은 방법들도 있습니다. 그러나 이식성/예측 가능성 문제 때문에 더 높은 수준의 C 보호가 실제로는 훨씬 간단할 수 있습니다.

일반적으로 보호 기능이 없는 C를 사용하지 않기 위해 많은 방법을 탐구할 수 있으며, C 위에 추상화 계층을 제공하는 라이브러리를 만들어 프로그래밍을 더 안전하게 할 수 있습니다.

4) 무작위 테스트. 단위 테스트는 경계 조건과 실패한 검사를 유발할 가능성이 낮습니다. 수십 년 동안 알려져 왔지만 제 생각에는 충분히 사용되지 않는 테스트 종류가 있습니다: 퍼지 테스트.

OpenSSL 버그는 다양한 무작위 매개변수를 가진 여러 종류의 OpenSSL 패킷을 Valgrind와 같은 동적 분석 도구와 함께 보냄으로써 분명히 발견할 수 있었을 것입니다.

제 경험에 비추어 볼 때, 무작위 테스트를 대량으로 수행하고 Valgrind 위에서 프로그램을 실행하면서 같은 테스트를 반복적으로 돌리는 환경은 그렇지 않으면 놓치기 쉬운 많은 실제 버그를 발견해 줍니다. 탐구할 모델은 여러 가지입니다. 일반적으로 완전히 무작위 데이터를 주입하는 모델이나, 유효한 패킷을 다양한 무작위 방식으로 변형하는 중간 모델을 원할 것입니다.

이 기술의 전형적인 예는 오래된 DNS 압축 무한 루프 버그입니다. 몇 개의 무작위 패킷을 단순한 구현에 보내면 몇 분 안에 그것을 발견할 수 있습니다.

5) 보안과 성능에 대한 사고방식의 변화. OpenSSL이 자체 할당 캐싱을 수행하는 것은 흥미롭습니다. 일부 시스템에서는 malloc/free가 느리기 때문입니다. 이는 보안에 중요한 코드에서도 성능이 여전히 안전보다 너무 존중받고 있다는 신호입니다. 이 특정 사례에서는 OpenSSL 개발자들이 malloc을 래핑할 때 그로 인한 보안 영향을 전혀 고려하지 않았을 것임을 인정해야 합니다. 그러나 *일부* 시스템에서 할당 함수와 같은 저수준 세부 사항에 신경을 썼다는 것은 성능에 대한 깊은 관심의 신호이며, 그들은 시스템의 정확성/안전성에 더 깊이 관심을 가져야 합니다.

일반적으로, 오늘날 서버 인프라의 사실상 표준인 Linux는 여기저기서 찾아볼 수 있는 가장 나쁜 할당자 중 하나를 과거에도 가졌고 지금도 가지고 있는데, 이는 도움이 되지 않습니다. 주로 더 나은 할당자들이 GPL이 아니라 BSD 라이선스이기 때문입니다.

아마도 대기업들이 기여해야 할 또 다른 영역은 glibc malloc에 상당한 개선을 제공하는 것입니다. 더 나은 대안이 있더라도 glibc malloc은 많은 실제 시스템 소프트웨어가 어차피 사용하게 될 것입니다.

저는 Heartbleed에 대한 논의가 더 실용적인 접근 방식을 취하는 것을 보고 싶습니다. 한 가지는 확실하기 때문입니다. 여기저기를 비난한다고 해서 OpenSSL이나 다른 것들의 실제 보안 수준이 바뀌지는 않습니다. 그리고 미래에는 새로운 도전이 있습니다. 예를 들어 HTTP/2.0 구현은 보안 측면에서 매우 민감한 시기가 될 수 있습니다.

편집: 실제로 제가 틀렸습니다. glibc 내부의 malloc 구현은 BSD 라이선스이므로 라이선스 문제가 아닙니다. 왜 glibc가 매우 훌륭하고 활발히 개발되는 할당자인 Jemalloc을 사용하지 않는지 모르겠습니다.

원문은 Salvatore Sanfilippo님이 에 게재했습니다.

이 글은 deepseek-v4-flash 모델을 사용해 번역했습니다.