후니의 IT인프라 사전

컨테이너 만들기 2) pivot_root를 활용한 네임스페이스 격리 본문

카테고리 없음

컨테이너 만들기 2) pivot_root를 활용한 네임스페이스 격리

james_janghun 2024. 9. 1. 07:39

 

chroot의 문제점

앞서 chroot를 통해서 컨테이너의 격리를 학습할 수 있었다. 그런데 chroot의 단점은 탈옥이 가능하다는 것이다.

 

탈옥 코드

#include <sys/stat.h>
#include <unistd.h>
int main(void)
{
 mkdir(".out", 0755);
 chroot(".out");
 chdir("../../../../../");
 chroot(".");
 return execl("/bin/sh", "-i", NULL);
}

 

해당 코드를 간단하게 분석하면 일단 현재 디렉터리에 .out 폴더를 755권한으로 생성한다. 그리고 프로세스의 루트 디렉터리를 .out 디렉터리로 변경해, 프로세스가 그 디렉터리 안에서만 파일 시스템에 접근할 수 있도록 한다. 새로운 루트 디렉터리 내에서 가능한 최상위 디렉터리로 이동한다. 현재 디렉터리를 루트 디렉터리로 설정해 파일 시스템 접근을 제한된 상태로 재설정한다. 즉 루트 디렉터리가 .out 디렉터리내 특정 하위 디렉터리가 된다. 그 이후 /bin/sh를 실행해 제한된 환경에서 쉘을 실행한다.

 

해당 코드를 컴파일해서 실행해보도록한다.

gcc -o myroot/escape_chroot escape_chroot.c
tree -L 1 myroot


# chroot를 통해 다시 접속해서 확인해본다.
chroot myroot /bin/sh
./escape_chroot
ls

 

실제로 호스트의 루트 디렉터리 경로를 따르고 있다.

 

그래서 이 탈옥을 막기 위해 "루트파일시스템"을 피봇하는 pivot_root를 사용한다.

 

pivot_root

시스템이 부팅될 때 해당 시스템에 붙어있는 하드웨어 장치(usb, 랜카드 등)를 올리기 위한 부팅 파일 시스템을 올린다.

부팅 파일 시스템이 올라오면 root 파일 시스템으로 전환하기 위해서 pivot_root를 사용하는데, 이걸 이용해서 컨테이너의 루트 파일 시스템도 같이 다루는 방식으로 탈옥을 방지할 수 있었다.

 

루트 파일 시스템이란 무엇인가?

 

루트 파일 시스템은 최상위 파일 시스템으로 루트디렉터리를 포함한 하위 형태를 모두 포함한다. 하위의 모든 파일시스템들이 마운트 되고 있다. 우리가 흔히 cd / 에서 ls 하면 확인할 수 있는 파일이 모두 루트파일 시스템으로 보면 된다.

 

리눅스의 루트파일 시스템 (https://ghchoi0427.tistory.com/94)

 

 

 

네임스페이스(namespace)의 등장

다만 이 루트파일시스템을 직접 다루는 것은 호스트에 영향을 미치는 아주 위험한 작업이기 때문에 2002년에 프로세스의 환경을 격리하는 "네임스페이스"를 개발했다. 즉 마운트 환경 격리만 고려하게 된 것이다. 이를 통해 호스트에 영향을 주지 않으면서 컨테이너의 환경만 격리하도록 할 수 있었다.

 

마운트(mount)

마운트는 파일 시스템을 루트파일시스템의 하위 디렉터리로 부착하는 시스템 콜이다. 부착하는 지점을 마운트 포인트라고 한다.

예를 들어, cdrom을 마운트 하면 /mnt/cdrom이라는 마운트 포인트에 부착하는 것이다. 즉 루트 하위 경로의 파일시스템에 등록된다.

ifkakao2022_container-internal.pdf

 

 

마운트 네임스페이스

 

파일 시스템이 루트 파일시스템에 부착하는 경로인 마운트 포인트를 격리하는 방식으로 먼저 등장했다. 마운트 네임스페이스를 격리(unshare)하게 되면 부모 마운트 네임스페이스를 복사해 자식 마운트 네임스페이스를 만들게 된다. 아래 그림에서 왼쪽은 부모, 오른쪽은 자식 네임스페이스로 해당 내용을 복사했다고 보면 되고, 서로는 격리되어 있다.

ifkakao2022_container-internal.pdf

 

 

예를 들어 USB를 마운트한다고 치자. M 마운트 포인트에 X를 부착하면 자식 네임스페이스에서만 USB 내용을 확인할 수 있고, 부모(호스트) 네임스페이스에서는 확인할 수 없다.

ifkakao2022_container-internal.pdf

 

 

따라서 이러한 방식으로 자식 네임스페이스로 사용할 마운트 포인트인 디렉터리를 만들어서 격리를 시켜주는 방식으로 피봇하겠다.

 

 

 

마운트 네임스페이스 실습

unshare라는 명령어를 통해서 mount 옵션을 주어 마운트 네임스페이스를 통해 격리를 한다.

unshare --mount /bin/sh

왼쪽은 격리된 네임스페이스, 오른쪽은 호스트


해당 명령어를 통해 손쉽게 격리가 가능하다. 다만 df -h 등 명령어를 확인해 보았을 때 호스트와 결과가 동일한 것을 알 수 있다. 그 이유는 부모 프로세스의 마운트 네임스페이스의 정보를 복사해서 자식 네임스페이스를 만들기 때문에 동일하다.

따라서 처음에만 정보가 동일하다. 이제 자식 네임스페이스에서 예시로 마운트를 진행해보자.

 

자식 네임스페이스에서 new_root 디렉터리를 만들고, 해당 디렉터리에 다음 내용을 마운트 시켜보자.

mkdir new_root
mount -t tmpfs non new_root

왼쪽은 격리된 자식 네임스페이스, 오른쪽은 호스트(부모 네임스페이스)

 

ls 명령어를 보았을 때 호스트에도 new_root라는 파일이 생성되는 것을 확인할 수 있다. 다만 마운트 정보를 확인해보면 호스트에서는 다음과 같이 확인할 수 없고, 자식 네임스페이스에서만 확인할 수 있다.

mount | grep new_root

 

해당 내용은 자식 네임스페이스에서 cp로 마운트된 디렉터리로 무언가 복사하면 자식 네임스페이스에서만 확인할 수 있고, 부모네임스페이스에서는 확인할 수 없다.

 

pivot_root 실행

이제 pivot_root 명령어로 신규 루트 마운트 포인트인 new_root와 기존 루트를 그대로 옮겨줄 put_old 디렉터리를 만들어 준다. 기존에 호스트의 루트 파일시스템을 그대로 put_old라는 마운트 포인트에 복사하고 이 곳을 루트 파일시스템으로 사용하는 것이다.

pivot_root <new_root> <put_old>

 

이제 실제로 new_root/put_old라는 디렉터리를 생성하고, 새로운 루트 파일시스템의 최상단 경로가 되는 new_root 디렉터리에 들어간 다음 pivot_root 명령어를 통해서 현재를 최상단 /로 선언하고, put_old를 마운트 파일시스템으로 피봇한다.

mkdir new_root/put_old
cd new_root
pivot_root . put_old
cd /

 

이렇게 하면 put_old 파일 안에 기존 호스트의 루트 파일시스템이 그대로 마운트 되는 것을 확인할 수 있으며 탈옥 코드를 사용해도 루트 디렉터리의 경로가 변하지 않는다는 것 또한 확인할 수 있었다.

 

루트 파일시스템이 변경되었기 때문에 탈옥이 불가능하다. 애초에 격리된 컨테이너의 루트 파일시스템 자체가 바뀌었기 때문이다.

 

정리

마운트 네임스페이스와 pivot_root를 통해 탈옥을 방지해서 좀 더 완전한 격리를 할 수 있었다. 특히 루트 파일시스템과 완전히 동일하게 복제하지만 격리된 환경이므로 좀 더 안전한 환경을 구성할 수 있다.