A Static Analysis to Find Null Pointer Bugs with Property Simulation, Kwang Yul Seo

1학기 과제 연구의 결과로 나온 논문입니다. 자바 프로그램에서 컴파일 타임에 널 포인터 버그를 잡는 걸 주제로 하고 있습니다. Property Simulation이란 기법을 이용하였습니다. 방학 때 구현을 목표로 잡았으나 아르바이트에 발목을 잡혀서 논문인 채로 두게 되었네요.

Cyclone

Posted 2006. 8. 31. 04:01
예전에 마소에 실었던 글을 조금 수정하였습니다.

C 거의 모든 소프트웨어의 인프라스트럭처가 되고 있는 강력한 언어이지만, 타입 안전성을 고려하지 않은 언어 디자인으로 각종 버그와 보안 사고의 원인이 되고 있다. 그 이후에 출연한 여러 고급 언어는 이런 타입 안전성 문제를 해결하였지만, 기존의 C 언어를 대체하는데 실패하였다. 싸이클론(Cyclone) 프로젝트의 목표는 타입 안전성을 제공하면서도, C 언어처럼 각종 인프라스트럭처 소프트웨어 작성에 사용될 수 있을 만큼의 자유도를 주는 언어를 만드는데 있다. 싸이클론 언어의 특성에 대해 살펴보자.


C 언어는 강력한 프로그래밍 언어이다. 운영체제 커널과 디바이스 드라이버, 파일 시스템, 웹 서버 등 오늘날 소프트웨어의 대부분은 C 언어로 작성되어 있다. 자바나 C#과 같은 고급 언어도 런타임과 컴파일러를 작성하는 데는 결국 C 언어가 사용된다. 인터넷의 혁명을 가능하게 만든 각종 네트워크 장비, 통신 설비 등에 들어가는 시스템 소프트웨어도 C 언어로 작성되어 있다.

하지만 C 언어는 강력한 동시에 위험하다. 뉴스를 오르내리는 각종 보안사고와 웜의 직접적인 원인은 잘못 작성된 소프트웨어에 있다. 하지만 기술적인 취약점의 대부분을 차지하는, 버퍼 오버플로우, 포맷 문자열 버그, 힙 오버플로우 등의 문제점은 결국 이 소프트웨어를 작성하는데 사용된 C 언어가 타입 안전성을 제공하지 않기 때문이다. 오늘날 소프트웨어의 작동이 멈추면 얼마나 끔찍한 일이 벌어질지 상상해보면, 그런 중요한 인프라스트럭처가 이처럼 취약한 C 언어로 만들어졌다는 사실이 놀라울 뿐이다.

그렇다면 대안은 무엇일까? 가장 쉽게 생각할 수 있는 방법은 타입 안전성을 제공하는 자바나 C#과 같은 언어로 기존의 소프트웨어를 새로 작성하는 것이다. 실제로 전통적으로 C 언어로 작성되던 일부 응용 소프트웨어는 이 방법으로 큰 성공을 거두었다. 실제로 고급 언어일수록 높은 생상성과 품질, 단축된 프로젝트 시간을 가져다줄 확률이 크기 때문이다.

하지만 정작 가장 중요한 시스템 소프트웨어는 어떨까? 자바로 작성된 방화벽, 스위치, 라우터는 어떤가? 반응 시간이 치명적으로 중요한 리얼 타임 시스템은 또 어떨까? 자바가 큰 성공을 거두긴 하였지만, 어디까지나 웹 어플리케이션과 임베디드용 응용 프로그램 등 특정 분야에 한정되어 있다. 아직도 저수준 제어와 직접적인 메모리 제어가 필요한 대부분의 중요 시스템은 C 언어로 작성된다.

싸이클론(Cyclone)는 이런 문제를 해결하고자 만들어진 언어이다. 싸이클론의 목표는 디바이스 드라이버나 커널을 만들 수 있을 만큼 강력한 코딩 파워를 가지면서도, 컴파일 타임에 많은 오류를 잡고 런타임에 발생하는 문제를 즉각 처리할 수 있는 자바 수준의 안전성을 가지는 언어를 만드는 것이다. 물론 C 언어로 작성된 기존 코드 베이스를 무시할 수 없으므로, C 언어로 작성된 프로그램을 최소한의 노력으로 싸이클론으로 포팅할 수 있게 한다. 따라서 싸이클론을 안전한 C 언어라고 부르기도 한다.

실제로 싸이클론 코드는 C 언어와 매우 유사하다. 싸이클론은 ANSI-C 표준에서 출발하여 임의의 타입으로의 캐스팅(casting) 같은 안전성을 해치는 기능을 버리고, 각종 타입 메커니즘을 추가한 언어이다. 싸이클론의 컴파일러는 프로그램 분석 방법을 이용해서 기존 C 언어 사용 방법 중에서 안전한 것들은 자동으로 싸이클론 타입을 유추해낸다. 최상의 경우 C 언어 코드를 그대로 컴파일할 수도 있다. 대부분의 경우 기존 C 코드에 약간의 어노테이션(일종의 프로그램 주석)을 붙여주는 정도만 해주면 된다.

C의 문제점과 싸이클론의 해법

C 언어에서 발생하는 가장 흔한 오류의 예로 버퍼 오버플로우가 있다. 배열의 크기보다 더 큰 인덱스로 배열을 읽으면, 자바에서는 ArrayOutOfBoundException이 나지만, C는 이에 대한 검사가 없기 때문에 그 주소 위치에 있는 임의의 메모리를 읽어오게 된다. 마찬가지 방법으로 배열을 끝을 넘어서는 메모리에 임의의 값을 쓸 수도 있는데, 그 값이 스택상의 리턴 주소일 경우 임의의 코드로 점프시킬 수도 있다. 이것이 C 언어에 대한 가장 흔한 공격 방법 중 하나인 버퍼 오버플로우 공격이다.

반대로 자바는 버퍼 오버플로우 공격이 불가능하다. 배열마다 배열의 길이를 저장하고 있고, 배열의 원소에 접근하기 전에 인덱스가 배열의 크기보다 작은지 런타임에 항상 검사를 수행하기 때문이다. 이런 검사를 통해 인덱스가 잘못 되었으면 ArrayOutOfBoundException을 던지는 것이다. 물론 C 언어에도 이와 유사한 방법을 사용할 수 있다. 하지만 C 언어는 배열뿐만 아니라 포인터와 포인터 연산이 존재하기 때문에 문제를 복잡해진다.

포인터 x는 배열의 처음, 중간, 끝 어느 위치든 가리킬 수 있다. x++이나 x--를 사용하면 메모리의 위치를 바꿀 수도 있다. 즉 현재 x가 포인터의 끝을 가리키고 있는데, x++을 통해 포인터를 증가시킨 후 참조하면, 배열이 아닌 임의의 메모리를 읽게 된다. 싸이클론은 이 문제를 해결하기 위해 뚱뚱한 포인터(fat pointer)라는 개념을 도입하였다. 다음 그림을 살펴보자.





뚱뚱한 포인터는 일반 포인터와 달리 배열의 처음과 끝, 현재를 모두 가리킨다. q++을 통해 포인터의 값을 증가시키면 현재 포인터(c)만 다음으로 변하고 처음과 끝을 그대로다. 따라서 배열의 경계를 넘어가는 포인터 연산의 경우 오류를 발생시킬 수 있게 된다. 물론 이 방식은 큰 런타임 오버헤드가 있다. 특히 루프처럼 반복적으로 배열 값을 읽는 경우 반복된 체크의 오버헤드는 프로그램 수행 속도를 크게 떨어뜨린다. 싸이클론은 기존 포인터와 같은 날씬한 포인터(thin pointer) 또한 제공한다.

이와 유사한 문제는 NULL 값과 관련해서도 발생한다. 주로 NULL 포인터를 참조해서 발생하는 공포의 메모리 오류(segmentation fault)는 C 언어 개발자라면 피해갈 수 없는 숙명이다. 다음과 같은 간단한 파일 IO 프로그램을 살펴보자.

NULL 검사가 없는 파일 IO

 
int foo(char ?filename, char ?buf) {
  FILE *fp;
  fp = fopen(filename, "r");
  fread(fp, buf);
}


이 간단한 프로그램은 파일 열기에 실패했을 경우 프로그램 크래시(crash)가 발생한다. NULL 검사 없이 fread를 호출하였기 때문이다. 싸이클론 포인터는 int *와 int *@notnull로 나뉜다. int *@notnull은 줄여서 int @라고 쓰기도 하는데, 이 포인터는 절대 NULL이 될 수 없다는 뜻이다. NULL을 넘겼을 때 오류가 발생하는 fread와 같은 함수는 인자를 @로 선언해서 프로그래머가 반드시 NULL 검사를 하도록 만들 수 있다.

싸이클론 버전

struct FILE;
extern FILE *fopen(char ?name, char ?mode);
extern int putc(char, FILE@);
void foo() {
  FILE *f = fopen("/tmp/bar.txt", "+wb");
  char s[] = "hello";
  int i;
  for (i = 0; i < 5; i++) { putc(s[i], (FILE @)f); }


위 프로그램에서 putc 함수는 두 번째 인자로 FILE *@notnull을 받으므로, FILE은 NULL이 될 수 없다. foo()의 FILE *f는 FILE * 타입이므로 FILE @와 달라서 컴파일 타임에 타입 오류가 발생한다. (File @)f 는 FILE *를 FILE @로 캐스팅해 줄 뿐만 아니라 런타임 NULL 검사도 추가해준다. 따라서 파일이 없다고 프로그램이 크래시 되는 대신에 자바처럼 예외가 발생하게 되는 것이다.

정리

지금까지 싸이클론의 기능 한 가지를 살펴보았다. 싸이클론은 이보다 훨씬 방대한 언어이다. 싸이클론에서 메모리 할당은 리전 기반 타입 시스템(region based type system)을 사용해서 힙과 스택 영역 등의 포인터를 서로 다른 타입으로 구분한다. 또 각종 현대 타입 시스템 이론을 적절히 활용하여 C 언어의 부족함을 메워주고 있다.

필요는 발명의 어머니라는 말이 있다. C 언어의 부족함과 새로운 언어에 대한 필요성은 앞으로도 여러 가지 언어를 낳을 것이다. 싸이클론은 그런 언어의 하나로 아직 널리 사용되고 있지는 않다. 하지만 저수준 프로그래밍의 고충을 경험한 개발자라면 중요 시스템의 일부 영역만이라도 싸이클론의 적용을 고려해보기 바란다. 추가 정보는 최근에 통합된 싸이클론 홈페이지에서 얻길 바란다.
윈도우즈에 까는 프로그램 10가지입니다.

1. GOM Player
요즘 컴퓨터를 켜서 하는 일의 대부분이 영화, 드라마 감상인지라 윈도를 새로 깔면 자연스럽게 GOM Plyaer를 먼저 깔게 되네요. 예전에는 최의종 군이 만든 사사미(Sasami)도 즐겨썼던 거 같은데 어느 순간부터 업데이트 안 되고 어느 틈에 Gom Player를 쓰고 있더군요. 따로 코덱을 안 구해다 깔아도 되서 자연스럽게 사용하게 된 것 같습니다.

2. 빵집
다른 분들은 7-ZIP이라는 프로그램을 많이 쓰시던데 저는 처음 들어봅니다. 이해할 수 없는 알집의 alz 포맷과 윈도우의 리소스를 다 잡아 먹는듯한 알집 자동 풀기(exe)에 화가 나서 빵집으로 바꾸었습니다.

3. JDK
자바 환경에서 작업을 많이 하다보니 역시 JDK를 빼놓을 수가 없습니다. JDK6를 받아서 사용해보다가 요즘은 다시 안정적인 JDK5로 돌아왔습니다.

4. MS Office
윈도 깔아서 하는 일이라는 게 결국 리포트 쓰고, 피피티 만드는 일이 많은지라 역시 오피스도 필수 아이템입니다.

5. Acrobat Reader
PDF 포맷을 읽지 못하면 컴퓨터가 반쯤 바보가 되기 때문에 아크로밧도 우선 순위에 올라있습니다.

6. VIM
제 버릇 개못준다고 워낙 VIM 에디터에 손이 익어서 윈도우즈에서 윈도우용 VIM을 쓸 수 밖에 없습니다. 설치하고 나서 파일에 대고 오른쪽 버튼 클릭하면 빔으로 편집하기가 나오기 때문에 애용하고 있습니다.

7. Cygwin
윈도 개발도 거의 Cygwin에서 유닉스 환경으로 하기 때문에 Cygwin도 필수 항목입니다.

8. V3와 스파이제로
실시간 감시하면 컴퓨터 느려지기 때문에 최대한 이용을 피했는데, 윈도 설치하면서 각종 웜과 바이러스에 걸리는 현 상황에서는 안 깔수가 없더군요. 특히 스파이제로가 없으면 언제 걸렸는지도 모르는 이상한 스파이웨어가 익스플로러에 기생해 살고 있더군요.

9. 노턴 고스트
성격이 부주의한 편인지 하드를 날려 먹는 일이 많아서 이제 반드시 노턴 고스트를 깔고 주기적으로 하드 이미지를 백업 받고 있습니다. 윈도 새로 까려면 하루 반나절을 날려 먹기 때문에 이 방법이 좋더군요.

10. 네이트온
사실 네이트 온에 친구들이 별로 없기 때문에 필수적인 프로그램은 아니지만, SK에서 제공하는 공짜 문자 100건을 사용하기 위해 깔아둡니다. 핸드폰으로 문자 보내는 속도가 느려서 보통 네이트온으로 문자 보내는 경우가 많습니다.
« PREV : 1 : ··· : 76 : 77 : 78 : 79 : 80 : 81 : 82 : NEXT »