경계 검사
경계 검사(bounds checking)는 변수가 사용되기 전에 어떤 경계 내</i>에 위치하는지를 검사하는 기법으로, 주로 숫자 데이터가 특정 범위 내에 존재하는지 여부나 (범위 검사), 배열 인덱스가 배열의 경계 안에 있는지 여부를 검사하는데 사용된다 (인덱스 검사). 경계 검사에서 실패한 경우 (가령, 접근할 배열 인덱스가 배열 경계 바깥에 있는 경우)는 보통 프로그램 예외로 처리된다.
경계 검사는 주로 컴퓨터 보안과 관련하여 메모리 취약점 발생을 근본적으로 방지하는 목적으로 사용되지만, 성능 측면에서 상당한 오버헤드를 발생시켜 통상 모든 경우에 항상 수행되지는 않는다. 이와 관련한 컴파일러 최적화 기법 가운데 하나는 불필요한 경계 검사 코드를 제거하는 것을 목표로 하기도 한다. (Bound-checking elimination)
범위 검사는 어떤 숫자가 특정 범위(Range) 내에 존재하는지 여부를 검사하는 것이다. 범위 검사의 대표적인 예는 다음과 같다.
(어떤 숫자를 16비트 정수형 변수에 대입하는 상황에서) 숫자가 "16비트 정수형으로 표현될 수 있는 범위" 내에 있는지에 대한 검사 (월을 표현하는 변수의 경우) 값이 1부터 12 사이에 있는지에 대한 검사
범위 검사는 데이터 타입 검사와는 다른 종류의 검사이다.
인덱스 검사는 접근할 배열의 인덱스가 (배열 정의 시점에 결정된) 배열의 경계 안에 존재하는지 여부를 검사하는 것으로, 통상적인 경계 검사</i>라고 함은 이러한 인덱스 검사를 이르는 말이다. 인덱스 검사 실패는 통상적으로 에러로 인한 프로그램 종료로 이어진다. 많은 고급 프로그래밍 언어들이 인덱스 검사 기능을 내장하고 있는데, 이는 배열 경계 바깥에 데이터 읽기/쓰기가 허용되면 프로그램 오동작, 충돌, 심지어 (버퍼 오버플로와 같은) 보안 취약점 등이 발생할 수 있기 때문이다.
파스칼, 포트란, 자바와 같은 고급 프로그래밍 언어들은 인덱스 검사를 내장하고 있다. VAX 컴퓨터는 INDEX라는 배열 인덱스 검사를 위한 어셈블리 명령어가 존재하는데, 이 명령어는 총 6개의 피연산자를 받아들이며, 각각의 피연산자는 VAX에서 지원하는 모든 어드레싱 모드를 사용할 수 있다. B6500 및 이와 유사한 Burroughs 사의 컴퓨터는 작성된 프로그래밍 언어와 무관하게 하드웨어 차원에서 경계 검사를 수행하였다. 이후 세대의 CPU 모델 중에선 소수만이 경계 검사를 위한 특수 명령어를 도입하였는데, CHK2 명령어가 있는 모토로라 68000 계열 CPU가 그 중 하나이다.
한편 (C를 포함한) 많은 프로그래밍 언어는 빠른 실행 속도를 위해 경계 검사를 자동으로 수행하지 않으며, 이 점으로 인해 대다수의 Off-by-one 에러나 버퍼 오버플로 취약점 등이 발견되지 않은 채 방치되기도 한다. 대다수의 프로그래머들은 이러한 언어들이 속도를 위해 지나치게 많은 것을 희생하고 있다고 생각한다고 한다.[1] 영국의 컴퓨터 과학자 C. A. R. Hoare는 1980년 튜링상 강연에서 과거 ALGOL 60 프로그래밍 언어를 디자인하던 시절의 경험에 대해 다음과 같이 술회하였다. (ALGOL 60은 자동 경계 검사 기능을 내장한 언어이다.)
이러한 원칙의 결과는 바로, 모든 첨자를 갖는 변수의 모든 첨자 (예: 배열의 인덱스)는 실행 시간에 항상, 미리 선언된 배열의 상/하한 사이에 있는지 체크된다는 것이었다. 몇년 후에 우리는 고객들에게 상용 서비스 중인 프로그램의 효율적인 동작을 위해 이런 경계 검사 기능을 켜고 끌 수 있는 옵션을 포함시키면 어떤지에 대해 의견을 물었는데, 고객들 모두 만장일치로 그런 기능이 있으면 안된다</i>고 주장했다. 그들 모두 상용 서비스 중에 첨자 오류 (경계 바깥을 접근하는 오류)가 얼마나 자주 발생하는지, 또 그것들이 발견되지 않을 때 얼마나 파괴적일 수 있는지 알고 있었던 것이다. 심지어 1980년씩이나 되어서 충격과 공포로 내가 깨달은 점이 있다면, 많은 프로그래밍 언어 디자이너와 사용자들이 아직도 이 점에서 크게 교훈을 얻지 못했다는 것이다. 어떤 멀쩡한 공학 부분이든 간에, 이런 기본적인 주의 사항을 간과하는 것 자체가 오래 전부터 불법 수준</i>으로 간주될 수도 있는 일이었다.
주류 프로그래밍 언어 가운데 실행 시간 (런타임) 경계 검사 기능을 지원하는 언어로는 Ada, C#, 하스켈, Java, 자바스크립트, 리스프, PHP, 파이썬, Ruby, 비주얼 베이직 등이 있다. D 언어와 OCaml은 컴파일러 옵션을 통해 실행 시간 경계 검사 기능을 켜고 끌 수 있다. C# 역시 일정 부분의 코드에 대해서는 일시적으로 경계 검사를 수행하지 않도록 하는 기능을 지원한다 (unsafe 구역). 이런 기능들은 전체 프로그램의 안전성을 훼손시키지 않으면서도 일부 실행 시간 병목을 해소시킬 수 있어 성능 개선에 유용하게 사용될 수 있다.
소프트웨어적인 경계 검사는 프로그램의 안전성을 높여주는 대신 추가 CPU 시간을 소모시키지만, 하드웨어적인 경계 검사는 이론적으로는 실행 시간 손실 없이 "공짜로" 안전성의 이점만을 취할 수 있다. 1974년 초창기 컴퓨터 시스템 중 하나인 ICL 2900 시리즈 시스템이 하드웨어 경계 검사를 차용한 이래로,[2] 일러도 2005년에 들어 x86 시스템의 내장 메모리 관리 유닛 (MMU)을 배열 및 버퍼 접근 시에 안전성을 확보하는 데 사용할 방법에 대한 연구가 시작되었다.[3] 2015년에는 Intel 사에서 배열 경계를 CPU 레지스터와 메모리 상에 테이블로 저장해두는 신형 Skylake 프로세서 아키텍처 확장을 발표하고 (Intel MPX), 2017년 GCC 컴파일러에서 Intel MPX 확장을 지원하기 시작했다. 하지만 이후 다수의 연구에 의해 기존 소프트웨어적 기술들과 비교해 성능상/기능상 이점을 갖추지 못한 것으로 평가받으며[4][5][6] 실세계 적용이 둔화되거나 위축되는 추세이다.[7]
동적 프로그램 분석 정적 프로그램 분석
“”, IEEE Transactions On Computers, Volume C-22, Number 7, July, 1973. “”, The 1980 ACM Turing Award Lecture, CACM volume 24 number 2, February 1981, pp 75–83. “Bcc: Runtime checking for C programs”, S. C. Kendall, Proceedings of the USENIX Summer 1983 Conference. “”, Richard Jones and Paul Kelly, Imperial College, July 1995. “”, Unisys, April 2006. “”, John Criswell, Andrew Lenharth, Dinakar Dhurjati, Vikram Adve, SOSP'07 21st ACM Symposium on Operating Systems Principles, 2007. “”, Yutaka Oiwa. Implementation of the Memory-safe Full ANSI-C Compiler. ACM SIGPLAN Conference on Programing Language Design and Implementations (PLDI2009), June 2009. “”, Timur Iskhodzhanov, Alexander Potapenko, Alexey Samsonov, Kostya Serebryany, Evgeniy Stepanov, Dmitriy Vyukov, LLVM Dev Meeting, November 18, 2011. Safe C Library of Bounded APIs
분류:컴퓨터 오류 분류:배열