From ca0927e1683d3bc816d264987c0218df247671b5 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 22 May 2026 03:50:05 +0000 Subject: [PATCH] runtime: handle underscore in Linux kernel release strings Some embedded Linux vendors append a platform identifier after an underscore in the uname release field. For example, Synology NVRs report a kernel release like "3.4.35_hi3535". parseRelease only stripped suffixes after '-' or '+', so the patch component "35_hi3535" failed to parse as an integer and the runtime threw "failed to parse kernel version from uname" before malloc heap initialization, making the program unbootable. Also strip everything after '_', mirroring the existing '-' and '+' handling. Regressed in f4de14a515 / CL 758902 on 2026-03-24, which started calling parseRelease unconditionally during osinit on 32-bit Linux to decide between 32- and 64-bit time syscalls. Updates golang/go#79612 Updates tailscale/go#162 Updates tailscale/tailscale#6860 --- src/runtime/export_linux_test.go | 1 + src/runtime/os_linux.go | 6 ++++-- src/runtime/runtime_linux_test.go | 25 +++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/runtime/export_linux_test.go b/src/runtime/export_linux_test.go index 52afd28666e9af..f8c4b4fdc37ea2 100644 --- a/src/runtime/export_linux_test.go +++ b/src/runtime/export_linux_test.go @@ -11,6 +11,7 @@ const SigeventMaxSize = _sigev_max_size var NewOSProc0 = newosproc0 var Mincore = mincore +var ParseRelease = parseRelease type Siginfo siginfo type Sigevent sigevent diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index 493567b5303673..400dd18e7d98ad 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -962,9 +962,11 @@ func getKernelVersion() kernelVersion { // semver syntax, but allows the minor and patch versions to be // elided. func parseRelease(rel string) (major, minor, patch int, ok bool) { - // Strip anything after a dash or plus. + // Strip anything after a dash, plus, or underscore. Some + // embedded Linux vendors (e.g., Synology) append a platform + // identifier with an underscore, like "3.4.35_hi3535". for i := 0; i < len(rel); i++ { - if rel[i] == '-' || rel[i] == '+' { + if rel[i] == '-' || rel[i] == '+' || rel[i] == '_' { rel = rel[:i] break } diff --git a/src/runtime/runtime_linux_test.go b/src/runtime/runtime_linux_test.go index ab2452c9e72910..8bd3a40c99a69e 100644 --- a/src/runtime/runtime_linux_test.go +++ b/src/runtime/runtime_linux_test.go @@ -53,6 +53,31 @@ func TestMincoreErrorSign(t *testing.T) { } } +func TestParseRelease(t *testing.T) { + tests := []struct { + in string + major, minor, patch int + ok bool + }{ + {"6.1.0", 6, 1, 0, true}, + {"5.15.0-91-generic", 5, 15, 0, true}, + {"4.19.0+", 4, 19, 0, true}, + // Synology embedded Linux appends a platform identifier + // after an underscore. + {"3.4.35_hi3535", 3, 4, 35, true}, + {"2.6.32_synology", 2, 6, 32, true}, + {"3.10", 3, 10, 0, true}, + {"bogus", 0, 0, 0, false}, + } + for _, tt := range tests { + major, minor, patch, ok := ParseRelease(tt.in) + if major != tt.major || minor != tt.minor || patch != tt.patch || ok != tt.ok { + t.Errorf("ParseRelease(%q) = (%d, %d, %d, %v); want (%d, %d, %d, %v)", + tt.in, major, minor, patch, ok, tt.major, tt.minor, tt.patch, tt.ok) + } + } +} + func TestKernelStructSize(t *testing.T) { // Check that the Go definitions of structures exchanged with the kernel are // the same size as what the kernel defines.