본문 바로가기

격리 Linux 권한 실습과 파일 속성

전용 임시 디렉터리에서 파일 쓰기·디렉터리 삭제·umask를 구분하고 chattr의 모드와 capability 조건을 확인합니다.

권한 숫자를 외우는 것만으로는 “읽기 전용 파일을 왜 삭제할 수 있나?”를 설명하기 어렵습니다. 삭제는 파일 내용을 쓰는 동작이 아니라 부모 디렉터리에서 이름을 제거하는 동작입니다. 이 절은 모드 비트와 권한 판정을 읽은 뒤 독립 실험으로 그 차이를 확인합니다.

실습 환경과 사용자 확인

Linux의 격리 VM 또는 전용 컨테이너에서 Bash와 아래 명령을 지원하는 파일 도구로 실행합니다. stat -c를 지원하는 GNU coreutils 또는 BusyBox 환경을 사용할 수 있습니다. root는 DAC 권한 검사를 우회할 수 있으므로 기본 실험은 UID가 0이 아닌 사용자로 진행합니다. 컨테이너도 호스트 공유 폴더를 쓰지 않고 자체 /tmp에만 파일을 만듭니다. 기본 ACL·MAC·capability·읽기 전용 마운트는 결과에 영향을 주므로 결과가 다르면 환경 정보를 먼저 확인합니다.

환경 기록
id
uname -r
cat /etc/os-release
command -v bash stat chmod mktemp

서로 다른 파일 시스템이나 컨테이너에서 chattr 결과가 같다고 가정하지 않습니다. 성공/거절의 이유와 실행 UID를 결과와 함께 적습니다.

Bash를 처음 실행할 때

Windows PowerShell의 chmod 같은 별도 도구로 이 결과를 대신하지 않습니다. 먼저 Linux VM/WSL/전용 컨테이너 안의 터미널을 엽니다. 관리자가 Bash·stat·chmod·mktemp·rm을 준비한 뒤, 기본 실습은 일반 사용자로 실행합니다. id -u가 0이면 root 터미널을 나가 준비된 일반 사용자로 다시 접속합니다. 일반 사용자 계정이 없는 환경은 관리자에게 준비를 요청하고 파일 권한 관찰부터 읽습니다.

일반 사용자 터미널의 첫 확인
pwd                    # 현재 폴더
id -u                  # 0이면 아래 기본 실습 중단
bash --version
command -v stat chmod mktemp rm

코드 블록은 permissions-lab.sh라는 자신의 새 파일에 저장한 뒤 bash permissions-lab.sh로 실행합니다. Bash로 직접 읽으므로 파일에 실행 비트를 추가할 필요는 없습니다. 명령의 exit status는 바로 다음 echo "$?"로 확인합니다. 0은 정상 종료, 이 스크립트의 2는 root 사용자 중단, 1은 기대한 거절이 일어나지 않은 경우입니다. >는 파일 내용 쓰기, /dev/null은 출력 버리기, 2>/dev/null은 오류 출력만 버리기입니다. 코드의 조건문은 실패를 감추는 대신 예상한 거절을 PASS로 표시합니다.

mktemp -d가 만든 경로는 /tmp/cs-os-permissions.무작위문자처럼 보이며 자신이 소유한 모드 700의 디렉터리입니다. 아래 스크립트는 여기에만 파일을 만듭니다. 정상 종료 때 자신이 만든 파일과 빈 디렉터리만 정리합니다. 중간 오류가 있으면 lab_dir 경로를 확인하고 그 안의 work에 owner 쓰기를 복구한 뒤 해당 파일만 정리합니다. 원인을 확인하려면 스크립트의 2>/dev/null 한 곳을 일시적으로 빼고 같은 새 실습 경로에서 관찰할 수 있다.

파일 쓰기와 이름 삭제를 나눠 보기

다음 실습은 전용 임시 디렉터리에서만 실행합니다. 셸을 새로 열거나 subshell 안에서 실행해 작업 폴더와 umask 변경을 밖으로 남기지 않습니다.

permissions-lab.sh
#!/usr/bin/env bash
set -eu
[ "$(id -u)" -ne 0 ] || { echo 'Run as an unprivileged user'; exit 2; }
lab_dir=$(mktemp -d /tmp/cs-os-permissions.XXXXXX)
cd "$lab_dir"
umask 027
printf 'original\n' > report.txt
mkdir work
printf 'created modes: '
stat -c '%a %n' report.txt work
# 기본 ACL이 없고 통상 요청 모드이면 파일 640, 디렉토리 750
chmod 400 report.txt
if (printf 'changed\n' > report.txt) 2>/dev/null; then
  echo 'unexpected: file write succeeded'; exit 1
else
  echo 'PASS file write denied'
fi
rm -f -- report.txt
[ ! -e report.txt ] && echo 'PASS name deletion allowed by parent directory'
printf 'kept\n' > work/keep.txt
chmod 500 work
if rm -- work/keep.txt 2>/dev/null; then
  echo 'unexpected: deletion succeeded'; exit 1
else
  echo 'PASS parent directory write denied'
fi
chmod 700 work
rm -- work/keep.txt
rmdir -- work
cd /tmp
rmdir -- "$lab_dir"
echo 'PASS cleaned only the lab directory'

rm -f는 이 전용 파일 하나의 삭제 확인 프롬프트를 생략합니다. 파일·디렉터리 접근 권한을 우회하는 옵션은 아닙니다. 먼저 쓰기가 거절되는데 삭제는 성공합니다. report.txt의 owner r--는 내용 수정을 막고, 부모 임시 디렉터리의 owner wx는 항목 삭제를 허용합니다. 두 번째는 파일 내용 권한을 그대로 둔 채 부모 work의 쓰기를 제거하므로 삭제가 거절됩니다. 검색 x도 디렉터리 항목을 찾는 데 필요합니다. sticky bit·immutable·MAC 같은 추가 조건은 이 기본 실험에서 제외합니다. Linux unlink의 권한 조건

에러가 나면 이미 만든 전용 경로를 확인하고 해당 파일만 정리합니다. 호스트의 임의 경로나 /var, /home 전체를 재귀 변경하는 실습으로 확장하지 않습니다.

umask는 뺄셈이 아니다

요청 모드 0666, umask 0027이면 결과는 0666 & ~0027 = 0640입니다. 요청 모드 0600에서 같은 mask를 제거해도 0600입니다. mask는 없던 group 읽기를 추가하지 않습니다. 기본 ACL이 있으면 상속 규칙도 영향을 줍니다.

디렉터리의 SetGID는 새 항목의 그룹 상속, sticky bit는 삭제/이름 변경 시 소유자 등의 추가 조건을 다룹니다. owner·group·other 권한을 다 더하는 방식이 아니며, SetGID가 파일 내용을 모두에게 쓰게 만드는 것도 아닙니다.

내용 변경과 이름 삭제는 검사하는 객체가 다르다

추가 ACL·MAC·sticky/immutable·특권 예외가 없는 기본 DAC 모델을 전제로 비교합니다.

내용 변경과 이름 삭제는 검사하는 객체가 다르다추가 ACL·MAC·sticky/immutable·특권 예외가 없는 기본 DAC 모델을 전제로 비교합니다. 파일 내용 쓰기:파일 owner w 검사;디렉토리 항목 삭제:부모 owner wx 검사;같은 파일 400:쓰기 거절·삭제 가능。마지막 결과는 부모가 자신 소유의 700인 전제입니다. 파일 400을 바꿔도 부모 500의 쓰기 부재는 해결되지 않습니다.파일 내용 쓰기파일 owner w 검사디렉토리 항목 삭제부모 owner wx 검사같은 파일 400쓰기 거절·삭제 가능
내용 변경과 이름 삭제는 검사하는 객체가 다르다추가 ACL·MAC·sticky/immutable·특권 예외가 없는 기본 DAC 모델을 전제로 비교합니다. 파일 내용 쓰기:파일 owner w 검사;디렉토리 항목 삭제:부모 owner wx 검사;같은 파일 400:쓰기 거절·삭제 가능。마지막 결과는 부모가 자신 소유의 700인 전제입니다. 파일 400을 바꿔도 부모 500의 쓰기 부재는 해결되지 않습니다.파일 내용 쓰기파일 owner w 검사디렉토리 항목 삭제부모 owner wx 검사같은 파일 400쓰기 거절·삭제 가능
마지막 결과는 부모가 자신 소유의 700인 전제입니다. 파일 400을 바꿔도 부모 500의 쓰기 부재는 해결되지 않습니다.

lsattr/chattr는 별도 파일 속성이다

chmod는 모드 비트, chattr는 파일 시스템의 추가 속성을 바꿉니다. ls -l의 rwx만 보고 immutable 여부를 알 수 없습니다. immutable은 소문자 i입니다.

이 선택 실험은 기본 DAC 실험과 별도로, immutable을 지원하는 전용 Linux 파일 시스템과 관리자가 준비한 capability가 있을 때만 실행합니다. 컨테이너/overlay/tmpfs의 지원을 가정하지 않습니다. 호스트 공유 폴더에는 실행하지 않습니다. stat -f -c '%T' /tmp, 가능하면 findmnt -T /tmp로 파일 시스템·마운트 정보를, command -v lsattr chattr로 도구를 확인합니다. 관리자에게 CAP_LINUX_IMMUTABLE을 확인받습니다. UID 0만으로 그 capability가 있다고 증명할 수 없습니다.

선택 immutable 관찰 — 자신의 새 임시 파일만
(
  set -eu
  attr_root=${CS_ATTR_ROOT:-/tmp}
  attr_dir=$(mktemp -d "$attr_root/cs-os-attr.XXXXXX")
  file="$attr_dir/demo.txt"
  printf 'owned lab file\n' > "$file"
  stat -f -c 'filesystem=%T' "$attr_dir"
  lsattr -- "$file"
  if chattr +i "$file"; then
    lsattr -- "$file"                # 소문자 i 확인
    if rm -f -- "$file"; then
      echo 'unexpected: immutable deletion succeeded'; exit 1
    else
      echo 'PASS immutable deletion denied'
    fi
    chattr -i "$file"                # 해제 성공 뒤에만 정리
    rm -- "$file"
    rmdir -- "$attr_dir"
  else
    echo 'SKIP immutable: check filesystem, capability and mount'
    rm -- "$file"
    rmdir -- "$attr_dir"
  fi
)

+i가 거절되면 오류 메시지와 환경을 남기고 SKIP 분기로 끝냅니다. -i가 실패하면 set -e로 멈추며 경로를 보존합니다. 같은 전용 파일의 속성을 해제할 수 있는 관리자가 확인하기 전에는 재귀 삭제를 시도하지 않습니다. chattr의 모든 속성이 언제나 root만 바꿀 수 있다는 일반화도 틀립니다. 대문자 -I를 변경 불가 설정 명령으로 쓰지 않습니다. +a는 append-only이며 모든 일반 쓰기를 허용하는 모드가 아닙니다. chattr 매뉴얼, Linux capability

기본은 /tmp입니다. 관리자가 준비한 전용 Linux 실습 파일 시스템이 다른 위치이면, 확인한 디렉터리를 CS_ATTR_ROOT로 지정하고 그 안에서만 mktemp가 새 경로를 만듭니다. 예를 들어 격리 VM의 일회용 ext4가 /mnt/attr-lab이면 export CS_ATTR_ROOT=/mnt/attr-lab로 선택합니다. 기존 자료나 호스트 공유 파일 시스템을 이 위치로 쓰지 않습니다.

이 관찰의 rm -f는 전용 파일 하나에 대한 확인 질문을 생략합니다. immutable이나 접근 권한을 우회하지 않습니다. 대화형 rm에서 사용자가 삭제를 거절한 결과를 커널의 삭제 거절과 혼동하지 않도록 종료 상태를 관찰합니다.

검증 기록에서 +i가 거절됐다면 실제 확인한 것은 해당 환경에서의 거절입니다. immutable 설정 후 삭제 거절까지 통과했다고 적지 않습니다. 실험용 파일에 속성을 설정했다면 반드시 -i로 해제한 뒤 그 파일을 정리합니다.

독립 문제와 해설

  1. 파일이 000, 부모 디렉터리가 owner 700이고 둘 다 자신이 소유한 기본 DAC 환경입니다. 파일을 읽을 수 있나요? 삭제할 수 있나요?
  2. 부모 디렉터리가 500인 상황에서 파일을 600으로 바꾸면 삭제가 가능해지나요?
  3. 컨테이너 root의 chattr +i가 실패하면 명령의 철자가 틀렸다고 단정할 수 있나요?
힌트

각 작업이 어느 객체의 어떤 비트를 검사하는지 먼저 적습니다.

풀이

1번은 내용 읽기 거절, 이름 삭제 허용입니다. 단 추가 ACL·MAC·sticky/immutable·특권 예외는 제외한 전제입니다. 2번은 부모 쓰기 권한이 여전히 없으므로 삭제가 거절됩니다. 3번은 지원 파일 시스템과 capability·마운트 조건을 확인해야 합니다. UID 0은 모든 커널 검사를 무조건 통과시키는 증명이 아닙니다.

권한을 이해하려면 파일 내용·디렉터리 항목·프로세스의 자격·추가 정책을 구분해야 합니다. 예상 결과와 실제 실행 결과를 환경 정보와 함께 비교합니다.