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