News

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.

August 29, 2026· 2 min read· Source: sigma star gmbh
Go Runtime Bug on 32-bit Embedded Systems: A Deep Dive into netpoll Aliasing

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.
Manul X Editorial
Pointer layout in ev.Data on 32-bit little-endian systems
At a glance
CaseLower 4 bytesUpper 4 bytesComparison result
Tagged pollDescfdseq (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