파일 · 권한 · 사용자 관리 02 / 50 · Part 1. Linux 파일 관리 기초
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (Docker 격리 컨테이너, 테스트 계정 analyst)
기초편 연계: 「리눅스 시스템 기초」 6. Linux 파일 시스템 구조 · 14. inode와 파일 시스템

1. 들어가며

01편에서 파일은 이름 + inode + 데이터 블록 으로 이루어진다는 것을 확인했다. 이번 글은 한 단계 아래로 내려가 다음 질문에 답한다.

  • 파일 시스템(ext4, xfs)은 inode와 데이터 블록을 어떻게 관리하는가?
  • 프로세스는 파일을 어떻게 "열고", 커널은 그것을 어떻게 기억하는가?
  • 파일을 지웠는데 디스크 용량이 줄지 않는 이유 는 무엇인가?

마지막 질문은 운영 장애 질문이면서 동시에 침해사고 분석 질문이다. 실행 중인 악성 프로세스가 자기 실행 파일이나 로그를 지워도 열린 파일 디스크립터를 통해 흔적이 남기 때문 이다.


2. 핵심 개념

2-1. 파일 시스템

파일 시스템은 블록 장치(디스크 파티션)를 inode 테이블 + 데이터 블록 + 관리 정보 로 나누어 파일을 저장하는 규칙이다.

파일 시스템주 사용처특징
xfsRocky/RHEL 계열 기본대용량·병렬 I/O에 강함, 축소(shrink) 불가
ext4Ubuntu 기본가장 널리 쓰임, 생성 시 inode 수가 고정됨
tmpfs/dev/shm, /run, (일부 배포판) /tmp메모리 기반, 재부팅 시 삭제
proc / sysfs/proc, /sys디스크가 없는 가상 파일 시스템 (커널 정보)
overlay컨테이너 루트여러 레이어를 합쳐 보여 줌

배포판 차이: Rocky Linux 9 설치 기본값은 xfs, Ubuntu 24.04 설치 기본값은 ext4 다. 이 실습은 Docker 컨테이너 안에서 진행했기 때문에 루트(/)는 overlay이고, inode 실습을 위해 별도의 ext4 볼륨 을 /data에 연결했다.

2-2. 파일 디스크립터 (File Descriptor, fd)

프로세스가 open()을 호출하면 커널은 작은 정수 하나를 돌려준다. 이것이 파일 디스크립터다. 이후 read(3, ...), write(3, ...)처럼 이 번호로 파일을 다룬다.

fd기본 연결의미
0stdin표준 입력
1stdout표준 출력
2stderr표준 에러
3~open() 할 때마다 할당열린 파일, 소켓, 파이프

/proc/<PID>/fd/ 디렉터리에서 어떤 프로세스가 무엇을 열고 있는지 를 파일처럼 볼 수 있다.


3. 동작 원리

프로세스 → 파일 디스크립터 → VFS → 파일 시스템 → 블록 장치

3-1. 파일을 열 때

  1. 프로세스가 open("/data/app.log", O_WRONLY|O_APPEND) 호출
  2. VFS가 경로를 해석해 inode를 찾고 권한을 검사 한다 (21편)
  3. 커널이 열린 파일 객체 (오프셋, 모드, inode 참조)를 만들고
  4. 프로세스의 fd 테이블 빈 칸(보통 3)에 연결해 번호를 돌려준다

3-2. 파일을 지울 때

rm은 파일 내용을 지우는 명령이 아니다. 내부적으로 unlink() 시스템 콜을 호출해 디렉터리에서 이름 하나를 제거 하고 inode의 링크 수를 1 줄인다. 커널은 다음 두 조건이 모두 만족될 때만 inode와 데이터 블록을 실제로 해제한다.

링크 수(이름 수) == 0   AND   이 inode를 연 fd 수 == 0

그래서 로그를 쓰고 있는 프로세스가 있는 상태에서 로그 파일을 rm 하면, 이름은 사라지지만 공간은 해제되지 않는다.

3-3. 권한 검사는 "열 때" 한 번

권한 검사는 open() 시점에 수행된다. 이미 열린 fd로 읽고 쓰는 동안에는 파일 권한을 다시 검사하지 않는다. 그래서 파일 권한을 바꿔도 이미 열어 둔 프로세스는 계속 접근할 수 있다. 권한 회수 후에는 관련 프로세스 재시작까지 확인해야 하는 이유다.


4. 명령어 실습

# 1) 마운트된 파일 시스템 종류 확인
df -hT / /data /dev/shm
df -i /data                     # inode 총량과 사용량

# 2) 파일 하나의 inode와 블록
cd /data && echo "log line" > app.log
stat -c 'inode=%i size=%s blocks(512B)=%b io_block=%o' app.log

# 3) 현재 쉘에서 fd 3 으로 파일 열기
exec 3>>app.log
ls -l /proc/$$/fd/0 /proc/$$/fd/1 /proc/$$/fd/3
echo "written via fd 3" >&3
cat app.log
exec 3>&-                       # fd 3 닫기

# 4) 열린 상태에서 삭제된 파일 재현
tail -f app.log > /dev/null &   # 파일을 계속 열고 있는 프로세스
PID=$!; sleep 1
rm app.log
ls -l /proc/$PID/fd | grep app.log
kill $PID
명령의미
df -T파일 시스템 종류 표시
df -iinode 기준 사용량
exec 3>>file현재 쉘에 fd 3을 append 모드로 연결
>&3출력을 fd 3으로 보냄
exec 3>&-fd 3 닫기
$$ / $!현재 쉘 PID / 마지막 백그라운드 프로세스 PID

5. 실행 결과

실제 실행 결과 — Rocky Linux 9.8 · analyst@rocky9-lab — 파일 시스템과 파일 디스크립터

텍스트 원본(실제 출력):

[analyst@rocky9-lab ~]$ df -hT / /data /dev/shm
Filesystem     Type     Size  Used Avail Use% Mounted on
overlay        overlay  252G   14G   29G  32% /
/dev/loop0     ext4     224M  8.0K  206M   1% /data
shm            tmpfs     64M     0   64M   0% /dev/shm
[analyst@rocky9-lab ~]$ df -i /data
Filesystem     Inodes IUsed IFree IUse% Mounted on
/dev/loop0      65536    10 65526    1% /data
[analyst@rocky9-lab ~]$ cd /data && echo "log line" > app.log
[analyst@rocky9-lab data]$ stat -c 'inode=%i size=%s blocks(512B)=%b io_block=%o' app.log
inode=11 size=9 blocks(512B)=8 io_block=4096
[analyst@rocky9-lab data]$ exec 3>>app.log
[analyst@rocky9-lab data]$ ls -l /proc/$$/fd/0 /proc/$$/fd/1 /proc/$$/fd/3
lr-x------ 1 analyst analyst 64 Sep 24 10:16 /proc/51/fd/0 -> /dev/null
l-wx------ 1 analyst analyst 64 Sep 24 10:16 /proc/51/fd/1 -> pipe:[33712]
l-wx------ 1 analyst analyst 64 Sep 24 10:16 /proc/51/fd/3 -> /data/app.log
[analyst@rocky9-lab data]$ echo "written via fd 3" >&3
[analyst@rocky9-lab data]$ cat app.log
log line
written via fd 3
[analyst@rocky9-lab data]$ exec 3>&-
[analyst@rocky9-lab data]$ tail -f app.log > /dev/null &
[analyst@rocky9-lab data]$ PID=$!; sleep 1
[analyst@rocky9-lab data]$ rm app.log
[analyst@rocky9-lab data]$ ls -l /proc/$PID/fd | grep app.log
lr-x------ 1 analyst analyst 64 Sep 24 10:16 3 -> /data/app.log (deleted)
[analyst@rocky9-lab data]$ kill $PID

6. 결과 해석

출력해석
overlay ... /컨테이너 루트는 overlay FS. 실제 VM이라면 Rocky는 xfs, Ubuntu는 ext4 로 보인다
/dev/loop0 ext4 ... /data실습용 ext4 볼륨
shm tmpfs ... /dev/shm메모리 기반 FS. 누구나 쓸 수 있는 경로라 공격자가 파일을 두는 위치로 자주 쓰인다
Inodes 65536 IUsed 10이 볼륨은 파일을 최대 65,536개 만들 수 있다 (ext4는 생성 시 고정)
size=9 blocks(512B)=8 io_block=4096내용은 9바이트지만 4KiB 블록 1개(= 512B × 8) 를 차지한다
fd/0 -> /dev/null, fd/1 -> pipe:[...]이 실습은 스크립트로 실행했기 때문에 stdin은 /dev/null, stdout은 파이프다. 터미널에서 직접 실행하면 /dev/pts/0 같은 터미널 장치가 보인다
fd/3 -> /data/app.logexec 3>>로 연 파일이 fd 3에 연결됨
3 -> /data/app.log (deleted)이름은 지워졌지만 tail 프로세스가 아직 inode를 열고 있다

마지막 줄이 이번 글의 핵심이다. 이 상태에서는 ls /data에 app.log가 보이지 않지만, 데이터는 디스크에 그대로 있고 cat /proc/<PID>/fd/3으로 내용을 읽을 수도 있다.


7. 보안 관점

상황공격/장애 시나리오확인 방법
실행 후 자기 파일 삭제악성코드가 /tmp/.x를 실행하고 바로 rm → 디스크 검색으로 안 보임ls -l /proc/*/exe 2>/dev/null \| grep deleted
로그 삭제공격자가 /var/log/secure 삭제 → rsyslog는 계속 삭제된 inode에 기록ls -l /proc/$(pidof rsyslogd)/fd
inode 고갈작은 파일 대량 생성 → 용량이 남아도 로그·세션 파일 생성 실패df -i
메모리 FS 악용/dev/shm에 페이로드 저장 → 재부팅하면 사라짐find /dev/shm -type f -ls

주의: (deleted) 표시는 정상 상황에서도 흔하다. 패키지 업데이트 후 재시작하지 않은 서비스가 삭제된 옛 라이브러리를 열고 있는 경우가 대표적이다. 판단 기준은 어떤 프로세스가, 어떤 경로의 파일을, 언제부터 열고 있었는가다.


8. 보안관제 관점

[Detection]   EDR/auditd: /tmp 경로에서 실행된 프로세스, 이후 해당 파일 unlink
     ↓
[Evidence]    /proc/<PID>/exe → /tmp/.cache/kworker (deleted)
     ↓
[보존]        cp /proc/<PID>/exe /evidence/kworker.bin   ← 프로세스 종료 전에
              sha256sum /evidence/kworker.bin
     ↓
[IOC]         해시 → 위협 인텔리전스 조회, 연결 IP(ss -tnp) 수집
     ↓
[Response]    네트워크 격리 → 프로세스 종료 → 계정·지속성(cron, systemd) 확인
관제 포인트설명
SIEM파일 삭제는 기본 syslog에 남지 않는다. auditd의 unlink, unlinkat 규칙이나 EDR 이벤트가 있어야 수집된다
이상 행위실행 파일이 /tmp, /dev/shm, /var/tmp에 있고 이미 삭제된 상태
Evidence 순서프로세스를 먼저 죽이면 /proc/<PID>/가 사라진다. 보존 → 종료 순서를 지킨다
용량 이상du 합계와 df 사용량이 크게 다르면 삭제된 열린 파일을 의심한다

9. 실무에서 자주 발생하는 실수

실수결과올바른 방법
용량 확보를 위해 사용 중인 로그를 rm공간이 해제되지 않음: > app.log로 비우거나 logrotate 사용, 필요 시 서비스 재시작
의심 프로세스를 먼저 kill/proc의 증거 소실실행 파일·fd·네트워크 정보를 먼저 보존
df -h만 보고 여유 있다고 판단inode 고갈을 놓침df -i도 함께 확인
권한 변경 후 바로 안전하다고 판단이미 열린 fd는 계속 사용 가능관련 프로세스 재시작 여부 확인
컨테이너 결과를 VM과 동일하게 해석루트 FS 종류가 다름(overlay)환경을 먼저 df -T로 확인

10. 실습 체크리스트

[ ] df -T 로 파일 시스템 종류를 확인했다
[ ] df -i 로 inode 사용량을 확인했다
[ ] /proc/$$/fd 에서 fd 번호와 연결 대상을 확인했다
[ ] 열린 상태에서 삭제된 파일이 (deleted) 로 표시되는 것을 재현했다
[ ] 파일 공간이 해제되는 두 조건(링크 0, 열린 fd 0)을 설명할 수 있다
[ ] 의심 프로세스 대응 시 "보존 → 종료" 순서를 이해했다

11. 핵심 정리

  • 파일 시스템은 블록 장치를 inode 테이블 + 데이터 블록 으로 관리한다. Rocky 기본은 xfs, Ubuntu 기본은 ext4다.
  • VFS가 서로 다른 파일 시스템을 같은 인터페이스 로 보이게 한다.
  • 프로세스는 파일을 fd 번호 로 다루며, /proc/<PID>/fd/에서 확인할 수 있다.
  • rm은 이름을 제거(unlink)할 뿐이고, 공간은 링크 수 0 + 열린 fd 0 일 때 해제된다.
  • 권한 검사는 open() 시점에 한 번 수행된다. 이미 열린 fd에는 권한 변경이 즉시 영향을 주지 않는다.
  • (deleted) 파일은 정상 업데이트에서도 생기므로 경로·프로세스·시점 으로 판단한다.
  • 의심 프로세스 대응은 증거 보존 → 종료 순서다.

12. 다음 편 예고

다음 글 「03. Linux 디렉터리 구조 이해」 에서는 /, /etc, /var, /tmp, /usr, /opt가 각각 무엇을 저장하는지, 그리고 각 디렉터리의 기본 권한이 왜 그렇게 설정되어 있는지 를 Rocky와 Ubuntu 실제 출력으로 비교한다.


참고 자료


시리즈 이동

profile
코드에 숨겨진 위협을 읽고 AI로 보안의 미래를 설계합니다. 프론트엔드 개발 경험을 자산 삼아 더 견고하고 지능적인 보안 운영 시스템을 구축해 나가는 과정을 기록합니다

0개의 댓글