기계 학습과 지향성 실행탐험기
이 글에서는 컴퓨터 과학에서 부르는 Fuzzing을 실행탐험, Fuzzer를 실행탐험기라고 부릅니다.
지금 인공지능 분야를 이끄는 기계학습은 컴퓨터 프로그램이 데이터를 학습하는 기틀을 제공한다. 이 학습의 틀은 다음과 같다.
- 데이터를 알맞은 형식에 맞게 준비한다. 이 때, 입력 데이터와 정답 데이터를 구분한다.
- 입력 데이터에서 결과물을 도출하는 모형 함수를 정의한다. 이 때, 함수는 계수(Parameter)를 가진다.
- 모형함수의 결과값과 정답 데이터를 비교해 평가하는 손실함수(Loss Function)를 정의한다.
- 손실함수값이 줄어들도록 모형함수의 계수를 조절한다.
입력 데이터를 모형함수에 넣고, 손실함수값을 통해 모형함수를 계속해서 조절해나가는 것이 기계학습의 핵심이다. 모형함수를 선형함수로 설정하고 손실함수로 평균제곱오류(Mean Squared Error)를 사용하면 선형회귀법이 되고, 모형함수가 확률을 내놓게하고 손실함수로 교차 엔트로피(Cross Entropy)를 사용하면 로지스틱 회귀법이 되는 것이다.
이러한 접근법은 우리가 탐색할 공간에 대해 충분한 지식이 있다는 것을 가정하고 있다. 그러니까 기계학습 방법을 사용할 때 우리는 데이터를 이용해 그럴듯한 설명을 할 수 있는 모형함수를 얻을 수 있다고하고 시작하는 것이다. 이런 가정은 아주 매력적이다. 적절한 손실함수를 설계하면 우리는 모형함수를 얻을 수 있다. 데이터가 정답을 가지고 있으므로 그럴듯한 좋은 일이 일어날 논리적 기틀이 있는 것이다.
실행탐험은 오묘하게 기계학습과 비슷한 틀을 가지고 있다. 프로그램의 예상치 못한 행동을 포착하기 위해 실행탐험기는 대상 프로그램에 갖가지 입력을 넣어본다. 처음 제안된 실햄탐험기는 완전 마구잡이로 입력을 넣었다. 입력값의 어떤 바이트를 어떤 값으로 무작위 변환을 하는 식이다. 이런 방법은 발전하여 프로그램이 입력값을 실행하였을 때 전체 코드 중에 얼마나 많은 코드가 실행되는 지를 측정하여, 더 많은 코드를 실행시키는 입력을 위주로 실행탐험을 하는 기법이 제안되었다. 더 많은 코드를 실행시키는 입력이 프로그램의 더 많은 부분을 실행시키기에, 프로그램의 행동을 더 많이 관측할 수 있을 것이라는 직관이다.
이런 방식을 커버리지(Coverage) 기반 실행탐험이라고 한다. 기계학습의 틀을 다시 생각해보면 커버리지란 일종의 손실함수이다. 실행탐험에 사용되는 입력이 얼마나 좋은지 평가하기 때문이다. 그러나 커버리지는 손실함수가 되기에는 조금 모자라다. 우리는 손실함수를 미분해 모형함수를 어떻게 좋은 방향으로 조절할 수 있는지 알고 있다 (경사하강법). 하지만 커버리지는 입력값이 얼마나 좋은지만 평가하고 입력값을 어떻게 변경해야 프로그램의 오류를 탐지할 수 있을지 알려주지 않는다. 일단 입력값을 무작위로 변경해보고 커버리지로 평가해 커버리지가 좋아지면 취하고 아니면 버리는 식이다.
현재 활발하게 진행되고 있는 지향성 실행탐험 연구는 이런 커버리지의 한계를 돌파하는 방법을 찾고 있다. 일단 프로그램의 모든 실행경로를 탐험하는 것은 비현실적이라는 것을 받아들이자. 단순한 컴퓨터 프로그램이라도 가능한 모든 입력값을 헤아리면 우주에 존재하는 원자보다 그 수가 많다. 따라서 그럴듯한 입력만을 추리고 결함이 존재할법한 지점을 노려 실행탐험하는 것이 의미가 있다. 이것이 지향성 실행탐험의 기틀이 되는 생각이다.
지금까지의 연구성과는 커버리지보다 더 효과적인 입력 평가방법을 찾아낸 것에 있다. 프로그램의 제어흐름도(Control Flow Graph)와 데이터흐름도(Def-Use Graph)를 이용해 입력값을 평가하는 것이다. 또한 이런 기법을 확장해 프로그램의 조건문을 통과하도록 입력값의 특정 부분을 고정하는 기법들이 제안되고 있다.
실행탐험기가 내놓은 좋은 입력에는 확실한 보상을, 그렇지 않은 입력에는 따끔한 혼을 낼 수 있는 평가방법은 무엇일까? 기계학습의 손실함수에 필적하는 실행탐험 방법은 무엇일까?