...
Run Format

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 = []string{"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 = []string{"GO_DEATHSIG_CHILD=1"}
   229  	cmd.Stdin = os.Stdin
   230  	cmd.Stdout = os.Stdout
   231  	attrs := syscall.SysProcAttr{
   232  		Pdeathsig: syscall.SIGUSR1,
   233  		// UID/GID 99 is the user/group "nobody" on RHEL/Fedora and is
   234  		// unused on Ubuntu
   235  		Credential: &syscall.Credential{Uid: 99, Gid: 99},
   236  	}
   237  	cmd.SysProcAttr = &attrs
   238  
   239  	err := cmd.Start()
   240  	if err != nil {
   241  		fmt.Fprintf(os.Stderr, "death signal parent error: %v\n", err)
   242  		os.Exit(1)
   243  	}
   244  	cmd.Wait()
   245  	os.Exit(0)
   246  }
   247  
   248  func deathSignalChild() {
   249  	c := make(chan os.Signal, 1)
   250  	signal.Notify(c, syscall.SIGUSR1)
   251  	go func() {
   252  		<-c
   253  		fmt.Println("ok")
   254  		os.Exit(0)
   255  	}()
   256  	fmt.Println("start")
   257  
   258  	buf := make([]byte, 32)
   259  	os.Stdin.Read(buf)
   260  
   261  	// We expected to be signaled before stdin closed
   262  	fmt.Println("not ok")
   263  	os.Exit(1)
   264  }
   265  
   266  func TestParseNetlinkMessage(t *testing.T) {
   267  	for i, b := range [][]byte{
   268  		{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,
   269  			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,
   270  			1, 127, 0, 0, 1, 230, 228, 31, 144, 32, 186, 155, 211, 185, 151, 209, 179, 128, 24, 1, 86,
   271  			53, 119, 0, 0, 1, 1, 8, 10, 0, 17, 234, 12, 0, 17, 189, 126, 107, 106, 108, 107, 106, 13, 10,
   272  		},
   273  		{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,
   274  			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,
   275  			1, 127, 0, 0, 1, 237, 206, 31, 144, 73, 197, 128, 65, 250, 60, 192, 97, 128, 24, 1, 86, 253, 21, 0,
   276  			0, 1, 1, 8, 10, 0, 51, 106, 89, 0, 51, 102, 198, 108, 104, 106, 108, 107, 104, 108, 107, 104, 10,
   277  		},
   278  		{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,
   279  			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,
   280  			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,
   281  			10, 0, 52, 198, 200, 0, 51, 135, 232, 101, 115, 97, 103, 103, 10,
   282  		},
   283  	} {
   284  		m, err := syscall.ParseNetlinkMessage(b)
   285  		if err != syscall.EINVAL {
   286  			t.Errorf("#%d: got %v; want EINVAL", i, err)
   287  		}
   288  		if m != nil {
   289  			t.Errorf("#%d: got %v; want nil", i, m)
   290  		}
   291  	}
   292  }
   293  
   294  func TestSyscallNoError(t *testing.T) {
   295  	// On Linux there are currently no syscalls which don't fail and return
   296  	// a value larger than 0xfffffffffffff001 so we could test RawSyscall
   297  	// vs. RawSyscallNoError on 64bit architectures.
   298  	if unsafe.Sizeof(uintptr(0)) != 4 {
   299  		t.Skip("skipping on non-32bit architecture")
   300  	}
   301  
   302  	if os.Getuid() != 0 {
   303  		t.Skip("skipping root only test")
   304  	}
   305  
   306  	if runtime.GOOS == "android" {
   307  		t.Skip("skipping on rooted android, see issue 27364")
   308  	}
   309  
   310  	// Copy the test binary to a location that a non-root user can read/execute
   311  	// after we drop privileges
   312  	tempDir, err := ioutil.TempDir("", "TestSyscallNoError")
   313  	if err != nil {
   314  		t.Fatalf("cannot create temporary directory: %v", err)
   315  	}
   316  	defer os.RemoveAll(tempDir)
   317  	os.Chmod(tempDir, 0755)
   318  
   319  	tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0]))
   320  
   321  	src, err := os.Open(os.Args[0])
   322  	if err != nil {
   323  		t.Fatalf("cannot open binary %q, %v", os.Args[0], err)
   324  	}
   325  	defer src.Close()
   326  
   327  	dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
   328  	if err != nil {
   329  		t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err)
   330  	}
   331  	if _, err := io.Copy(dst, src); err != nil {
   332  		t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err)
   333  	}
   334  	err = dst.Close()
   335  	if err != nil {
   336  		t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
   337  	}
   338  
   339  	uid := uint32(0xfffffffe)
   340  	err = os.Chown(tmpBinary, int(uid), -1)
   341  	if err != nil {
   342  		t.Fatalf("failed to chown test binary %q, %v", tmpBinary, err)
   343  	}
   344  
   345  	err = os.Chmod(tmpBinary, 0755|os.ModeSetuid)
   346  	if err != nil {
   347  		t.Fatalf("failed to set setuid bit on test binary %q, %v", tmpBinary, err)
   348  	}
   349  
   350  	cmd := exec.Command(tmpBinary)
   351  	cmd.Env = []string{"GO_SYSCALL_NOERROR=1"}
   352  
   353  	out, err := cmd.CombinedOutput()
   354  	if err != nil {
   355  		t.Fatalf("failed to start first child process: %v", err)
   356  	}
   357  
   358  	got := strings.TrimSpace(string(out))
   359  	want := strconv.FormatUint(uint64(uid)+1, 10) + " / " +
   360  		strconv.FormatUint(uint64(-uid), 10) + " / " +
   361  		strconv.FormatUint(uint64(uid), 10)
   362  	if got != want {
   363  		if filesystemIsNoSUID(tmpBinary) {
   364  			t.Skip("skipping test when temp dir is mounted nosuid")
   365  		}
   366  		t.Errorf("expected %s, got %s", want, got)
   367  	}
   368  }
   369  
   370  // filesystemIsNoSUID reports whether the filesystem for the given
   371  // path is mounted nosuid.
   372  func filesystemIsNoSUID(path string) bool {
   373  	var st syscall.Statfs_t
   374  	if syscall.Statfs(path, &st) != nil {
   375  		return false
   376  	}
   377  	return st.Flags&syscall.MS_NOSUID != 0
   378  }
   379  
   380  func syscallNoError() {
   381  	// Test that the return value from SYS_GETEUID32 (which cannot fail)
   382  	// doesn't get treated as an error (see https://golang.org/issue/22924)
   383  	euid1, _, e := syscall.RawSyscall(syscall.Sys_GETEUID, 0, 0, 0)
   384  	euid2, _ := syscall.RawSyscallNoError(syscall.Sys_GETEUID, 0, 0, 0)
   385  
   386  	fmt.Println(uintptr(euid1), "/", int(e), "/", uintptr(euid2))
   387  	os.Exit(0)
   388  }
   389  

View as plain text