PyPy의 샌드박싱 기능

Warning

This describes the old, unmaintained version. A new version sandbox-2py3.6-sandbox-2 브랜치, 그리고 sandboxlib저장소에서 진행 중입니다. 그 설명은 다음에서 확인할 수 있습니다: https://mail.python.org/pipermail/pypy-dev/2019-August/015797.html

또한 python 3.7+는 _thread 모듈을 요구하며, 이는 샌드박스를 벗어나는 데 있어 고려 사항이 될 수 있음에 유의하십시오.

소개

PyPy는 OS 수준 샌드박싱(예: Linux의 SECCOMP)과 유사한 수준의 샌드박싱을 제공하지만, 완전히 이식 가능한 방식으로 구현되어 있습니다. 이를 사용하려면, (일반적인, 신뢰할 수 있는) 프로그램이 PyPy의 특별한 샌드박스 버전인 서브프로세스를 실행합니다. 이 서브프로세스는 신뢰할 수 없는 임의의 Python 코드를 실행할 수 있지만, 모든 입출력은 직접 수행되는 대신 stdin/stdout 파이프로 직렬화됩니다. 외부 프로세스는 파이프를 읽어 어떤 명령이 허용되는지 여부를 결정하거나(샌드박싱), 심지어 다르게 재해석하기도 합니다(가상화). 잠재적인 공격자는 서브프로세스에서 임의의 코드를 실행시킬 수 있지만, 외부 프로세스에 의해 제어되지 않는 어떤 입출력도 실제로는 수행할 수 없습니다. 사용되는 RAM과 CPU 시간의 양을 제한하기 위해 추가적인 장벽이 마련되어 있습니다.

이는 Python 언어 수준에서의 샌드박싱, 즉 공격자가 실행할 수 있는 Python 코드의 종류를 제한하는 것과는 매우 다르다는 점에 유의하십시오(이유는? pysandbox를 읽어보십시오).

비교할 또 다른 지점: 만약 대신 CPython을 특수한 가상화 C 라이브러리에 연결하려 한다면, OS 종속적일 뿐만 아니라 안전하지도 않은 결과를 얻게 될 것입니다. CPython은 세그폴트를 일으킬 수 있기 때문입니다(여러 방식으로, 그것도 모두 정말, 정말 난해한 방식으로). 충분한 노력을 들이면, 공격자는 거의 모든 세그폴트를 취약점으로 바꿀 수 있습니다. PyPy가 생성하는 C 코드는 코드 생성기가 올바른 한 세그폴트가 발생하지 않습니다 - 이는 신뢰해야 할 코드 줄 수가 더 적다는 뜻입니다. 편집증적인 사용자를 위해, 샌드박싱을 적용해 번역(translation)된 PyPy는 일반적으로 디버깅 버전에만 존재하는 체계적인 런타임 검사(예를 들어 버퍼 오버플로우 방지)도 포함합니다.

Warning

PyPy 쪽에서 해야 할 어려운 작업은 이미 끝나 있으므로, 완전히 안전한 버전을 그대로 얻을 수 있습니다. 아직 실험적이고 다듬어지지 않은 부분은 이 샌드박스화된 PyPy를 일반 Python 인터프리터(CPython 또는 샌드박스화되지 않은 PyPy)에서 사용하기 위한 라이브러리입니다. 기여를 환영합니다.

Warning

PyPy2로 테스트되었습니다. PyPy3에서는 별도 수정 없이 바로 동작하지 않을 수 있습니다.

개요

PyPy의 변환 측면 중 하나는 샌드박싱 기능입니다. 이는 “완전 가상화”에서 말하는 “샌드박싱”과 같은 의미이지만, OS 지원이 전혀 없는 일반 C로 구현됩니다. 이는 두 프로세스 모델입니다: PyPy를 특수한 “pypy-c-sandbox” 실행 파일로 번역(translation)할 수 있으며, 이 실행 파일은 어떠한 라이브러리 호출이나 시스템 호출도 수행하지 않는다는 점에서 안전합니다 - 대신, 그런 작업을 수행하려고 할 때마다 작업 이름과 인자들을 표준 출력으로 마샬링하고, 표준 입력으로 마샬링된 결과를 기다립니다. 이 pypy-c-sandbox 프로세스는 이러한 작업 요청에 응답하는 외부 “컨트롤러” 프로그램이 실행하도록 되어 있습니다.

pypy-c-sandbox 프로그램은 변환 과정에서 변환을 하나 추가하여 얻어지는데, 이 변환은 RPython 수준의 모든 외부 함수 호출을 마셜링/대기/언마셜링을 수행하는 스텁으로 바꾸어 놓습니다. 샌드박스를 탈출하려는 공격자는 표준 출력에 쓰거나 표준 입력에서 읽는 것 외에는 외부 함수 호출이 전혀 없는 C 프로그램 안에 갇히게 됩니다. (이론적으로는 여전히 공격 가능합니다. 예를 들어 세그폴트와 유사한 상황을 악용하는 방식으로요. 하지만 서론에서 설명했듯이 PyPy는 이러한 공격에 대해 비교적 안전하다고 생각합니다.)

외부 컨트롤러는 CPython이나 일반 PyPy에서 실행할 수 있는 평범한 Python 프로그램입니다. 이것은 서브프로세스에 그 세계에 대한 임의의 커스텀 뷰를 제공함으로써 원하는 어떤 가상화든 수행할 수 있습니다. 예를 들어, 서브프로세스는 자신이 파일 핸들을 사용하고 있다고 생각하지만, 실제로는 그 숫자들은 컨트롤러 프로세스가 만들어내는 것이므로 실제 OS 수준의 파일 핸들일 필요가 전혀 없습니다(그리고 아마 그래서는 안 될 것입니다). 제가 구현한 데모 컨트롤러에는 단순히 숫자에서 파일과 유사한 객체로의 매핑이 있습니다. 컨트롤러는 요청된 경로를 어떤 가상의, 완전히 커스텀한 디렉터리 계층 구조 내의 파일이나 파일과 유사한 객체로 변환함으로써 “os_open” 연산에 응답합니다. 이 파일과 유사한 객체는 3 이상의 사용되지 않은 임의의 숫자를 키로 하여 매핑에 넣어지고, 그 숫자는 서브프로세스에 반환됩니다. “os_read” 연산은 서브프로세스가 제공한 가짜 파일 핸들을 컨트롤러 내의 파일과 유사한 객체로 다시 매핑한 다음, 그 파일과 유사한 객체로부터 읽어들이는 방식으로 동작합니다.

샌드박싱을 활성화한 상태로 RPython 프로그램을 번역(translation)하면 인덱스 범위 초과 접근에 대한 다양한 C 수준 어서션(assertion)을 활성화하는 특수 플래그도 사용합니다.

그런데 이미 눈치채셨겠지만, 우리가 번역(translation)하는 대상이 PyPy라는 사실과는 실제로 무관합니다. 어떤 RPython 프로그램이든 상관없습니다. 저는 JS 인터프리터에서도 성공적으로 시도해 보았습니다. 컨트롤러가 “pypy_interact”라고만 불리는 이유는 pypy-c-sandbox를 만족시키는 파일 계층 구조를 흉내 내기 때문입니다 - 여기에는 /bin/lib/pypy1.2/lib-python 및 /bin/lib/pypy1.2/lib_pypy 같은 (읽기 전용) 가상 디렉터리가 포함되어 있으며, 실행 파일이 /bin/pypy-c인 것처럼 가장합니다.

사용법

pypy repository의 사본을 받으십시오. pypy/goal 디렉터리에서 다음을 실행합니다::

../../rpython/bin/rpython -O2 --sandbox targetpypystandalone.py

일반 PyPy가 설치되어 있지 않다면 설치하시는 것이 좋습니다. 번역(translation) 속도가 더 빠르기 때문입니다. 하지만 앞에 python을 붙여서 같은 줄을 실행할 수도 있습니다.

이를 실행하려면 pypy/sandbox 디렉터리에 있는 도구를 사용하십시오.:

./pypy_interact.py /some/path/pypy-c-sandbox [args...]

pypy-c와 마찬가지로, 인자를 전달하지 않으면 대화형 프롬프트를 얻게 됩니다. 이론적으로 이 프롬프트에서 나쁜 행위를 하거나 컴퓨터의 임의 파일을 읽는 것은 불가능합니다. 스크립트를 인자로 전달하려면 이를 모든 의존성과 함께 하나의 디렉터리에 넣고, --tmp=DIR 옵션을 사용해 pypy_interact가 이 디렉터리를(읽기 전용으로) 서브프로세스의 가상 /tmp 디렉터리로 내보내도록 요청해야 합니다. 예시:

mkdir myexported
cp script.py myexported/
./pypy_interact.py --tmp=myexported /some/path/pypy-c-sandbox /tmp/script.py

이는 script.py가 임의의 신뢰할 수 없는 소스, 예를 들어 HTTP 서버에서 비롯된 것이더라도 안전하게 수행할 수 있습니다.

사용되는 heap 크기를 제한하려면 pypy_interact.py의 --heapsize=N 옵션을 사용합니다. --timeout=N 옵션을 사용하여 CPU 시간(실제 시간)에 제한을 줄 수도 있습니다.

모든 연산이 지원되는 것은 아닙니다. 예를 들어 os.readlink(‘…’)를 입력하면 컨트롤러가 예외로 인해 크래시되고 하위 프로세스가 종료됩니다. 다른 연산들은 하위 프로세스를 곧바로 “Fatal RPython error”로 죽게 만듭니다. 이 중 어느 것도 보안 취약점은 아닙니다. 더 중요하게는, 대부분의 다른 내장 모듈은 활성화되어 있지 않습니다. 이에 대해 불평하기 전에 이 페이지의 모든 경고를 읽어주시기 바랍니다. 기여는 환영합니다.