Hunting Down a Go Runtime Bug on 32-Bit Embedded Systems
A Go application experienced intermittent crashes on a 32-bit ARM embedded Linux system due to a bug in Go’s netpoll mechanism. The crash occurred when the netpoll code expected only the EPOLLIN event but received something else, specifically EPOLLIN|EPOLLOUT. The issue was traced back to the way Go represented tagged pointers in memory on 32-bit platforms.
On 32-bit systems, Go packs a full 32-bit address and up to 32 tag bits into an 8-byte word, with the tag in the lower 4 bytes and the address in the upper 4 bytes. When comparing the ev.Data field with the address of the netpollEventFd object, only the lower 4 bytes were considered after converting ev.Data to uintptr. This caused an aliasing issue when the fdseq counter in the tagged pointer matched &netpollEventFd, leading the netpoll logic to incorrectly identify a socket file descriptor as an event file descriptor.
The bug had been reported in the Go project’s issue tracker since March 2025, but rejected attempts to fix it. After investigating the issue internally, a fix was proposed that changed how netpoll distinguished event file descriptors (event fds) from socket file descriptors (socket fds). Instead of storing the raw pointer to netpollEventFd, the fix stored a nil pollDesc as a tagged pointer.
When unpacking the tagged pointer, if it yielded nil, the event belonged to the event fd; otherwise, it belonged to a socket fd. This alteration eliminated the aliasing problem.
The fix was subsequently merged into the Go runtime, resolving the sporadic crashes on 32-bit ARM embedded Linux systems.
Written by urgent.news from Hacker News's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.