brunch

You can make anything
by writing

C.S.Lewis

by Extreme Code Jun 27. 2021

Rust in the Android

Android의 핵심 코드는 이제 Rust도 사용합니다.

  몇 개월 전에 구글에서 Rust를 사용하여 Android 개발을 하고 있다는 내용의 글을 올렸습니다. 이 사실 자체는 그리 중요하지 않을 수도 있습니다만, 구글의 개발 철학도 살짝 엿볼 수 있고 왜 Rust를 써야 되는지도 잘 설명된 거 같아서 해당 글을 한번 소개해볼까 합니다.




안드로이드가 겪는 문제점

  다들 Android Application 은 Java/Kotlin으로 개발한다는 걸 알고 있을겁니다. 그럼 Android 내부 코드 (보통 AOSP - Android Open Source Project 라고 부릅니다.) 는 뭘로 되어 있을까요? 당연하게도 Low-level로 갈수록 대부분 C/C++로 되어 있습니다. 커널이나 디바이스 드라이버와 같은 low-level은 아직까진 C/C++ 이 대세이죠. (대세라기보다는 선택권이 이거밖에 없는 경우가 많습니다.)


근데 C/C++ 을 좋아하는 사람은 거의 없을 겁니다. Memory safety 를 보장하기가 매우 어렵기 때문인데요, 안드로이드도 마찬가지로 심각한 버그의 70% 가량은 memory safety 와 관련된 문제라고 합니다. 뭐 이건 system programming 을 많이 해 보신분들은 메모리 문제가 젤 어렵다는 걸 다들 동감하실 것 같습니다. 


안드로이드의 경우 Application level 에서 사용하는 Java/Kotlin은 ART (Android runtime) 에서 메모리를 관리해 주기 때문에 큰 문제가 없는 편입니다. AOSP 의 많은 부분이 이걸로 이뤄져 있구요. 하지만 OS level까지 내려가서 시스템의 리소스와 하드웨어를 직접 제어하는 부분에서는 리소스를 적게 사용해야 하며 예측 가능한 퍼포먼스를 내야 하기 때문에 Java/Kotlin을 사용하기 힘듭니다. 그래서 C/C++/Rust를 사용하게 됩니다.

안드로이드 플랫폼의 사용언어

Rust는 C/C++ 처럼 기계어로 컴파일이 되므로 동일한 퍼포먼스를 보이는데 memory safety 기능을 제공하기 때문에 이번에 사용하게 되었다고 합니다.



기존의 안드로이드 개발

  기존에는 C/C++ 로 이뤄진 부분을 개발할 때 Rule of 2 법칙에 따라서 개발했다고 합니다. 이름은 뭔가 있어보이지만 심플한 룰입니다. 아래 그림에 나온 것과 같은 문제 (리스크) 가 있을 때 리스크가 2개를 넘어가면 안된다는 것입니다.

구글의 Rule of 2

여기서는 세 가지 리스크로 구분합니다.

신뢰할 수 없는 입력값을 처리하는지 : 예를 들어, 사용자 입력이나 외부 네트웍 입력 같은 내부 코드 처리값이 아닌 외부 입력값 등을 처리하는지

Unsafe language (C/C++) 를 사용한 코드인지

샌드박스가 없이 돌아가는지 : 여기서 sandbox는 분리된 프로세스 실행 환경이라고 생각하면 됩니다. sandboxing이 안된다면 프로세스가 시스템 권한 탈취를 하거나 큰 문제를 일으킬 수 있습니다. 특히 해커들이 노리는 지점이 바로 이 지점입니다. 따라서 low-level programming 에서는 이런 부분에 대한 고려가 중요합니다.


3가지를 다 포함하게 되면 굉장히 위험하기 때문에 2가지 리스크만 선택해야 하는 것이죠. 그렇다면 C/C++ 을 사용하여 프로그래밍을 하고 외부 입력값을 처리하게 된다면 무조건 sandboxing을 해야 되는 상황이 벌어집니다. 문제는 sandboxing이 매우 비싼 작업이라는 점입니다. 분리된 프로세스 환경을 만들어서 돌려야 하므로, 메모리도 추가적으로 사용하고 별도 프로세스이므로 IPC 를 통한 통신도 추가로 해야 하므로 오버헤드가 늘어나게 되며 결과적으로 latency도 늘어나게 됩니다. 또한 sandboxing 을 한다고 취약점이 제거되는 건 아닙니다. 버그가 많다면 이런 것의 효과도 떨어집니다. (해커가 여러 개 취약점을 섞어서 공격할 수 있으므로)


그렇기 때문에 안드로이드에 리포트된 버그들 중 가장 최근에 리포트된 것들 부터 Rust로 변경하는 작업을 하였다고 합니다. 어차피 모든 것을 Rust로 바꿀 수는 없기 때문이죠. 또한 오래된 버그는 구지 고칠 필요가 없을 가능성이 높습니다. (그다지 크리티컬한 버그가 아니라는 의미)

50% 정도가 1년 내로 리포트된 버그입니다.


버그 수정 프로세스

  버그 수정 프로세스는 아래와 같이 이루어져 있습니다. 버그 검출 및 QA의 정석 프로세스를 보여주는 것 같은 느낌입니다.

안드로이드 팀의 버그 수정 프로세스

다양한 테스팅, sanitization, fuzzing 등을 통해 버그를 발견하는 것은 당연하게도 중요한데요, memory safety bug를 검출하는게 쉽지는 않습니다. 특정 환경 등을 트리거하여 에러 상태를 발견해야 하는데 실제와 차이가 있을 수 있고, 검출 안되는 경우도 많기 때문입니다. 훌륭한 test coverage, fuzz coverage를 가지더라도 발견된지 않은 버그가 있을 수 있습니다.


게다가 어차피 버그 수정보다 bug detection 이 빠르고 많기 때문에 다 수정하기도 힘듭니다. 버그 수정 프로세스에는 많은 리소스가 투입되고, 그럼에도 불구하고 하나라도 놓치면 그냥 릴리즈되게 됩니다. 또한 C++ 코드베이스에서는 소수 인원만 고치는게 가능하고, bug fix 된 코드도 틀리는 경우가 있습니다.



Rust를 활용한 버그 감소

  안드로이드에서는 Rust를 사용함으로써 아래와 같은 장점을 통해 버그를 감소시킬 수 있었다고 합니다.


Memory safety

컴파일러 및 런타임 체크를 통해 memory safety 를 강제합니다.


Data concurrency

Data race 를 방지하고, 그렇게 함으로써 효율적이고 thread-safe 한 코드를 작성할 수 있게 되었습니다. Rust의 슬로건 중 하나가 "두려워하지 말고 concurrency 를 사용해라!" 입니다ㅎㅎ


개선된 type system

newtype wrapper 라던가 적절한 enum을 값에 따라 넣어준다거나 하는 개선된 type system을 사용한다고 합니다.


Immutable reference/variables

Rust는 기본적으로 모든 레퍼런스와 변수과 immutable 입니다. 꼭 필요한 경우에만 mutable이죠. Rust compiler는 mutable 이 변경되지 않아도 warning을 내려주고요. C++에도 const가 있긴 하지만 잘 쓰이지는 않죠. (거의 상수값 선언에만 쓰이고 함수형 스타일처럼 일상적인 선언에 항상 구별하는 편이 아닙니다.)


Error handling

Rust는 귀찮을 수도 있지만 fail 값 리턴 등에도 명시적으로 코드 작성을 해 주어야 하고, 이는 unhandled error를 줄일 수 있습니다.


Initilization

Rust에서는 변수가 사용 전 반드시 초기화 되어야 합니다. 안드로이드 보안 취약점 중 3~5%는 uninitialized memory 관련 취약점이라고 합니다. Android 11 부터 auto init 을 적용했는데 0값으로 init하는 것도 항상 안전한 건 아니고 새로운 문제를 만들수 있겠죠. Rust에서는 의도하지 않은 init값으로 unsafe value가 들어가는 것을 막으며, Clang 컴파일러가 그러는 것 처럼 중복 init 같은 퍼포먼스 저하도 막는다고 합니다.


Safe integer type

Rust에서는 모든 int 타입이 명시적으로 변환되어야 합니다. overflow 방지를 위한 기능도 default로 들어가 있죠. (안드로이드에도 integer overflow sanitization 이 있기는 하지만, Rust에서는 기본값)


이렇게 Rust를 사용해서 안드로이드의 memory safety 개선을 할 수 있었다고 합니다. 새로운 언어를 프로젝트에 추가하는 것은 많은 노력이 필요한 일입니다. 툴체인, 의존성, 인프라, 개발툴 등도 업데이트 되야 하며 참여하는 개발자 트레이닝도 필요하기 때문입니다. 해당 블로그 글 작성 전에 18개월 가량 구글의 AOSP 에서 Rust 지원을 추가했다고 하며 곧 관련된 여러 프로젝트들을 공개할 계획이라고 합니다.




  저도 개인적으로 Rust에 관심이 많긴 하지만 튜토리얼 정도 해봤지 서비스를 개발해 본 적은 없습니다. 근데 주변에서 Rust를 활용해서 서비스까지 해 본 사람들의 말에 의하면 개발할 때 제약이 많아서 좀 불편하긴 하지만 그만큼 버그도 거의 없고 안정적이라고 합니다. 


원문을 읽어보면 구글의 개발 철학도 살짝 엿볼 수 있습니다. Rule of 2와 같은 개념을 강제하는게 재미있네요. 물론 system programming 에 한정된 내용이겠지만 다양한 서비스 개발팀마다 이러한 개념들을 가지고 있을 것입니다. 버그 수정 프로세스의 경우에도 정말 정석을 보여주는 듯 합니다. 수억~수백억개의 디바이스에 올라가는 제품을 만드는 것이기 때문에 이렇게 할 만도 합니다. 하지만 그만큼 버그 수정의 어려움을 토로하는 걸 보면 전세계 어디나 똑같은거 같네요 ㅎㅎ


Rust가 좋은 언어인것은 맞지만 아직까지는 C/C++같은 메이저 언어에 비해서 ecosystem 이 부족하다고 생각됩니다. 앞으로 더 많은 개발자들이 참여하고 다양한 라이브러리와 자료들이 공유된다면 좀 더 많은 분야에 활용될 수 있지 않을까 싶고, 언젠가는 low-level 에서 C/C++의 왕좌를 위협할 날이 오지 않을까 싶습니다.




매거진의 이전글 웹 서비스가 사용자를 판별하는 법
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari