-
메모리 안전성과 스레드 안전성은 분리할 수 있는 개념이 아니며, 스레드 안전성이 없으면 진정한 메모리 안전성을 달성할 수 없음
- Go처럼 스레드 안전하지 않은 언어의 경우, 단순히 스레드 이슈만으로도 메모리 안전성이 깨질 수 있음
- Java 등 일부 언어는 동시성 메모리 모델을 통해 데이터 레이스조차 정의된 동작으로 처리하여 언어 수준 안전성을 확보함
- Go는 데이터 레이스에 취약하며 실제 메모리 안전 침해 사례가 존재함
- 진정 중요하게 다뤄야 할 속성은 Undefined Behavior(정의되지 않은 동작)의 부재임
개념의 혼동: 메모리 안전성 vs 스레드 안전성
- 최근 메모리 안전성이 크게 주목받고 있으나, 실제로 무엇을 의미하는지 정의가 명확하지 않음
- 전통적으로 메모리 안전성은 use-after-free 또는 out-of-bounds 메모리 접근을 막아주는 언어를 지칭함
- 반면, 스레드 안전성은 동시성 버그가 없는 프로그램을 의미하며, 두 개념은 종종 별개로 취급됨
- 작성자는 이 구분이 실질적으로 쓸모 없다고 주장하며, 우리가 실제로 원하는 것은 Undefined Behavior(UB) 부재임을 강조함
데이터 레이스로 인한 메모리 안전성 침해: Go 예시
- 메모리 안전성과 스레드 안전성을 따로 취급해온 문제점을 보여주기 위해 Go 언어의 예시 제시
- Go는 메모리 안전 언어로 분류되나, 아래와 같은 프로그램에서 데이터 레이스만으로도 메모리 오류가 발생함
globalVar를 반복적으로 다른 타입 값(Int, Ptr)으로 변경하면서 동시에 별도 고루틴에서 이를 읽어 메서드를 호출
- 두 스레드가 겹쳐서 globalVar의 내부 두 포인터(데이터, vtable)를 따로따로 갱신함에 따라, 중간에 읽는 경우 혼합 상태가 발생해서 잘못된 메모리 접근 발생
- 결과적으로 잘못된 주소(예, 0x2a; 십육진수 42)를 참조하려고 시도해서 프로그램이 오류로 종료함
- 이 현상은 Go의 인터페이스, 슬라이스 등도 유사하며, 원자적으로 여러 필드를 갱신하지 않아 발생함
다른 언어의 동시성 처리 방식 및 메모리 안전성
- Java 등 다른 언어도 데이터 레이스 가능성이 있지만, 정의된 동시성 메모리 모델을 적용함으로써 프로그램이 언어 자체를 깨지 않도록 보장함
- 예시: Java는 멀티스레드 환경에서도 런타임 오류(예, 강제 세그먼트 폴트)에 빠지지 않도록 메모리 모델을 정교하게 설계함
- 대부분의 언어는 아래 두 가지 방식 중 하나로 동시성 문제를 통제함
-
모든 동시성 프로그램이 일관적 동작을 보장하도록 메모리 모델 정의(대신 컴파일러 최적화 제한 및 구현 부담 증가)
- Java, C#, OCaml, JavaScript, WebAssembly 등
-
강력한 타입 시스템으로 대부분의 데이터 레이스를 금지하고, 소수 예외만 안전하게 처리(Rust, Swift의 strict concurrency)
- Go는 위 두 옵션 모두를 따르지 않음
- 데이터 레이스가 없는 경우에만 메모리 안전을 보장함
- 데이터 레이스 탐지 도구가 있으나, 실제 프로그램에서는 모든 상황을 테스트로 검증하는 데 한계 있음
- 연구 결과와 현장 경험에서 실제 메모리 안전 위반 사례가 다수 보고됨
Go의 메모리 모델 및 문서화 이슈
- Go 메모리 모델 공식 문서는 대부분의 레이스는 결과가 제한적이라고는 하지만, 일부 데이터 레이스는 결과가 무한대임을 명확히 설명하지 않음
- Java/JavaScript와 유사하다는 주장도 있으나, 두 언어는 Go에 비해 동시성 안전성 확보를 위해 훨씬 더 많은 노력을 들임
- 문서의 일부 세부 섹션에서만 제한적으로 일부 데이터 레이스가 완전히 정의되지 않은 동작을 유발할 수 있음을 언급함
결론: Undefined Behavior(UB)의 부재가 진정한 목표
- 실질적으로 사용자가 진짜로 원하는 속성은 프로그램이 언어 자체를 깨지 않음(UB 부재) 임
- 메모리 안전 침해로 발생하는 각종 보안 취약점은 UB가 실제로 발생했기 때문임
- UB가 발생하는 순간 이후의 모든 동작은 예측 불가하며, 공격자가 이를 악용할 수 있음
- '안전' 언어와 '비안전' 언어를 가르는 본질적 차이는 UB의 발생 가능성에 있음
- 메모리 안전성, 스레드 안전성, 타입 안전성 등 세부로 구분하는 것보다 UB 발생 여부 자체가 핵심임
- 실제로는 안전성에도 스펙트럼이 존재하며, Go는 C보다는 안전하지만 완전한 안전함을 보장하지 않음
- 데이터를 기반으로 Go에서 실제 안전성을 '증명'하기는 매우 어렵고, 각 언어가 취한 선택의 비직관적 결과를 제대로 아는 것이 중요함