Ch8 Linux LAB
실습 설명을 읽고 소스를 확인한다. 원본 파일 링크는 소스 텍스트를 연다.
소스는 hello.c(write), flows.c(fork), maps.c(private anonymous mapping)다.
바이너리는 임시 디렉터리에 생성하고 종료 시 삭제한다.
레포 root에서:
docker build --platform linux/amd64 -t csapp-ch8-lab:amd64 examples/0002-exceptional-control-flow
docker run --rm --platform linux/amd64 --cap-add=SYS_PTRACE csapp-ch8-lab:amd64
Linux/x86-64 native 환경의 전체 LAB 명령이다. macOS ARM64의 amd64 에뮬레이션에서는 ptrace/strace가 syscall을 완전히 보여주지 못할 수 있다. 이 경우 아래 native ARM64 LAB을 실행하고 관측 환경을 ARM64로 기록한다.
docker build --platform linux/arm64 -t csapp-ch8-lab:arm64 examples/0002-exceptional-control-flow
docker run --rm --platform linux/arm64 --cap-add=SYS_PTRACE csapp-ch8-lab:arm64
필요 도구가 설치된 Linux host에서는 sh examples/0002-exceptional-control-flow/verify.sh로 실행한다.
스크립트는 syscall, 부모·자식 출력, rw-p mapping을 검사한다. pagemap 권한 거절은 그 상태를
출력한다. PFN 0은 권한에 의해 가려진 값일 수 있다. kernel·container 정책에 따라 ptrace 자체가
제한되면 syscall LAB은 실패하며 결과를 생략해 성공으로 처리하지 않는다.
베이스 이미지는 Debian bookworm-slim이며 패키지 업데이트에 따라 결과 환경이 달라질 수 있다. 실행 시 kernel·ISA·GCC·strace 버전을 출력한다.
Apple Silicon에서 x86-64 커널을 포함한 전체 LAB 검증:
python3 tools/ch8/qemu-lab.py
QEMU full-system TCG로 별도 guest를 실행한다. Orb의 amd64 userspace에서 strace trace가 불완전할 때 사용할 수 있다. guest 종료 뒤 임시 rootfs를 자동 정리한다.
flows.c
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
int main(void) {
setvbuf(stdout, NULL, _IONBF, 0);
pid_t child = fork();
if (child < 0) { perror("fork"); return 1; }
struct timespec pause = {.tv_sec = 0, .tv_nsec = 20000000};
for (int i = 0; i < 3; ++i) {
printf("%s pid=%ld step=%d\n", child == 0 ? "child" : "parent",
(long)getpid(), i);
nanosleep(&pause, NULL);
}
if (child > 0) {
int status;
if (waitpid(child, &status, 0) < 0) { perror("waitpid"); return 1; }
return WIFEXITED(status) ? WEXITSTATUS(status) : 1;
}
return 0;
}
hello.c
#include <unistd.h>
int main(void) {
static const char message[] = "hello, world\n";
ssize_t written = write(STDOUT_FILENO, message, sizeof message - 1);
return written == (ssize_t)(sizeof message - 1) ? 0 : 1;
}
maps.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
int main(void) {
long size = sysconf(_SC_PAGESIZE);
if (size <= 0) return 1;
char *page = mmap(NULL, (size_t)size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) { perror("mmap"); return 1; }
page[0] = 'A';
printf("pid=%ld page=%p value=%c\n", (long)getpid(), (void *)page, page[0]);
fflush(stdout);
sleep(3);
return munmap(page, (size_t)size) == 0 ? 0 : 1;
}
verify.sh
#!/bin/sh
set -eu
lab_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
lab_build=$(mktemp -d)
trap 'rm -rf "$lab_build"' EXIT
cd "$lab_build"
uname -srmo
gcc --version | head -1
strace --version | head -1
for name in hello flows maps; do
gcc -std=c11 -Wall -Wextra -Werror -O0 -g "$lab_dir/$name.c" -o "$name"
done
strace -qq -e trace=write,exit_group -o trace.txt ./hello > hello.txt
cat hello.txt trace.txt
test "$(cat hello.txt)" = 'hello, world'
grep -E 'write\(1, "hello, world\\n", 13\)[[:space:]]+= 13' trace.txt
grep -F 'exit_group(0)' trace.txt
./flows > flows.txt
cat flows.txt
test "$(grep -c '^parent ' flows.txt)" -eq 3
test "$(grep -c '^child ' flows.txt)" -eq 3
./maps > maps.txt &
lab_pid=$!
python3 - "$lab_pid" maps.txt <<'PY'
import pathlib, struct, sys, time
pid, output = sys.argv[1:]
for _ in range(100):
text = pathlib.Path(output).read_text()
if 'page=' in text:
break
time.sleep(.01)
else:
raise SystemExit('mapping process did not become ready')
print(text.strip())
address = int(text.split('page=')[1].split()[0], 16)
lines = pathlib.Path(f'/proc/{pid}/maps').read_text().splitlines()
mapping = next(line for line in lines
if int(line.split('-')[0], 16) <= address
< int(line.split('-')[1].split()[0], 16))
print('maps:', mapping.rstrip())
assert mapping.split()[1] == 'rw-p', mapping
try:
with open(f'/proc/{pid}/pagemap', 'rb') as f:
import os
f.seek((address // os.sysconf('SC_PAGE_SIZE')) * 8)
entry, = struct.unpack('Q', f.read(8))
print('pagemap: present=%d PFN=%d (PFN visibility depends on CAP_SYS_ADMIN)'
% ((entry >> 63) & 1, entry & ((1 << 55) - 1)))
assert (entry >> 63) & 1, 'touched page should be resident during this short LAB'
except PermissionError as e:
print('pagemap: permission denied:', e)
PY
wait "$lab_pid"
echo 'PASS: write/exit_group, parent-child flows, private writable mapping'