Blog 102: Alpine Linux root login on Kevlar — OpenRC boots, shell works

Date: 2026-03-21 Milestone: M10 Alpine Linux

Context

Blog 101 fixed the pipe crash (PIE relocation pre-faulting). This session pushed through to a working Alpine login — fixing the remaining blockers one by one with systematic tracing.


Fix 1: Interpreter pre-fault (SIGSEGV at 0x19)

The blog 101 pre-fault fix only covered the main executable's writable data pages. musl's interpreter also has a writable LOAD segment (vaddr=0xa1aa0, filesz=0x964) that needs pre-faulting. Without it, fork children during OpenRC service execution hit SIGSEGV at address 0x19 (another unpatched relocation value).

Fix: refactored prefault_writable_segments() helper, called for both main binary and interpreter ELF segments.


Fix 2: Unix socket STREAM connect → ECONNREFUSED

Root cause traced with syscall debug:

socket(AF_UNIX, SOCK_STREAM) → fd 3
connect(3, "/var/run/nscd/socket") → 0     ← BUG: should be ECONNREFUSED
sendmsg(3, ...) → -107 ENOTCONN

musl's initgroups() tries to connect to nscd (name service cache daemon) via a Unix socket. Our connect() returned success for non-existent listener paths — even for SOCK_STREAM where POSIX requires ECONNREFUSED. The stale ENOTCONN errno propagated through initgroups → getgrouplist → setgroups, causing BusyBox login to report "can't set groups: Socket not connected".

Fix: return ECONNREFUSED for SOCK_STREAM connect to non-existent listeners. SOCK_DGRAM still returns success (systemd sd_notify pattern).

Verified with test_login_flow.c:

  • setgroups(0, NULL) → 0 ✓
  • initgroups("root", 0) → 0 ✓ (was -1/ENOTCONN)
  • getgrouplist("root", 0, ...) → 12 groups ✓

Fix 3: pivot_root syscall

Implemented real pivot_root(new_root, put_old):

  • Looks up filesystem mounted at new_root
  • Makes its root directory the new root via set_root()
  • Resets cwd to /
  • Added get_mount_at_dir() to find mounted filesystems

This eliminates the /mnt/root/ path prefix that broke OpenRC in blog 100. OpenRC now starts cleanly without chroot path artifacts.


Fix 4: make run-alpine target

Added make run-alpine Makefile target:

  • First run builds ext4 image from Docker (alpine:3.21 + openrc)
  • Configures ttyS0 serial getty, empty root password
  • Subsequent runs reuse cached build/alpine.img

Alpine Boot Output

   OpenRC 0.55.1 is starting up Linux 6.19.8 (x86_64) [DOCKER]

 * /proc is already mounted
 * Mounting /run ...                                                [ ok ]
 * /run/openrc: creating directory
 * /run/openrc: correcting mode
 * /run/lock: creating directory
 * /run/lock: correcting mode
 * /run/lock: correcting owner
 * Caching service dependencies ...                                 [ ok ]

Welcome to Alpine Linux 3.21
Kernel 6.19.8 on an x86_64 (/dev/ttyS0)

kevlar login: root
Welcome to Alpine!

The Alpine Wiki contains a large amount of how-to guides and general
information about administrating Alpine systems.
See <https://wiki.alpinelinux.org/>.

login[31]: root login on 'ttyS0'
kevlar:~#

Known Issues

IssueSeverityNotes
1 null pointer SIGSEGV (pid=21) during OpenRC bootLowNon-fatal, OpenRC recovers
apk update → "Error loading libz.so.1"MediumLibrary at /usr/lib/ not found by dynamic linker
/dev/tty1-6 not foundNoneStock inittab, harmless
Clock skew warningsNoneNo RTC, expected

Session Statistics

MetricValue
Commits this session15+
Contract tests118/118 PASS
Benchmarks0 REGRESSION
ext4 integration30/30 PASS
Alpine bootLogin works
New syscallspivot_root
Bug fixesPIE pre-fault (main+interp), ECONNREFUSED, SIGPIPE
Test programs written7 (pipe isolation) + 2 (login flow, Alpine shell)
Debug toolingpage_trace.rs (PTE walker, stack dumper)