aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/os_linux_x86.go
blob: 97f870707de7de08f4e28253f69442f007a98131 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build linux
// +build 386 amd64

package runtime

import (
	"runtime/internal/atomic"
	"unsafe"
)

//go:noescape
func uname(utsname *new_utsname) int

func mlock(addr, len uintptr) int

func osArchInit() {
	// Linux 5.2 introduced a bug that can corrupt vector
	// registers on return from a signal if the signal stack isn't
	// faulted in:
	// https://bugzilla.kernel.org/show_bug.cgi?id=205663
	//
	// It was fixed in 5.3.15, 5.4.2, and all 5.5 and later
	// kernels.
	//
	// If we're on an affected kernel, work around this issue by
	// mlocking the top page of every signal stack. This doesn't
	// help for signal stacks created in C, but there's not much
	// we can do about that.
	//
	// TODO(austin): Remove this in Go 1.15, at which point it
	// will be unlikely to encounter any of the affected kernels
	// in the wild.

	var uts new_utsname
	if uname(&uts) < 0 {
		throw("uname failed")
	}
	// Check for null terminator to ensure gostringnocopy doesn't
	// walk off the end of the release string.
	found := false
	for _, b := range uts.release {
		if b == 0 {
			found = true
			break
		}
	}
	if !found {
		return
	}
	rel := gostringnocopy(&uts.release[0])

	major, minor, patch, ok := parseRelease(rel)
	if !ok {
		return
	}

	if major == 5 && minor == 4 && patch < 2 {
		// All 5.4 versions of Ubuntu are patched.
		procVersion := []byte("/proc/version\000")
		f := open(&procVersion[0], _O_RDONLY, 0)
		if f >= 0 {
			var buf [512]byte
			p := noescape(unsafe.Pointer(&buf[0]))
			n := read(f, p, int32(len(buf)))
			closefd(f)

			needle := []byte("Ubuntu")
		contains:
			for i, c := range buf[:n] {
				if c != needle[0] {
					continue
				}
				if int(n)-i < len(needle) {
					break
				}
				for j, c2 := range needle {
					if c2 != buf[i+j] {
						continue contains
					}
				}
				// This is an Ubuntu system.
				return
			}
		}
	}

	if major == 5 && (minor == 2 || minor == 3 && patch < 15 || minor == 4 && patch < 2) {
		gsignalInitQuirk = mlockGsignal
		if m0.gsignal != nil {
			throw("gsignal quirk too late")
		}
		throwReportQuirk = throwBadKernel
	}
}

func mlockGsignal(gsignal *g) {
	if atomic.Load(&touchStackBeforeSignal) != 0 {
		// mlock has already failed, don't try again.
		return
	}

	// This mlock call may fail, but we don't report the failure.
	// Instead, if something goes badly wrong, we rely on prepareSignalM
	// and throwBadKernel to do further mitigation and to report a problem
	// to the user if mitigation fails. This is because many
	// systems have a limit on the total mlock size, and many kernels
	// that appear to have bad versions are actually patched to avoid the
	// bug described above. We want Go 1.14 to run on those systems.
	// See #37436.
	if errno := mlock(gsignal.stack.hi-physPageSize, physPageSize); errno < 0 {
		atomic.Store(&touchStackBeforeSignal, uint32(-errno))
	}
}

// throwBadKernel is called, via throwReportQuirk, by throw.
func throwBadKernel() {
	if errno := atomic.Load(&touchStackBeforeSignal); errno != 0 {
		println("runtime: note: your Linux kernel may be buggy")
		println("runtime: note: see https://golang.org/wiki/LinuxKernelSignalVectorBug")
		println("runtime: note: mlock workaround for kernel bug failed with errno", errno)
	}
}