1// Copyright 2012 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// +build linux
6
7package unix_test
8
9import (
10	"bytes"
11	"go/build"
12	"net"
13	"os"
14	"testing"
15
16	"golang.org/x/sys/unix"
17)
18
19// TestSCMCredentials tests the sending and receiving of credentials
20// (PID, UID, GID) in an ancillary message between two UNIX
21// sockets. The SO_PASSCRED socket option is enabled on the sending
22// socket for this to work.
23func TestSCMCredentials(t *testing.T) {
24	socketTypeTests := []struct {
25		socketType int
26		dataLen    int
27	}{
28		{
29			unix.SOCK_STREAM,
30			1,
31		}, {
32			unix.SOCK_DGRAM,
33			0,
34		},
35	}
36
37	for _, tt := range socketTypeTests {
38		if tt.socketType == unix.SOCK_DGRAM && !atLeast1p10() {
39			t.Log("skipping DGRAM test on pre-1.10")
40			continue
41		}
42
43		fds, err := unix.Socketpair(unix.AF_LOCAL, tt.socketType, 0)
44		if err != nil {
45			t.Fatalf("Socketpair: %v", err)
46		}
47		defer unix.Close(fds[0])
48		defer unix.Close(fds[1])
49
50		err = unix.SetsockoptInt(fds[0], unix.SOL_SOCKET, unix.SO_PASSCRED, 1)
51		if err != nil {
52			t.Fatalf("SetsockoptInt: %v", err)
53		}
54
55		srvFile := os.NewFile(uintptr(fds[0]), "server")
56		defer srvFile.Close()
57		srv, err := net.FileConn(srvFile)
58		if err != nil {
59			t.Errorf("FileConn: %v", err)
60			return
61		}
62		defer srv.Close()
63
64		cliFile := os.NewFile(uintptr(fds[1]), "client")
65		defer cliFile.Close()
66		cli, err := net.FileConn(cliFile)
67		if err != nil {
68			t.Errorf("FileConn: %v", err)
69			return
70		}
71		defer cli.Close()
72
73		var ucred unix.Ucred
74		ucred.Pid = int32(os.Getpid())
75		ucred.Uid = uint32(os.Getuid())
76		ucred.Gid = uint32(os.Getgid())
77		oob := unix.UnixCredentials(&ucred)
78
79		// On SOCK_STREAM, this is internally going to send a dummy byte
80		n, oobn, err := cli.(*net.UnixConn).WriteMsgUnix(nil, oob, nil)
81		if err != nil {
82			t.Fatalf("WriteMsgUnix: %v", err)
83		}
84		if n != 0 {
85			t.Fatalf("WriteMsgUnix n = %d, want 0", n)
86		}
87		if oobn != len(oob) {
88			t.Fatalf("WriteMsgUnix oobn = %d, want %d", oobn, len(oob))
89		}
90
91		oob2 := make([]byte, 10*len(oob))
92		n, oobn2, flags, _, err := srv.(*net.UnixConn).ReadMsgUnix(nil, oob2)
93		if err != nil {
94			t.Fatalf("ReadMsgUnix: %v", err)
95		}
96		if flags != 0 {
97			t.Fatalf("ReadMsgUnix flags = 0x%x, want 0", flags)
98		}
99		if n != tt.dataLen {
100			t.Fatalf("ReadMsgUnix n = %d, want %d", n, tt.dataLen)
101		}
102		if oobn2 != oobn {
103			// without SO_PASSCRED set on the socket, ReadMsgUnix will
104			// return zero oob bytes
105			t.Fatalf("ReadMsgUnix oobn = %d, want %d", oobn2, oobn)
106		}
107		oob2 = oob2[:oobn2]
108		if !bytes.Equal(oob, oob2) {
109			t.Fatal("ReadMsgUnix oob bytes don't match")
110		}
111
112		scm, err := unix.ParseSocketControlMessage(oob2)
113		if err != nil {
114			t.Fatalf("ParseSocketControlMessage: %v", err)
115		}
116		newUcred, err := unix.ParseUnixCredentials(&scm[0])
117		if err != nil {
118			t.Fatalf("ParseUnixCredentials: %v", err)
119		}
120		if *newUcred != ucred {
121			t.Fatalf("ParseUnixCredentials = %+v, want %+v", newUcred, ucred)
122		}
123	}
124}
125
126// atLeast1p10 reports whether we are running on Go 1.10 or later.
127func atLeast1p10() bool {
128	for _, ver := range build.Default.ReleaseTags {
129		if ver == "go1.10" {
130			return true
131		}
132	}
133	return false
134}
135