Go Runtime Bug on 32-bit Embedded Systems: A Deep Dive into netpoll Aliasing
A sporadic crash in Go's netpoll on 32-bit ARM systems traced to a pointer aliasing bug in the runtime, fixed after years of reports.

A Go application crashing on a 32-bit ARM embedded Linux system turned out to be a runtime bug, not an application bug. The fatal error runtime: netpoll: eventfd ready for something unexpected pointed to an internal assumption in Go's netpoll mechanism.
The root cause: on 32-bit little-endian systems, Go's tagged pointer logic packs a 32-bit address and tag bits into an 8-byte word. The ev.Data field in epoll events stores either a raw pointer to netpollEventFd or a tagged pointer to a pollDesc. When comparing ev.Data to the event fd's address, the code casts to uintptr, which on 32-bit is only 4 bytes, so it only checks the lower half of the 8-byte field.
This aliasing means the fdseq tag (a counter for recycled pollDesc objects) can match the address of netpollEventFd once it grows into the millions. Long-running programs that create many pollDesc objects eventually hit this, causing netpoll to mistake a socket fd for the event fd.
The bug was introduced in Go 1.14 (2020) and went unnoticed until March 2025, with reports only from 32-bit ARM and i386 systems. The fix, merged in 2026, stores a tagged nil pollDesc for the event fd instead of a raw pointer, eliminating the aliasing.
This story highlights the importance of testing on less-common architectures. The Go team's own lack of 32-bit testing likely allowed this bug to persist for years.
The Go runtime stores both a raw pointer and tagged pointers in the same field, and on 32-bit little-endian systems, the raw pointer aliases with the fdseq tag, causing netpoll to mistake a socket fd for the event fd.
| Case | Lower 4 bytes | Upper 4 bytes | Comparison result |
|---|---|---|---|
| Tagged pollDesc | fdseq (e.g., 0x00000012) | *pollDesc (e.g., 0x00123456) | Lower bytes match fdseq, not event fd |
| Raw event fd pointer | &netpollEventFd (e.g., 0x00123456) | 0 (untouched) | Lower bytes match event fd address |
Discussion
0 Comments
Be the first to start the discussion.