aboutsummaryrefslogtreecommitdiff
path: root/src/runtime
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@golang.org>2026-03-24 23:02:09 +0000
committerBrad Fitzpatrick <bradfitz@golang.org>2026-03-25 10:11:26 -0700
commit04dc12c1a17d3fa4ff49af84de5641099716e234 (patch)
tree4b11d9f4e70b9e284d618bc3ccee16e772d0921a /src/runtime
parentfa238516b782bd1f233e85b719b7ab90889a5634 (diff)
downloadgo-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.go62
-rw-r--r--src/runtime/os_linux32.go38
-rw-r--r--src/runtime/os_linux64.go2
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