Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 330 – Python 바이트코드 검증

Author:
Michel Pelletier <michel at users.sourceforge.net>
Status:
Rejected
Type:
Standards Track
Created:
17-Jun-2004
Python-Version:
2.6
Post-History:


Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

Python 가상 머신(PVM) 바이트코드가 “올바른 형식”이 아니면, 값 스택의 언더플로/오버플로나 PVM 프로그램 공간의 임의 영역에 대한 읽기/쓰기 같은 다양한 오류를 일으켜 PVM을 충돌시키거나 악용할 수 있습니다. 이런 종류의 오류는 대부분 실행 전에 PVM 바이트코드가 몇 가지 간단한 제약 조건을 위반하지 않는지 검증하여 없앨 수 있습니다.

이 PEP는 Python 가상 머신(PVM) 바이트코드의 형식과 구조에 대한 일련의 제약 조건을 제안하고, 이 검증 과정의 Python 구현을 제공합니다.

결정

Guido는 검증 도구가 어느 정도 가치가 있다고 생각합니다. 누군가 이 도구를 Tools/scripts에 추가하고 싶다면 PEP는 필요하지 않습니다.

이런 도구는 “bytecodehacks”의 출력이나 PYC 파일을 직접 편집한 결과를 검증하는 데 쓸모가 있을 수 있습니다. 완벽하게 유효한 바이트코드도 여전히 끔찍한 일을 할 수 있으므로 보안 수단으로서의 가치는 다소 제한적입니다. 제한된 실행(restricted execution)이라는 개념이 성공적으로 되살아난다면 이 상황은 달라질 수 있습니다.

동기

Python 가상 머신은 Python 언어에서 바이트코드 표현으로 컴파일된 Python 프로그램을 실행합니다. PVM은 실행되는 모든 바이트코드가 여러 암묵적 제약 조건에 비추어 “올바른 형식”이라고 가정합니다. 이 제약 조건 중 일부는 실행 시간에 검사되지만, 대부분은 검사에 따르는 오버헤드 때문에 검사되지 않습니다.

디버그 모드로 실행될 때 PVM은 특정 바이트코드가 이러한 제약 조건을 위반하지 못하도록 여러 가지 실행 시간 검사를 수행하며, 이 제약 조건은 바이트코드가 인터프리터를 크래시시키거나 악용하는 것을 어느 정도 막아 줍니다. 이런 검사는 인터프리터에 측정 가능한 수준의 오버헤드를 더하므로 일반적인 사용에서는 보통 꺼져 있습니다.

올바른 형식이 아닌 바이트코드를 디버그 모드가 아닌 PVM에서 실행하면 치명적이거나 치명적이지 않은 다양한 오류가 발생할 수 있습니다. 보통 잘못된 형식의 코드는 PVM에서 세그멘테이션 폴트를 일으키고, 그러면 OS가 인터프리터를 즉시 갑작스럽게 종료합니다.

형식이 잘못된 바이트코드가 인터프리터를 악용하여 Python 바이트코드가 임의의 C 수준 기계 명령어를 실행하거나 인터프리터의 비공개 내부 자료 구조를 수정하게 할 수도 있습니다. 이를 교묘하게 사용하면 애플리케이션이 자신의 객체에 적용하려는 어떤 형태의 보안 정책도 무력화할 수 있습니다.

현실적으로 악의적인 사용자가 악용을 목적으로 PVM에 유효하지 않은 바이트코드를 “주입”하기는 어렵겠지만 불가능하지는 않습니다. 버퍼 오버플로와 메모리 덮어쓰기 공격은 널리 알려져 있으며, 특히 익스플로잇 페이로드가 네트워크를 통해 암호화되지 않은 채 전송되거나 파일 또는 네트워크 보안 권한의 약점이 추가 공격의 발판으로 쓰일 때 그렇습니다.

이상적으로는, 바이트코드가 악의적으로 만들어졌든 아니든 어떤 바이트코드도 기반 C 수준 자료 구조를 읽거나 써서 PVM의 동작을 무력화하도록 허용되어서는 안 됩니다. 간단한 실행 전 검증 단계를 거치면 바이트코드가 실행 시간에 값 스택을 오버플로/언더플로시키거나 PVM 프로그램 공간의 다른 민감한 영역에 접근하지 못하도록 보장할 수 있습니다.

이 PEP는 Python 바이트코드가 명령어와 그 피연산자에 대한 정적 제약 조건과 구조적 제약 조건을 준수하도록, PVM이 실행하기 전에 Python 바이트코드에 대해 수행해야 할 몇 가지 검증 단계를 제안합니다. 이 단계들은 간단하며, 크래시를 일으킬 수 있는 유효하지 않은 바이트코드의 상당 부분을 잡아냅니다. 검증 과정을 통해 일부 실행 시간 검사를 미리 없앨 수 있을 가능성도 있습니다.

물론 “완전”과 “안전”을 어떻게 정의하든 바이트코드가 “완전히 안전”하다고 검증할 방법은 없습니다. 바이트코드 검증을 하더라도 Python 프로그램은 다양한 이유로 세그폴트를 일으킬 수 있고 앞으로도 그럴 가능성이 매우 높으며, 치명적이든 아니든 여러 종류의 런타임 오류를 계속 일으킬 것입니다. 여기에서 제안하는 검증 단계는 바이트코드 수준에서 치명적이고 미묘한 오류를 대거 일으킬 수 있는, 손쉽게 막을 수 있는 구멍 하나를 막을 뿐입니다.

현재 Java 가상 머신(JVM)은 여기서 제안하는 것과 매우 비슷한 방식으로 Java 바이트코드를 검증합니다. 따라서 아래에서 설명하는 제약 조건 중 일부는 JVM 명세 버전 2 [1]의 4.8절과 4.9절을 바탕으로 삼았습니다. 모든 Python 바이트코드 검증 구현은 최소한 이 제약 조건들을 강제해야 하지만, 이것에만 국한되지는 않을 수 있습니다.

바이트코드 명령어에 대한 정적 제약 조건

  1. 바이트코드 문자열은 비어 있으면 안 됩니다. (len(co_code) > 0).
  2. 바이트코드 문자열은 최대 크기를 초과할 수 없습니다 (len(co_code) < sizeof(unsigned char) - 1).
  3. 바이트코드 문자열의 첫 번째 명령어는 인덱스 0에서 시작합니다.
  4. 바이트코드 문자열에는 올바른 개수의 피연산자를 가진 유효한 바이트코드만 들어갈 수 있습니다.

바이트코드 명령어 피연산자에 대한 정적 제약 조건

  1. 점프 명령어의 대상은 코드 경계 안에 있어야 하며, 반드시 명령어 위치에 놓여야 하고 명령어와 그 피연산자 사이에 놓여서는 절대 안 됩니다.
  2. LOAD_* 명령어의 피연산자는 그에 대응하는 자료 구조에 대한 유효한 인덱스여야 합니다.
  3. STORE_* 명령어의 피연산자는 그에 대응하는 자료 구조에 대한 유효한 인덱스여야 합니다.

바이트코드 명령어 사이의 구조적 제약 조건

  1. 각 명령어는 그 명령어의 호출로 이어지는 실행 경로와 관계없이, 값 스택에 적절한 개수의 인자가 있을 때만 실행되어야 합니다.
  2. 어떤 명령어가 여러 다른 실행 경로를 따라 실행될 수 있다면, 어느 경로를 거쳤든 그 명령어가 실행되기 전의 값 스택 깊이는 같아야 합니다.
  3. 실행 중 어느 시점에서도 값 스택이 co_stacksize가 나타내는 깊이보다 깊어질 수 없습니다.
  4. 실행이 co_code의 끝을 벗어나는 일은 절대 없어야 합니다.

구현

이 PEP는 Python으로 작성된 Python 바이트코드 검증 구현을 위한 작업 문서입니다. 이 구현은 PVM이 바이트코드를 실행하기 전에 암묵적으로 사용하지 않으며, 유효하지 않은 바이트코드일 가능성을 우려하는 사용자가 다음 코드 조각처럼 명시적으로 사용해야 합니다:

import verify
verify.verify(object)

verify 모듈은 dis.dis와 같은 종류의 인자, 즉 클래스, 메서드, 함수, 코드 객체를 받는 verify 함수를 제공합니다. 이 함수는 객체의 바이트코드가 이 PEP의 명세에 따라 올바른 형식인지 검증합니다.

코드가 올바른 형식이면 verify 호출은 오류 없이 조용히 반환됩니다. 오류가 발견되면 실패 원인을 나타내는 인자와 함께 VerificationError를 발생시킵니다. 이 오류를 어떤 방식으로든 처리할지, 아니면 개의치 않고 유효하지 않은 코드를 실행할지는 프로그래머가 결정합니다.

Phillip Eby는 참조 구현에서 사용하는 바이트코드 스택 깊이 검증을 위한 의사 코드 알고리즘을 제안했습니다.

검증 관련 문제

이 PEP는 적은 수의 검증만 설명합니다. 논의와 분석을 거치면 훨씬 더 많은 검증이 나오겠지만, 앞으로 추가 검증이나 프로젝트별 사용자 정의 검증이 필요해질 가능성이 매우 높습니다. 그러므로 앞으로 추가될 검증기를 등록할 수 있도록 테스트 구현에 검증 등록 인터페이스를 추가하는 것이 바람직할 수 있습니다. 사용자 정의 검증기는 현재 구현을 서브클래스로 만들어 확장하는 방식으로 동작을 추가할 수 있으므로, 이러한 인터페이스의 필요성은 크지 않습니다.

필요한 변경 사항

Armin Rigo는 스택 효과를 정적으로 분석하려면 몇몇 바이트코드를 수정해야 한다고 지적했습니다. 해당 바이트코드는 END_FINALLY, POP_BLOCK, MAKE_CLOSURE입니다. Armin과 Guido는 이 명령어들을 어떻게 바로잡을지 이미 합의했습니다. 현재 Python 구현은 이 명령어들을 처리하지 않고 넘어갑니다.

이 PEP는 인터프리터에 검증 단계를 추가하자는 것이 아니라, 선택적으로 사용할 수 있도록 표준 라이브러리에 Python 구현을 제공하는 것만을 제안합니다. 이 검증 절차를 C로 옮길지, PVM에 포함할지, 어떤 방식으로든 강제할지는 향후 논의 과제로 남겨 둡니다.

참고 문헌