Blog 121: HTTPS/TLS works, Python3 runs, ext4 read cache staleness fix
Date: 2026-03-25 Milestone: M10 Alpine Linux
Summary
Major Alpine compatibility advances in a single session:
- HTTPS/TLS 1.3 works via curl + OpenSSL on Alpine
- Python3 installs via
apk addand runs pure Python code - Ext4 read cache staleness bug fixed — large package installs now work
- UDP getsockname restored — fixes curl DNS via c-ares
- msync dispatch restored — lost to git stash
- Kernel stack overflow fixed by increasing to 8 pages (32KB)
Ext4 read cache staleness (the big fix)
Symptom
Installing Python3 via apk add python3 failed with:
ERROR: python3-3.12.12-r0: failed to rename usr/lib/.apk.xxx to usr/lib/libpython3.12.so.1.0.
APK extracts package files to temporary names (.apk.<hash>) then renames
them to their final paths. The rename failed with ENOENT — the temp file
wasn't found, even though it was just created moments before.
Root cause
The ext4 block I/O layer has a two-level cache:
- dirty_cache (BTreeMap): blocks that have been written but not flushed
- read_cache (Vec): blocks previously read from disk
read_block() checks dirty_cache first, then read_cache, then falls through
to disk. write_block() inserts into dirty_cache. When flush_dirty() fires
(dirty cache full), it writes all dirty blocks to disk and clears
dirty_cache — but did not invalidate the read_cache.
The race:
- Block X read from disk → cached in read_cache (old data)
- Block X modified (new directory entry added) → cached in dirty_cache
- dirty_cache fills up during large install →
flush_dirty()fires - dirty_cache cleared, blocks written to disk
- Block X read again → dirty_cache miss, read_cache hit with STALE data
Fix
Invalidate read_cache entries for flushed blocks in flush_dirty():
#![allow(unused)] fn main() { fn flush_dirty(&self) -> Result<()> { let entries = core::mem::take(&mut *self.dirty_cache.lock_no_irq()); // Invalidate stale read cache entries self.read_cache.lock_no_irq().retain(|e| !entries.contains_key(&e.block_num)); // Write to disk... } }
This ensures subsequent reads go to disk and get the up-to-date data.
UDP getsockname (re-applied)
The getsockname() and getpeername() implementations for UDP sockets were
lost to a git stash operation earlier. c-ares (curl's DNS resolver) calls
getsockname() after connect() on its UDP DNS socket. Without it, the
call returned EBADF, causing all DNS resolution to fail:
curl: (6) Could not resolve host: example.com
BusyBox wget worked because it uses musl's blocking DNS resolver (which doesn't call getsockname on UDP sockets).
Kernel stack overflow during TLS
Symptom
curl HTTPS caused a kernel page fault at RIP=0x0 with all-zero registers and stack. The crash was in kernel mode (CS=0x8, ring 0).
Root cause
The x86_64 kernel stack was 4 pages (16KB) — matching Linux's default. But Kevlar processes the entire TCP stack (smoltcp) inline during syscalls, unlike Linux which handles TCP in separate kernel threads. The TLS handshake creates deep call chains:
syscall → write → tcp_socket::sendto → smoltcp::dispatch →
smoltcp::tcp::process → retransmit logic → ARP handling → ...
This exceeded the 16KB stack during the complex TLS handshake, overflowing into unmapped memory (all zeros), causing the null function pointer call.
Fix
Increased kernel stack to 8 pages (32KB). This is 2x Linux's default but necessary because Kevlar's in-kernel networking has deeper call chains than Linux's separate TCP processing model.
HTTPS/TLS 1.3
With the stack fix, HTTPS works via curl + OpenSSL 3.3.6:
- DNS resolution via c-ares (UDP)
- TCP connection to port 443
- TLS 1.3 handshake (ECDHE key exchange, AES-256-GCM)
- Certificate verification (requires ca-certificates package)
- Encrypted data transfer
Currently tested with -k (skip cert verification) because
update-ca-certificates has symlink issues on our ext4. The TLS handshake
and encryption are the real kernel-level test.
Python3
Python 3.12.12 installs via apk add python3 (15 packages, ~291 MiB) and
runs pure Python code:
python3 --version— interpreter loads correctlyprint("hello")— basic I/O worksimport os; os.getpid()— syscall interface works- List comprehensions — bytecode execution works
import sys; sys.platform— standard library loads
C extension modules (math, socket, hashlib) crash with SIGSEGV. This appears
to be related to dlopen() loading .so files at runtime. Tracked for
future investigation.
Test results
- Contract tests: 159/159 PASS
- Alpine APK tests: all pass including:
- curl HTTP (DNS + TCP)
- curl HTTPS (TLS 1.3)
- Python3 install + 5 pure Python tests
- 29 ext4 filesystem tests
- Dynamic linking tests (busybox, openrc, curl, apk, file)