더 정확한 정적 분석기를 위해
필립 K.딕의 소설 <안드로이드는 전기양의 꿈을 꾸는가?>에 나오는 주인공 릭 데카드는 화성에서 탈주한 안드로이드를 사냥하는 현상금 사냥꾼이다. 정적 분석기 또한 코드 곳곳에 숨어 있는 오류와 취약점을 찾아내는 사냥꾼과 같다. 하지만 정적 분석기에게 혼자 임무를 맡기고 손 놓고 있다간 큰일이 날 수도 있다. 한정된 시간과 자원 안에서, 가장 현대적인 정적 분석기조차 정상적인 코드와 오류가 있는 코드를 완벽히 구분할 수는 없기 때문이다.
정적 분석기의 이런 약점은 이론적 한계에서부터 기인한다. 어떤 프로그램의 실행이 유한한 시간 내에 끝날지 아닐지를 판정하는 알고리즘은 존재하지 않음이 증명되어 있다. 유명한 정지 문제 (Halting Problem) 다. 프로그램에 존재하는 오류와 취약점을 완벽하게 예측할 수 있는 정적 분석기 또한 근본적으로 정지 문제와 같은 결정 불가능 (Undecidable) 한 문제다. 그러므로, 안타깝지만, 완전무결한 정적 분석기는 결코 존재할 수 없다.
그렇다고 해서 정적 분석기가 무결성이라는 꿈을 꾸지 말라는 법은 없다. 수많은 연구를 통해 고도화된 현대의 정적 분석기는 완벽하지는 않지만, 어느 정도 신뢰할 수 있는 결과를 제공한다. 정적 분석기의 탐지 결과의 정확도는 크게 두 가지 측면에서 측정할 수 있다. 정적 분석기가 실제 존재하는 오류 중 얼마나 성공적으로 탐지하는지 (Recall), 그리고 정적 분석기가 탐지한 오류 중 어느 정도가 실제 오류인지 (Precision) 확인하는 것이다. 실험실 단위에서 정적 분석기는 이 두 가지의 명확한 목표를 추구하며 발전해 왔다. 하지만 실제 소프트웨어 업계에서, 이 좌충우돌한 사냥꾼들을 제대로 길들여 사용하는 곳은 아직 몇몇 거대 기업에 국한되어 있는 것으로 보인다. 심지어 구글에서 보편적으로 사용하는 정적 분석기는 최신 기술을 반영하기보다, 프로시저 간 분석 (Interprocedural Analysis) 을 지원하지 않는 매우 간단한 형태라는 언급도 있다.
산업에 적용하는 건 다른 문제
왜 최신 연구와 실제 산업계 사이에 이런 간극이 벌어지는 것일까? 실험용 벤치마크에서 정적 분석기를 평가할 때에 간과하기 쉬운 것이 바로 확장성 (Scalability), 그리고 인간과 기술 간의 상호작용 (Human-Computer Interaction) 이다. 페이스북이 두 정적 분석기, Infer, 그리고 Zoncolan을 개발하고 사내에 적용하면서 직면했던 문제들도 이 두 가지 측면과 맞닿아 있다.
정적 분석기들이 큰 시스템에 대한 프로시저 간 분석을 지원하면서도 합리적인 실행 시간을 유지할 수 있도록 하기 위해서는 어떻게 해야 할까? 컴퓨터 과학 설계 기법의 핵심이라 할 수 있는 분할 정복법 (Divide and Conquer), 그리고 추상화 (Abstraction) 가 여기서도 해답을 준다. 특정한 값을 가지고 분석하는 것이 아니라, 추상화된 값의 범위를 활용하면 각 프로시저를 독립적으로 다룰 수 있어 병렬화가 쉬워진다. 프로그램이 지속적으로 변경되는 상황에서도 수정된 프로시저에 대해서만 추가로 분석을 수행할 수 있어 효율적이기도 하다. 다만, 확장성을 위한 이러한 노력이 이점만 가져다 주는 것은 아니다. 추정의 영역이 커질수록 분석 결과의 정확성을 희생하게 되고, 더 큰 확장성을 요구할수록 무결한 분석 결과와는 점점 더 멀어지고 만다.
정적 분석기와 인간의 공존 (?)
이처럼 정적 분석기가 갖고 태어난 불완전함이라는 속성을 고려하면 할수록, 그가 내놓은 결과를 그대로 믿고 따르는 것은 안 될 일이라는 것을 알았다. 결국 정적 분석기가 내놓은 결과를 검증하고, 코드를 손보는 등의 이후 대처는 인간의 몫이다. 문제는 인간 또한 불완전하다는 것이다. 정적 분석기가 가진 명료한 이론적 불완전성과는 조금 다른 형태지만, 인간의 불완전성은 더 많은 변수, 이를테면 게으름, 책임감, 집중력, 그리고 퇴근 욕구 등에 의해 좌지우지된다는 점에서 더욱 혼란스럽고 복잡하다. 이런 변수들을 조금이나마 통제하기 위한 방법은 정적 분석기의 결과를 인간에게 언제, 어떻게 전달하는지 신중하게 결정하는 것이다. 정적 분석기의 결과를 전체 코드 범위에서 한꺼번에 생성하고 전달했을 때 이 알림은 거의 무시되다시피 했지만, 이 결과를 특정 개발자가 올린 코드에 대해 코드 리뷰 형식으로 전달했을 때에는 이 개발자가 정적 분석기의 제안을 훨씬 공들여 처리하게 됐다. 기존 결함을 바탕으로 현재 알림의 중요도를 판단해서, 보안 담당 엔지니어와 개발자에게 단계적으로 전달하는 전략 또한 효과적으로 작동했다.
인간, 그리고 정적 분석기가 상호작용하면서 더 나은 결함 분석을 지향하는, 원글에서 제시한 피드백 순환 (Feedback Loop) 개념은 매우 유망해 보인다. 여기서 좀더 공격적인 관점을 제시해보자면, 정적 분석기의 설계 이전에 사용 시나리오를 먼저 고려해야 한다는 것이다. 시스템 전체 범위 대신 개발자가 새롭게 수정하는 코드에 대해서 분석을 수행하는 것을 처음부터 가정한다면, 시스템의 과거 결함과 코드 수정에 대한 기록을 연구자들 또한 적극적으로 사용한다면, 산업계의 사용 예시와 좀더 밀접하게 연결된 채로 최신 기술을 진화시킬 수 있지 않을까 조심스레 예상해 본다. 제목에 대한 답을 감히 내리자면 이렇다. 정적 분석기는 실현될 수 없는 무결성의 꿈을 꾸지만, 그 무결성의 꿈은 인간이 정적 분석기에 의해 “움직일 때” 비로소 현실과 가까워진다.