Black Lives Matter. Support the Equal Justice Initiative.

Source file src/syscall/syscall_linux_test.go

Documentation: syscall

     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  	"bufio"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"os/exec"
    14  	"os/signal"
    15  	"path/filepath"
    16  	"runtime"
    17  	"strconv"
    18  	"strings"
    19  	"syscall"
    20  	"testing"
    21  	"time"
    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.
    27  func chtmpdir(t *testing.T) func() {
    28  	oldwd, err := os.Getwd()
    29  	if err != nil {
    30  		t.Fatalf("chtmpdir: %v", err)
    31  	}
    32  	d, err := ioutil.TempDir("", "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  
    47  func 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  
    57  const (
    58  	_AT_SYMLINK_NOFOLLOW = 0x100
    59  	_AT_FDCWD            = -0x64
    60  	_AT_EACCESS          = 0x200
    61  	_F_OK                = 0
    62  	_R_OK                = 4
    63  )
    64  
    65  func 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  
   117  func 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  
   143  func 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  
   155  func TestLinuxDeathSignal(t *testing.T) {
   156  	if os.Getuid() != 0 {
   157  		t.Skip("skipping root only test")
   158  	}
   159  
   160  	// Copy the test binary to a location that a non-root user can read/execute
   161  	// after we drop privileges
   162  	tempDir, err := ioutil.TempDir("", "TestDeathSignal")
   163  	if err != nil {
   164  		t.Fatalf("cannot create temporary directory: %v", err)
   165  	}
   166  	defer os.RemoveAll(tempDir)
   167  	os.Chmod(tempDir, 0755)
   168  
   169  	tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0]))
   170  
   171  	src, err := os.Open(os.Args[0])
   172  	if err != nil {
   173  		t.Fatalf("cannot open binary %q, %v", os.Args[0], err)
   174  	}
   175  	defer src.Close()
   176  
   177  	dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
   178  	if err != nil {
   179  		t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err)
   180  	}
   181  	if _, err := io.Copy(dst, src); err != nil {
   182  		t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err)
   183  	}
   184  	err = dst.Close()
   185  	if err != nil {
   186  		t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
   187  	}
   188  
   189  	cmd := exec.Command(tmpBinary)
   190  	cmd.Env = append(os.Environ(), "GO_DEATHSIG_PARENT=1")
   191  	chldStdin, err := cmd.StdinPipe()
   192  	if err != nil {
   193  		t.Fatalf("failed to create new stdin pipe: %v", err)
   194  	}
   195  	chldStdout, err := cmd.StdoutPipe()
   196  	if err != nil {
   197  		t.Fatalf("failed to create new stdout pipe: %v", err)
   198  	}
   199  	cmd.Stderr = os.Stderr
   200  
   201  	err = cmd.Start()
   202  	defer cmd.Wait()
   203  	if err != nil {
   204  		t.Fatalf("failed to start first child process: %v", err)
   205  	}
   206  
   207  	chldPipe := bufio.NewReader(chldStdout)
   208  
   209  	if got, err := chldPipe.ReadString('\n'); got == "start\n" {
   210  		syscall.Kill(cmd.Process.Pid, syscall.SIGTERM)
   211  
   212  		go func() {
   213  			time.Sleep(5 * time.Second)
   214  			chldStdin.Close()
   215  		}()
   216  
   217  		want := "ok\n"
   218  		if got, err = chldPipe.ReadString('\n'); got != want {
   219  			t.Fatalf("expected %q, received %q, %v", want, got, err)
   220  		}
   221  	} else {
   222  		t.Fatalf("did not receive start from child, received %q, %v", got, err)
   223  	}
   224  }
   225  
   226  func deathSignalParent() {
   227  	cmd := exec.Command(os.Args[0])
   228  	cmd.Env = append(os.Environ(),
   229  		"GO_DEATHSIG_PARENT=",
   230  		"GO_DEATHSIG_CHILD=1",
   231  	)
   232  	cmd.Stdin = os.Stdin
   233  	cmd.Stdout = os.Stdout
   234  	attrs := syscall.SysProcAttr{
   235  		Pdeathsig: syscall.SIGUSR1,
   236  		// UID/GID 99 is the user/group "nobody" on RHEL/Fedora and is
   237  		// unused on Ubuntu
   238  		Credential: &syscall.Credential{Uid: 99, Gid: 99},
   239  	}
   240  	cmd.SysProcAttr = &attrs
   241  
   242  	err := cmd.Start()
   243  	if err != nil {
   244  		fmt.Fprintf(os.Stderr, "death signal parent error: %v\n", err)
   245  		os.Exit(1)
   246  	}
   247  	cmd.Wait()
   248  	os.Exit(0)
   249  }
   250  
   251  func deathSignalChild() {
   252  	c := make(chan os.Signal, 1)
   253  	signal.Notify(c, syscall.SIGUSR1)
   254  	go func() {
   255  		<-c
   256  		fmt.Println("ok")
   257  		os.Exit(0)
   258  	}()
   259  	fmt.Println("start")
   260  
   261  	buf := make([]byte, 32)
   262  	os.Stdin.Read(buf)
   263  
   264  	// We expected to be signaled before stdin closed
   265  	fmt.Println("not ok")
   266  	os.Exit(1)
   267  }
   268  
   269  func TestParseNetlinkMessage(t *testing.T) {
   270  	for i, b := range [][]byte{
   271  		{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,
   272  			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,
   273  			1, 127, 0, 0, 1, 230, 228, 31, 144, 32, 186, 155, 211, 185, 151, 209, 179, 128, 24, 1, 86,
   274  			53, 119, 0, 0, 1, 1, 8, 10, 0, 17, 234, 12, 0, 17, 189, 126, 107, 106, 108, 107, 106, 13, 10,
   275  		},
   276  		{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,
   277  			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,
   278  			1, 127, 0, 0, 1, 237, 206, 31, 144, 73, 197, 128, 65, 250, 60, 192, 97, 128, 24, 1, 86, 253, 21, 0,
   279  			0, 1, 1, 8, 10, 0, 51, 106, 89, 0, 51, 102, 198, 108, 104, 106, 108, 107, 104, 108, 107, 104, 10,
   280  		},
   281  		{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,
   282  			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,
   283  			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,
   284  			10, 0, 52, 198, 200, 0, 51, 135, 232, 101, 115, 97, 103, 103, 10,
   285  		},
   286  	} {
   287  		m, err := syscall.ParseNetlinkMessage(b)
   288  		if err != syscall.EINVAL {
   289  			t.Errorf("#%d: got %v; want EINVAL", i, err)
   290  		}
   291  		if m != nil {
   292  			t.Errorf("#%d: got %v; want nil", i, m)
   293  		}
   294  	}
   295  }
   296  
   297  func TestSyscallNoError(t *testing.T) {
   298  	// On Linux there are currently no syscalls which don't fail and return
   299  	// a value larger than 0xfffffffffffff001 so we could test RawSyscall
   300  	// vs. RawSyscallNoError on 64bit architectures.
   301  	if unsafe.Sizeof(uintptr(0)) != 4 {
   302  		t.Skip("skipping on non-32bit architecture")
   303  	}
   304  
   305  	// See https://golang.org/issue/35422
   306  	// On MIPS, Linux returns whether the syscall had an error in a separate
   307  	// register (R7), not using a negative return value as on other
   308  	// architectures.
   309  	if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" {
   310  		t.Skipf("skipping on %s", runtime.GOARCH)
   311  	}
   312  
   313  	if os.Getuid() != 0 {
   314  		t.Skip("skipping root only test")
   315  	}
   316  
   317  	if runtime.GOOS == "android" {
   318  		t.Skip("skipping on rooted android, see issue 27364")
   319  	}
   320  
   321  	// Copy the test binary to a location that a non-root user can read/execute
   322  	// after we drop privileges
   323  	tempDir, err := ioutil.TempDir("", "TestSyscallNoError")
   324  	if err != nil {
   325  		t.Fatalf("cannot create temporary directory: %v", err)
   326  	}
   327  	defer os.RemoveAll(tempDir)
   328  	os.Chmod(tempDir, 0755)
   329  
   330  	tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0]))
   331  
   332  	src, err := os.Open(os.Args[0])
   333  	if err != nil {
   334  		t.Fatalf("cannot open binary %q, %v", os.Args[0], err)
   335  	}
   336  	defer src.Close()
   337  
   338  	dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
   339  	if err != nil {
   340  		t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err)
   341  	}
   342  	if _, err := io.Copy(dst, src); err != nil {
   343  		t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err)
   344  	}
   345  	err = dst.Close()
   346  	if err != nil {
   347  		t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
   348  	}
   349  
   350  	uid := uint32(0xfffffffe)
   351  	err = os.Chown(tmpBinary, int(uid), -1)
   352  	if err != nil {
   353  		t.Fatalf("failed to chown test binary %q, %v", tmpBinary, err)
   354  	}
   355  
   356  	err = os.Chmod(tmpBinary, 0755|os.ModeSetuid)
   357  	if err != nil {
   358  		t.Fatalf("failed to set setuid bit on test binary %q, %v", tmpBinary, err)
   359  	}
   360  
   361  	cmd := exec.Command(tmpBinary)
   362  	cmd.Env = append(os.Environ(), "GO_SYSCALL_NOERROR=1")
   363  
   364  	out, err := cmd.CombinedOutput()
   365  	if err != nil {
   366  		t.Fatalf("failed to start first child process: %v", err)
   367  	}
   368  
   369  	got := strings.TrimSpace(string(out))
   370  	want := strconv.FormatUint(uint64(uid)+1, 10) + " / " +
   371  		strconv.FormatUint(uint64(-uid), 10) + " / " +
   372  		strconv.FormatUint(uint64(uid), 10)
   373  	if got != want {
   374  		if filesystemIsNoSUID(tmpBinary) {
   375  			t.Skip("skipping test when temp dir is mounted nosuid")
   376  		}
   377  		// formatted so the values are aligned for easier comparison
   378  		t.Errorf("expected %s,\ngot      %s", want, got)
   379  	}
   380  }
   381  
   382  // filesystemIsNoSUID reports whether the filesystem for the given
   383  // path is mounted nosuid.
   384  func filesystemIsNoSUID(path string) bool {
   385  	var st syscall.Statfs_t
   386  	if syscall.Statfs(path, &st) != nil {
   387  		return false
   388  	}
   389  	return st.Flags&syscall.MS_NOSUID != 0
   390  }
   391  
   392  func syscallNoError() {
   393  	// Test that the return value from SYS_GETEUID32 (which cannot fail)
   394  	// doesn't get treated as an error (see https://golang.org/issue/22924)
   395  	euid1, _, e := syscall.RawSyscall(syscall.Sys_GETEUID, 0, 0, 0)
   396  	euid2, _ := syscall.RawSyscallNoError(syscall.Sys_GETEUID, 0, 0, 0)
   397  
   398  	fmt.Println(uintptr(euid1), "/", int(e), "/", uintptr(euid2))
   399  	os.Exit(0)
   400  }
   401  

View as plain text