LWN Weekly Report: 2026년 8월 13일 주요 뉴스

이번 주 LWN.net 위클리 에디션 주요 내용이다. 30년 가까이 큰 변화가 없던 binfmt_misc에 BPF 프로그램이 인터프리터를 고르는 인터페이스가 들어오고, 반대로 블록 계층 오류 주입에서는 BPF 기반 설계가 명시적으로 거부됐다. 단일 가상 머신 안에 여러 보안 도메인을 두는 CPU 기능들을 하나로 묶는 KVM planes, BPF 검증기가 보장하지 못하는 영역을 형식 검증으로 메우자는 LSFMM+BPF 세션, 주기적 패스워드 만료 기능을 걷어낸 shadow-utils 4.20.0, 그리고 ESP32 기반 소형 전자책 리더용 오픈소스 펌웨어까지 다룬다. 커널 7.2-rc7이 나왔고 다음 주말 정식 릴리스가 예상된다.

  • Bringing BPF to binfmt_misc — Nix의 재배치 가능 바이너리 문제에서 출발해, BPF 프로그램이 실행 파일의 인터프리터를 결정하게 된 과정
  • KVM planes head for takeoff — Arm Realm Plane, AMD VMPL, Intel TDX 파티션, Hyper-V VTL을 하나의 추상화로 묶으려는 시도
  • Even more formal verification for BPF — 커널이 안 죽는 것만으로는 부족하다, 도메인별 불변조건을 Verus로 증명하자는 제안
  • Changes in shadow-utils password-expiration features — NIST가 금지한 주기적 패스워드 변경, 그런데도 기능을 다 지우지 못하는 이유
  • Block-layer error injection — 디스크에 원하는 오류를 원하는 섹터에 주입하는 debugfs 인터페이스, 그리고 BPF 제안이 거부된 이유
  • A look at CrossPoint e-reader firmware — 수백 KB RAM으로 돌아가는 소형 전자책 리더용 대체 펌웨어, CJK 렌더링 개선
  • 짧은 소식: 커널 7.2-rc7과 첫 기여자 599명 기록, OpenSSH의 릴리스 주기 단축, Django 연간 릴리스 전환, LightDM 4년 만의 부활, QEMU 11.1, Rust Coreutils 0.10

binfmt_misc에 BPF 인터프리터 선택기가 들어온다

커널은 ELF 네이티브 바이너리와 #!로 시작하는 스크립트를 실행할 줄 안다. 그 외의 형식은 1997년 2.1.43pre1에 들어온 binfmt_misc가 담당한다. /proc/sys/fs/binfmt_misc/register에 문자열을 쓰면 새 형식을 등록할 수 있는데, 인식 기준은 파일 앞부분 256바이트의 패턴이거나 파일 확장자다.

# 앞 4바이트가 \x0eDEX인 파일을 /usr/bin/dosexec로 실행
:DEXE:M::\x0eDEX::/usr/bin/dosexec:

# .py 확장자에 실행 권한이 있으면 #! 줄이 없어도 python3로 실행
:python:E::py::/usr/bin/python3:

항목을 여러 개 등록하면 첫 번째로 매칭되는 것이 선택되는데, 검사 순서는 기대와 반대로 가장 최근에 추가한 항목부터다. 이 기능은 도입 이래 근본적인 변화가 거의 없었지만, 이번에 상당한 확장을 앞두고 있다.

발단은 Nix 배포판이었다. 지난 6월 Farid Zakaria는 파일시스템 어디에 놓여도 동작하는 자기완결적(hermetic) 바이너리가 필요하다고 문제를 제기했다. 동적 링커의 위치는 ELF의 PT_INTERP 세그먼트에 들어있는데 이게 절대 경로여야 한다는 게 문제다. Zakaria는 PT_INTERP에 실행 파일 위치 기준 상대 경로를 허용하자고 제안했고, Kees Cook은 커널에서 경로를 조작하는 데 따르는 보안 문제를 우려했다. Christian Brauner는 더 강하게 반대하며 이 접근이 “악성 로더 주입 공격에 무르익은” 것이라고 했고, 대신 binfmt_misc에 BPF 프로그램을 등록해 인터프리터를 고르게 하자고 제안했다.

그리고 2주도 안 돼 Brauner 본인이 그 기능을 구현한 패치셋을 들고 왔다. 휴가 중에도 개발을 이어간 끝에 7월 14일 판이 최종 형태가 될 것으로 보인다. BPF 쪽은 struct_ops 인터페이스로, 콜백 두 개를 가진 구조체를 등록한다.

struct binfmt_misc_ops {
	bool (*match)(struct linux_binprm *bprm);
	int (*load)(struct linux_binprm *bprm);
	char name[BINFMT_MISC_OPS_NAME_MAX];
};

match()는 실행하려는 프로그램을 이 BPF 프로그램이 담당할지 판단한다. 기존 binfmt_misc의 바이트 패턴·확장자 검사와 같은 역할이지만, sleepable이고 실행 파일의 어느 부분이든 읽을 수 있어 256바이트 제한이 없다. match()가 참을 반환하면 load()가 호출돼 bpf_binprm_set_interp() kfunc로 인터프리터를 지정하는데, 이때 load()는 반드시 성공해야 하고 실패하면 프로그램 실행 자체가 실패한다. 등록은 :name:B::::bpf-handler-name: 형태의 문자열을 register에 쓰면 된다. Zakaria는 이 기능으로 Nix의 요구가 충족된다고 확인했고, 덤으로 #! 줄의 인터프리터도 재배치 가능해진다고 지적했다.

ps에 인터프리터 이름이 뜨는 문제

Zakaria가 지적한 작은 문제가 하나 있었다. 실제로 실행되는 프로그램은 인터프리터이고, 사용자가 실행했다고 생각한 프로그램은 인자로 넘어간다. 스크립트를 실행할 때 ps에 스크립트가 아니라 인터프리터가 보이는 것과 같은 현상인데, 시스템 전체가 동적 링커를 실행 중인 것으로 보이면 불만이 나올 만하다.

Brauner의 답은 21개짜리 패치 시리즈였다. 여기에 모드 두 개가 추가된다. T 플래그로 선택하는 “transparent dispatch” 모드는 커널이 바이너리를 직접 실행하는 것처럼 행동하면서 뒤에서 인터프리터를 대신 끼워 넣는다. argv와 task 명령 문자열은 execve()에 넘어온 그대로 유지되고, 인터프리터는 보조 벡터의 AT_EXECFD 항목으로 열린 파일 디스크립터를 받아 알아서 로드한다. 실행할 프로그램이 대상 시스템의 네이티브 바이너리인 경우에는 더 단순한 L 플래그(“loader substitution”) 모드로 충분하다. BPF 프로그램이 반환한 인터프리터가 실행 파일에 박힌 PT_INTERP를 덮어쓰기만 하며, 동적 링커 위치만 알아내면 되는 Nix 케이스가 여기 해당한다.

남은 문제는 경로 해석이다. BPF 프로그램은 인터프리터의 경로 이름을 정할 수 있지만 그 경로가 어떻게 해석될지는 통제하지 못하고, 해석은 프로그램이 호출된 네임스페이스에서 일어난다. 그 네임스페이스에서 권한을 가진 누군가가 새 파일시스템을 마운트해 자기 “인터프리터”로 유도할 수 있고, setuid 프로그램에서는 특히 위험하다. 일반 binfmt_misc 항목은 커널이 인터프리터 파일을 미리 열어두는 기능이 2016년에 추가돼 있지만, BPF는 경로를 임의로 정할 수 있어 미리 열어둘 대상을 특정하기 어렵다. 해법은 설정 시점에 커널이 열어둘 인터프리터 목록을 등록해두고 BPF 프로그램은 그중에서 이름으로 고르게 하는 것이다.

cd /proc/sys/fs/binfmt_misc
echo ':qemu:B::::qemu_user:D' > register   # D = 비활성 상태로 생성
echo '+aarch64 /usr/bin/qemu-aarch64' > qemu
echo '+arm /usr/bin/qemu-arm' > qemu
echo 1 > qemu                                # 활성화

기존 커널은 register에 한 번 쓰면 항목이 즉시 전역에 보이기 때문에 절반만 설정된 상태를 둘 수 없었고, 한 줄 길이 제한 때문에 인터프리터를 여러 개 넣기도 어려웠다. 위의 D 플래그로 만드는 “disabled” 모드가 두 문제를 함께 푼다. 이후 load() 콜백은 전체 경로 대신 aarch64 같은 이름만 넘기면 되고, 어느 네임스페이스에서 호출됐든 커널이 열어둔 같은 파일이 쓰인다. 마지막으로 사용자별로 열어둘 수 있는 인터프리터 파일 개수를 제한하는 노브도 추가됐다. 사용자 네임스페이스 안의 악의적 사용자가 “인터프리터” 파일을 잔뜩 열어 자원을 고갈시키는 것을 막기 위해서다. 이 자원 제한 시리즈를 제외한 나머지는 이미 linux-next에 있어 7.3 머지 윈도우에 들어갈 전망이다.

KVM planes: 한 가상 머신 안의 여러 보안 도메인

가상화는 게스트를 별도 보안 도메인에 넣는다. 그런데 최근에는 가상화된 시스템 내부에 또 여러 보안 도메인을 만들려는 요구가 늘고 있다. 예를 들어 가상 머신 안에 TPM을 소프트웨어로 구현하는 작은 커널을 두는 경우, 가상 머신 전체가 그 TPM을 조작할 수 있다면 결과를 신뢰할 수 없다. TPM만 접근 가능한 메모리 영역을 두고 그 CPU 상태를 바깥에서 바꿀 수 없게 해야 의미가 있다.

문제는 CPU 벤더마다 이 기능을 다르게 구현했다는 점이다.

플랫폼기능 이름특징
ArmRealm Planes메모리 보호·예외 처리 등에 대해 권한 수준이 다름
AMD SEV-SNPVMPL (virtual machine privilege level)최대 4단계, x86 링처럼 0번이 최고 권한
Intel TDXpartitions
Microsoft Hyper-VVTL (Virtual Trust Level)보안 커널과 “trustlet” 개념, VTL0에서 시작해 더 높은 VTL1을 생성

Jörg Rödel, Paolo Bonzini 등이 개발 중인 KVM planes는 이 모두를 리눅스에서 공통으로 쓸 수 있게 하는 추상화 계층이다. 이름을 고른 이유는 문서 패치에 솔직하게 적혀 있다. “x86은 이걸 부르는 이름이 셋이고 Arm은 하나니, bikeshedding을 피하고 모두를(아마 KVM/arm64 쪽도 포함해서) 골고루 불쾌하게 만들기 위해 모든 아키텍처에 Arm 이름을 쓴다.”

plane은 프로세서나 하이퍼바이저가 구현하는 권한 수준 하나를 감싼다. 모든 plane은 같은 주소 공간과 상당수 프로세서 자원을 공유하지만 CPU 레지스터 셋은 각자 갖는다. plane 사이 전환은 하이퍼바이저 호출로 처리하며, 각 plane은 정수 ID로 식별되는데 ID와 권한 수준 사이에 정해진 관계는 없다. 가상 머신은 생성 시 가장 높은 권한의 plane 하나를 갖고 시작하고, KVM_CREATE_PLANE ioctl로 새 plane을 만들면 그 plane을 제어할 파일 디스크립터가 반환된다. 가상 CPU는 plane 안에 속하며, 어떤 plane의 가상 CPU 집합은 그보다 권한이 높은 모든 plane의 부분집합이어야 한다.

한 가상 CPU에서는 한 번에 하나의 plane만 실행된다. 현재 패치셋의 정책은 실행 가능한 plane이 여럿이면 번호가 가장 낮은 것을 고르는 것인데, Bonzini가 이 결정에 의문을 제기했다. 기사 댓글에서 그가 직접 밝힌 배경이 흥미롭다. Hyper-V VBS에서는 시작 VTL이 유일한 동안만 최고 권한(VTL0)이고, 이후 자신을 깎아내릴 수 있는 더 높은 VTL1을 만든다. 반면 AMD VMPL은 x86 링과 같은 번호 체계라 VMPL0이 최고 권한이고 여기서 더 낮은 레벨을 만든다. 방향이 정반대라, 코어 코드는 어느 번호가 더 높은 권한인지에 대해 아무 선호도 갖지 않는 편이 낫다는 것이다.

패치셋에는 AMD SEV-SNP 지원이 포함돼 있어, Coconut secure VM service module을 VMPL0에 설치하고 리눅스를 VMPL2에서 돌린다. Sriram Nambakam이 올린 별도 패치셋은 Hyper-V VTL과 planes를 엮어 보안 커널을 VTL1, 게스트 커널을 VTL0에서 실행하며 하이퍼바이저 강제 커널 무결성(HEKI)까지 시연한다. 다만 이쪽은 개별 패치 대부분에 changelog조차 없을 정도로 문서가 부실해 리뷰가 어렵다는 지적을 받았다.

BPF에 도메인별 형식 검증을 더할 수 있을까

BPF 검증기는 프로그램이 커널의 불변조건을 깨지 못하게 막는다. 잘못된 컨텍스트에서 락을 잡거나 커널 함수를 틀린 타입의 인자로 호출하는 일은 걸러진다. Meta의 Kumar Kartikeya Dwivedi는 2026 LSFMM+BPF 서밋 세션에서 그것만으로는 충분하지 않다고 주장했다. 커널이 죽지 않는 것은 리눅스 시스템 전체가 쓸 만한 상태로 유지되는 것의 일부일 뿐이라는 얘기다.

Meta에서는 매달 한두 번씩 sched_ext 워치독이 스케줄러를 커널에서 쫓아낸다. 대개 개발자가 예상하지 못한 특정 하드웨어와 워크로드 조합의 코너 케이스다. 더 나쁜 것은 아예 실패하지 않고 성능 회귀만 일으키는 경우로, 탐지도 진단도 어렵다. 스케줄링 버그 상당수가 부하가 낮을 때만 나타나는 것도 배포 전 테스트를 어렵게 만든다. Dwivedi가 보여준 슬라이드의 사례는 저부하에서 서버 처리량이 떨어지는 버그였는데, 엔지니어가 실험하다 우연히 발견해서 잡혔다. XDP 로드 밸런싱도 같은 문제다. 프로그램이 네트워크 트래픽을 떨구기 시작하면 서버에 원격 접근 자체가 끊긴다. 하트비트를 듣다가 네트워크가 끊기면 XDP 프로그램을 걷어내는 데몬으로 우회할 수는 있지만, 결국 추가 검사가 필요하다는 뜻이다.

두 경우 모두 런타임에 검사되는 도메인별 제약이며, 이론적으로는 정적 검증이 불가능하지 않다. 스케줄러는 “실행 가능한 태스크가 있는데 CPU를 놀리지 않는다”를, XDP 프로그램은 “모든 패킷을 어딘가로 라우팅한다”를 증명할 수 있을 것이다. 다만 그런 검증은 지금 검증기의 능력 밖이고, 애초에 그 보장을 일부러 깨고 싶은 경우도 있다(동시성 버그를 드러내려고 일부러 나쁘게 스케줄링하는 concurrency-fuzz-scheduler 같은 것). 어떤 속성이 정확성에 중요한지가 문맥에 따라 달라진다는 얘기다. 청중 한 명이 성능 속성도 프로그램 정확성의 일부로 봐야 한다는 뜻이냐고 묻자 Dwivedi는 동의했다. “쓰임에 따라서는 성능 관련 동작도 다른 속성만큼이나 안전 속성이다.”

커널 인터페이스가 정적 분석을 염두에 두고 설계되지 않는다는 점도 짚었다. 그는 bpf_obj_new()를 지나고 보니 나빴던 인터페이스의 예로 들었다. BPF 프로그램이 자기 자료구조를 조립하게 하려던 의도였지만 커널 객체 수명 관리 제약을 암묵적으로 물려받았고, 그 제약이 프로그램 전체로 전파되면서 API를 쓰기 어렵게 만들었다. 유연성이 떨어지더라도 검증기가 확인할 것이 적은 단순한 인터페이스가 다루기 쉽다는 것이다.

그가 제안한 방향은 영역별로 다른 자동 검증 도구를 쓰는 것이다. Rust 프로그램의 속성을 자동 증명하는 Verus로, 커널 안전성 자체는 아니지만 BPF 프로그램의 기능에 결정적인 속성을 다루자는 얘기다. 예시로 든 것이 앞의 스케줄러 불변조건이다. Inria와 시드니대 등의 형식 검증 가능 스케줄링 연구에 기반해, 큐 조작 함수를 push()pop() 둘로만 단순화한 인터페이스를 만든다. 태스크를 어떤 CPU 큐에 넣으라는 요청이 오면 push()는 그 CPU가 이미 유휴였거나 다른 유휴 CPU가 없을 때만 그대로 넣고, 유휴 CPU가 있으면 그쪽 큐에 넣는다. pop()은 대칭이다. 이 둘이 불변조건을 유지한다는 것을 증명하고 큐 사이로 태스크를 옮길 다른 경로가 없다면, 메인 스케줄러 로직이 무엇을 하든 불변조건은 유지된다.

구현 그림은 이렇다. sched_ext 같은 서브시스템이 노출하는 인터페이스에 단순화된 Rust 래퍼를 씌우고 Verus 증명을 주석으로 붙인다. Verus가 컴파일 타임에 증명을 확인하고, rustc가 평소대로 BPF 바이트코드를 뽑고, 커널 내 검증기가 통상적인 안전 속성을 확인한다. Alexei Starovoitov는 정적 분석 파이프라인 두 개를 나란히 돌리는 게 현실적인지 회의적이었지만, 제안 자체에는 문제를 못 느끼겠다고 했다. 세션은 시간이 다 돼 끝났고 회의적인 개발자도 있어 보였지만, 추가 정적 검증은 프로젝트 단위로 도입할 수 있다는 점이 이 제안의 현실성을 받쳐준다.

shadow-utils, 주기적 패스워드 만료를 걷어내다

/etc/shadow/etc/passwd를 다루는 도구 모음인 shadow-utils의 4.20.0 릴리스는 추가된 것이 아니라 제거된 것으로 주목할 만하다. 2025년 12월 4.19.0에서 deprecated로 표시했던 주기적 패스워드 만료 관련 유틸리티와 기능이 예정대로 빠졌다. 기사는 shadow-utils 메인테이너 중 한 명인 Alejandro Colomar가 기고했다.

주기적 강제 변경은 아주 최근까지도 관행이었지만 근거가 무너진 지 오래다. 2015년 논문 “Quantifying the security advantage of password expiration policies”는 그 이득이 “잘해야 비교적 미미하고, 전체 비용을 감안하면 의심스럽다”고 결론냈다. NIST는 2017년 디지털 신원 가이드라인 800-63B 개정3에서 주기적 만료를 권장하지 않는다고 밝혔고, 2018년 FAQ에서 이유를 설명했다. 자주 바꿔야 한다는 걸 아는 사용자는 “숫자를 하나 올리는 식의 흔한 변형을 적용해 이전 비밀과 비슷한 것을 고르는” 경향이 있어, 공격자가 변경 방식을 예측할 수 있다는 것이다. 2025년 개정4에서는 표현이 더 강해져 아예 금지가 됐다.

검증자와 CSP는 가입자에게 주기적으로 패스워드를 변경하도록 요구해서는 안 된다(SHALL NOT). 다만 인증자가 침해됐다는 증거가 있으면 변경을 강제해야 한다(SHALL).

구분대상사유
제거됨expiry 명령만료 정책 확인·강제용. getentpasswd -e와 기능이 겹쳤다
제거됨/etc/shadow 4번째 필드(최소 변경 주기)무시되고 있으면 제거된다. 침해 시 즉시 변경을 막을 수 있어 오히려 취약점
제거됨groupmemsusermod와 기능 중복. 배포판이 setuid root로 설치하지 않아 비루트 사용자에겐 무용지물이었다
제거됨DES·MD5 해시 지원기본값이 SHA512로 변경. 단 libpam 등 다른 프로그램도 login.defs를 읽으므로 ENCRYPT_METHOD는 계속 명시하는 편이 안전하다
제거됨logoutd/etc/porttime 기반 로그인 시간·포트 제한. FHS에도 없고 주요 배포판 어디도 패키징하지 않는다
deprecatedchage, passwd, useradd, usermod의 만료 관련 옵션최소 수년간 유지 후 제거 예정
deprecated/etc/shadow 3·5·6·7번째 필드마지막 변경일, 최대 사용 기간, 경고 기간, 비활성 기간

기능을 한 번에 다 지우지 못하는 이유는 기술이 아니라 정책이다. 일부 국가와 계약이 여전히 패스워드 만료를 요구한다. 메인테이너들은 조사 과정에서 스페인 국가정보원(CNI)이 만료를 요구하는 정책을 두고 chage 등을 사용하는 준수용 스크립트를 공개 배포하고 있음을 발견했다. CNI에 연락을 시도했지만 답을 받지 못했다. 결국 4.20.0에서 최소 패스워드 사용 기간 강제는 우회 방법 없이 사라졌고, 나머지 deprecated 기능은 최소 몇 년은 유지될 전망이다. 불필요한 주기적 변경을 요구하는 정책을 운영 중이라면 지금부터 단계적 폐지를 계획해두는 편이 좋다.

블록 계층 오류 주입: 원하는 오류를 원하는 섹터에

스토리지 코드는 하드웨어가 불편한 방식으로 고장 나는 상황을 감당해야 하는데, 멀쩡한 디스크에서 그런 실패를 원할 때 만들어내기는 어렵다. 커널에 블록 계층 I/O 오류를 주입하는 방법이 여럿 있지만, 어떤 연산을 실패시킬지 고르거나, 반환할 상태 코드를 정하거나, 스택 디바이스 없이 디스크를 직접 겨냥하는 것은 어느 쪽도 못 한다. Christoph Hellwig의 패치 시리즈가 이 셋을 모두 해결하는 인터페이스를 디스크별 debugfs 파일로 추가했다.

기존 방식한계
fail_make_request (2006)확률·간격·횟수만 조절. 읽기/쓰기/discard 구분 불가, 섹터 범위 제한 불가, BLK_STS_IOERR만 반환
should_fail_bio() + BPF (2018)어떤 bio를 실패시킬지는 고를 수 있지만 어떻게 실패할지는 못 정한다. submit_bio_noacct()가 반환값을 무시하고 항상 BLK_STS_IOERR로 완료
dm-error모든 요청에 오류. 상태 코드 선택 불가, 대상 디바이스 위에 스택을 쌓아야 함
dm-flakey, dm-dust간헐적 실패·개별 배드 섹터 에뮬레이션은 되지만 상태 코드 선택은 여전히 불가. device-mapper의 정렬 규칙에 묶여 zone 경계 단위로만 범위 지정 가능(dm-dust는 zoned 디바이스 미지원)

새 방식은 CONFIG_BLK_ERROR_INJECTION을 켜면 등록된 gendisk마다 /sys/kernel/debug/block/ 아래에 error_injection 파일을 만든다. 읽으면 적용 중인 규칙 목록이 나오고, 쓰면 규칙이 추가되거나 removeall로 전부 지워진다. 필수 필드는 실패시킬 연산(op)과 반환할 블록 계층 상태(status) 둘이고, 선택 필드로 start·nr_sectors(범위 한정)와 chance(값이 N이면 1/N 확률로 실패, 기본값 1)가 있다.

# nvme0n1의 섹터 1000~1499에 대한 읽기 10건 중 1건을 전송 오류로 실패시킨다
cd /sys/kernel/debug/block/nvme0n1
echo 'add,op=READ,start=1000,nr_sectors=500,status=TRANSPORT,chance=10' > error_injection

같은 파일을 읽으면 활성 규칙이 섹터 범위·연산·상태·확률 순으로 나온다.

1000:1499 op=READ,status=TRANSPORT,chance=10

구현은 대부분 block/error-injection.c 한 파일에 들어 있다. I/O 경로의 훅은 submit_bio_noacct_nocheck()에 있고 모든 bio마다 blk_error_inject()를 호출하는데, 이 인라인 함수는 static key가 켜져 있지 않으면 아무 일도 하지 않는다. 어느 디스크든 첫 규칙이 추가될 때 키가 켜지고, 디스크별 GD_ERROR_INJECT 비트가 실제 작업 범위를 더 좁힌다. static key는 Jens Axboe의 제안이었다. 없었다면 모든 bio가 거의 항상 꺼져 있는 디스크별 상태 비트까지 포인터를 따라가야 했겠지만, 주입을 쓰지 않을 때는 분기 자체가 패치 아웃된다. 실제 매칭은 RCU 아래에서 규칙 목록을 훑으며 연산 비교, 섹터 범위 겹침 확인, chance가 있으면 N면 주사위를 굴린다. 첫 매칭 규칙이 이기고 새 규칙은 목록 앞에 붙으므로, 같은 I/O를 덮는 규칙 중에서는 나중에 추가한 것이 우선한다. 규칙이 겹치거나 중복되는 것은 의도적으로 허용된다.

매칭이 절대 걸리지 않는 경우가 하나 있다. 길이가 0인 bio는 범위 검사를 통과할 수 없어서 순수 캐시 플러시와 ZONE_RESET_ALL은 실패시킬 방법이 없다. 특수 처리를 넣는 것 자체는 쉽지만, 순수 플러시가 별도 연산이 아니라 REQ_PREFLUSH를 단 빈 쓰기로 블록 계층에 도달하기 때문에 모든 WRITE 규칙이 플러시에도 걸리기 시작한다. 제대로 하려면 blk-mq I/O 스케줄러가 이미 그러듯 bio 수준에서도 순수 플러시에 REQ_OP_FLUSH를 쓰도록 블록 계층을 먼저 고쳐야 한다.

BPF 제안이 거부된 이유

리뷰에서 Daniel Gomez가 BPF_PROG_TYPE_STRUCT_OPS 훅을 제안했다. 커널에는 bio_endio_status() 호출만 남기고 매칭 정책은 로드 가능한 프로그램으로 옮기자는 것이었는데, Hellwig은 이 일에 비해 기계장치가 과하다며 거부했다. 어떤 요청을 실패시킬지 정하려면 프로그램이 bio에서 연산과 섹터 범위를 읽어야 하고, 그것을 BTF로 노출하면 블록 계층의 명령·상태 코드가 안정적인 인터페이스가 되어버린다는 논리다. 계속 바뀌어야 하는 내부 구조를 그렇게 묶고 싶지 않다는 것이다. Gomez는 CO-RE 재배치가 실행 중인 커널의 BTF를 기준으로 필드 오프셋을 해석하니 레이아웃이 바뀌어도 기존 프로그램이 깨지지 않는다고 답했고, Hellwig은 그건 필드가 그 형태로 계속 존재한다는 전제이며 bio 이터레이터는 재작업이 예정돼 있다고 받았다.

실용적인 이유도 있었다. BPF 훅은 테스트용 가상 머신 안에 libbpf와 BTF 지원을 요구하고, 마땅한 범위 조회용 BPF 맵 타입이 없어 섹터 매칭까지 프로그램으로 옮겨야 한다. 결국 실패시킬 범위마다 코드를 따로 써야 하니 “쓰기 매우 어려워진다”는 것이다. Hellwig은 BPF 기반 주입을 직접 프로토타이핑해봤고 “엉망이었다”며, “쉘 스크립트에서 바로 쓸 수 있는 300줄 남짓한 단순한 코드”를 선호한다고 했다. 이 메커니즘은 리눅스 7.2에 머지됐다. 이후 Le Moal이 보고한 static key 불균형이 수정됐고, blktestsblock/044 테스트가 추가됐다.

CrossPoint: 소형 전자책 리더용 오픈소스 펌웨어

값싸고 저전력인 소형 전자책·전자잉크 기기들이 여럿 나와 있지만, 함께 출하되는 펌웨어가 하드웨어의 잠재력을 다 끌어내지 못한다는 공통된 문제가 있다. CrossPoint Reader 프로젝트는 그 자리를 대체할 펌웨어를 만든다. 8월 7일 나온 1.5.0은 큰 EPUB을 더 빨리 열고, 오프라인 사전 조회를 제공하며, 레이아웃·폰트 설정을 재정비했다. 오른쪽에서 왼쪽으로 쓰는 텍스트와 CJK 렌더링 개선도 포함됐다.

대상 하드웨어의 제약이 상당하다. 작년부터 세계 판매를 시작한 Xteink 4는 ESP32 기반 SoC에 77g 무게, 백라이트 없는 4.3인치 480×800(220PPI) 화면, 터치 대신 물리 버튼을 쓴다. 느린 CPU와 수백 KB 수준의 RAM이 가장 큰 제약이다. MIT 라이선스인 CrossPoint에는 200명 넘는 기여자가 모였고, ESP32-C3 또는 ESP32-S3 기반 기기 여섯 종을 지원한다. 대부분 $80 미만이고 싼 것은 $50이다. 1.5.0에서 처음으로 ESP32-S3 기기인 reTerminal Sticky가 추가됐다.

프로젝트 운영에서 눈에 띄는 점 둘. LLM 도구를 이용한 기여를 허용하되 풀 리퀘스트에 사용 사실을 밝히도록 요구한다. 그리고 LLM 사용 여부와 무관하게, 좋은 읽기 경험이라는 좁은 범위 밖의 기능은 받지 않는다. EPUB 렌더링, 라이브러리 관리, eInk 화면 최적화는 환영이지만 RSS 리더, PDF 렌더링, 작성 도구 같은 것은 자원을 너무 쓰고 배터리를 빠르게 소모시킨다는 이유로 거부된다.

기기 관리는 내장 웹 서버로 한다. 2.4GHz 무선 네트워크에 붙어 DHCP로 주소를 받고 crosspoint.local로 접속하면 파일 업로드, 디렉터리 조작, 버튼 할당·표시·레이아웃·폰트 등 모든 설정을 브라우저에서 다룰 수 있다. 다만 전부 HTTP로 서비스되고 보안 연결을 설정하지 않으므로 공용 무선 네트워크에 붙이는 것은 피하는 게 좋다. 폰트는 자체 cpfont 포맷을 쓰며 OpenType·TrueType을 직접 지원하지 않지만, 웹 기반 폰트 빌더로 변환할 수 있다. Calibre 플러그인은 복사뿐 아니라 EPUB 최적화(이미지 축소, 임베드 폰트 제거, 큰 챕터 분할)까지 해준다. 사전은 StarDict 포맷을 직접 구해 넣어야 하고, 읽기 진행 동기화는 KOReader 프로토콜을 따른다. 기사 필자는 읽기 경험을 “적당하다”고 평했는데, 소설이나 논픽션은 무난하지만 기술 콘텐츠는 약하다. LWN 기사를 EPUB으로 읽어보니 코드 스타일이 본문과 구분되지 않고 소제목도 일반 텍스트에 묻혔다고 한다.

짧은 소식

  • 커널 7.2-rc7: 8월 9일 릴리스. Linus는 “7.2 릴리스를 미룰 이유가 지금은 보이지 않으니, 정말 나쁜 일이 터지지 않는 한 다음 주말에 나올 것으로 예상한다”고 했다. 이번 릴리스는 개발자 2,628명이 올린 non-merge 체인지셋 16,223개를 담았고, 그중 599명이 첫 커널 기여자로 역대 기록이다.
  • OpenSSH 10.5: Damien Miller는 릴리스 노트에서 “AI 도구가 찾아낸 보안 버그를 이후 다른 연구자가 독립적으로 발견하는 사례를 여러 건 봤다”며, 버그를 신고하지 않는 공격자들도 같은 버그를 찾아낼 수 있다는 뜻이라고 밝혔다. 그래서 OpenSSH 팀은 당분간 수정을 다음 정기 릴리스까지 모으지 않고 릴리스 주기를 더 자주 가져가기로 했다.
  • Django 연간 릴리스 주기: 단기 피처 릴리스와 LTS를 섞던 복잡한 일정을 버리고 연 1회 릴리스로 간다. 모든 피처 릴리스가 3년 지원(1년 일반 버그 수정 + 2년 보안·데이터 손실 수정)을 받으면서 “LTS” 라벨 자체가 폐지된다. 항상 세 버전이 지원되므로 서드파티 패키지는 명확한 목표를 갖게 된다. 2028년 1월 Django 2028부터 적용된다.
  • LightDM 1.33.0: 4년 만의 릴리스다. Canonical 후원이 끝난 뒤 사실상 방치돼 있던 프로젝트가 새 커뮤니티 저장소로 이전돼 Joshua Peisach과 Neal Gompa가 유지보수를 맡았다. Qt6 지원과 그동안 묶여 있던 수정들이 들어갔다.
  • QEMU 11.1: 285명의 저자가 올린 커밋 3,200개 이상이 담겼다.
  • Rust Coreutils 0.10: GNU coreutils 호환성에 집중한 릴리스로, 테스트 690개 중 645개를 통과했다(0.9.0은 625개). mv --exchange 옵션, 체크섬 유틸리티용 OpenSSL 백엔드, mkdir·mkfifo·mknod 생성 시 SELinux 레이블 적용이 추가됐다. WebAssembly로 브라우저에서 바로 돌려보는 온라인 플레이그라운드도 있다.
  • Software Stewardship Lab 출범: 스코틀랜드에 기반한 비영리 조직으로, 의존성 그래프의 숨은 부분까지 포함해 세계적으로 중요한 오픈소스 패키지를 실시간 식별·모니터링하는 옵저버토리와 메인테이너 번아웃 연구를 진행 중이다. AI가 메인테이너 번아웃에 미치는 영향에 대한 후속 리포트도 준비하고 있다.
  • 그 외: GNOME 기여자 Tobias Bernard가 디자인 팀의 GNOME Shell 장기 구상을 정리한 글을 냈다. Debian 쪽에서는 LLM의 전력·물 사용을 둘러싼 논쟁이 이어졌는데, Ted Ts’o가 넷플릭스 시청이나 청바지 한 벌 생산과 비교하며 “이미 다른 이유로 내린 결론을 정당화하는 핑계인 경우가 있다”고 하자, Didier “OdyX” Raboud는 기술 자체가 아니라 누가 밀어붙이고 누가 어떻게 학습시키는지, 그 경제적·정치적 동기가 무엇인지를 빼놓고 이야기할 수 없다고 응수했다.

이번 주 핵심 요약

  • BPF를 두고 정반대 결론이 같은 주에 실렸다. binfmt_misc는 인터프리터 선택 정책 전체를 BPF로 옮기는 방향으로 갔고, 블록 계층 오류 주입은 BPF 제안을 거부하고 300줄짜리 debugfs 인터페이스를 택했다. 갈린 지점은 “정책을 사용자 공간으로 뺄 것인가”가 아니라 BTF로 내부 구조를 노출하는 순간 그것이 안정 인터페이스가 되어버리는가였다. bio처럼 계속 바뀌어야 하는 구조에는 Hellwig의 우려가 설득력 있다.
  • 정작 BPF 자신은 “검증이 더 필요하다”는 요구를 받고 있다. 검증기는 커널이 죽지 않는다는 것까지만 보장하는데, sched_ext 스케줄러가 매달 쫓겨나고 XDP 프로그램이 서버를 원격에서 접근 불가로 만들 수 있다면 시스템 차원의 안전은 아직 미해결이다. 성능 회귀도 안전 속성으로 봐야 한다는 Dwivedi의 주장이 이 세션의 핵심이다.
  • KVM planes는 기술적 난이도보다 벤더 간 개념 불일치가 더 까다로워 보인다. AMD VMPL은 0번이 최고 권한이고 Hyper-V VTL은 반대이며, 시작 시점의 권한 수준과 이후 생성 방향도 서로 다르다. 코어 코드가 번호와 권한의 관계에 아무 가정도 두지 않기로 한 것은 그래서 나온 선택이다.
  • shadow-utils 사례는 보안 표준이 바뀌어도 기능을 지우지 못하는 이유가 기술이 아님을 보여준다. NIST는 2025년에 주기적 만료를 금지 수준으로 못 박았지만, 국가 정책과 계약이 이를 요구하는 한 프로젝트는 deprecated 상태로 몇 년을 더 끌고 가야 한다. 메인테이너가 정책 기관에 연락했지만 답이 없었다는 대목이 현실을 잘 보여준다.
  • AI/LLM은 이번 주에도 여러 섹션에 등장했다. OpenSSH는 AI가 찾은 취약점이 공격자에게도 발견될 수 있다는 판단으로 릴리스 정책을 바꿨고, CrossPoint는 LLM 기여를 허용하되 공개를 요구하며, Debian은 환경 영향 논쟁 중이고, Software Stewardship Lab은 AI가 메인테이너 번아웃에 미치는 영향을 연구 주제로 잡았다. 도구 논의를 넘어 프로젝트 운영 정책의 문제로 자리 잡았다.

원문: LWN.net Weekly Edition for August 13, 2026

답글 남기기