safe — 안전한 인터프리터(safe interpreter) 생성과 조작

safe — 안전한 인터프리터(safe interpreter) 생성과 조작

신뢰할 수 없는 Tcl 스크립트를 실행해야 할 때가 있어요. 그런 스크립트가 호스트 애플리케이션을 해치지 못하게 하려면 safe 패키지를 사용해요. 이 패키지는 부모 인터프리터가 제한된 "안전한" 하위 인터프리터를 만들어 그 안에서 미리 정의된 별칭(alias)들만 통해서만 파일과 패키지에 접근하게 해 줘요.

출처: 문서

본문

문법

::safe::interpCreate ?child? ?options...?
::safe::interpInit child ?options...?
::safe::interpConfigure child ?options...?
::safe::interpDelete child
::safe::interpAddToAccessPath child directory
::safe::interpFindInAccessPath child directory
::safe::setLogCmd ?cmd arg...?

동작 방식

Safe Tcl은 신뢰할 수 없는 Tcl 스크립트를 안전하게 실행하고, 그런 스크립트가 잠재적으로 위험한 기능에 접근하는 것을 중재(mediation)하는 메커니즘이에요. 신뢰할 수 없는 Tcl 스크립트가 호스팅 애플리케이션을 해치지 못하게 보장해요. 무결성과 프라이버시 공격을 막아요. 신뢰할 수 없는 스크립트가 호스팅 애플리케이션 또는 컴퓨터의 상태를 손상시키지 못하게 하고, 호스팅 컴퓨터나 애플리케이션에 저장된 정보를 누구에게도 유출하지 못하게 해요.

Safe Tcl은 부모 인터프리터가 source, load, file, encoding, exit 명령에 대한 미리 정의된 별칭 집합을 포함한 안전하고 제한된 인터프리터를 만들 수 있게 하고, 자동 로딩과 패키지 메커니즘을 사용할 수 있게 해요.

파일 시스템 구조에 대한 지식은 안전한 인터프리터에 새지 않아요. 안전한 인터프리터는 토큰을 포함한 가상 경로에만 접근할 수 있거든요. 안전한 인터프리터가 파일을 source하도록 요청하면, 가상 경로의 토큰을 파일 이름의 일부로 사용해요. 부모 인터프리터는 그 토큰을 실제 디렉터리 이름으로 투명하게 번역해 요청된 연산을 실행해요 (SECURITY 절 참조).

부모 인터프리터에서 Safe Tcl이 제공하는 모든 명령은 safe 네임스페이스에 있어요.

명령(COMMANDS)

::safe::interpCreate ?child? ?options...? — 안전한 인터프리터를 만들고, ALIASES 절에서 설명한 별칭을 설치하고, 제공된 옵션에 지정된 대로 자동 로딩 및 패키지 메커니즘을 초기화해요. child 인자를 생략하면 이름이 생성돼요. ::safe::interpCreate는 항상 인터프리터 이름을 반환해요. 인터프리터 이름 child는 네임스페이스 구분자를 포함할 수 있지만, 앞뒤에 네임스페이스 구분자가 있거나 구분자에 과도한 콜론 문자를 가질 수 없어요. 인터프리터 이름은 ::safe::interpCreate 명령이 평가되는 네임스페이스가 아닌 전역 네임스페이스 ::에 상대적으로 한정돼요.

::safe::interpInit child ?options...?interpCreate와 비슷하지만 안전한 인터프리터를 만들지 않아요. childinterp create -safe 같은 다른 수단으로 만들어져 있어야 해요. 이름은 interpCreate와 같은 제한을 받아요.

::safe::interpConfigure child ?options...? — 옵션이 없으면 지정된 안전한 인터프리터의 모든 옵션 설정을 해당 child의 옵션과 현재 값의 리스트로 반환해요. 추가 인자 하나가 주어지면 요소 2개의 리스트 namevalue를 반환하는데, name은 그 옵션의 전체 이름, value는 그 옵션의 현재 값이에요. 추가 인자가 둘 이상이면 안전한 인터프리터를 재구성하고 제공된 옵션만 각각 바꿔요. 사용 예:

# "$i0"와 같은 구성으로 새 인터프리터 만들기:
set i1 [safe::interpCreate {*}[safe::interpConfigure $i0]]
# 현재 deleteHook 가져오기
set dh [safe::interpConfigure $i0 -del]
# 인터프리터의 (statics 로딩 허용) 속성과 deleteHook만 바꾸기 (나머지 유지):
safe::interpConfigure $i0 -delete {foo bar} -statics 0

::safe::interpDelete child — 안전한 인터프리터를 삭제하고 해당 부모 인터프리터 데이터 구조를 정리해요. 이 인터프리터에 deleteHook 스크립트가 지정됐다면, 인터프리터를 삭제하기 전에 인터프리터 이름을 추가 인자로 해서 평가돼요.

::safe::interpFindInAccessPath child directory — 안전한 인터프리터의 현재 가상 접근 경로에서 실제 디렉터리 directory의 토큰을 찾아 반환해요. 디렉터리를 찾지 못하면 오류를 생성해요. 사용 예:

$child eval [list set tk_library \
    [::safe::interpFindInAccessPath $name $tk_library]]

::safe::interpAddToAccessPath child directorydirectory를 부모에서 안전한 인터프리터에 대해 유지되는 가상 경로에 추가하고, 안전한 인터프리터에서 그 디렉터리의 파일에 접근하는 데 사용할 수 있는 토큰을 반환해요. 디렉터리가 이미 가상 경로에 있으면 가상 경로에 다시 추가하지 않고 토큰만 반환해요. 사용 예:

$child eval [list set tk_library \
    [::safe::interpAddToAccessPath $name $tk_library]]

::safe::setLogCmd ?cmd arg...? — 안전한 인터프리터에서 중요한 생애주기 이벤트가 발생할 때 호출될 스크립트를 설치해요. 인자가 없으면 현재 설치된 스크립트를 반환해요. 인자 하나(빈 문자열)로 호출하면 현재 설치된 스크립트가 제거되고 로깅이 꺼져요. 스크립트는 관심 이벤트를 설명하는 문자열을 추가 인자로 해서 호출돼요. 주 목적은 안전한 인터프리터 디버깅을 돕는 것이에요. 이 기능으로 안전한 인터프리터가 일반 오류 메시지만 받는 동안 완전한 오류 메시지를 얻을 수 있어요. 이는 안전한 인터프리터가 실제 디렉터리 이름 같은 민감한 정보를 포함할 수 있는 실패 및 기타 이벤트에 대한 메시지를 보는 것을 막아요. 사용 예:

::safe::setLogCmd puts stderr

다음은 안전한 인터프리터가 자신의 가상 접근 경로에 없는 파일을 source하려 한 샘플 세션 출력이에요. 안전한 인터프리터가 파일이 없다는 오류 메시지만 받은 점에 주목하세요:

NOTICE for child interp10 : Created
NOTICE for child interp10 : Setting accessPath=(/foo/bar) staticsok=1 nestedok=0 deletehook=()
NOTICE for child interp10 : auto_path in interp10 has been set to {$p(:0:)}
ERROR for child interp10 : /foo/bar/init.tcl: no such file or directory

옵션(OPTIONS)

다음 옵션들은 ::safe::interpCreate, ::safe::interpInit, ::safe::interpConfigure에 공통이에요. 어떤 옵션 이름도 최소 비모호 이름으로 축약할 수 있고, 대소문자를 구분하지 않아요.

  • -accessPath directoryList — 안전한 인터프리터가 source와 load할 수 있는 디렉터리 리스트를 설정해요. 지정되지 않거나 빈 리스트로 주어지면 안전한 인터프리터는 자동 로딩에 부모와 같은 디렉터리를 사용해요.
  • -statics boolean — 안전한 인터프리터가 정적으로 연결된 패키지(예: load {} Tk)를 로드할 수 있는지 지정해요. 기본값은 true로, 허용돼요.
  • -noStatics-statics false의 편의 축약으로, 정적으로 연결된 패키지를 로드할 수 없게 해요.
  • -nested boolean — 안전한 인터프리터가 패키지를 자신의 하위 인터프리터에 로드할 수 있는지 지정해요. 기본값은 false로, 허용되지 않아요.
  • -nestedLoadOk-nested true의 편의 축약으로, 하위 인터프리터에 패키지 로드를 허용해요.
  • -deleteHook script — 이 옵션에 비어 있지 않은 스크립트가 주어지면, 안전한 인터프리터를 실제로 삭제하기 직전에 안전한 인터프리터 이름을 추가 인자로 해서 부모에서 평가돼요. 빈 값을 주면 현재 설치된 삭제 훅 스크립트를 제거해요. 기본값({})은 삭제 콜백이 없는 것이에요.

별칭(ALIASES)

안전한 인터프리터에는 다음 별칭이 제공돼요:

  • source fileName — 요청된 파일(Tcl 소스 파일)이 발견되면 안전한 인터프리터로 source돼요. source 별칭은 안전한 인터프리터의 가상 경로에 있는 디렉터리의 파일만 source할 수 있어요. source 별칭은 안전한 인터프리터가 source할 파일을 찾을 수 있는 디렉터리를 나타내기 위해 가상 경로의 토큰 이름 중 하나를 사용해야 해요.
  • load fileName — 요청된 파일(공유 객체 파일)이 발견되면 안전한 인터프리터에 동적으로 로드돼요. 파일 이름은 성공적으로 발견되려면 안전한 인터프리터의 가상 경로에 언급된 토큰 이름을 포함해야 해요. 또한 공유 객체 파일은 안전한 진입점(safe entry point)을 포함해야 해요 (load 명령 매뉴얼 참조).
  • file ?subCmd args...? — file 별칭은 file 명령의 안전한 부분집합 서브커맨드에 대한 접근을 제공해요. dirname, join, extension, root, tail, pathtype, split 서브커맨드만 허용해요.
  • encoding ?subCmd args...? — encoding 별칭은 encoding 명령의 안전한 부분집합에 접근을 제공해요. 시스템 인코딩 설정은 금지하지만 system을 포함한 다른 모든 서브커맨드는 현재 인코딩을 확인하도록 허용해요.
  • exit — 호출 인터프리터가 삭제되고 계산이 중지되지만, 인터프리터가 존재하는 Tcl 프로세스는 종료되지 않아요.

보안(SECURITY)

Safe Tcl은 성가심과 서비스 거부 공격을 완전히 막으려 하지 않아요. 그런 공격은 애플리케이션이나 사용자가 유용한 작업을 위해 컴퓨터를 일시적으로 사용하는 것을 막는데, 예를 들어 사용 가능한 모든 CPU 시간이나 화면 공간을 소비하는 식이에요. 이런 공격은 짜증나지만, Safe Tcl이 막으려 하는 무결성·프라이버시 공격보다 일반적으로 중요도가 낮다고 간주돼요.

안전한 인터프리터에서 사용 가능한 명령은 interp 매뉴얼 페이지에 정의된 안전한 집합 외에, source, load, exitfile·encoding의 안전한 부분집합에 대한 중재된 별칭이에요. 안전한 인터프리터는 코드를 자동 로드할 수도 있고 패키지 로드를 요청할 수도 있어요.

이들 중 일부 명령이 로컬 파일 시스템에 접근하므로 디렉터리 구조에 대한 정보 누출 가능성이 있어요. 이를 막기 위해 안전한 인터프리터에서 파일 이름을 인자로 받는 명령은 실제 디렉터리 이름 대신 토큰을 사용해요. 이 토큰은 예를 들어 파일을 source하는 요청이 부모 인터프리터에 의해 중재되는 동안 실제 디렉터리 이름으로 번역돼요. 이 가상 경로 시스템은 ::safe::interpCreate가 만들거나 ::safe::interpInit이 초기화한 각 안전한 인터프리터에 대해 부모 인터프리터에서 유지되며, 안전한 인터프리터에서 접근 가능한 토큰을 로컬 파일 시스템의 실제 경로 이름으로 매핑해 안전한 인터프리터가 실행 중인 호스트의 파일 시스템 구조에 대한 지식을 얻지 못하게 해요.

하위에 제공되는 sourceload 별칭의 유효한 파일 이름 인자는 오직 [file join token filename] 형태의 경로(네이티브 형식으로는 Unix의 token/filename, Windows의 token\filename)뿐이에요. 여기서 token은 accessPath 리스트의 디렉터리 중 하나를 나타내고 filename은 그 디렉터리의 한 파일이에요 (하위 디렉터리 접근은 허용되지 않아요). 안전한 인터프리터에서 source 또는 load 요청에 토큰이 사용되면 토큰이 검사되고 실제 경로 이름으로 번역되며 source/load할 파일이 파일 시스템에서 위치해요. 안전한 인터프리터는 파일이 저장된 실제 경로 이름을 절대 알지 못해요.

안전한 인터프리터가 source할 수 있는 파일 집합에 실수로 포함된 민감한 파일로부터의 잠재적 정보 누출을 더 막기 위해, source 별칭은 다음 제약을 충족하는 파일에만 접근을 제한해요: 파일 이름이 14자 이하여야 하고, 점(.)을 1개 이상 포함하지 않아야 하며, 확장자(.tcl)로 끝나거나 tclIndex라고 불러야 해요.

초기 접근 경로 리스트의 각 요소에는 토큰이 할당되고 그 토큰은 하위 auto_path에 설정되며, 그 리스트의 첫 요소는 해당 하위의 tcl_library로 설정돼요.

접근 경로 인자가 주어지지 않거나 빈 리스트면, 기본 동작은 하위가 부모가 접근할 수 있는 것과 같은 패키지에 접근하게 하는 것이에요 (더 정확히는: 하위 인터프리터에서 실행되므로 정의상 위험할 수 없는 Tcl로 작성된 패키지, 그리고 _SafeInit 진입점을 제공하는 C 확장만). 이를 위해 부모의 auto_path가 하위 접근 경로 구성에 사용돼요.

하위가 Tcl 라이브러리 파일(자동 로딩 메커니즘 자체 포함)을 성공적으로 로드하려면, tcl_library가 필요 시 하위 접근 경로의 첫 위치에 추가되거나 이동돼 하위 tcl_library가 부모와 같게 돼요 (실제 경로는 여전히 하위에 보이지 않음). 이 기본 경우에 하위와 부모가 자동 로딩을 동일하게 동작하게 하기 위해, 부모 auto_path의 각 디렉터리의 1단계 하위 디렉터리도 하위 접근 경로에 추가돼요 (이미 포함되지 않았다면). -accessPath 플래그로 디렉터리 리스트를 명시적으로 지정하면 이 기본 메커니즘에 의존하는 대신 하위 디렉터리를 절대 검색하지 않는 더 제한적인 경로를 항상 지정할 수 있어요.

첫 생성 또는 초기화 후(interpConfigure -accessPath list를 통해) accessPath가 바뀌면, 안전한 인터프리터에서 자동으로 auto_reset이 평가되어 auto_index를 새 토큰 리스트와 동기화해요.

더 알아보기

  • interp: 인터프리터 생성과 안전한 집합
  • library, load, package, source, unknown: 패키지와 로딩 메커니즘