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..0474e6c90696cb 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -943,53 +943,66 @@ type kernelVersion struct { } // getKernelVersion returns major and minor kernel version numbers -// parsed from the uname release field. -func getKernelVersion() kernelVersion { +// parsed from the uname release field. ok reports whether parsing +// succeeded; on failure callers must pick a default rather than +// failing, because this is called during osinit before the runtime +// is fully initialized and a throw here is unrecoverable. +func getKernelVersion() (kv kernelVersion, ok bool) { var buf linux.Utsname if e := linux.Uname(&buf); e != 0 { - throw("uname failed") + return kernelVersion{}, false } - rel := gostringnocopy(&buf.Release[0]) major, minor, _, ok := parseRelease(rel) if !ok { - throw("failed to parse kernel version from uname") + return kernelVersion{}, false } - return kernelVersion{major: major, minor: minor} + return kernelVersion{major: major, minor: minor}, true } -// parseRelease parses a dot-separated version number. It follows the -// semver syntax, but allows the minor and patch versions to be -// elided. +// parseRelease parses a dot-separated version number from the prefix +// of rel. It returns ok=true only if at least the major and minor +// components were successfully parsed; the patch component is +// best-effort. Trailing vendor or build suffixes such as +// "-generic", "+", "_hi3535", or "-rc1" are ignored. func parseRelease(rel string) (major, minor, patch int, ok bool) { - // Strip anything after a dash or plus. - for i := 0; i < len(rel); i++ { - if rel[i] == '-' || rel[i] == '+' { - rel = rel[:i] - break + // next consumes a run of decimal digits from the front of rel, + // returning the parsed value. If the digits are followed by a + // '.', it is consumed and more is set so the caller knows to + // parse another component; otherwise scanning terminates and + // the rest of rel is discarded. + next := func() (n int, more, ok bool) { + i := 0 + for i < len(rel) && rel[i] >= '0' && rel[i] <= '9' { + i++ } - } - - next := func() (int, bool) { - for i := 0; i < len(rel); i++ { - if rel[i] == '.' { - ver, err := strconv.Atoi(rel[:i]) - rel = rel[i+1:] - return ver, err == nil - } + if i == 0 { + return 0, false, false + } + n, err := strconv.Atoi(rel[:i]) + if err != nil { + return 0, false, false + } + if i < len(rel) && rel[i] == '.' { + rel = rel[i+1:] + return n, true, true } - ver, err := strconv.Atoi(rel) rel = "" - return ver, err == nil + return n, false, true } - if major, ok = next(); !ok || rel == "" { - return + + var more bool + if major, more, ok = next(); !ok || !more { + return 0, 0, 0, false } - if minor, ok = next(); !ok || rel == "" { - return + if minor, more, ok = next(); !ok { + return 0, 0, 0, false + } + if !more { + return major, minor, 0, true } - patch, ok = next() - return + patch, _, _ = next() + return major, minor, patch, true } // GE checks if the running kernel version diff --git a/src/runtime/os_linux32.go b/src/runtime/os_linux32.go index 1ee1cdcaf90051..ff437ce6533282 100644 --- a/src/runtime/os_linux32.go +++ b/src/runtime/os_linux32.go @@ -10,8 +10,38 @@ import ( "unsafe" ) +// configure64bitsTimeOn32BitsArchitectures decides whether to use the +// 64-bit time variants of futex and timer_settime on 32-bit Linux. +// +// The choice is normally made by parsing the kernel release string +// from uname, because probing with -ENOSYS misbehaves on some +// kernels: Android 8.0-10 (API 26-29) has a seccomp filter that kills +// the process on unknown syscalls instead of returning -ENOSYS, and +// some older Synology kernels reuse the new syscall numbers for +// unrelated vendor syscalls, which silently runs the wrong thing. +// +// If the kernel release string can't be parsed, fall back to probing +// futex_time64 with a no-op FUTEX_WAKE: the kernels that motivated +// the version check above all report parseable uname strings, so +// reaching the probing fallback generally means we are on neither, +// and probing is the safest available signal. func configure64bitsTimeOn32BitsArchitectures() { - use64bitsTimeOn32bits = getKernelVersion().GE(5, 1) + if kv, ok := getKernelVersion(); ok { + use64bitsTimeOn32bits = kv.GE(5, 1) + return + } + use64bitsTimeOn32bits = probeFutexTime64() +} + +// probeFutexTime64 reports whether the futex_time64 syscall is +// available on the running kernel. It issues a FUTEX_WAKE_PRIVATE +// with a wake count of 0, which is a no-op if the syscall exists and +// returns -ENOSYS otherwise. timer_settime64 was added to the kernel +// in the same release (Linux 5.1), so a single probe covers both. +func probeFutexTime64() bool { + var word uint32 + ret := futex_time64(unsafe.Pointer(&word), _FUTEX_WAKE_PRIVATE, 0, nil, nil, 0) + return ret != -_ENOSYS } //go:noescape diff --git a/src/runtime/runtime_linux_test.go b/src/runtime/runtime_linux_test.go index ab2452c9e72910..728d65a9d588b4 100644 --- a/src/runtime/runtime_linux_test.go +++ b/src/runtime/runtime_linux_test.go @@ -53,6 +53,36 @@ 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}, + {"6.6.0-rc1", 6, 6, 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}, + // A single component is not enough; major+minor required. + {"3", 0, 0, 0, false}, + {"3-rc1", 0, 0, 0, false}, + {"", 0, 0, 0, false}, + {"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.