Skip to content

Commit f2e58c6

Browse files
AndrewGMorganianlancetaylor
authored andcommitted
syscall: improve TestSetuidEtc() /proc/ parsing against races
TestSetuidEtc() was failing sporadically on linux-ppc64. From the three https://build.golang.org/ logs, it looked like the logged errors could be associated with threads dying, but proc reads were, in some way, racing with their demise. Exploring ways to increase thread demise, revealed that races of this type can happen on non-ppc64 systems, and that os.IsNotExist(err) was not a sufficient error condition test for a thread's status file disappearing. This change includes a fix for that to. The actual issue on linux-ppc64 appears to be tied to PID reaping and reuse latency on whatever the build test environment is for linux-ppc64-buildlet. I suspect this can happen on any linux system, however, especially where the container has a limited PID range. The fix for this, limited to the test (the runtime syscall support is unchanged), is to confirm that the Pid for the interrogated thread's /proc/<TID>/status file confirms that it is still associated with the test-process' PID. linux-ppc64-buildlet: go/bin/go test syscall -run=TestSetuidEtc -count=10000 ok syscall 104.285s Fixes #42462 Change-Id: I55c84ab8361003570a405fa52ffec4949bf91113 Reviewed-on: https://go-review.googlesource.com/c/go/+/268717 Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Go Bot <[email protected]> Trust: Tobias Klauser <[email protected]>
1 parent 4c174a7 commit f2e58c6

File tree

2 files changed

+88
-32
lines changed

2 files changed

+88
-32
lines changed

misc/cgo/test/issue1435.go

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -62,28 +62,60 @@ import "C"
6262
// compareStatus is used to confirm the contents of the thread
6363
// specific status files match expectations.
6464
func compareStatus(filter, expect string) error {
65-
expected := filter + "\t" + expect
65+
expected := filter + expect
6666
pid := syscall.Getpid()
6767
fs, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/task", pid))
6868
if err != nil {
6969
return fmt.Errorf("unable to find %d tasks: %v", pid, err)
7070
}
71+
expectedProc := fmt.Sprintf("Pid:\t%d", pid)
72+
foundAThread := false
7173
for _, f := range fs {
7274
tf := fmt.Sprintf("/proc/%s/status", f.Name())
7375
d, err := ioutil.ReadFile(tf)
7476
if err != nil {
75-
return fmt.Errorf("unable to read %q: %v", tf, err)
77+
// There are a surprising number of ways this
78+
// can error out on linux. We've seen all of
79+
// the following, so treat any error here as
80+
// equivalent to the "process is gone":
81+
// os.IsNotExist(err),
82+
// "... : no such process",
83+
// "... : bad file descriptor.
84+
continue
7685
}
7786
lines := strings.Split(string(d), "\n")
7887
for _, line := range lines {
88+
// Different kernel vintages pad differently.
89+
line = strings.TrimSpace(line)
90+
if strings.HasPrefix(line, "Pid:\t") {
91+
// On loaded systems, it is possible
92+
// for a TID to be reused really
93+
// quickly. As such, we need to
94+
// validate that the thread status
95+
// info we just read is a task of the
96+
// same process PID as we are
97+
// currently running, and not a
98+
// recently terminated thread
99+
// resurfaced in a different process.
100+
if line != expectedProc {
101+
break
102+
}
103+
// Fall through in the unlikely case
104+
// that filter at some point is
105+
// "Pid:\t".
106+
}
79107
if strings.HasPrefix(line, filter) {
80108
if line != expected {
81-
return fmt.Errorf("%s %s (bad)\n", tf, line)
109+
return fmt.Errorf("%q got:%q want:%q (bad) [pid=%d file:'%s' %v]\n", tf, line, expected, pid, string(d), expectedProc)
82110
}
111+
foundAThread = true
83112
break
84113
}
85114
}
86115
}
116+
if !foundAThread {
117+
return fmt.Errorf("found no thread /proc/<TID>/status files for process %q", expectedProc)
118+
}
87119
return nil
88120
}
89121

@@ -110,34 +142,34 @@ func test1435(t *testing.T) {
110142
fn func() error
111143
filter, expect string
112144
}{
113-
{call: "Setegid(1)", fn: func() error { return syscall.Setegid(1) }, filter: "Gid:", expect: "0\t1\t0\t1"},
114-
{call: "Setegid(0)", fn: func() error { return syscall.Setegid(0) }, filter: "Gid:", expect: "0\t0\t0\t0"},
145+
{call: "Setegid(1)", fn: func() error { return syscall.Setegid(1) }, filter: "Gid:", expect: "\t0\t1\t0\t1"},
146+
{call: "Setegid(0)", fn: func() error { return syscall.Setegid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
115147

116-
{call: "Seteuid(1)", fn: func() error { return syscall.Seteuid(1) }, filter: "Uid:", expect: "0\t1\t0\t1"},
117-
{call: "Setuid(0)", fn: func() error { return syscall.Setuid(0) }, filter: "Uid:", expect: "0\t0\t0\t0"},
148+
{call: "Seteuid(1)", fn: func() error { return syscall.Seteuid(1) }, filter: "Uid:", expect: "\t0\t1\t0\t1"},
149+
{call: "Setuid(0)", fn: func() error { return syscall.Setuid(0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
118150

119-
{call: "Setgid(1)", fn: func() error { return syscall.Setgid(1) }, filter: "Gid:", expect: "1\t1\t1\t1"},
120-
{call: "Setgid(0)", fn: func() error { return syscall.Setgid(0) }, filter: "Gid:", expect: "0\t0\t0\t0"},
151+
{call: "Setgid(1)", fn: func() error { return syscall.Setgid(1) }, filter: "Gid:", expect: "\t1\t1\t1\t1"},
152+
{call: "Setgid(0)", fn: func() error { return syscall.Setgid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
121153

122-
{call: "Setgroups([]int{0,1,2,3})", fn: func() error { return syscall.Setgroups([]int{0, 1, 2, 3}) }, filter: "Groups:", expect: "0 1 2 3 "},
123-
{call: "Setgroups(nil)", fn: func() error { return syscall.Setgroups(nil) }, filter: "Groups:", expect: " "},
124-
{call: "Setgroups([]int{0})", fn: func() error { return syscall.Setgroups([]int{0}) }, filter: "Groups:", expect: "0 "},
154+
{call: "Setgroups([]int{0,1,2,3})", fn: func() error { return syscall.Setgroups([]int{0, 1, 2, 3}) }, filter: "Groups:", expect: "\t0 1 2 3"},
155+
{call: "Setgroups(nil)", fn: func() error { return syscall.Setgroups(nil) }, filter: "Groups:", expect: ""},
156+
{call: "Setgroups([]int{0})", fn: func() error { return syscall.Setgroups([]int{0}) }, filter: "Groups:", expect: "\t0"},
125157

126-
{call: "Setregid(101,0)", fn: func() error { return syscall.Setregid(101, 0) }, filter: "Gid:", expect: "101\t0\t0\t0"},
127-
{call: "Setregid(0,102)", fn: func() error { return syscall.Setregid(0, 102) }, filter: "Gid:", expect: "0\t102\t102\t102"},
128-
{call: "Setregid(0,0)", fn: func() error { return syscall.Setregid(0, 0) }, filter: "Gid:", expect: "0\t0\t0\t0"},
158+
{call: "Setregid(101,0)", fn: func() error { return syscall.Setregid(101, 0) }, filter: "Gid:", expect: "\t101\t0\t0\t0"},
159+
{call: "Setregid(0,102)", fn: func() error { return syscall.Setregid(0, 102) }, filter: "Gid:", expect: "\t0\t102\t102\t102"},
160+
{call: "Setregid(0,0)", fn: func() error { return syscall.Setregid(0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
129161

130-
{call: "Setreuid(1,0)", fn: func() error { return syscall.Setreuid(1, 0) }, filter: "Uid:", expect: "1\t0\t0\t0"},
131-
{call: "Setreuid(0,2)", fn: func() error { return syscall.Setreuid(0, 2) }, filter: "Uid:", expect: "0\t2\t2\t2"},
132-
{call: "Setreuid(0,0)", fn: func() error { return syscall.Setreuid(0, 0) }, filter: "Uid:", expect: "0\t0\t0\t0"},
162+
{call: "Setreuid(1,0)", fn: func() error { return syscall.Setreuid(1, 0) }, filter: "Uid:", expect: "\t1\t0\t0\t0"},
163+
{call: "Setreuid(0,2)", fn: func() error { return syscall.Setreuid(0, 2) }, filter: "Uid:", expect: "\t0\t2\t2\t2"},
164+
{call: "Setreuid(0,0)", fn: func() error { return syscall.Setreuid(0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
133165

134-
{call: "Setresgid(101,0,102)", fn: func() error { return syscall.Setresgid(101, 0, 102) }, filter: "Gid:", expect: "101\t0\t102\t0"},
135-
{call: "Setresgid(0,102,101)", fn: func() error { return syscall.Setresgid(0, 102, 101) }, filter: "Gid:", expect: "0\t102\t101\t102"},
136-
{call: "Setresgid(0,0,0)", fn: func() error { return syscall.Setresgid(0, 0, 0) }, filter: "Gid:", expect: "0\t0\t0\t0"},
166+
{call: "Setresgid(101,0,102)", fn: func() error { return syscall.Setresgid(101, 0, 102) }, filter: "Gid:", expect: "\t101\t0\t102\t0"},
167+
{call: "Setresgid(0,102,101)", fn: func() error { return syscall.Setresgid(0, 102, 101) }, filter: "Gid:", expect: "\t0\t102\t101\t102"},
168+
{call: "Setresgid(0,0,0)", fn: func() error { return syscall.Setresgid(0, 0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
137169

138-
{call: "Setresuid(1,0,2)", fn: func() error { return syscall.Setresuid(1, 0, 2) }, filter: "Uid:", expect: "1\t0\t2\t0"},
139-
{call: "Setresuid(0,2,1)", fn: func() error { return syscall.Setresuid(0, 2, 1) }, filter: "Uid:", expect: "0\t2\t1\t2"},
140-
{call: "Setresuid(0,0,0)", fn: func() error { return syscall.Setresuid(0, 0, 0) }, filter: "Uid:", expect: "0\t0\t0\t0"},
170+
{call: "Setresuid(1,0,2)", fn: func() error { return syscall.Setresuid(1, 0, 2) }, filter: "Uid:", expect: "\t1\t0\t2\t0"},
171+
{call: "Setresuid(0,2,1)", fn: func() error { return syscall.Setresuid(0, 2, 1) }, filter: "Uid:", expect: "\t0\t2\t1\t2"},
172+
{call: "Setresuid(0,0,0)", fn: func() error { return syscall.Setresuid(0, 0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
141173
}
142174

143175
for i, v := range vs {

src/syscall/syscall_linux_test.go

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -547,30 +547,54 @@ func compareStatus(filter, expect string) error {
547547
if err != nil {
548548
return fmt.Errorf("unable to find %d tasks: %v", pid, err)
549549
}
550+
expectedProc := fmt.Sprintf("Pid:\t%d", pid)
551+
foundAThread := false
550552
for _, f := range fs {
551553
tf := fmt.Sprintf("/proc/%s/status", f.Name())
552554
d, err := ioutil.ReadFile(tf)
553-
if os.IsNotExist(err) {
554-
// We are racing against threads dying, which
555-
// is out of our control, so ignore the
556-
// missing file and skip to the next one.
557-
continue
558-
}
559555
if err != nil {
560-
return fmt.Errorf("unable to read %q: %v", tf, err)
556+
// There are a surprising number of ways this
557+
// can error out on linux. We've seen all of
558+
// the following, so treat any error here as
559+
// equivalent to the "process is gone":
560+
// os.IsNotExist(err),
561+
// "... : no such process",
562+
// "... : bad file descriptor.
563+
continue
561564
}
562565
lines := strings.Split(string(d), "\n")
563566
for _, line := range lines {
564567
// Different kernel vintages pad differently.
565568
line = strings.TrimSpace(line)
569+
if strings.HasPrefix(line, "Pid:\t") {
570+
// On loaded systems, it is possible
571+
// for a TID to be reused really
572+
// quickly. As such, we need to
573+
// validate that the thread status
574+
// info we just read is a task of the
575+
// same process PID as we are
576+
// currently running, and not a
577+
// recently terminated thread
578+
// resurfaced in a different process.
579+
if line != expectedProc {
580+
break
581+
}
582+
// Fall through in the unlikely case
583+
// that filter at some point is
584+
// "Pid:\t".
585+
}
566586
if strings.HasPrefix(line, filter) {
567587
if line != expected {
568-
return fmt.Errorf("%q got:%q want:%q (bad)\n", tf, line, expected)
588+
return fmt.Errorf("%q got:%q want:%q (bad) [pid=%d file:'%s' %v]\n", tf, line, expected, pid, string(d), expectedProc)
569589
}
590+
foundAThread = true
570591
break
571592
}
572593
}
573594
}
595+
if !foundAThread {
596+
return fmt.Errorf("found no thread /proc/<TID>/status files for process %q", expectedProc)
597+
}
574598
return nil
575599
}
576600

0 commit comments

Comments
 (0)