
Secure code execution
CodeJail은 보안 샌드박스에서 신뢰할 수 없는 코드의 실행을 관리합니다. 주로 Python 실행을 위해 설계되었지만 다른 언어에도 사용할 수 있습니다.
보안은 AppArmor로 강제됩니다. 운영 체제가 AppArmor를 지원하지 않거나, AppArmor 프로필이 올바르게 정의 및 구성되지 않은 경우 CodeJail은 실행을 보호하지 않습니다.
CodeJail은 구성 가능하도록 설계되었으며, 제대로 설치하면 Python 실행을 위해 자동으로 구성됩니다.
CodeJail 샌드박스는 여러 부분으로 구성됩니다:
#) 샌드박스 환경. Python 설정의 경우 이는 virtualenv 형태의 Python과 관련 핵심 패키지입니다. 이 문서 전체에서 ****로 표기하며, 읽기 전용이며 샌드박스 인스턴스 간에 공유됩니다.
샌드박스된 코드는 AppArmor 프로필이 허용하는 범위 내에서 OS 라이브러리에 접근할 수도 있습니다.
#) 샌드박스 실행 디렉터리. /tmp/codejail-XXXXXXXX 형태의 임시 읽기 전용
디렉터리로, 제출된 코드(./jailed_code)와 선택적 추가 파일,
그리고 제출된 코드가 임시 공간으로 사용할 수 있는 쓰기 가능한 임시
디렉터리(./tmp)를 포함합니다.
제출된 코드는 일반적으로 서버에서 테스트할 학생이 제출한 코드이며,
추가 파일은 일반적으로 채점 또는 유틸리티 라이브러리가 포함된
python_lib.zip입니다.
실행하려면 CodeJail에 두 개의 사용자 계정이 필요합니다. 하나는
코드가 실행되는 기본 계정으로, 샌드박스를 만들 수 있는 권한이 있습니다.
이 계정을 **<SANDBOX_CALLER>**라고 합니다. 두 번째 계정은
샌드박스가 실행되는 계정으로, 일반적으로
sandbox 계정입니다.
이 라이브러리는 현재 다음 버전에서 작동하도록 테스트되었습니다
Python:
Ubuntu:
(샌드박스 내부에서 사용되는 Python 버전은 라이브러리 자체에 사용되는 버전과 다를 수 있습니다.)
다음 지침은 CodeJail이 Python 코드를 안전하게 실행할 수 있도록
운영 체제를 구성하는 방법을 자세히 설명합니다. 그러나
codejail.safe_exec.ALWAYS_BE_UNSAFE = True를 설정하고 제출된 Python을
보안 없이 머신에서 직접 실행하는 것도 가능합니다. 이는 보안에 신경
쓰지 않는 개발자 머신에서는 괜찮을 수 있으며, CodeJail API와의
통합을 테스트할 수 있게 해줍니다. 그러나 신뢰할 수 없는 출처의 입력이
들어오는 경우에는 사용해서는 안 됩니다. 프로덕션 시스템에서는 이 옵션을 사용하지 마십시오.
Python 실행을 보호하려면 새 virtualenv를 만들어야 합니다. 즉, 프로젝트용 기본 virtualenv와 샌드박스된 Python 코드용 새 virtualenv, 두 개를 갖게 됩니다.
새 virtualenv의 위치를 정하고 ****라고 부릅니다. 기존
virtualenv 바로 옆에 -sandbox를 붙여서 배치하면 자동으로 감지되어
사용됩니다. 기존 virtualenv가 /home/chris/ve/myproj에 있다면
****를 /home/chris/ve/myproj-sandbox로 만드십시오.
LMS를 실행하는 사용자가 **<SANDBOX_CALLER>**입니다. 예를 들어
개발 머신의 사용자나 서버의 www-data입니다.
구성에 따라 달라지는 기타 세부 사항은 다음과 같습니다:
제한할 별도의 Python 실행 파일이 있도록 --copies를 사용하여 새 virtualenv를 만듭니다::
$ sudo python3.12 -m venv --copies
기본적으로 virtualenv는 시스템 Python에 심볼릭 링크만 걸며, 일부 운영 체제의 apparmor 기본 구성은 해당 파일에 대한 격리 적용을 막을 수 있습니다.
(선택 사항) 샌드박스된 코드에 특정 패키지를 사용할 수 있게 하려면 샌드박스 virtual env를 활성화하고 pip를 사용하여 설치합니다::
$ /bin/pip install -r requirements/sandbox.txt
샌드박스 사용자를 추가합니다::
$ sudo addgroup sandbox $ sudo adduser --disabled-login sandbox --ingroup sandbox
웹 서버가 샌드박스된 Python을 sandbox 사용자로 실행하도록 허용합니다.
/etc/sudoers.d/01-sandbox 파일을 만듭니다::
$ sudo visudo -f /etc/sudoers.d/01-sandbox
<SANDBOX_CALLER> ALL=(sandbox) SETENV:NOPASSWD:/bin/python <SANDBOX_CALLER> ALL=(sandbox) SETENV:NOPASSWD:/usr/bin/find <SANDBOX_CALLER> ALL=(ALL) NOPASSWD:/usr/bin/pkill
(find 바이너리는 임의의 코드를 실행할 수 있으므로 이 sudoers 파일은 codejail 외의 용도로는 안전하지 않습니다.)
AppArmor 프로필을 편집합니다. 이는 샌드박스된 Python 실행 파일에
대한 제한을 지정하는 텍스트 파일입니다. 파일은 /etc/apparmor.d에 있어야
하며 실행 파일 이름을 기준으로 슬래시를 점으로 바꾼 이름으로 지정해야
합니다. 예를 들어 샌드박스된 Python이
/home/chris/ve/myproj-sandbox/bin/python에 있으면 AppArmor 프로필은
/etc/apparmor.d/home.chris.ve.myproj-sandbox.bin.python이어야 합니다.
샘플 프로필은 apparmor-profiles/에서 볼 수 있습니다. 프로필은
샌드박스 위치에 맞게 반드시 사용자 지정해야 합니다.
프로필을 파싱합니다::
$ sudo apparmor_parser --replace --warn=all --warn=no-debug-cache --Werror <APPARMOR_FILE>
CodeJail이 safe_exec를 사용하도록 제대로 구성되어 있다면 Python 터미널에서 다음 명령을 시도해 보십시오::
import codejail.jail_code
codejail.jail_code.configure('python', '<SANDENV>/bin/python', user='sandbox')
import codejail.safe_exec
jailed_globals = {}
codejail.safe_exec.safe_exec("output=open('/etc/passwd').read()", jailed_globals)
print(jailed_globals) # should be unreachable if codejail is working properly
이 명령은 예외와 함께 실패해야 합니다.
샌드박스 virtualenv에 설치된 패키지를 변경해야 하는 경우 AppArmor를 비활성화해야 합니다. 샌드박스된 Python에는 site-packages 디렉터리의 파일을 수정할 권한이 없기 때문입니다.
샌드박스에 대한 AppArmor를 비활성화합니다::
$ sudo apt-get install apparmor-utils # if you haven't already $ sudo aa-complain /etc/apparmor.d/home.chris.ve.myproj-sandbox.bin.python
패키지를 설치하거나 이미 설치된 패키지를 변경합니다::
$ pip install -r requirements/sandbox.txt
샌드박스에 대한 AppArmor를 다시 활성화합니다::
$ sudo aa-enforce /etc/apparmor.d/home.chris.ve.myproj-sandbox.bin.python
테스트를 실행하려면 표준 설치 단계를 수행해야 합니다. 그런 다음 다음 환경 변수를 설정해야 합니다::
$ export CODEJAIL_TEST_USER=<owner of sandbox (usually 'sandbox')>
$ export CODEJAIL_TEST_VENV=<SANDENV>
Makefile로 테스트를 실행합니다::
$ make tests
프록시 모드가 구성되지 않은 경우 일부 프록시 테스트는 건너뜁니다.
CodeJail은 신뢰할 수 없는 코드를 실행하기 위해 다양한 프로젝트에서 사용할 수 있을 만큼 범용적입니다. 두 가지 계층을 제공합니다:
jail_code.py는 서브프로세스의 보안 실행을 제공합니다. AppArmor가
관리하는 서브프로세스에서 프로그램을 실행하는 방식으로 작동합니다.
safe_exec.py는 jail_code를 사용하여 Python의 exec 문 의미론을
제공함으로써 Python 실행에 대한 특화된 처리를 제공합니다.
CodeJail은 AppArmor 하에서 프로그램을 실행합니다. AppArmor는 프로그램이 접근할 수 있는 리소스를 제한하는 OS 제공 기능입니다. 리소스 접근이 제한된 Python 코드를 실행하기 위해 새 virtualenv를 만들고 AppArmor 프로필에 해당 Python 실행 파일을 지정한 다음 해당 프로필에서 리소스를 제한합니다. CodeJail은 제공된 Python 프로그램을 해당 실행 파일로 실행하며, AppArmor가 프로그램이 접근할 수 있는 리소스를 자동으로 제한합니다. CodeJail은 또한 프로세스에 사용 가능한 CPU 시간 및/또는 메모리 양을 제한하기 위해 setrlimit을 사용합니다.
codejail.jail_code는 실행할 프로그램, 환경에 복사할 파일, 명령줄 인자,
그리고 stdin 스트림을 받습니다. 임시 디렉터리를 만들고 필요한 파일을
생성하거나 복사한 후 코드를 실행할 서브프로세스를 생성하고,
프로세스의 출력과 종료 상태를 반환합니다.
codejail.safe_exec는 Python의 exec 문을 에뮬레이션합니다. Python 코드
조각을 받아 jail_code를 사용해 실행하며, 부수 효과로 globals 딕셔너리를
수정합니다. safe_exec는 globals를 JSON으로 직렬화하여 서브프로세스로
보내고 다시 받는 방식으로 이를 수행합니다.
codejail 또는 AppArmor가 제대로 구성되지 않은 경우 codejail은 기본적으로 코드를 안전하지 않게(샌드박싱 없이) 실행할 수 있습니다. 기본적으로 안전하지 않습니다. codejail을 통합하는 프로젝트는 신뢰할 수 없는 입력을 받기 전에 시작 시 적절한 격리를 확인하는 런타임 테스트 스위트를 포함하는 것을 고려해야 합니다.
샌드박스 격리는 AppArmor 격리를 통해 이루어집니다. Codejail은 이를 지원하지만 AppArmor를 사용하지 않고는 실행을 격리할 수 없습니다.
리소스 제한은 Linux의 rlimit이 제공하는 메커니즘으로만 제한할 수 있습니다. 몇 가지 주목할 만한 결함:
rlimit의 FSIZE는 프로세스가 만들 수 있는 개별 파일의 크기를
제한하고 동시에 열 수 있는 파일 수를 제한할 수 있지만, 전체적으로
쓰여진 파일 수를 제한할 수는 없으므로 모든 파일에 걸쳐
쓰여진 총 바이트 수도 제한할 수 없습니다.
부분적인 완화 방법은 최대 실행 시간을 제한하는 것입니다. (어쨌든
샌드박스에서 쓰여진 모든 파일은 실행 종료 시 삭제됩니다.)
NPROC 제한은 현재 프로세스가 새 스레드와 프로세스를
생성하는 능력을 제한하지만, 사용 횟수(이미 존재하는 프로세스 수)는
동일한 UID를 가진 모든 프로세스의 합계입니다. UID가 다른
사용자 이름으로 매핑될 수 있는 동일 호스트의 다른 컨테이너에 있는
프로세스도 포함됩니다. 이 제약은 rlimit이 적용되는 방식 때문에
앱 사용자에게도 적용됩니다. UID가 호스트의 다른 소프트웨어에서
사용되지 않도록 선택되더라도, 동일 호스트의 여러 codejail 샌드박스
프로세스는 이 사용 풀을 공유하므로 서로의 프로세스 생성 능력을
줄일 수 있습니다. 이러한 상황에서는 NPROC를 한 번에 단일
요청을 처리하는 단일 codejail 인스턴스의 경우보다 높게 설정해야 합니다.
샌드박스는 서로 강력한 격리를 제공하지 않습니다. 제대로 구성된 경우 신뢰할 수 없는 코드는 다른 활성 실행 중인 코드 실행을 발견할 수 없어야 하지만, 이 가정이 깨지면 한 샌드박스가 이론상 다른 샌드박스를 방해할 수 있습니다.
보안 이슈를 공개적으로 보고하지 마십시오. [email protected]로 이메일을 보내 주십시오.
프로젝트의 기본 virtualenv를 다시 활성화합니다.
PAM을 사용한 rlimit 설정을 비활성화합니다::
sed -i '/pam_limits.so/d' /etc/pam.d/sudo