diff options
| author | Brad Fitzpatrick <bradfitz@golang.org> | 2026-03-24 23:02:09 +0000 |
|---|---|---|
| committer | Brad Fitzpatrick <bradfitz@golang.org> | 2026-03-25 10:11:26 -0700 |
| commit | 04dc12c1a17d3fa4ff49af84de5641099716e234 (patch) | |
| tree | 4b11d9f4e70b9e284d618bc3ccee16e772d0921a /src/runtime | |
| parent | fa238516b782bd1f233e85b719b7ab90889a5634 (diff) | |
| download | go-04dc12c1a17d3fa4ff49af84de5641099716e234.tar.xz | |
runtime: use uname version check for 64-bit time on 32-bit arch codepaths
The previous fallback-on-ENOSYS logic causes issues on forks of Linux.
Android: #77621 (CL 750040 added a workaround with a TODO,
this fixes that TODO)
Causes the OS to terminate the program when running on Android
versions <=10 since the seccomp jail does not know about the 64-bit
time syscall and is configured to terminate the program on any
unknown syscall.
Synology's Linux: #77930
On old versions of Synology's Linux they added custom vendor syscalls
without adding a gap in the syscall numbers, that means when we call
the newer Linux syscall which was added later, Synology's Linux
interprets it as a completely different vendor syscall.
Originally by Jorropo in CL 751340.
Fixes #77930
Co-authored-by: Jorropo <jorropo.pgm@gmail.com>
Change-Id: I90e15495d9249fd7f6e112f9e3ae8ad1322f56e0
Reviewed-on: https://go-review.googlesource.com/c/go/+/758902
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Jorropo <jorropo.pgm@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/runtime')
| -rw-r--r-- | src/runtime/os_linux.go | 62 | ||||
| -rw-r--r-- | src/runtime/os_linux32.go | 38 | ||||
| -rw-r--r-- | src/runtime/os_linux64.go | 2 |
3 files changed, 73 insertions, 29 deletions
diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index 7e6af22d48..493567b530 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -354,6 +354,7 @@ func osinit() { numCPUStartup = getCPUCount() physHugePageSize = getHugePageSize() vgetrandomInit() + configure64bitsTimeOn32BitsArchitectures() } var urandom_dev = []byte("/dev/urandom\x00") @@ -935,3 +936,64 @@ func mprotect(addr unsafe.Pointer, n uintptr, prot int32) (ret int32, errno int3 r, _, err := linux.Syscall6(linux.SYS_MPROTECT, uintptr(addr), n, uintptr(prot), 0, 0, 0) return int32(r), int32(err) } + +type kernelVersion struct { + major int + minor int +} + +// getKernelVersion returns major and minor kernel version numbers +// parsed from the uname release field. +func getKernelVersion() kernelVersion { + var buf linux.Utsname + if e := linux.Uname(&buf); e != 0 { + throw("uname failed") + } + + rel := gostringnocopy(&buf.Release[0]) + major, minor, _, ok := parseRelease(rel) + if !ok { + throw("failed to parse kernel version from uname") + } + return kernelVersion{major: major, minor: minor} +} + +// parseRelease parses a dot-separated version number. It follows the +// 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. + for i := 0; i < len(rel); i++ { + if rel[i] == '-' || rel[i] == '+' { + rel = rel[:i] + break + } + } + + 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 + } + } + ver, err := strconv.Atoi(rel) + rel = "" + return ver, err == nil + } + if major, ok = next(); !ok || rel == "" { + return + } + if minor, ok = next(); !ok || rel == "" { + return + } + patch, ok = next() + return +} + +// GE checks if the running kernel version +// is greater than or equal to the provided version. +func (kv kernelVersion) GE(x, y int) bool { + return kv.major > x || (kv.major == x && kv.minor >= y) +} diff --git a/src/runtime/os_linux32.go b/src/runtime/os_linux32.go index 748fc53c3f..02cb18f32d 100644 --- a/src/runtime/os_linux32.go +++ b/src/runtime/os_linux32.go @@ -7,32 +7,25 @@ package runtime import ( - "internal/runtime/atomic" "unsafe" ) +func configure64bitsTimeOn32BitsArchitectures() { + use64bitsTimeOn32bits = getKernelVersion().GE(5, 1) +} + //go:noescape func futex_time32(addr unsafe.Pointer, op int32, val uint32, ts *timespec32, addr2 unsafe.Pointer, val3 uint32) int32 //go:noescape func futex_time64(addr unsafe.Pointer, op int32, val uint32, ts *timespec, addr2 unsafe.Pointer, val3 uint32) int32 -var isFutexTime32bitOnly atomic.Bool +var use64bitsTimeOn32bits bool //go:nosplit func futex(addr unsafe.Pointer, op int32, val uint32, ts *timespec, addr2 unsafe.Pointer, val3 uint32) int32 { - // In Android versions 8.0-10 (API levels 26-29), futex_time64 - // is not in the allowlist of the seccomp filter and will lead to a - // runtime crash. See issue 77621. - // TODO: Check Android version and do not skip futex_time64 - // on Android 11 or higher (API level 30+). - if GOOS != "android" && !isFutexTime32bitOnly.Load() { - ret := futex_time64(addr, op, val, ts, addr2, val3) - // futex_time64 is only supported on Linux 5.0+ - if ret != -_ENOSYS { - return ret - } - isFutexTime32bitOnly.Store(true) + if use64bitsTimeOn32bits { + return futex_time64(addr, op, val, ts, addr2, val3) } // Downgrade ts. var ts32 timespec32 @@ -50,22 +43,10 @@ func timer_settime32(timerid int32, flags int32, new, old *itimerspec32) int32 //go:noescape func timer_settime64(timerid int32, flags int32, new, old *itimerspec) int32 -var isSetTime32bitOnly atomic.Bool - //go:nosplit func timer_settime(timerid int32, flags int32, new, old *itimerspec) int32 { - // In Android versions 8.0-10 (API levels 26-29), timer_settime64 - // is not in the allowlist of the seccomp filter and will lead to a - // runtime crash. See issue 77621. - // TODO: Check Android version and do not skip timer_settime64 - // on Android 11 or higher (API level 30+). - if GOOS != "android" && !isSetTime32bitOnly.Load() { - ret := timer_settime64(timerid, flags, new, old) - // timer_settime64 is only supported on Linux 5.0+ - if ret != -_ENOSYS { - return ret - } - isSetTime32bitOnly.Store(true) + if use64bitsTimeOn32bits { + return timer_settime64(timerid, flags, new, old) } var newts, oldts itimerspec32 @@ -83,6 +64,5 @@ func timer_settime(timerid int32, flags int32, new, old *itimerspec) int32 { old32 = &oldts } - // Fall back to 32-bit timer return timer_settime32(timerid, flags, new32, old32) } diff --git a/src/runtime/os_linux64.go b/src/runtime/os_linux64.go index 7b70d80fbe..f9571dd758 100644 --- a/src/runtime/os_linux64.go +++ b/src/runtime/os_linux64.go @@ -10,6 +10,8 @@ import ( "unsafe" ) +func configure64bitsTimeOn32BitsArchitectures() {} + //go:noescape func futex(addr unsafe.Pointer, op int32, val uint32, ts *timespec, addr2 unsafe.Pointer, val3 uint32) int32 |
