Lobsters

Infecting the Steam Link with NixOS

Steam Link에 NixOS 올리기

Valve 서명 커널만 부팅하는 Steam Link에서 기존 커널에 kexec 기능을 모듈로 추가해 NixOS 커널로 넘기는 방법을 설명합니다. 오래된 ARM 커널을 최신 GCC로 빌드하고 NixOS 이미지를 2.3GB에서 1.2GB로 줄인 과정과 디버깅 방법도 공개합니다.

에디터 노트

자물쇠를 자물쇠의 열쇠로 여는 연작입니다. Valve 서명 커널만 받는 Steam Link에 NixOS를 올리려, 서명된 커널로 부팅한 뒤 kexec로 넘기기로 했습니다. 그런데 kexec 기능 자체가 꺼져 있었죠. 그래서 kexec 소스를 커널 모듈로 묶어 시스템 콜을 추가했습니다. 남의 바이너리 대신 13년 된 3.8.13 커널을 최신 GCC로 직접 빌드했습니다. 화면도 UART도 없어 BusyBox initramfs로 USB에 마커를 남기며 디버깅했고, 이미지는 2.3GB에서 1.2GB로 줄였습니다. 웃긴 건 NixOS를 올리고 Nix 자체는 지웠다는 점입니다. 진짜 가치는 Steam Link가 아니라 서명 부트로더가 잠긴 ARM 기기에도 통하는 레시피라는 점입니다.

AI 요약

옷장에서 찾아낸 Steam Link를 저전력 상시 구동 ARM 기기로 활용하려고 NixOS를 올린 과정을 정리합니다. Steam Link의 부트로더는 Valve가 서명한 커널만 실행하므로 새 커널을 곧바로 부팅할 수 없습니다. 글쓴이는 Valve 커널로 먼저 부팅한 뒤 새 커널로 전환하는 kexec 방식을 택했습니다. 다만 기기에 포함된 커널은 CONFIG_KEXEC를 끈 상태였습니다. 이를 해결하려고 kexec 관련 소스 파일을 작은 커널 모듈로 묶어 실행 중인 커널에 kexec 시스템 호출을 추가했습니다.

오래된 커널과 교차 컴파일

NixOS 사용자 공간과 커널, initrd는 Nix flake에서 ARMv7 대상으로 구성합니다. Valve 도구 체인의 armv7a 설정은 Go 빌드 과정과 잘 맞지 않아, 동등한 대상으로 보이는 armv7l을 선택했습니다. 빌드 호스트는 AArch64로 두고, crossSystem 설정으로 ARMv7 하드웨어용 패키지를 만듭니다.

가장 까다로운 작업은 Steam Link에 들어 있는 13년 된 Linux 3.8.13 커널을 최신 GCC로 빌드하는 일이었습니다. ValveSoftware의 steamlink-sdk를 가져와 여러 패치를 적용하고, 오래된 코드가 새 컴파일러에서 빌드되도록 설정을 조정했습니다. CONFIG_KEXEC를 켠 커널과 모듈을 만들 때는 Kbuild가 사용할 커널 소스 트리에서 make modules를 실행해야 합니다. make modules_prepare만으로는 CONFIG_MODVERSIONS가 켜진 경우 필요한 Module.symvers가 만들어지지 않습니다. 그 결과 모듈 버전 정보와 심볼 주소가 맞지 않아 insmod가 모듈을 받아들이지 않습니다. 글쓴이는 NixOS 커널의 모듈 빌드 의존성을 활용해 오래된 커널 모듈을 빌드했습니다.

부팅 이미지 구성과 디버깅

부팅에 필요한 파일은 NixOS 커널과 initrd, Steam Link용 device tree blob(DTB), kexec 커널 모듈, 정적 빌드한 kexec 실행 파일입니다. 이 파일들을 USB 이미지의 FAT 파티션에 넣고, Steam Link의 factory_test/run.sh에서 실행할 스크립트가 기존 시스템에 모듈을 적재한 뒤 새 커널을 불러오고 실행합니다. 재부팅 몇 번 뒤 공장 초기화가 진행되는 일을 막으려고 steamlink.crashcounter도 초기화합니다.

첫 시험에는 화면 출력과 UART를 이용할 수 없어 부팅 성공 여부를 확인하기 어려웠습니다. 먼저 BusyBox 기반의 작은 initramfs를 만들어 USB 파티션을 찾아 마커 파일을 기록한 뒤 재부팅하게 했습니다. 이 방식으로 kexec 전환과 USB 접근을 확인한 다음, 네트워크 기능을 켠 NixOS initrd에 Netcat 역방향 셸을 붙여 추가 문제를 조사했습니다. USB 저장 장치 접근에 reset_berlin 커널 모듈이 필요했고, systemd 기반 initrd 대신 기존 initrd 체계를 써야 했습니다. 이후 사용자 공간까지 부팅해 SSH 접속에 성공했습니다.

이미지 크기 줄이기

처음 만든 USB 이미지는 2.3GB였습니다. 글쓴이는 minimal, headless 프로필을 적용하고 부트로더와 문서, NetworkManager, 방화벽, Nix 등 장치에서 쓰지 않는 기능을 줄였습니다. 전체 linux-firmware 패키지 대신 필요한 Marvell 펌웨어 파일 하나만 넣었습니다. 이 패키지는 압축 상태에서도 1.8GB에 달해 필요한 파일만 고르는 효과가 컸습니다.

줄이는 과정에서 OpenSSH가 i18n.glibcLocales나 security.wrappers 설정을 제거하면 제대로 동작하지 않는 문제도 확인했습니다. kexec로 커널을 넘기므로 NixOS 시스템 관리 도구 대부분이 필요하지 않았지만, initrd와 커널을 끄면 부팅이 깨져 제거하지 못했습니다. systemd 기반 initrd를 쓰지 못해 Perl을 빼는 perlless 프로필도 적용하지 못했습니다. 추가 변경이 시스템을 망가뜨리는 지점에 이르러 최종 이미지는 1.2GB로 정리했습니다.

Lobsters 반응

  • @k749gtnc9l3w — Nix나 Steam Link에만 국한되지 않는 점이 더 흥미롭습니다. 비공식 kexec 모듈을 위한 Nix 표현식을 연결하거나 사용하거나 제공하므로, 부트로더가 서명을 요구하고 kexec가 빠져 있어도 root 권한과 커널 적재 권한을 얻은 시스템을 원하는 커널로 부팅할 수 있습니다.

원문: Feyor / 번역·요약: Trawling