github.com/dorkamotorka/go/src@v0.0.0-20230614113921-187095f0e316/syscall/syscall_linux_test.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package syscall_test 6 7 import ( 8 "fmt" 9 "io" 10 "io/fs" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "runtime" 15 "sort" 16 "strconv" 17 "strings" 18 "sync" 19 "syscall" 20 "testing" 21 "unsafe" 22 ) 23 24 // chtmpdir changes the working directory to a new temporary directory and 25 // provides a cleanup function. Used when PWD is read-only. 26 func chtmpdir(t *testing.T) func() { 27 oldwd, err := os.Getwd() 28 if err != nil { 29 t.Fatalf("chtmpdir: %v", err) 30 } 31 d, err := os.MkdirTemp("", "test") 32 if err != nil { 33 t.Fatalf("chtmpdir: %v", err) 34 } 35 if err := os.Chdir(d); err != nil { 36 t.Fatalf("chtmpdir: %v", err) 37 } 38 return func() { 39 if err := os.Chdir(oldwd); err != nil { 40 t.Fatalf("chtmpdir: %v", err) 41 } 42 os.RemoveAll(d) 43 } 44 } 45 46 func touch(t *testing.T, name string) { 47 f, err := os.Create(name) 48 if err != nil { 49 t.Fatal(err) 50 } 51 if err := f.Close(); err != nil { 52 t.Fatal(err) 53 } 54 } 55 56 const ( 57 _AT_SYMLINK_NOFOLLOW = 0x100 58 _AT_FDCWD = -0x64 59 _AT_EACCESS = 0x200 60 _F_OK = 0 61 _R_OK = 4 62 ) 63 64 func TestFaccessat(t *testing.T) { 65 defer chtmpdir(t)() 66 touch(t, "file1") 67 68 err := syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, 0) 69 if err != nil { 70 t.Errorf("Faccessat: unexpected error: %v", err) 71 } 72 73 err = syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, 2) 74 if err != syscall.EINVAL { 75 t.Errorf("Faccessat: unexpected error: %v, want EINVAL", err) 76 } 77 78 err = syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, _AT_EACCESS) 79 if err != nil { 80 t.Errorf("Faccessat: unexpected error: %v", err) 81 } 82 83 err = os.Symlink("file1", "symlink1") 84 if err != nil { 85 t.Fatal(err) 86 } 87 88 err = syscall.Faccessat(_AT_FDCWD, "symlink1", _R_OK, _AT_SYMLINK_NOFOLLOW) 89 if err != nil { 90 t.Errorf("Faccessat SYMLINK_NOFOLLOW: unexpected error %v", err) 91 } 92 93 // We can't really test _AT_SYMLINK_NOFOLLOW, because there 94 // doesn't seem to be any way to change the mode of a symlink. 95 // We don't test _AT_EACCESS because such tests are only 96 // meaningful if run as root. 97 98 err = syscall.Fchmodat(_AT_FDCWD, "file1", 0, 0) 99 if err != nil { 100 t.Errorf("Fchmodat: unexpected error %v", err) 101 } 102 103 err = syscall.Faccessat(_AT_FDCWD, "file1", _F_OK, _AT_SYMLINK_NOFOLLOW) 104 if err != nil { 105 t.Errorf("Faccessat: unexpected error: %v", err) 106 } 107 108 err = syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, _AT_SYMLINK_NOFOLLOW) 109 if err != syscall.EACCES { 110 if syscall.Getuid() != 0 { 111 t.Errorf("Faccessat: unexpected error: %v, want EACCES", err) 112 } 113 } 114 } 115 116 func TestFchmodat(t *testing.T) { 117 defer chtmpdir(t)() 118 119 touch(t, "file1") 120 os.Symlink("file1", "symlink1") 121 122 err := syscall.Fchmodat(_AT_FDCWD, "symlink1", 0444, 0) 123 if err != nil { 124 t.Fatalf("Fchmodat: unexpected error: %v", err) 125 } 126 127 fi, err := os.Stat("file1") 128 if err != nil { 129 t.Fatal(err) 130 } 131 132 if fi.Mode() != 0444 { 133 t.Errorf("Fchmodat: failed to change mode: expected %v, got %v", 0444, fi.Mode()) 134 } 135 136 err = syscall.Fchmodat(_AT_FDCWD, "symlink1", 0444, _AT_SYMLINK_NOFOLLOW) 137 if err != syscall.EOPNOTSUPP { 138 t.Fatalf("Fchmodat: unexpected error: %v, expected EOPNOTSUPP", err) 139 } 140 } 141 142 func TestMain(m *testing.M) { 143 if os.Getenv("GO_DEATHSIG_PARENT") == "1" { 144 deathSignalParent() 145 } else if os.Getenv("GO_DEATHSIG_CHILD") == "1" { 146 deathSignalChild() 147 } else if os.Getenv("GO_SYSCALL_NOERROR") == "1" { 148 syscallNoError() 149 } 150 151 os.Exit(m.Run()) 152 } 153 154 func TestParseNetlinkMessage(t *testing.T) { 155 for i, b := range [][]byte{ 156 {103, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 11, 0, 1, 0, 0, 0, 0, 5, 8, 0, 3, 157 0, 8, 0, 6, 0, 0, 0, 0, 1, 63, 0, 10, 0, 69, 16, 0, 59, 39, 82, 64, 0, 64, 6, 21, 89, 127, 0, 0, 158 1, 127, 0, 0, 1, 230, 228, 31, 144, 32, 186, 155, 211, 185, 151, 209, 179, 128, 24, 1, 86, 159 53, 119, 0, 0, 1, 1, 8, 10, 0, 17, 234, 12, 0, 17, 189, 126, 107, 106, 108, 107, 106, 13, 10, 160 }, 161 {106, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 11, 0, 1, 0, 0, 0, 0, 3, 8, 0, 3, 162 0, 8, 0, 6, 0, 0, 0, 0, 1, 66, 0, 10, 0, 69, 0, 0, 62, 230, 255, 64, 0, 64, 6, 85, 184, 127, 0, 0, 163 1, 127, 0, 0, 1, 237, 206, 31, 144, 73, 197, 128, 65, 250, 60, 192, 97, 128, 24, 1, 86, 253, 21, 0, 164 0, 1, 1, 8, 10, 0, 51, 106, 89, 0, 51, 102, 198, 108, 104, 106, 108, 107, 104, 108, 107, 104, 10, 165 }, 166 {102, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 11, 0, 1, 0, 0, 0, 0, 1, 8, 0, 3, 0, 167 8, 0, 6, 0, 0, 0, 0, 1, 62, 0, 10, 0, 69, 0, 0, 58, 231, 2, 64, 0, 64, 6, 85, 185, 127, 0, 0, 1, 127, 168 0, 0, 1, 237, 206, 31, 144, 73, 197, 128, 86, 250, 60, 192, 97, 128, 24, 1, 86, 104, 64, 0, 0, 1, 1, 8, 169 10, 0, 52, 198, 200, 0, 51, 135, 232, 101, 115, 97, 103, 103, 10, 170 }, 171 } { 172 m, err := syscall.ParseNetlinkMessage(b) 173 if err != syscall.EINVAL { 174 t.Errorf("#%d: got %v; want EINVAL", i, err) 175 } 176 if m != nil { 177 t.Errorf("#%d: got %v; want nil", i, m) 178 } 179 } 180 } 181 182 func TestSyscallNoError(t *testing.T) { 183 // On Linux there are currently no syscalls which don't fail and return 184 // a value larger than 0xfffffffffffff001 so we could test RawSyscall 185 // vs. RawSyscallNoError on 64bit architectures. 186 if unsafe.Sizeof(uintptr(0)) != 4 { 187 t.Skip("skipping on non-32bit architecture") 188 } 189 190 // See https://golang.org/issue/35422 191 // On MIPS, Linux returns whether the syscall had an error in a separate 192 // register (R7), not using a negative return value as on other 193 // architectures. 194 if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" { 195 t.Skipf("skipping on %s", runtime.GOARCH) 196 } 197 198 if os.Getuid() != 0 { 199 t.Skip("skipping root only test") 200 } 201 202 if runtime.GOOS == "android" { 203 t.Skip("skipping on rooted android, see issue 27364") 204 } 205 206 // Copy the test binary to a location that a non-root user can read/execute 207 // after we drop privileges 208 tempDir, err := os.MkdirTemp("", "TestSyscallNoError") 209 if err != nil { 210 t.Fatalf("cannot create temporary directory: %v", err) 211 } 212 defer os.RemoveAll(tempDir) 213 os.Chmod(tempDir, 0755) 214 215 tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0])) 216 217 src, err := os.Open(os.Args[0]) 218 if err != nil { 219 t.Fatalf("cannot open binary %q, %v", os.Args[0], err) 220 } 221 defer src.Close() 222 223 dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) 224 if err != nil { 225 t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err) 226 } 227 if _, err := io.Copy(dst, src); err != nil { 228 t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err) 229 } 230 err = dst.Close() 231 if err != nil { 232 t.Fatalf("failed to close test binary %q, %v", tmpBinary, err) 233 } 234 235 uid := uint32(0xfffffffe) 236 err = os.Chown(tmpBinary, int(uid), -1) 237 if err != nil { 238 t.Fatalf("failed to chown test binary %q, %v", tmpBinary, err) 239 } 240 241 err = os.Chmod(tmpBinary, 0755|fs.ModeSetuid) 242 if err != nil { 243 t.Fatalf("failed to set setuid bit on test binary %q, %v", tmpBinary, err) 244 } 245 246 cmd := exec.Command(tmpBinary) 247 cmd.Env = append(os.Environ(), "GO_SYSCALL_NOERROR=1") 248 249 out, err := cmd.CombinedOutput() 250 if err != nil { 251 t.Fatalf("failed to start first child process: %v", err) 252 } 253 254 got := strings.TrimSpace(string(out)) 255 want := strconv.FormatUint(uint64(uid)+1, 10) + " / " + 256 strconv.FormatUint(uint64(-uid), 10) + " / " + 257 strconv.FormatUint(uint64(uid), 10) 258 if got != want { 259 if filesystemIsNoSUID(tmpBinary) { 260 t.Skip("skipping test when temp dir is mounted nosuid") 261 } 262 // formatted so the values are aligned for easier comparison 263 t.Errorf("expected %s,\ngot %s", want, got) 264 } 265 } 266 267 // filesystemIsNoSUID reports whether the filesystem for the given 268 // path is mounted nosuid. 269 func filesystemIsNoSUID(path string) bool { 270 var st syscall.Statfs_t 271 if syscall.Statfs(path, &st) != nil { 272 return false 273 } 274 return st.Flags&syscall.MS_NOSUID != 0 275 } 276 277 func syscallNoError() { 278 // Test that the return value from SYS_GETEUID32 (which cannot fail) 279 // doesn't get treated as an error (see https://golang.org/issue/22924) 280 euid1, _, e := syscall.RawSyscall(syscall.Sys_GETEUID, 0, 0, 0) 281 euid2, _ := syscall.RawSyscallNoError(syscall.Sys_GETEUID, 0, 0, 0) 282 283 fmt.Println(uintptr(euid1), "/", int(e), "/", uintptr(euid2)) 284 os.Exit(0) 285 } 286 287 // reference uapi/linux/prctl.h 288 const ( 289 PR_GET_KEEPCAPS uintptr = 7 290 PR_SET_KEEPCAPS = 8 291 ) 292 293 // TestAllThreadsSyscall tests that the go runtime can perform 294 // syscalls that execute on all OSThreads - with which to support 295 // POSIX semantics for security state changes. 296 func TestAllThreadsSyscall(t *testing.T) { 297 if _, _, err := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, 0, 0); err == syscall.ENOTSUP { 298 t.Skip("AllThreadsSyscall disabled with cgo") 299 } 300 301 fns := []struct { 302 label string 303 fn func(uintptr) error 304 }{ 305 { 306 label: "prctl<3-args>", 307 fn: func(v uintptr) error { 308 _, _, e := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, v, 0) 309 if e != 0 { 310 return e 311 } 312 return nil 313 }, 314 }, 315 { 316 label: "prctl<6-args>", 317 fn: func(v uintptr) error { 318 _, _, e := syscall.AllThreadsSyscall6(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, v, 0, 0, 0, 0) 319 if e != 0 { 320 return e 321 } 322 return nil 323 }, 324 }, 325 } 326 327 waiter := func(q <-chan uintptr, r chan<- uintptr, once bool) { 328 for x := range q { 329 runtime.LockOSThread() 330 v, _, e := syscall.Syscall(syscall.SYS_PRCTL, PR_GET_KEEPCAPS, 0, 0) 331 if e != 0 { 332 t.Errorf("tid=%d prctl(PR_GET_KEEPCAPS) failed: %v", syscall.Gettid(), e) 333 } else if x != v { 334 t.Errorf("tid=%d prctl(PR_GET_KEEPCAPS) mismatch: got=%d want=%d", syscall.Gettid(), v, x) 335 } 336 r <- v 337 if once { 338 break 339 } 340 runtime.UnlockOSThread() 341 } 342 } 343 344 // launches per fns member. 345 const launches = 11 346 question := make(chan uintptr) 347 response := make(chan uintptr) 348 defer close(question) 349 350 routines := 0 351 for i, v := range fns { 352 for j := 0; j < launches; j++ { 353 // Add another goroutine - the closest thing 354 // we can do to encourage more OS thread 355 // creation - while the test is running. The 356 // actual thread creation may or may not be 357 // needed, based on the number of available 358 // unlocked OS threads at the time waiter 359 // calls runtime.LockOSThread(), but the goal 360 // of doing this every time through the loop 361 // is to race thread creation with v.fn(want) 362 // being executed. Via the once boolean we 363 // also encourage one in 5 waiters to return 364 // locked after participating in only one 365 // question response sequence. This allows the 366 // test to race thread destruction too. 367 once := routines%5 == 4 368 go waiter(question, response, once) 369 370 // Keep a count of how many goroutines are 371 // going to participate in the 372 // question/response test. This will count up 373 // towards 2*launches minus the count of 374 // routines that have been invoked with 375 // once=true. 376 routines++ 377 378 // Decide what value we want to set the 379 // process-shared KEEPCAPS. Note, there is 380 // an explicit repeat of 0 when we change the 381 // variant of the syscall being used. 382 want := uintptr(j & 1) 383 384 // Invoke the AllThreadsSyscall* variant. 385 if err := v.fn(want); err != nil { 386 t.Errorf("[%d,%d] %s(PR_SET_KEEPCAPS, %d, ...): %v", i, j, v.label, j&1, err) 387 } 388 389 // At this point, we want all launched Go 390 // routines to confirm that they see the 391 // wanted value for KEEPCAPS. 392 for k := 0; k < routines; k++ { 393 question <- want 394 } 395 396 // At this point, we should have a large 397 // number of locked OS threads all wanting to 398 // reply. 399 for k := 0; k < routines; k++ { 400 if got := <-response; got != want { 401 t.Errorf("[%d,%d,%d] waiter result got=%d, want=%d", i, j, k, got, want) 402 } 403 } 404 405 // Provide an explicit opportunity for this Go 406 // routine to change Ms. 407 runtime.Gosched() 408 409 if once { 410 // One waiter routine will have exited. 411 routines-- 412 } 413 414 // Whatever M we are now running on, confirm 415 // we see the wanted value too. 416 if v, _, e := syscall.Syscall(syscall.SYS_PRCTL, PR_GET_KEEPCAPS, 0, 0); e != 0 { 417 t.Errorf("[%d,%d] prctl(PR_GET_KEEPCAPS) failed: %v", i, j, e) 418 } else if v != want { 419 t.Errorf("[%d,%d] prctl(PR_GET_KEEPCAPS) gave wrong value: got=%v, want=1", i, j, v) 420 } 421 } 422 } 423 } 424 425 // compareStatus is used to confirm the contents of the thread 426 // specific status files match expectations. 427 func compareStatus(filter, expect string) error { 428 expected := filter + expect 429 pid := syscall.Getpid() 430 fs, err := os.ReadDir(fmt.Sprintf("/proc/%d/task", pid)) 431 if err != nil { 432 return fmt.Errorf("unable to find %d tasks: %v", pid, err) 433 } 434 expectedProc := fmt.Sprintf("Pid:\t%d", pid) 435 foundAThread := false 436 for _, f := range fs { 437 tf := fmt.Sprintf("/proc/%s/status", f.Name()) 438 d, err := os.ReadFile(tf) 439 if err != nil { 440 // There are a surprising number of ways this 441 // can error out on linux. We've seen all of 442 // the following, so treat any error here as 443 // equivalent to the "process is gone": 444 // os.IsNotExist(err), 445 // "... : no such process", 446 // "... : bad file descriptor. 447 continue 448 } 449 lines := strings.Split(string(d), "\n") 450 for _, line := range lines { 451 // Different kernel vintages pad differently. 452 line = strings.TrimSpace(line) 453 if strings.HasPrefix(line, "Pid:\t") { 454 // On loaded systems, it is possible 455 // for a TID to be reused really 456 // quickly. As such, we need to 457 // validate that the thread status 458 // info we just read is a task of the 459 // same process PID as we are 460 // currently running, and not a 461 // recently terminated thread 462 // resurfaced in a different process. 463 if line != expectedProc { 464 break 465 } 466 // Fall through in the unlikely case 467 // that filter at some point is 468 // "Pid:\t". 469 } 470 if strings.HasPrefix(line, filter) { 471 if line == expected { 472 foundAThread = true 473 break 474 } 475 if filter == "Groups:" && strings.HasPrefix(line, "Groups:\t") { 476 // https://github.com/golang/go/issues/46145 477 // Containers don't reliably output this line in sorted order so manually sort and compare that. 478 a := strings.Split(line[8:], " ") 479 sort.Strings(a) 480 got := strings.Join(a, " ") 481 if got == expected[8:] { 482 foundAThread = true 483 break 484 } 485 486 } 487 return fmt.Errorf("%q got:%q want:%q (bad) [pid=%d file:'%s' %v]\n", tf, line, expected, pid, string(d), expectedProc) 488 } 489 } 490 } 491 if !foundAThread { 492 return fmt.Errorf("found no thread /proc/<TID>/status files for process %q", expectedProc) 493 } 494 return nil 495 } 496 497 // killAThread locks the goroutine to an OS thread and exits; this 498 // causes an OS thread to terminate. 499 func killAThread(c <-chan struct{}) { 500 runtime.LockOSThread() 501 <-c 502 return 503 } 504 505 // TestSetuidEtc performs tests on all of the wrapped system calls 506 // that mirror to the 9 glibc syscalls with POSIX semantics. The test 507 // here is considered authoritative and should compile and run 508 // CGO_ENABLED=0 or 1. Note, there is an extended copy of this same 509 // test in ../../misc/cgo/test/issue1435.go which requires 510 // CGO_ENABLED=1 and launches pthreads from C that run concurrently 511 // with the Go code of the test - and the test validates that these 512 // pthreads are also kept in sync with the security state changed with 513 // the syscalls. Care should be taken to mirror any enhancements to 514 // this test here in that file too. 515 func TestSetuidEtc(t *testing.T) { 516 if syscall.Getuid() != 0 { 517 t.Skip("skipping root only test") 518 } 519 if _, err := os.Stat("/etc/alpine-release"); err == nil { 520 t.Skip("skipping glibc test on alpine - go.dev/issue/19938") 521 } 522 vs := []struct { 523 call string 524 fn func() error 525 filter, expect string 526 }{ 527 {call: "Setegid(1)", fn: func() error { return syscall.Setegid(1) }, filter: "Gid:", expect: "\t0\t1\t0\t1"}, 528 {call: "Setegid(0)", fn: func() error { return syscall.Setegid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"}, 529 530 {call: "Seteuid(1)", fn: func() error { return syscall.Seteuid(1) }, filter: "Uid:", expect: "\t0\t1\t0\t1"}, 531 {call: "Setuid(0)", fn: func() error { return syscall.Setuid(0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"}, 532 533 {call: "Setgid(1)", fn: func() error { return syscall.Setgid(1) }, filter: "Gid:", expect: "\t1\t1\t1\t1"}, 534 {call: "Setgid(0)", fn: func() error { return syscall.Setgid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"}, 535 536 {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"}, 537 {call: "Setgroups(nil)", fn: func() error { return syscall.Setgroups(nil) }, filter: "Groups:", expect: ""}, 538 {call: "Setgroups([]int{0})", fn: func() error { return syscall.Setgroups([]int{0}) }, filter: "Groups:", expect: "\t0"}, 539 540 {call: "Setregid(101,0)", fn: func() error { return syscall.Setregid(101, 0) }, filter: "Gid:", expect: "\t101\t0\t0\t0"}, 541 {call: "Setregid(0,102)", fn: func() error { return syscall.Setregid(0, 102) }, filter: "Gid:", expect: "\t0\t102\t102\t102"}, 542 {call: "Setregid(0,0)", fn: func() error { return syscall.Setregid(0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"}, 543 544 {call: "Setreuid(1,0)", fn: func() error { return syscall.Setreuid(1, 0) }, filter: "Uid:", expect: "\t1\t0\t0\t0"}, 545 {call: "Setreuid(0,2)", fn: func() error { return syscall.Setreuid(0, 2) }, filter: "Uid:", expect: "\t0\t2\t2\t2"}, 546 {call: "Setreuid(0,0)", fn: func() error { return syscall.Setreuid(0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"}, 547 548 {call: "Setresgid(101,0,102)", fn: func() error { return syscall.Setresgid(101, 0, 102) }, filter: "Gid:", expect: "\t101\t0\t102\t0"}, 549 {call: "Setresgid(0,102,101)", fn: func() error { return syscall.Setresgid(0, 102, 101) }, filter: "Gid:", expect: "\t0\t102\t101\t102"}, 550 {call: "Setresgid(0,0,0)", fn: func() error { return syscall.Setresgid(0, 0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"}, 551 552 {call: "Setresuid(1,0,2)", fn: func() error { return syscall.Setresuid(1, 0, 2) }, filter: "Uid:", expect: "\t1\t0\t2\t0"}, 553 {call: "Setresuid(0,2,1)", fn: func() error { return syscall.Setresuid(0, 2, 1) }, filter: "Uid:", expect: "\t0\t2\t1\t2"}, 554 {call: "Setresuid(0,0,0)", fn: func() error { return syscall.Setresuid(0, 0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"}, 555 } 556 557 for i, v := range vs { 558 // Generate some thread churn as we execute the tests. 559 c := make(chan struct{}) 560 go killAThread(c) 561 close(c) 562 563 if err := v.fn(); err != nil { 564 t.Errorf("[%d] %q failed: %v", i, v.call, err) 565 continue 566 } 567 if err := compareStatus(v.filter, v.expect); err != nil { 568 t.Errorf("[%d] %q comparison: %v", i, v.call, err) 569 } 570 } 571 } 572 573 // TestAllThreadsSyscallError verifies that errors are properly returned when 574 // the syscall fails on the original thread. 575 func TestAllThreadsSyscallError(t *testing.T) { 576 // SYS_CAPGET takes pointers as the first two arguments. Since we pass 577 // 0, we expect to get EFAULT back. 578 r1, r2, err := syscall.AllThreadsSyscall(syscall.SYS_CAPGET, 0, 0, 0) 579 if err == syscall.ENOTSUP { 580 t.Skip("AllThreadsSyscall disabled with cgo") 581 } 582 if err != syscall.EFAULT { 583 t.Errorf("AllThreadSyscall(SYS_CAPGET) got %d, %d, %v, want err %v", r1, r2, err, syscall.EFAULT) 584 } 585 } 586 587 // TestAllThreadsSyscallBlockedSyscall confirms that AllThreadsSyscall 588 // can interrupt threads in long-running system calls. This test will 589 // deadlock if this doesn't work correctly. 590 func TestAllThreadsSyscallBlockedSyscall(t *testing.T) { 591 if _, _, err := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, 0, 0); err == syscall.ENOTSUP { 592 t.Skip("AllThreadsSyscall disabled with cgo") 593 } 594 595 rd, wr, err := os.Pipe() 596 if err != nil { 597 t.Fatalf("unable to obtain a pipe: %v", err) 598 } 599 600 // Perform a blocking read on the pipe. 601 var wg sync.WaitGroup 602 ready := make(chan bool) 603 wg.Add(1) 604 go func() { 605 data := make([]byte, 1) 606 607 // To narrow the window we have to wait for this 608 // goroutine to block in read, synchronize just before 609 // calling read. 610 ready <- true 611 612 // We use syscall.Read directly to avoid the poller. 613 // This will return when the write side is closed. 614 n, err := syscall.Read(int(rd.Fd()), data) 615 if !(n == 0 && err == nil) { 616 t.Errorf("expected read to return 0, got %d, %s", n, err) 617 } 618 619 // Clean up rd and also ensure rd stays reachable so 620 // it doesn't get closed by GC. 621 rd.Close() 622 wg.Done() 623 }() 624 <-ready 625 626 // Loop here to give the goroutine more time to block in read. 627 // Generally this will trigger on the first iteration anyway. 628 pid := syscall.Getpid() 629 for i := 0; i < 100; i++ { 630 if id, _, e := syscall.AllThreadsSyscall(syscall.SYS_GETPID, 0, 0, 0); e != 0 { 631 t.Errorf("[%d] getpid failed: %v", i, e) 632 } else if int(id) != pid { 633 t.Errorf("[%d] getpid got=%d, want=%d", i, id, pid) 634 } 635 // Provide an explicit opportunity for this goroutine 636 // to change Ms. 637 runtime.Gosched() 638 } 639 wr.Close() 640 wg.Wait() 641 }