diff options
Diffstat (limited to 'test')
48 files changed, 2052 insertions, 1480 deletions
diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go index 1accc3b3b..f53417cab 100644 --- a/test/e2e/integration_test.go +++ b/test/e2e/integration_test.go @@ -30,6 +30,7 @@ import ( "net/http" "os" "path/filepath" + "regexp" "strconv" "strings" "testing" @@ -426,10 +427,10 @@ func TestTmpMount(t *testing.T) { // Test that it is allowed to mount a file on top of /dev files, e.g. // /dev/random. func TestMountOverDev(t *testing.T) { - if usingVFS2, err := dockerutil.UsingVFS2(); !usingVFS2 { - t.Skip("VFS1 doesn't allow /dev/random to be mounted.") - } else if err != nil { + if vfs2, err := dockerutil.UsingVFS2(); err != nil { t.Fatalf("Failed to read config for runtime %s: %v", dockerutil.Runtime(), err) + } else if !vfs2 { + t.Skip("VFS1 doesn't allow /dev/random to be mounted.") } random, err := ioutil.TempFile(testutil.TmpDir(), "random") @@ -574,11 +575,12 @@ func runIntegrationTest(t *testing.T, capAdd []string, args ...string) { d := dockerutil.MakeContainer(ctx, t) defer d.CleanUp(ctx) - if got, err := d.Run(ctx, dockerutil.RunOpts{ + opts := dockerutil.RunOpts{ Image: "basic/integrationtest", WorkDir: "/root", CapAdd: capAdd, - }, args...); err != nil { + } + if got, err := d.Run(ctx, opts, args...); err != nil { t.Fatalf("docker run failed: %v", err) } else if got != "" { t.Errorf("test failed:\n%s", got) @@ -609,6 +611,107 @@ func TestBindOverlay(t *testing.T) { } } +func TestStdios(t *testing.T) { + if vfs2, err := dockerutil.UsingVFS2(); err != nil { + t.Fatalf("Failed to read config for runtime %s: %v", dockerutil.Runtime(), err) + } else if !vfs2 { + t.Skip("VFS1 doesn't adjust stdios user") + } + + ctx := context.Background() + d := dockerutil.MakeContainer(ctx, t) + defer d.CleanUp(ctx) + + testStdios(t, func(user string, args ...string) (string, error) { + defer d.CleanUp(ctx) + opts := dockerutil.RunOpts{ + Image: "basic/alpine", + User: user, + } + return d.Run(ctx, opts, args...) + }) +} + +func TestStdiosExec(t *testing.T) { + if vfs2, err := dockerutil.UsingVFS2(); err != nil { + t.Fatalf("Failed to read config for runtime %s: %v", dockerutil.Runtime(), err) + } else if !vfs2 { + t.Skip("VFS1 doesn't adjust stdios user") + } + + ctx := context.Background() + d := dockerutil.MakeContainer(ctx, t) + defer d.CleanUp(ctx) + + runOpts := dockerutil.RunOpts{Image: "basic/alpine"} + if err := d.Spawn(ctx, runOpts, "sleep", "100"); err != nil { + t.Fatalf("docker run failed: %v", err) + } + + testStdios(t, func(user string, args ...string) (string, error) { + opts := dockerutil.ExecOpts{User: user} + return d.Exec(ctx, opts, args...) + }) +} + +func testStdios(t *testing.T, run func(string, ...string) (string, error)) { + const cmd = "stat -L /proc/self/fd/0 /proc/self/fd/1 /proc/self/fd/2 | grep 'Uid:'" + got, err := run("123", "/bin/sh", "-c", cmd) + if err != nil { + t.Fatalf("docker exec failed: %v", err) + } + if len(got) == 0 { + t.Errorf("Unexpected empty output from %q", cmd) + } + re := regexp.MustCompile(`Uid: \(\s*(\w+)\/.*\)`) + for _, line := range strings.SplitN(got, "\n", 3) { + t.Logf("stat -L: %s", line) + matches := re.FindSubmatch([]byte(line)) + if len(matches) != 2 { + t.Fatalf("wrong output format: %q: matches: %v", line, matches) + } + if want, got := "123", string(matches[1]); want != got { + t.Errorf("wrong user, want: %q, got: %q", want, got) + } + } + + // Check that stdout and stderr can be open and written to. This checks + // that ownership and permissions are correct inside gVisor. + got, err = run("456", "/bin/sh", "-c", "echo foobar | tee /proc/self/fd/1 > /proc/self/fd/2") + if err != nil { + t.Fatalf("docker run failed: %v", err) + } + t.Logf("echo foobar: %q", got) + // Check it repeats twice, once for stdout and once for stderr. + if want := "foobar\nfoobar\n"; want != got { + t.Errorf("Wrong echo output, want: %q, got: %q", want, got) + } + + // Check that timestamps can be changed. Setting timestamps require an extra + // write check _after_ the file was opened, and may fail if the underlying + // host file is not setup correctly. + if _, err := run("789", "touch", "/proc/self/fd/0", "/proc/self/fd/1", "/proc/self/fd/2"); err != nil { + t.Fatalf("docker run failed: %v", err) + } +} + +func TestStdiosChown(t *testing.T) { + if vfs2, err := dockerutil.UsingVFS2(); err != nil { + t.Fatalf("Failed to read config for runtime %s: %v", dockerutil.Runtime(), err) + } else if !vfs2 { + t.Skip("VFS1 doesn't adjust stdios user") + } + + ctx := context.Background() + d := dockerutil.MakeContainer(ctx, t) + defer d.CleanUp(ctx) + + opts := dockerutil.RunOpts{Image: "basic/alpine"} + if _, err := d.Run(ctx, opts, "chown", "123", "/proc/self/fd/0", "/proc/self/fd/1", "/proc/self/fd/2"); err != nil { + t.Fatalf("docker run failed: %v", err) + } +} + func TestMain(m *testing.M) { dockerutil.EnsureSupportedDockerVersion() flag.Parse() diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go index 0cac0bf1b..7e89ba2b3 100644 --- a/test/packetimpact/testbench/dut.go +++ b/test/packetimpact/testbench/dut.go @@ -180,9 +180,7 @@ func (dut *DUT) CreateListener(t *testing.T, typ, proto, backlog int32) (int32, func (dut *DUT) Accept(t *testing.T, sockfd int32) (int32, unix.Sockaddr) { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - fd, sa, err := dut.AcceptWithErrno(ctx, t, sockfd) + fd, sa, err := dut.AcceptWithErrno(context.Background(), t, sockfd) if fd < 0 { t.Fatalf("failed to accept: %s", err) } @@ -209,9 +207,7 @@ func (dut *DUT) AcceptWithErrno(ctx context.Context, t *testing.T, sockfd int32) func (dut *DUT) Bind(t *testing.T, fd int32, sa unix.Sockaddr) { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.BindWithErrno(ctx, t, fd, sa) + ret, err := dut.BindWithErrno(context.Background(), t, fd, sa) if ret != 0 { t.Fatalf("failed to bind socket: %s", err) } @@ -238,9 +234,7 @@ func (dut *DUT) BindWithErrno(ctx context.Context, t *testing.T, fd int32, sa un func (dut *DUT) Close(t *testing.T, fd int32) { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.CloseWithErrno(ctx, t, fd) + ret, err := dut.CloseWithErrno(context.Background(), t, fd) if ret != 0 { t.Fatalf("failed to close: %s", err) } @@ -266,9 +260,7 @@ func (dut *DUT) CloseWithErrno(ctx context.Context, t *testing.T, fd int32) (int func (dut *DUT) Connect(t *testing.T, fd int32, sa unix.Sockaddr) { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.ConnectWithErrno(ctx, t, fd, sa) + ret, err := dut.ConnectWithErrno(context.Background(), t, fd, sa) // Ignore 'operation in progress' error that can be returned when the socket // is non-blocking. if err != unix.EINPROGRESS && ret != 0 { @@ -297,9 +289,7 @@ func (dut *DUT) ConnectWithErrno(ctx context.Context, t *testing.T, fd int32, sa func (dut *DUT) GetSockName(t *testing.T, sockfd int32) unix.Sockaddr { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, sa, err := dut.GetSockNameWithErrno(ctx, t, sockfd) + ret, sa, err := dut.GetSockNameWithErrno(context.Background(), t, sockfd) if ret != 0 { t.Fatalf("failed to getsockname: %s", err) } @@ -349,9 +339,7 @@ func (dut *DUT) getSockOpt(ctx context.Context, t *testing.T, sockfd, level, opt func (dut *DUT) GetSockOpt(t *testing.T, sockfd, level, optname, optlen int32) []byte { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, optval, err := dut.GetSockOptWithErrno(ctx, t, sockfd, level, optname, optlen) + ret, optval, err := dut.GetSockOptWithErrno(context.Background(), t, sockfd, level, optname, optlen) if ret != 0 { t.Fatalf("failed to GetSockOpt: %s", err) } @@ -378,9 +366,7 @@ func (dut *DUT) GetSockOptWithErrno(ctx context.Context, t *testing.T, sockfd, l func (dut *DUT) GetSockOptInt(t *testing.T, sockfd, level, optname int32) int32 { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, intval, err := dut.GetSockOptIntWithErrno(ctx, t, sockfd, level, optname) + ret, intval, err := dut.GetSockOptIntWithErrno(context.Background(), t, sockfd, level, optname) if ret != 0 { t.Fatalf("failed to GetSockOptInt: %s", err) } @@ -405,9 +391,7 @@ func (dut *DUT) GetSockOptIntWithErrno(ctx context.Context, t *testing.T, sockfd func (dut *DUT) GetSockOptTimeval(t *testing.T, sockfd, level, optname int32) unix.Timeval { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, timeval, err := dut.GetSockOptTimevalWithErrno(ctx, t, sockfd, level, optname) + ret, timeval, err := dut.GetSockOptTimevalWithErrno(context.Background(), t, sockfd, level, optname) if ret != 0 { t.Fatalf("failed to GetSockOptTimeval: %s", err) } @@ -434,9 +418,7 @@ func (dut *DUT) GetSockOptTimevalWithErrno(ctx context.Context, t *testing.T, so func (dut *DUT) GetSockOptTCPInfo(t *testing.T, sockfd int32) linux.TCPInfo { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, info, err := dut.GetSockOptTCPInfoWithErrno(ctx, t, sockfd) + ret, info, err := dut.GetSockOptTCPInfoWithErrno(context.Background(), t, sockfd) if ret != 0 || err != unix.Errno(0) { t.Fatalf("failed to GetSockOptTCPInfo: %s", err) } @@ -463,9 +445,7 @@ func (dut *DUT) GetSockOptTCPInfoWithErrno(ctx context.Context, t *testing.T, so func (dut *DUT) Listen(t *testing.T, sockfd, backlog int32) { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.ListenWithErrno(ctx, t, sockfd, backlog) + ret, err := dut.ListenWithErrno(context.Background(), t, sockfd, backlog) if ret != 0 { t.Fatalf("failed to listen: %s", err) } @@ -510,13 +490,7 @@ func (dut *DUT) PollOne(t *testing.T, fd int32, events int16, timeout time.Durat func (dut *DUT) Poll(t *testing.T, pfds []unix.PollFd, timeout time.Duration) []unix.PollFd { t.Helper() - ctx := context.Background() - var cancel context.CancelFunc - if timeout >= 0 { - ctx, cancel = context.WithTimeout(ctx, timeout+RPCTimeout) - defer cancel() - } - ret, result, err := dut.PollWithErrno(ctx, t, pfds, timeout) + ret, result, err := dut.PollWithErrno(context.Background(), t, pfds, timeout) if ret < 0 { t.Fatalf("failed to poll: %s", err) } @@ -559,9 +533,7 @@ func (dut *DUT) PollWithErrno(ctx context.Context, t *testing.T, pfds []unix.Pol func (dut *DUT) Send(t *testing.T, sockfd int32, buf []byte, flags int32) int32 { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.SendWithErrno(ctx, t, sockfd, buf, flags) + ret, err := dut.SendWithErrno(context.Background(), t, sockfd, buf, flags) if ret == -1 { t.Fatalf("failed to send: %s", err) } @@ -590,9 +562,7 @@ func (dut *DUT) SendWithErrno(ctx context.Context, t *testing.T, sockfd int32, b func (dut *DUT) SendTo(t *testing.T, sockfd int32, buf []byte, flags int32, destAddr unix.Sockaddr) int32 { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.SendToWithErrno(ctx, t, sockfd, buf, flags, destAddr) + ret, err := dut.SendToWithErrno(context.Background(), t, sockfd, buf, flags, destAddr) if ret == -1 { t.Fatalf("failed to sendto: %s", err) } @@ -625,10 +595,8 @@ func (dut *DUT) SetNonBlocking(t *testing.T, fd int32, nonblocking bool) { Fd: fd, Nonblocking: nonblocking, } - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - resp, err := dut.posixServer.SetNonblocking(ctx, req) + resp, err := dut.posixServer.SetNonblocking(context.Background(), req) if err != nil { t.Fatalf("failed to call SetNonblocking: %s", err) } @@ -661,9 +629,7 @@ func (dut *DUT) setSockOpt(ctx context.Context, t *testing.T, sockfd, level, opt func (dut *DUT) SetSockOpt(t *testing.T, sockfd, level, optname int32, optval []byte) { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.SetSockOptWithErrno(ctx, t, sockfd, level, optname, optval) + ret, err := dut.SetSockOptWithErrno(context.Background(), t, sockfd, level, optname, optval) if ret != 0 { t.Fatalf("failed to SetSockOpt: %s", err) } @@ -684,9 +650,7 @@ func (dut *DUT) SetSockOptWithErrno(ctx context.Context, t *testing.T, sockfd, l func (dut *DUT) SetSockOptInt(t *testing.T, sockfd, level, optname, optval int32) { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.SetSockOptIntWithErrno(ctx, t, sockfd, level, optname, optval) + ret, err := dut.SetSockOptIntWithErrno(context.Background(), t, sockfd, level, optname, optval) if ret != 0 { t.Fatalf("failed to SetSockOptInt: %s", err) } @@ -705,9 +669,7 @@ func (dut *DUT) SetSockOptIntWithErrno(ctx context.Context, t *testing.T, sockfd func (dut *DUT) SetSockOptTimeval(t *testing.T, sockfd, level, optname int32, tv *unix.Timeval) { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.SetSockOptTimevalWithErrno(ctx, t, sockfd, level, optname, tv) + ret, err := dut.SetSockOptTimevalWithErrno(context.Background(), t, sockfd, level, optname, tv) if ret != 0 { t.Fatalf("failed to SetSockOptTimeval: %s", err) } @@ -746,8 +708,7 @@ func (dut *DUT) SocketWithErrno(t *testing.T, domain, typ, proto int32) (int32, Type: typ, Protocol: proto, } - ctx := context.Background() - resp, err := dut.posixServer.Socket(ctx, req) + resp, err := dut.posixServer.Socket(context.Background(), req) if err != nil { t.Fatalf("failed to call Socket: %s", err) } @@ -760,9 +721,7 @@ func (dut *DUT) SocketWithErrno(t *testing.T, domain, typ, proto int32) (int32, func (dut *DUT) Recv(t *testing.T, sockfd, len, flags int32) []byte { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, buf, err := dut.RecvWithErrno(ctx, t, sockfd, len, flags) + ret, buf, err := dut.RecvWithErrno(context.Background(), t, sockfd, len, flags) if ret == -1 { t.Fatalf("failed to recv: %s", err) } @@ -805,9 +764,7 @@ func (dut *DUT) SetSockLingerOption(t *testing.T, sockfd int32, timeout time.Dur func (dut *DUT) Shutdown(t *testing.T, fd, how int32) { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.ShutdownWithErrno(ctx, t, fd, how) + ret, err := dut.ShutdownWithErrno(context.Background(), t, fd, how) if ret != 0 { t.Fatalf("failed to shutdown(%d, %d): %s", fd, how, err) } diff --git a/test/packetimpact/testbench/dut_client.go b/test/packetimpact/testbench/dut_client.go index 0fc3d97b4..3b69b28aa 100644 --- a/test/packetimpact/testbench/dut_client.go +++ b/test/packetimpact/testbench/dut_client.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build go1.1 +// +build go1.1 + package testbench import ( diff --git a/test/packetimpact/testbench/testbench.go b/test/packetimpact/testbench/testbench.go index caa389780..38ae9c1d7 100644 --- a/test/packetimpact/testbench/testbench.go +++ b/test/packetimpact/testbench/testbench.go @@ -31,8 +31,6 @@ var ( Native = false // RPCKeepalive is the gRPC keepalive. RPCKeepalive = 10 * time.Second - // RPCTimeout is the gRPC timeout. - RPCTimeout = 100 * time.Millisecond // dutInfosJSON is the json string that describes information about all the // duts available to use. @@ -124,7 +122,6 @@ func (n *DUTTestNet) SubnetBroadcast() net.IP { // functions. func registerFlags(fs *flag.FlagSet) { fs.BoolVar(&Native, "native", Native, "whether the test is running natively") - fs.DurationVar(&RPCTimeout, "rpc_timeout", RPCTimeout, "gRPC timeout") fs.DurationVar(&RPCKeepalive, "rpc_keepalive", RPCKeepalive, "gRPC keepalive") fs.StringVar(&dutInfosJSON, "dut_infos_json", dutInfosJSON, "json that describes the DUTs") } diff --git a/test/packetimpact/tests/generic_dgram_socket_send_recv_test.go b/test/packetimpact/tests/generic_dgram_socket_send_recv_test.go index 00e0f7690..a9ffafc74 100644 --- a/test/packetimpact/tests/generic_dgram_socket_send_recv_test.go +++ b/test/packetimpact/tests/generic_dgram_socket_send_recv_test.go @@ -48,7 +48,6 @@ func maxUDPPayloadSize(addr net.IP) int { func init() { testbench.Initialize(flag.CommandLine) - testbench.RPCTimeout = 500 * time.Millisecond } func expectedEthLayer(t *testing.T, dut testbench.DUT, socketFD int32, sendTo net.IP) testbench.Layer { @@ -437,9 +436,7 @@ func (test *icmpV6Test) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net copy(destSockaddr.Addr[:], sendTo.To16()) // Tell the DUT to send a packet out the ICMPv6 socket. - ctx, cancel := context.WithTimeout(context.Background(), testbench.RPCTimeout) - defer cancel() - gotRet, gotErrno := dut.SendToWithErrno(ctx, t, env.socketFD, bytes, 0, &destSockaddr) + gotRet, gotErrno := dut.SendToWithErrno(context.Background(), t, env.socketFD, bytes, 0, &destSockaddr) if gotErrno != wantErrno { t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (_, %s), want = (_, %s)", env.socketFD, sendTo, gotErrno, wantErrno) @@ -677,9 +674,7 @@ func (test *udpTest) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP } // Tell the DUT to send a packet out the UDP socket. - ctx, cancel := context.WithTimeout(context.Background(), testbench.RPCTimeout) - defer cancel() - gotRet, gotErrno := dut.SendToWithErrno(ctx, t, env.socketFD, payload, 0, destSockaddr) + gotRet, gotErrno := dut.SendToWithErrno(context.Background(), t, env.socketFD, payload, 0, destSockaddr) if gotErrno != wantErrno { t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (_, %s), want = (_, %s)", env.socketFD, sendTo, gotErrno, wantErrno) diff --git a/test/packetimpact/tests/tcp_connect_icmp_error_test.go b/test/packetimpact/tests/tcp_connect_icmp_error_test.go index 3b4c4cd63..15d603328 100644 --- a/test/packetimpact/tests/tcp_connect_icmp_error_test.go +++ b/test/packetimpact/tests/tcp_connect_icmp_error_test.go @@ -15,9 +15,7 @@ package tcp_connect_icmp_error_test import ( - "context" "flag" - "sync" "testing" "time" @@ -66,35 +64,38 @@ func TestTCPConnectICMPError(t *testing.T) { t.Fatalf("expected SYN, %s", err) } - done := make(chan bool) - defer close(done) - var wg sync.WaitGroup - defer wg.Wait() - wg.Add(1) - var block sync.WaitGroup - block.Add(1) + // Continuously try to read the ICMP error in an attempt to trigger a race + // condition. + start := make(chan struct{}) + done := make(chan struct{}) go func() { - defer wg.Done() - _, cancel := context.WithTimeout(context.Background(), time.Second*3) - defer cancel() + defer close(done) - block.Done() + close(start) for { select { case <-done: return default: - if errno := dut.GetSockOptInt(t, clientFD, unix.SOL_SOCKET, unix.SO_ERROR); errno != 0 { - return - } } + const want = unix.EHOSTUNREACH + switch got := unix.Errno(dut.GetSockOptInt(t, clientFD, unix.SOL_SOCKET, unix.SO_ERROR)); got { + case unix.Errno(0): + continue + case want: + return + default: + t.Fatalf("got SO_ERROR = %s, want %s", got, want) + } + } }() - block.Wait() + <-start sendICMPError(t, &conn, tcp) dut.PollOne(t, clientFD, unix.POLLHUP, time.Second) + <-done conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) // The DUT should reply with RST to our ACK as the state should have diff --git a/test/packetimpact/tests/tcp_info_test.go b/test/packetimpact/tests/tcp_info_test.go index b7514e846..5410cc368 100644 --- a/test/packetimpact/tests/tcp_info_test.go +++ b/test/packetimpact/tests/tcp_info_test.go @@ -47,7 +47,7 @@ func TestTCPInfo(t *testing.T) { samplePayload := &testbench.Payload{Bytes: sampleData} dut.Send(t, acceptFD, sampleData, 0) if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) + t.Fatalf("expected a packet with payload %s: %s", samplePayload, err) } conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) @@ -55,20 +55,23 @@ func TestTCPInfo(t *testing.T) { if got, want := uint32(info.State), linux.TCP_ESTABLISHED; got != want { t.Fatalf("got %d want %d", got, want) } - rtt := time.Duration(info.RTT) * time.Microsecond - rttvar := time.Duration(info.RTTVar) * time.Microsecond - rto := time.Duration(info.RTO) * time.Microsecond - if rtt == 0 || rttvar == 0 || rto == 0 { - t.Errorf("expected rtt(%v), rttvar(%v) and rto(%v) to be greater than zero", rtt, rttvar, rto) + if info.RTT == 0 { + t.Errorf("got RTT=0, want nonzero") + } + if info.RTTVar == 0 { + t.Errorf("got RTTVar=0, want nonzero") + } + if info.RTO == 0 { + t.Errorf("got RTO=0, want nonzero") } if info.ReordSeen != 0 { - t.Errorf("expected the connection to not have any reordering, got: %v want: 0", info.ReordSeen) + t.Errorf("expected the connection to not have any reordering, got: %d want: 0", info.ReordSeen) } if info.SndCwnd == 0 { t.Errorf("expected send congestion window to be greater than zero") } if info.CaState != linux.TCP_CA_Open { - t.Errorf("expected the connection to be in open state, got: %v want: %v", info.CaState, linux.TCP_CA_Open) + t.Errorf("expected the connection to be in open state, got: %d want: %d", info.CaState, linux.TCP_CA_Open) } if t.Failed() { @@ -80,20 +83,20 @@ func TestTCPInfo(t *testing.T) { seq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t))) dut.Send(t, acceptFD, sampleData, 0) if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) + t.Fatalf("expected a packet with payload %s: %s", samplePayload, err) } - // Expect retransmission of the packet within 1.5*RTO. - timeout := time.Duration(float64(info.RTO)*1.5) * time.Microsecond + // Given a generous retransmission timeout. + timeout := time.Duration(info.RTO) * 2 * time.Microsecond if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, timeout); err != nil { - t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) + t.Fatalf("expected a packet with payload %s: %s", samplePayload, err) } info = dut.GetSockOptTCPInfo(t, acceptFD) if info.CaState != linux.TCP_CA_Loss { - t.Errorf("expected the connection to be in loss recovery, got: %v want: %v", info.CaState, linux.TCP_CA_Loss) + t.Errorf("expected the connection to be in loss recovery, got: %d want: %d", info.CaState, linux.TCP_CA_Loss) } if info.SndCwnd != 1 { - t.Errorf("expected send congestion window to be 1, got: %v %v", info.SndCwnd) + t.Errorf("expected send congestion window to be 1, got: %d", info.SndCwnd) } } diff --git a/test/packetimpact/tests/tcp_linger_test.go b/test/packetimpact/tests/tcp_linger_test.go index 88942904d..46b5ca5d8 100644 --- a/test/packetimpact/tests/tcp_linger_test.go +++ b/test/packetimpact/tests/tcp_linger_test.go @@ -98,20 +98,19 @@ func TestTCPLingerNonZeroTimeout(t *testing.T) { dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn) - // Increase timeout as Close will take longer time to - // return when SO_LINGER is set with non-zero timeout. - timeout := lingerDuration + 1*time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() start := time.Now() - dut.CloseWithErrno(ctx, t, acceptFD) - end := time.Now() - diff := end.Sub(start) - - if tt.lingerOn && diff < lingerDuration { - t.Errorf("expected close to return after %v seconds, but returned sooner", lingerDuration) - } else if !tt.lingerOn && diff > 1*time.Second { - t.Errorf("expected close to return within a second, but returned later") + dut.CloseWithErrno(context.Background(), t, acceptFD) + elapsed := time.Since(start) + + expectedMaximum := time.Second + if tt.lingerOn { + expectedMaximum += lingerDuration + if elapsed < lingerDuration { + t.Errorf("expected close to take at least %s, but took %s", lingerDuration, elapsed) + } + } + if elapsed >= expectedMaximum { + t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed) } if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { @@ -144,20 +143,19 @@ func TestTCPLingerSendNonZeroTimeout(t *testing.T) { sampleData := []byte("Sample Data") dut.Send(t, acceptFD, sampleData, 0) - // Increase timeout as Close will take longer time to - // return when SO_LINGER is set with non-zero timeout. - timeout := lingerDuration + 1*time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() start := time.Now() - dut.CloseWithErrno(ctx, t, acceptFD) - end := time.Now() - diff := end.Sub(start) - - if tt.lingerOn && diff < lingerDuration { - t.Errorf("expected close to return after %v seconds, but returned sooner", lingerDuration) - } else if !tt.lingerOn && diff > 1*time.Second { - t.Errorf("expected close to return within a second, but returned later") + dut.CloseWithErrno(context.Background(), t, acceptFD) + elapsed := time.Since(start) + + expectedMaximum := time.Second + if tt.lingerOn { + expectedMaximum += lingerDuration + if elapsed < lingerDuration { + t.Errorf("expected close to take at least %s, but took %s", lingerDuration, elapsed) + } + } + if elapsed >= expectedMaximum { + t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed) } samplePayload := &testbench.Payload{Bytes: sampleData} @@ -221,20 +219,19 @@ func TestTCPLingerShutdownSendNonZeroTimeout(t *testing.T) { dut.Shutdown(t, acceptFD, unix.SHUT_RDWR) - // Increase timeout as Close will take longer time to - // return when SO_LINGER is set with non-zero timeout. - timeout := lingerDuration + 1*time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() start := time.Now() - dut.CloseWithErrno(ctx, t, acceptFD) - end := time.Now() - diff := end.Sub(start) - - if tt.lingerOn && diff < lingerDuration { - t.Errorf("expected close to return after %v seconds, but returned sooner", lingerDuration) - } else if !tt.lingerOn && diff > 1*time.Second { - t.Errorf("expected close to return within a second, but returned later") + dut.CloseWithErrno(context.Background(), t, acceptFD) + elapsed := time.Since(start) + + expectedMaximum := time.Second + if tt.lingerOn { + expectedMaximum += lingerDuration + if elapsed < lingerDuration { + t.Errorf("expected close to take at least %s, but took %s", lingerDuration, elapsed) + } + } + if elapsed >= expectedMaximum { + t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed) } samplePayload := &testbench.Payload{Bytes: sampleData} @@ -259,9 +256,10 @@ func TestTCPLingerNonEstablished(t *testing.T) { // and return immediately. start := time.Now() dut.CloseWithErrno(context.Background(), t, newFD) - diff := time.Since(start) + elapsed := time.Since(start) - if diff > lingerDuration { - t.Errorf("expected close to return within %s, but returned after %s", lingerDuration, diff) + expectedMaximum := time.Second + if elapsed >= time.Second { + t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed) } } diff --git a/test/packetimpact/tests/tcp_network_unreachable_test.go b/test/packetimpact/tests/tcp_network_unreachable_test.go index 60a2dbf3d..e92e6aa9b 100644 --- a/test/packetimpact/tests/tcp_network_unreachable_test.go +++ b/test/packetimpact/tests/tcp_network_unreachable_test.go @@ -41,11 +41,9 @@ func TestTCPSynSentUnreachable(t *testing.T) { defer conn.Close(t) // Bring the DUT to SYN-SENT state with a non-blocking connect. - ctx, cancel := context.WithTimeout(context.Background(), testbench.RPCTimeout) - defer cancel() sa := unix.SockaddrInet4{Port: int(port)} copy(sa.Addr[:], dut.Net.LocalIPv4) - if _, err := dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != unix.EINPROGRESS { + if _, err := dut.ConnectWithErrno(context.Background(), t, clientFD, &sa); err != unix.EINPROGRESS { t.Errorf("got connect() = %v, want EINPROGRESS", err) } @@ -86,14 +84,12 @@ func TestTCPSynSentUnreachable6(t *testing.T) { defer conn.Close(t) // Bring the DUT to SYN-SENT state with a non-blocking connect. - ctx, cancel := context.WithTimeout(context.Background(), testbench.RPCTimeout) - defer cancel() sa := unix.SockaddrInet6{ Port: int(conn.SrcPort()), ZoneId: dut.Net.RemoteDevID, } copy(sa.Addr[:], dut.Net.LocalIPv6) - if _, err := dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != unix.EINPROGRESS { + if _, err := dut.ConnectWithErrno(context.Background(), t, clientFD, &sa); err != unix.EINPROGRESS { t.Errorf("got connect() = %v, want EINPROGRESS", err) } diff --git a/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go b/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go index 1c8b72ebe..974c15384 100644 --- a/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go +++ b/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go @@ -20,7 +20,6 @@ import ( "encoding/hex" "errors" "flag" - "sync" "testing" "time" @@ -54,37 +53,39 @@ func TestQueueSendInSynSentHandshake(t *testing.T) { // Test blocking send. dut.SetNonBlocking(t, socket, false) - var wg sync.WaitGroup - defer wg.Wait() - wg.Add(1) - var block sync.WaitGroup - block.Add(1) + start := make(chan struct{}) + done := make(chan struct{}) go func() { - defer wg.Done() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) - defer cancel() + defer close(done) - block.Done() + close(start) // Issue SEND call in SYN-SENT, this should be queued for // process until the connection is established. - n, err := dut.SendWithErrno(ctx, t, socket, sampleData, 0) - if n == -1 { + if _, err := dut.SendWithErrno(context.Background(), t, socket, sampleData, 0); err != unix.Errno(0) { t.Errorf("failed to send on DUT: %s", err) - return } }() // Wait for the goroutine to be scheduled and before it // blocks on endpoint send/receive. - block.Wait() + <-start // The following sleep is used to prevent the connection // from being established before we are blocked: there is // still a small time window between we sending the RPC // request and the system actually being blocked. time.Sleep(100 * time.Millisecond) + select { + case <-done: + t.Fatal("expected send to be blocked in SYN-SENT") + default: + } + // Bring the connection to Established. conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}) + + <-done + // Expect the data from the DUT's enqueued send request. // // On Linux, this can be piggybacked with the ACK completing the @@ -126,21 +127,16 @@ func TestQueueRecvInSynSentHandshake(t *testing.T) { // Test blocking read. dut.SetNonBlocking(t, socket, false) - var wg sync.WaitGroup - defer wg.Wait() - wg.Add(1) - var block sync.WaitGroup - block.Add(1) + start := make(chan struct{}) + done := make(chan struct{}) go func() { - defer wg.Done() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) - defer cancel() + defer close(done) - block.Done() + close(start) // Issue RECEIVE call in SYN-SENT, this should be queued for // process until the connection is established. - n, buff, err := dut.RecvWithErrno(ctx, t, socket, int32(len(sampleData)), 0) - if n == -1 { + n, buff, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0) + if err != unix.Errno(0) { t.Errorf("failed to recv on DUT: %s", err) return } @@ -151,7 +147,8 @@ func TestQueueRecvInSynSentHandshake(t *testing.T) { // Wait for the goroutine to be scheduled and before it // blocks on endpoint send/receive. - block.Wait() + <-start + // The following sleep is used to prevent the connection // from being established before we are blocked: there is // still a small time window between we sending the RPC @@ -169,6 +166,8 @@ func TestQueueRecvInSynSentHandshake(t *testing.T) { if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { t.Fatalf("expected an ACK from DUT, but got none: %s", err) } + + <-done } // TestQueueSendInSynSentRST tests send behavior when the TCP state @@ -192,20 +191,15 @@ func TestQueueSendInSynSentRST(t *testing.T) { // Test blocking send. dut.SetNonBlocking(t, socket, false) - var wg sync.WaitGroup - defer wg.Wait() - wg.Add(1) - var block sync.WaitGroup - block.Add(1) + start := make(chan struct{}) + done := make(chan struct{}) go func() { - defer wg.Done() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) - defer cancel() + defer close(done) - block.Done() + close(start) // Issue SEND call in SYN-SENT, this should be queued for // process until the connection is established. - n, err := dut.SendWithErrno(ctx, t, socket, sampleData, 0) + n, err := dut.SendWithErrno(context.Background(), t, socket, sampleData, 0) if err != unix.ECONNREFUSED { t.Errorf("expected error %s, got %s", unix.ECONNREFUSED, err) } @@ -216,14 +210,23 @@ func TestQueueSendInSynSentRST(t *testing.T) { // Wait for the goroutine to be scheduled and before it // blocks on endpoint send/receive. - block.Wait() + <-start + // The following sleep is used to prevent the connection // from being established before we are blocked: there is // still a small time window between we sending the RPC // request and the system actually being blocked. time.Sleep(100 * time.Millisecond) + select { + case <-done: + t.Fatal("expected send to be blocked in SYN-SENT") + default: + } + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}) + + <-done } // TestQueueRecvInSynSentRST tests recv behavior when the TCP state @@ -251,20 +254,15 @@ func TestQueueRecvInSynSentRST(t *testing.T) { // Test blocking read. dut.SetNonBlocking(t, socket, false) - var wg sync.WaitGroup - defer wg.Wait() - wg.Add(1) - var block sync.WaitGroup - block.Add(1) + start := make(chan struct{}) + done := make(chan struct{}) go func() { - defer wg.Done() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) - defer cancel() + defer close(done) - block.Done() + close(start) // Issue RECEIVE call in SYN-SENT, this should be queued for // process until the connection is established. - n, _, err := dut.RecvWithErrno(ctx, t, socket, int32(len(sampleData)), 0) + n, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0) if err != unix.ECONNREFUSED { t.Errorf("expected error %s, got %s", unix.ECONNREFUSED, err) } @@ -275,7 +273,8 @@ func TestQueueRecvInSynSentRST(t *testing.T) { // Wait for the goroutine to be scheduled and before it // blocks on endpoint send/receive. - block.Wait() + <-start + // The following sleep is used to prevent the connection // from being established before we are blocked: there is // still a small time window between we sending the RPC @@ -283,4 +282,5 @@ func TestQueueRecvInSynSentRST(t *testing.T) { time.Sleep(100 * time.Millisecond) conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}) + <-done } diff --git a/test/packetimpact/tests/udp_icmp_error_propagation_test.go b/test/packetimpact/tests/udp_icmp_error_propagation_test.go index 087aeb66e..bb33ca4b3 100644 --- a/test/packetimpact/tests/udp_icmp_error_propagation_test.go +++ b/test/packetimpact/tests/udp_icmp_error_propagation_test.go @@ -141,8 +141,6 @@ func testRecv(ctx context.Context, t *testing.T, d testData) { d.conn.Send(t, testbench.UDP{}) if d.wantErrno != unix.Errno(0) { - ctx, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() ret, _, err := d.dut.RecvWithErrno(ctx, t, d.remoteFD, 100, 0) if ret != -1 { t.Fatalf("recv after ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", d.wantErrno) @@ -167,8 +165,6 @@ func testSendTo(ctx context.Context, t *testing.T, d testData) { } if d.wantErrno != unix.Errno(0) { - ctx, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() ret, err := d.dut.SendToWithErrno(ctx, t, d.remoteFD, nil, 0, d.conn.LocalAddr(t)) if ret != -1 { @@ -315,10 +311,7 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) { defer wg.Done() if wantErrno != unix.Errno(0) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - ret, _, err := dut.RecvWithErrno(ctx, t, remoteFD, 100, 0) + ret, _, err := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0) if ret != -1 { t.Errorf("recv during ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", wantErrno) return @@ -329,10 +322,7 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) { } } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - if ret, _, err := dut.RecvWithErrno(ctx, t, remoteFD, 100, 0); ret == -1 { + if ret, _, err := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0); ret == -1 { t.Errorf("recv after ICMP error failed with (%[1]d) %[1]", err) } }() @@ -340,10 +330,7 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) { go func() { defer wg.Done() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - if ret, _, err := dut.RecvWithErrno(ctx, t, cleanFD, 100, 0); ret == -1 { + if ret, _, err := dut.RecvWithErrno(context.Background(), t, cleanFD, 100, 0); ret == -1 { t.Errorf("recv on clean socket failed with (%[1]d) %[1]", err) } }() diff --git a/test/runner/BUILD b/test/runner/BUILD index f9f788726..2d93aa6af 100644 --- a/test/runner/BUILD +++ b/test/runner/BUILD @@ -8,6 +8,7 @@ go_binary( srcs = ["main.go"], data = [ "//runsc", + "//test/runner/setup_container", ], visibility = ["//:sandbox"], deps = [ diff --git a/test/runner/defs.bzl b/test/runner/defs.bzl index 416f51935..405e03832 100644 --- a/test/runner/defs.bzl +++ b/test/runner/defs.bzl @@ -103,6 +103,8 @@ def _syscall_test( if platform == "native": tags.append("nogotsan") + container = "container" in tags + runner_args = [ # Arguments are passed directly to runner binary. "--platform=" + platform, @@ -115,6 +117,7 @@ def _syscall_test( "--fuse=" + str(fuse), "--strace=" + str(debug), "--debug=" + str(debug), + "--container=" + str(container), ] # Call the rule above. diff --git a/test/runner/main.go b/test/runner/main.go index 7e8e88ba2..34e9c6279 100644 --- a/test/runner/main.go +++ b/test/runner/main.go @@ -40,16 +40,18 @@ import ( ) var ( - debug = flag.Bool("debug", false, "enable debug logs") - strace = flag.Bool("strace", false, "enable strace logs") - platform = flag.String("platform", "ptrace", "platform to run on") - network = flag.String("network", "none", "network stack to run on (sandbox, host, none)") - useTmpfs = flag.Bool("use-tmpfs", false, "mounts tmpfs for /tmp") - fileAccess = flag.String("file-access", "exclusive", "mounts root in exclusive or shared mode") - overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable tmpfs overlay") - vfs2 = flag.Bool("vfs2", false, "enable VFS2") - fuse = flag.Bool("fuse", false, "enable FUSE") - runscPath = flag.String("runsc", "", "path to runsc binary") + debug = flag.Bool("debug", false, "enable debug logs") + strace = flag.Bool("strace", false, "enable strace logs") + platform = flag.String("platform", "ptrace", "platform to run on") + network = flag.String("network", "none", "network stack to run on (sandbox, host, none)") + useTmpfs = flag.Bool("use-tmpfs", false, "mounts tmpfs for /tmp") + fileAccess = flag.String("file-access", "exclusive", "mounts root in exclusive or shared mode") + overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable tmpfs overlay") + vfs2 = flag.Bool("vfs2", false, "enable VFS2") + fuse = flag.Bool("fuse", false, "enable FUSE") + container = flag.Bool("container", false, "run tests in their own namespaces (user ns, network ns, etc), pretending to be root") + setupContainerPath = flag.String("setup-container", "", "path to setup_container binary (for use with --container)") + runscPath = flag.String("runsc", "", "path to runsc binary") addUDSTree = flag.Bool("add-uds-tree", false, "expose a tree of UDS utilities for use in tests") // TODO(gvisor.dev/issue/4572): properly support leak checking for runsc, and @@ -105,6 +107,27 @@ func runTestCaseNative(testBin string, tc gtest.TestCase, t *testing.T) { cmd.Stderr = os.Stderr cmd.SysProcAttr = &unix.SysProcAttr{} + if *container { + // setup_container takes in its target argv as positional arguments. + cmd.Path = *setupContainerPath + cmd.Args = append([]string{cmd.Path}, cmd.Args...) + cmd.SysProcAttr = &unix.SysProcAttr{ + Cloneflags: unix.CLONE_NEWUSER | unix.CLONE_NEWNET | unix.CLONE_NEWIPC | unix.CLONE_NEWUTS, + // Set current user/group as root inside the namespace. + UidMappings: []syscall.SysProcIDMap{ + {ContainerID: 0, HostID: os.Getuid(), Size: 1}, + }, + GidMappings: []syscall.SysProcIDMap{ + {ContainerID: 0, HostID: os.Getgid(), Size: 1}, + }, + GidMappingsEnableSetgroups: false, + Credential: &syscall.Credential{ + Uid: 0, + Gid: 0, + }, + } + } + if specutils.HasCapabilities(capability.CAP_SYS_ADMIN) { cmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWUTS } @@ -454,6 +477,13 @@ func main() { } *runscPath = specutils.ExePath } + if *container && *setupContainerPath == "" { + setupContainer, err := testutil.FindFile("test/runner/setup_container/setup_container") + if err != nil { + fatalf("cannot find setup_container: %v", err) + } + *setupContainerPath = setupContainer + } // Make sure stdout and stderr are opened with O_APPEND, otherwise logs // from outside the sandbox can (and will) stomp on logs from inside diff --git a/test/runner/setup_container/BUILD b/test/runner/setup_container/BUILD new file mode 100644 index 000000000..5b99d1de9 --- /dev/null +++ b/test/runner/setup_container/BUILD @@ -0,0 +1,19 @@ +# setup_container contains a shim binary that runs within the test container +# for syscall tests with container=True. + +load("//tools:defs.bzl", "cc_binary") + +package(licenses = ["notice"]) + +cc_binary( + name = "setup_container", + testonly = 1, + srcs = ["setup_container.cc"], + visibility = ["//test/runner:__subpackages__"], + deps = [ + "//test/syscalls/linux:socket_netlink_util", + "//test/syscalls/linux:socket_test_util", + "//test/util:capability_util", + "//test/util:posix_error", + ], +) diff --git a/test/runner/setup_container/setup_container.cc b/test/runner/setup_container/setup_container.cc new file mode 100644 index 000000000..9a4e3fb8b --- /dev/null +++ b/test/runner/setup_container/setup_container.cc @@ -0,0 +1,79 @@ +// Copyright 2021 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <linux/capability.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include "test/syscalls/linux/socket_netlink_util.h" +#include "test/syscalls/linux/socket_test_util.h" +#include "test/util/capability_util.h" +#include "test/util/posix_error.h" + +namespace gvisor { +namespace testing { + +// SetupContainer sets up the networking settings in the current container. +PosixError SetupContainer() { + const PosixErrorOr<bool> have_net_admin = HaveCapability(CAP_NET_ADMIN); + if (!have_net_admin.ok()) { + std::cerr << "Cannot determine if we have CAP_NET_ADMIN." << std::endl; + return have_net_admin.error(); + } + if (have_net_admin.ValueOrDie() && !IsRunningOnGvisor()) { + PosixErrorOr<FileDescriptor> sockfd = Socket(AF_INET, SOCK_DGRAM, 0); + if (!sockfd.ok()) { + std::cerr << "Cannot open socket." << std::endl; + return sockfd.error(); + } + int sock = sockfd.ValueOrDie().get(); + struct ifreq ifr = {}; + strncpy(ifr.ifr_name, "lo", IFNAMSIZ); + if (ioctl(sock, SIOCGIFFLAGS, &ifr) == -1) { + std::cerr << "Cannot get 'lo' flags: " << strerror(errno) << std::endl; + return PosixError(errno); + } + if ((ifr.ifr_flags & IFF_UP) == 0) { + ifr.ifr_flags |= IFF_UP; + if (ioctl(sock, SIOCSIFFLAGS, &ifr) == -1) { + std::cerr << "Cannot set 'lo' as UP: " << strerror(errno) << std::endl; + return PosixError(errno); + } + } + } + return NoError(); +} + +} // namespace testing +} // namespace gvisor + +using ::gvisor::testing::SetupContainer; + +// Binary setup_container initializes the container environment in which tests +// with container=True will run, then execs the actual test binary. +// Usage: +// ./setup_container test_binary [arguments forwarded to test_binary...] +int main(int argc, char *argv[], char *envp[]) { + if (!SetupContainer().ok()) { + return 1; + } + if (argc < 2) { + std::cerr << "Must provide arguments to exec." << std::endl; + return 2; + } + if (execve(argv[1], &argv[1], envp) == -1) { + std::cerr << "execv returned errno " << errno << std::endl; + return 1; + } +} diff --git a/test/runtimes/runner/lib/lib.go b/test/runtimes/runner/lib/lib.go index f2db5f9ea..d6b652897 100644 --- a/test/runtimes/runner/lib/lib.go +++ b/test/runtimes/runner/lib/lib.go @@ -152,7 +152,7 @@ func getTests(ctx context.Context, d *dockerutil.Container, lang, image string, return itests, nil } -// getBlacklist reads the exclude file and returns a set of test names to +// getExcludes reads the exclude file and returns a set of test names to // exclude. func getExcludes(excludeFile string) (map[string]struct{}, error) { excludes := make(map[string]struct{}) diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 99743b14a..213c7e96c 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -648,6 +648,13 @@ syscall_test( syscall_test( size = "large", shard_count = most_shards, + tags = ["container"], + test = "//test/syscalls/linux:socket_inet_loopback_isolated_test", +) + +syscall_test( + size = "large", + shard_count = most_shards, # Takes too long for TSAN. Creates a lot of TCP sockets. tags = ["nogotsan"], test = "//test/syscalls/linux:socket_inet_loopback_nogotsan_test", @@ -731,6 +738,7 @@ syscall_test( ) syscall_test( + add_hostinet = True, test = "//test/syscalls/linux:socket_netdevice_test", ) @@ -876,6 +884,10 @@ syscall_test( ) syscall_test( + test = "//test/syscalls/linux:verity_symlink_test", +) + +syscall_test( add_overlay = True, test = "//test/syscalls/linux:sync_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index d8b562e9d..fa2a080f1 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -9,6 +9,8 @@ exports_files( [ "socket.cc", "socket_inet_loopback.cc", + "socket_inet_loopback_isolated.cc", + "socket_inet_loopback_test_params.h", "socket_ip_loopback_blocking.cc", "socket_ip_tcp_generic_loopback.cc", "socket_ip_tcp_loopback.cc", @@ -477,6 +479,7 @@ cc_binary( "//test/util:cleanup", "//test/util:file_descriptor", "//test/util:fs_util", + "@com_google_absl//absl/cleanup", "@com_google_absl//absl/strings", gtest, "//test/util:logging", @@ -1883,6 +1886,7 @@ cc_binary( linkstatic = 1, deps = [ "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/strings", "@com_google_absl//absl/time", gtest, "//test/util:capability_util", @@ -2391,6 +2395,7 @@ cc_library( "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", gtest, + "//test/util:capability_util", "//test/util:test_util", ], alwayslink = 1, @@ -3135,6 +3140,16 @@ cc_binary( ], ) +cc_library( + name = "socket_inet_loopback_test_params", + testonly = 1, + hdrs = ["socket_inet_loopback_test_params.h"], + deps = [ + ":socket_test_util", + gtest, + ], +) + cc_binary( name = "socket_inet_loopback_test", testonly = 1, @@ -3142,6 +3157,7 @@ cc_binary( linkstatic = 1, deps = [ ":ip_socket_test_util", + ":socket_inet_loopback_test_params", ":socket_test_util", "//test/util:file_descriptor", "@com_google_absl//absl/memory", @@ -3163,16 +3179,31 @@ cc_binary( linkstatic = 1, deps = [ ":ip_socket_test_util", + ":socket_inet_loopback_test_params", ":socket_test_util", "//test/util:file_descriptor", - "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", gtest, "//test/util:posix_error", "//test/util:save_util", "//test/util:test_main", "//test/util:test_util", - "//test/util:thread_util", + ], +) + +cc_binary( + name = "socket_inet_loopback_isolated_test", + testonly = 1, + srcs = ["socket_inet_loopback_isolated.cc"], + linkstatic = 1, + deps = [ + ":socket_inet_loopback_test_params", + ":socket_netlink_util", + ":socket_test_util", + gtest, + "//test/util:test_main", + "//test/util:test_util", + "@com_google_absl//absl/time", ], ) @@ -3693,6 +3724,23 @@ cc_binary( ) cc_binary( + name = "verity_symlink_test", + testonly = 1, + srcs = ["verity_symlink.cc"], + linkstatic = 1, + deps = [ + "//test/util:capability_util", + gtest, + "//test/util:fs_util", + "//test/util:mount_util", + "//test/util:temp_path", + "//test/util:test_main", + "//test/util:test_util", + "//test/util:verity_util", + ], +) + +cc_binary( name = "sync_test", testonly = 1, # Android does not support syncfs in r22. diff --git a/test/syscalls/linux/accept_bind.cc b/test/syscalls/linux/accept_bind.cc index aa13e9f84..ba3747290 100644 --- a/test/syscalls/linux/accept_bind.cc +++ b/test/syscalls/linux/accept_bind.cc @@ -37,9 +37,7 @@ TEST_P(AllSocketPairTest, Listen) { sockets->first_addr_size()), SyscallSucceeds()); - ASSERT_THAT(listen(sockets->first_fd(), - /* backlog = */ 5), // NOLINT(bugprone-argument-comment) - SyscallSucceeds()); + ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); } TEST_P(AllSocketPairTest, ListenIncreaseBacklog) { @@ -49,10 +47,8 @@ TEST_P(AllSocketPairTest, ListenIncreaseBacklog) { sockets->first_addr_size()), SyscallSucceeds()); - ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 5), - SyscallSucceeds()); - ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 10), - SyscallSucceeds()); + ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); + ASSERT_THAT(listen(sockets->first_fd(), 10), SyscallSucceeds()); } TEST_P(AllSocketPairTest, ListenDecreaseBacklog) { @@ -62,10 +58,8 @@ TEST_P(AllSocketPairTest, ListenDecreaseBacklog) { sockets->first_addr_size()), SyscallSucceeds()); - ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 5), - SyscallSucceeds()); - ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 1), - SyscallSucceeds()); + ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); + ASSERT_THAT(listen(sockets->first_fd(), 1), SyscallSucceeds()); } TEST_P(AllSocketPairTest, ListenBacklogSizes) { diff --git a/test/syscalls/linux/chroot.cc b/test/syscalls/linux/chroot.cc index fab79d300..7e4626f03 100644 --- a/test/syscalls/linux/chroot.cc +++ b/test/syscalls/linux/chroot.cc @@ -20,16 +20,17 @@ #include <syscall.h> #include <unistd.h> +#include <algorithm> #include <string> #include <vector> #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/cleanup/cleanup.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "test/util/capability_util.h" -#include "test/util/cleanup.h" #include "test/util/file_descriptor.h" #include "test/util/fs_util.h" #include "test/util/logging.h" @@ -46,13 +47,52 @@ namespace testing { namespace { +// Async-signal-safe conversion from integer to string, appending the string +// (including a terminating NUL) to buf, which is a buffer of size len bytes. +// Returns the number of bytes written, or 0 if the buffer is too small. +// +// Preconditions: 2 <= radix <= 16. +template <typename T> +size_t SafeItoa(T val, char* buf, size_t len, int radix) { + size_t n = 0; +#define _WRITE_OR_FAIL(c) \ + do { \ + if (len == 0) { \ + return 0; \ + } \ + buf[n] = (c); \ + n++; \ + len--; \ + } while (false) + if (val == 0) { + _WRITE_OR_FAIL('0'); + } else { + // Write digits in reverse order, then reverse them at the end. + bool neg = val < 0; + while (val != 0) { + // C/C++ define modulo such that the result is negative if exactly one of + // the dividend or divisor is negative, so this handles both positive and + // negative values. + char c = "fedcba9876543210123456789abcdef"[val % radix + 15]; + _WRITE_OR_FAIL(c); + val /= 10; + } + if (neg) { + _WRITE_OR_FAIL('-'); + } + std::reverse(buf, buf + n); + } + _WRITE_OR_FAIL('\0'); + return n; +#undef _WRITE_OR_FAIL +} + TEST(ChrootTest, Success) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); + auto temp_dir = TempPath::CreateDir().ValueOrDie(); + const std::string temp_dir_path = temp_dir.path(); - const auto rest = [] { - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); - }; + const auto rest = [&] { TEST_CHECK_SUCCESS(chroot(temp_dir_path.c_str())); }; EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); } @@ -101,28 +141,34 @@ TEST(ChrootTest, CreatesNewRoot) { SyscallSucceeds()); auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const std::string new_root_path = new_root.path(); auto file_in_new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(new_root.path())); + const std::string file_in_new_root_path = file_in_new_root.path(); const auto rest = [&] { // chroot into new_root. - TEST_CHECK_SUCCESS(chroot(new_root.path().c_str())); + TEST_CHECK_SUCCESS(chroot(new_root_path.c_str())); // getcwd should return "(unreachable)" followed by the initial_cwd. - char cwd[1024]; - TEST_CHECK_SUCCESS(syscall(__NR_getcwd, cwd, sizeof(cwd))); - std::string expected_cwd = "(unreachable)"; - expected_cwd += initial_cwd; - TEST_CHECK(strcmp(cwd, expected_cwd.c_str()) == 0); + char buf[1024]; + TEST_CHECK_SUCCESS(syscall(__NR_getcwd, buf, sizeof(buf))); + constexpr char kUnreachablePrefix[] = "(unreachable)"; + TEST_CHECK( + strncmp(buf, kUnreachablePrefix, sizeof(kUnreachablePrefix) - 1) == 0); + TEST_CHECK(strcmp(buf + sizeof(kUnreachablePrefix) - 1, initial_cwd) == 0); // Should not be able to stat file by its full path. struct stat statbuf; - TEST_CHECK_ERRNO(stat(file_in_new_root.path().c_str(), &statbuf), ENOENT); + TEST_CHECK_ERRNO(stat(file_in_new_root_path.c_str(), &statbuf), ENOENT); // Should be able to stat file at new rooted path. - auto basename = std::string(Basename(file_in_new_root.path())); - auto rootedFile = "/" + basename; - TEST_CHECK_SUCCESS(stat(rootedFile.c_str(), &statbuf)); + buf[0] = '/'; + absl::string_view basename = Basename(file_in_new_root_path); + TEST_CHECK(basename.length() < (sizeof(buf) - 2)); + memcpy(buf + 1, basename.data(), basename.length()); + buf[basename.length() + 1] = '\0'; + TEST_CHECK_SUCCESS(stat(buf, &statbuf)); // Should be able to stat cwd at '.' even though it's outside root. TEST_CHECK_SUCCESS(stat(".", &statbuf)); @@ -131,8 +177,8 @@ TEST(ChrootTest, CreatesNewRoot) { TEST_CHECK_SUCCESS(chdir("/")); // getcwd should return "/". - TEST_CHECK_SUCCESS(syscall(__NR_getcwd, cwd, sizeof(cwd))); - TEST_CHECK_SUCCESS(strcmp(cwd, "/") == 0); + TEST_CHECK_SUCCESS(syscall(__NR_getcwd, buf, sizeof(buf))); + TEST_CHECK_SUCCESS(strcmp(buf, "/") == 0); // Statting '.', '..', '/', and '/..' all return the same dev and inode. struct stat statbuf_dot; @@ -160,10 +206,11 @@ TEST(ChrootTest, DotDotFromOpenFD) { auto fd = ASSERT_NO_ERRNO_AND_VALUE( Open(dir_outside_root.path(), O_RDONLY | O_DIRECTORY)); auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const std::string new_root_path = new_root.path(); const auto rest = [&] { // chroot into new_root. - TEST_CHECK_SUCCESS(chroot(new_root.path().c_str())); + TEST_CHECK_SUCCESS(chroot(new_root_path.c_str())); // openat on fd with path .. will succeed. int other_fd; @@ -184,15 +231,18 @@ TEST(ChrootTest, ProcFdLinkResolutionInChroot) { const TempPath file_outside_chroot = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const std::string file_outside_chroot_path = file_outside_chroot.path(); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file_outside_chroot.path(), O_RDONLY)); const FileDescriptor proc_fd = ASSERT_NO_ERRNO_AND_VALUE( Open("/proc", O_DIRECTORY | O_RDONLY | O_CLOEXEC)); + auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const std::string temp_dir_path = temp_dir.path(); + const auto rest = [&] { - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); + TEST_CHECK_SUCCESS(chroot(temp_dir_path.c_str())); // Opening relative to an already open fd to a node outside the chroot // works. @@ -201,9 +251,10 @@ TEST(ChrootTest, ProcFdLinkResolutionInChroot) { // Proc fd symlinks can escape the chroot if the fd the symlink refers to // refers to an object outside the chroot. + char fd_buf[11]; + TEST_CHECK(SafeItoa(fd.get(), fd_buf, sizeof(fd_buf), 10)); struct stat s = {}; - TEST_CHECK_SUCCESS( - fstatat(proc_self_fd.get(), absl::StrCat(fd.get()).c_str(), &s, 0)); + TEST_CHECK_SUCCESS(fstatat(proc_self_fd.get(), fd_buf, &s, 0)); // Try to stat the stdin fd. Internally, this is handled differently from a // proc fd entry pointing to a file, since stdin is backed by a host fd, and @@ -223,10 +274,12 @@ TEST(ChrootTest, ProcMemSelfFdsNoEscapeProcOpen) { const FileDescriptor proc = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY)); + const auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const std::string temp_dir_path = temp_dir.path(); + const auto rest = [&] { - // Create and enter a chroot directory. - const auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); + // Enter the chroot directory. + TEST_CHECK_SUCCESS(chroot(temp_dir_path.c_str())); // Open a file inside the chroot at /foo. const FileDescriptor foo = @@ -234,11 +287,15 @@ TEST(ChrootTest, ProcMemSelfFdsNoEscapeProcOpen) { // Examine /proc/self/fd/{foo_fd} to see if it exposes the fact that we're // inside a chroot, the path should be /foo and NOT {chroot_dir}/foo. - const std::string fd_path = absl::StrCat("self/fd/", foo.get()); + constexpr char kSelfFdRelpath[] = "self/fd/"; + char path_buf[20]; + strcpy(path_buf, kSelfFdRelpath); // NOLINT: need async-signal-safety + TEST_CHECK(SafeItoa(foo.get(), path_buf + sizeof(kSelfFdRelpath) - 1, + sizeof(path_buf) - (sizeof(kSelfFdRelpath) - 1), 10)); char buf[1024] = {}; size_t bytes_read = 0; - TEST_CHECK_SUCCESS(bytes_read = readlinkat(proc.get(), fd_path.c_str(), buf, - sizeof(buf) - 1)); + TEST_CHECK_SUCCESS( + bytes_read = readlinkat(proc.get(), path_buf, buf, sizeof(buf) - 1)); // The link should resolve to something. TEST_CHECK(bytes_read > 0); @@ -258,10 +315,12 @@ TEST(ChrootTest, ProcMemSelfMapsNoEscapeProcOpen) { const FileDescriptor proc = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY)); + const auto temp_dir = TEST_CHECK_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const std::string temp_dir_path = temp_dir.path(); + const auto rest = [&] { - // Create and enter a chroot directory. - const auto temp_dir = TEST_CHECK_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); + // Enter the chroot directory. + TEST_CHECK_SUCCESS(chroot(temp_dir_path.c_str())); // Open a file inside the chroot at /foo. const FileDescriptor foo = @@ -272,9 +331,12 @@ TEST(ChrootTest, ProcMemSelfMapsNoEscapeProcOpen) { MAP_PRIVATE, foo.get(), 0); TEST_CHECK_SUCCESS(reinterpret_cast<int64_t>(foo_map)); - // Always unmap. - auto cleanup_map = - Cleanup([&] { TEST_CHECK_SUCCESS(munmap(foo_map, kPageSize)); }); + // Always unmap. Since this function is called between fork() and execve(), + // we can't use gvisor::testing::Cleanup, which uses std::function + // and thus may heap-allocate (which is async-signal-unsafe); instead, use + // absl::Cleanup, which is templated on the callback type. + auto cleanup_map = absl::MakeCleanup( + [&] { TEST_CHECK_SUCCESS(munmap(foo_map, kPageSize)); }); // Examine /proc/self/maps to be sure that /foo doesn't appear to be // mapped with the full chroot path. @@ -289,8 +351,8 @@ TEST(ChrootTest, ProcMemSelfMapsNoEscapeProcOpen) { TEST_CHECK(bytes_read > 0); // Finally we want to make sure the maps don't contain the chroot path - TEST_CHECK(std::string(buf, bytes_read).find(temp_dir.path()) == - std::string::npos); + TEST_CHECK( + !absl::StrContains(absl::string_view(buf, bytes_read), temp_dir_path)); }; EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); } @@ -302,72 +364,72 @@ TEST(ChrootTest, ProcMountsMountinfoNoEscape) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); // Create nested tmpfs mounts. - auto const outer_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto const outer_mount = ASSERT_NO_ERRNO_AND_VALUE( - Mount("none", outer_dir.path(), "tmpfs", 0, "mode=0700", 0)); - - auto const inner_dir = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(outer_dir.path())); - auto const inner_mount = ASSERT_NO_ERRNO_AND_VALUE( - Mount("none", inner_dir.path(), "tmpfs", 0, "mode=0700", 0)); + const auto outer_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const std::string outer_dir_path = outer_dir.path(); + const auto outer_mount = ASSERT_NO_ERRNO_AND_VALUE( + Mount("none", outer_dir_path, "tmpfs", 0, "mode=0700", 0)); + + const auto inner_dir = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(outer_dir_path)); + const std::string inner_dir_path = inner_dir.path(); + const auto inner_mount = ASSERT_NO_ERRNO_AND_VALUE( + Mount("none", inner_dir_path, "tmpfs", 0, "mode=0700", 0)); + const std::string inner_dir_in_outer_chroot_path = + absl::StrCat("/", Basename(inner_dir_path)); + + // Filenames that will be checked for mounts, all relative to /proc dir. + std::string paths[3] = {"mounts", "self/mounts", "self/mountinfo"}; + + for (const std::string& path : paths) { + // We should have both inner and outer mounts. + const std::string contents = + ASSERT_NO_ERRNO_AND_VALUE(GetContents(JoinPath("/proc", path))); + EXPECT_THAT(contents, + AllOf(HasSubstr(outer_dir_path), HasSubstr(inner_dir_path))); + // We better have at least two mounts: the mounts we created plus the + // root. + std::vector<absl::string_view> submounts = + absl::StrSplit(contents, '\n', absl::SkipWhitespace()); + ASSERT_GT(submounts.size(), 2); + } - const auto rest = [&outer_dir, &inner_dir] { - // Filenames that will be checked for mounts, all relative to /proc dir. - std::string paths[3] = {"mounts", "self/mounts", "self/mountinfo"}; - - for (const std::string& path : paths) { - // We should have both inner and outer mounts. - const std::string contents = - TEST_CHECK_NO_ERRNO_AND_VALUE(GetContents(JoinPath("/proc", path))); - EXPECT_THAT(contents, AllOf(HasSubstr(outer_dir.path()), - HasSubstr(inner_dir.path()))); - // We better have at least two mounts: the mounts we created plus the - // root. - std::vector<absl::string_view> submounts = - absl::StrSplit(contents, '\n', absl::SkipWhitespace()); - TEST_CHECK(submounts.size() > 2); - } - - // Get a FD to /proc before we enter the chroot. - const FileDescriptor proc = - TEST_CHECK_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY)); + // Get a FD to /proc before we enter the chroot. + const FileDescriptor proc = + ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY)); + const auto rest = [&] { // Chroot to outer mount. - TEST_CHECK_SUCCESS(chroot(outer_dir.path().c_str())); + TEST_CHECK_SUCCESS(chroot(outer_dir_path.c_str())); + char buf[8 * 1024]; for (const std::string& path : paths) { const FileDescriptor proc_file = TEST_CHECK_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), path, O_RDONLY)); // Only two mounts visible from this chroot: the inner and outer. Both // paths should be relative to the new chroot. - const std::string contents = - TEST_CHECK_NO_ERRNO_AND_VALUE(GetContentsFD(proc_file.get())); - EXPECT_THAT(contents, - AllOf(HasSubstr(absl::StrCat(Basename(inner_dir.path()))), - Not(HasSubstr(outer_dir.path())), - Not(HasSubstr(inner_dir.path())))); - std::vector<absl::string_view> submounts = - absl::StrSplit(contents, '\n', absl::SkipWhitespace()); - TEST_CHECK(submounts.size() == 2); + ssize_t n = ReadFd(proc_file.get(), buf, sizeof(buf)); + TEST_PCHECK(n >= 0); + buf[n] = '\0'; + TEST_CHECK(absl::StrContains(buf, Basename(inner_dir_path))); + TEST_CHECK(!absl::StrContains(buf, outer_dir_path)); + TEST_CHECK(!absl::StrContains(buf, inner_dir_path)); + TEST_CHECK(std::count(buf, buf + n, '\n') == 2); } // Chroot to inner mount. We must use an absolute path accessible to our // chroot. - const std::string inner_dir_basename = - absl::StrCat("/", Basename(inner_dir.path())); - TEST_CHECK_SUCCESS(chroot(inner_dir_basename.c_str())); + TEST_CHECK_SUCCESS(chroot(inner_dir_in_outer_chroot_path.c_str())); for (const std::string& path : paths) { const FileDescriptor proc_file = TEST_CHECK_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), path, O_RDONLY)); - const std::string contents = - TEST_CHECK_NO_ERRNO_AND_VALUE(GetContentsFD(proc_file.get())); // Only the inner mount visible from this chroot. - std::vector<absl::string_view> submounts = - absl::StrSplit(contents, '\n', absl::SkipWhitespace()); - TEST_CHECK(submounts.size() == 1); + ssize_t n = ReadFd(proc_file.get(), buf, sizeof(buf)); + TEST_PCHECK(n >= 0); + buf[n] = '\0'; + TEST_CHECK(std::count(buf, buf + n, '\n') == 1); } }; EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); diff --git a/test/syscalls/linux/packet_socket.cc b/test/syscalls/linux/packet_socket.cc index 4f68de176..1e246c421 100644 --- a/test/syscalls/linux/packet_socket.cc +++ b/test/syscalls/linux/packet_socket.cc @@ -231,9 +231,6 @@ TEST_P(CookedPacketTest, Receive) { // Send via a packet socket. TEST_P(CookedPacketTest, Send) { - // We don't implement writing to packet sockets on gVisor. - SKIP_IF(IsRunningOnGvisor()); - // Let's send a UDP packet and receive it using a regular UDP socket. FileDescriptor udp_sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); @@ -287,6 +284,14 @@ TEST_P(CookedPacketTest, Send) { memcpy(send_buf + sizeof(iphdr), &udphdr, sizeof(udphdr)); memcpy(send_buf + sizeof(iphdr) + sizeof(udphdr), kMessage, sizeof(kMessage)); + // We don't implement writing to packet sockets on gVisor. + if (IsRunningOnGvisor()) { + ASSERT_THAT(sendto(socket_, send_buf, sizeof(send_buf), 0, + reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), + SyscallFailsWithErrno(EINVAL)); + GTEST_SKIP(); + } + // Send it. ASSERT_THAT(sendto(socket_, send_buf, sizeof(send_buf), 0, reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), diff --git a/test/syscalls/linux/packet_socket_raw.cc b/test/syscalls/linux/packet_socket_raw.cc index 9b54dda8b..7e439466e 100644 --- a/test/syscalls/linux/packet_socket_raw.cc +++ b/test/syscalls/linux/packet_socket_raw.cc @@ -235,9 +235,6 @@ TEST_P(RawPacketTest, Receive) { // Send via a packet socket. TEST_P(RawPacketTest, Send) { - // We don't implement writing to packet sockets on gVisor. - SKIP_IF(IsRunningOnGvisor()); - // Let's send a UDP packet and receive it using a regular UDP socket. FileDescriptor udp_sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); @@ -299,6 +296,14 @@ TEST_P(RawPacketTest, Send) { memcpy(send_buf + sizeof(ethhdr) + sizeof(iphdr) + sizeof(udphdr), kMessage, sizeof(kMessage)); + // We don't implement writing to packet sockets on gVisor. + if (IsRunningOnGvisor()) { + ASSERT_THAT(sendto(s_, send_buf, sizeof(send_buf), 0, + reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), + SyscallFailsWithErrno(EINVAL)); + GTEST_SKIP(); + } + // Send it. ASSERT_THAT(sendto(s_, send_buf, sizeof(send_buf), 0, reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), diff --git a/test/syscalls/linux/ptrace.cc b/test/syscalls/linux/ptrace.cc index d519b65e6..f64c23ac0 100644 --- a/test/syscalls/linux/ptrace.cc +++ b/test/syscalls/linux/ptrace.cc @@ -30,6 +30,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/flags/flag.h" +#include "absl/strings/string_view.h" #include "absl/time/clock.h" #include "absl/time/time.h" #include "test/util/capability_util.h" @@ -51,17 +52,10 @@ ABSL_FLAG(bool, ptrace_test_execve_child, false, ABSL_FLAG(bool, ptrace_test_trace_descendants_allowed, false, "If set, run the child workload for " "PtraceTest_TraceDescendantsAllowed."); -ABSL_FLAG(bool, ptrace_test_prctl_set_ptracer_pid, false, - "If set, run the child workload for PtraceTest_PrctlSetPtracerPID."); -ABSL_FLAG(bool, ptrace_test_prctl_set_ptracer_any, false, - "If set, run the child workload for PtraceTest_PrctlSetPtracerAny."); -ABSL_FLAG(bool, ptrace_test_prctl_clear_ptracer, false, - "If set, run the child workload for PtraceTest_PrctlClearPtracer."); -ABSL_FLAG(bool, ptrace_test_prctl_replace_ptracer, false, - "If set, run the child workload for PtraceTest_PrctlReplacePtracer."); -ABSL_FLAG(int, ptrace_test_prctl_replace_ptracer_tid, -1, - "Specifies the replacement tracer tid in the child workload for " - "PtraceTest_PrctlReplacePtracer."); +ABSL_FLAG(bool, ptrace_test_ptrace_attacher, false, + "If set, run the child workload for PtraceAttacherSubprocess."); +ABSL_FLAG(bool, ptrace_test_prctl_set_ptracer, false, + "If set, run the child workload for PrctlSetPtracerSubprocess."); ABSL_FLAG(bool, ptrace_test_prctl_set_ptracer_and_exit_tracee_thread, false, "If set, run the child workload for " "PtraceTest_PrctlSetPtracerPersistsPastTraceeThreadExit."); @@ -161,6 +155,86 @@ int CheckPtraceAttach(pid_t pid) { return 0; } +class SimpleSubprocess { + public: + explicit SimpleSubprocess(absl::string_view child_flag) { + int sockets[2]; + TEST_PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0); + + // Allocate vector before forking (not async-signal-safe). + ExecveArray const owned_child_argv = {"/proc/self/exe", child_flag, + "--ptrace_test_fd", + std::to_string(sockets[0])}; + char* const* const child_argv = owned_child_argv.get(); + + pid_ = fork(); + if (pid_ == 0) { + TEST_PCHECK(close(sockets[1]) == 0); + execve(child_argv[0], child_argv, /* envp = */ nullptr); + TEST_PCHECK_MSG(false, "Survived execve to test child"); + } + TEST_PCHECK(pid_ > 0); + TEST_PCHECK(close(sockets[0]) == 0); + sockfd_ = sockets[1]; + } + + SimpleSubprocess(SimpleSubprocess&& orig) + : pid_(orig.pid_), sockfd_(orig.sockfd_) { + orig.pid_ = -1; + orig.sockfd_ = -1; + } + + SimpleSubprocess& operator=(SimpleSubprocess&& orig) { + if (this != &orig) { + this->~SimpleSubprocess(); + pid_ = orig.pid_; + sockfd_ = orig.sockfd_; + orig.pid_ = -1; + orig.sockfd_ = -1; + } + return *this; + } + + SimpleSubprocess(SimpleSubprocess const&) = delete; + SimpleSubprocess& operator=(SimpleSubprocess const&) = delete; + + ~SimpleSubprocess() { + if (pid_ < 0) { + return; + } + EXPECT_THAT(shutdown(sockfd_, SHUT_RDWR), SyscallSucceeds()); + EXPECT_THAT(close(sockfd_), SyscallSucceeds()); + int status; + EXPECT_THAT(waitpid(pid_, &status, 0), SyscallSucceedsWithValue(pid_)); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) + << " status " << status; + } + + pid_t pid() const { return pid_; } + + // Sends the child process the given value, receives an errno in response, and + // returns a PosixError corresponding to the received errno. + template <typename T> + PosixError Cmd(T val) { + if (WriteFd(sockfd_, &val, sizeof(val)) < 0) { + return PosixError(errno, "write failed"); + } + return RecvErrno(); + } + + private: + PosixError RecvErrno() { + int resp_errno; + if (ReadFd(sockfd_, &resp_errno, sizeof(resp_errno)) < 0) { + return PosixError(errno, "read failed"); + } + return PosixError(resp_errno); + } + + pid_t pid_ = -1; + int sockfd_ = -1; +}; + TEST(PtraceTest, AttachSelf) { EXPECT_THAT(ptrace(PTRACE_ATTACH, gettid(), 0, 0), SyscallFailsWithErrno(EPERM)); @@ -343,289 +417,128 @@ TEST(PtraceTest, PrctlSetPtracerInvalidPID) { EXPECT_THAT(prctl(PR_SET_PTRACER, 123456789), SyscallFailsWithErrno(EINVAL)); } -TEST(PtraceTest, PrctlSetPtracerPID) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1); - - AutoCapability cap(CAP_SYS_PTRACE, false); - - // Use sockets to synchronize between tracer and tracee. - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); - - // Allocate vector before forking (not async-signal-safe). - ExecveArray const owned_child_argv = { - "/proc/self/exe", "--ptrace_test_prctl_set_ptracer_pid", - "--ptrace_test_fd", std::to_string(sockets[0])}; - char* const* const child_argv = owned_child_argv.get(); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - TEST_PCHECK(close(sockets[1]) == 0); - // This test will create a new thread in the child process. - // pthread_create(2) isn't async-signal-safe, so we execve() first. - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - ASSERT_THAT(close(sockets[0]), SyscallSucceeds()); - - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - // Wait until tracee has called prctl. - char done; - TEST_PCHECK(read(sockets[1], &done, 1) == 1); - MaybeSave(); - - TEST_PCHECK(CheckPtraceAttach(tracee_pid) == 0); - _exit(0); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; +SimpleSubprocess CreatePtraceAttacherSubprocess() { + return SimpleSubprocess("--ptrace_test_ptrace_attacher"); } -[[noreturn]] void RunPrctlSetPtracerPID(int fd) { - ScopedThread t([fd] { - // Perform prctl in a separate thread to verify that it is process-wide. - TEST_PCHECK(prctl(PR_SET_PTRACER, getppid()) == 0); - MaybeSave(); - // Indicate that the prctl has been set. - TEST_PCHECK(write(fd, "x", 1) == 1); - MaybeSave(); +[[noreturn]] static void RunPtraceAttacher(int sockfd) { + // execve() may have restored CAP_SYS_PTRACE if we had real UID 0. + TEST_CHECK(SetCapability(CAP_SYS_PTRACE, false).ok()); + // Perform PTRACE_ATTACH in a separate thread to verify that permissions + // apply process-wide. + ScopedThread t([&] { + while (true) { + pid_t pid; + int rv = read(sockfd, &pid, sizeof(pid)); + if (rv == 0) { + _exit(0); + } + if (rv < 0) { + _exit(1); + } + int resp_errno = 0; + if (CheckPtraceAttach(pid) < 0) { + resp_errno = errno; + } + TEST_PCHECK(write(sockfd, &resp_errno, sizeof(resp_errno)) == + sizeof(resp_errno)); + } }); while (true) { SleepSafe(absl::Seconds(1)); } } -TEST(PtraceTest, PrctlSetPtracerAny) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1); - AutoCapability cap(CAP_SYS_PTRACE, false); - - // Use sockets to synchronize between tracer and tracee. - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); - - // Allocate vector before forking (not async-signal-safe). - ExecveArray const owned_child_argv = { - "/proc/self/exe", "--ptrace_test_prctl_set_ptracer_any", - "--ptrace_test_fd", std::to_string(sockets[0])}; - char* const* const child_argv = owned_child_argv.get(); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - // This test will create a new thread in the child process. - // pthread_create(2) isn't async-signal-safe, so we execve() first. - TEST_PCHECK(close(sockets[1]) == 0); - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - ASSERT_THAT(close(sockets[0]), SyscallSucceeds()); - - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - // Wait until tracee has called prctl. - char done; - TEST_PCHECK(read(sockets[1], &done, 1) == 1); - MaybeSave(); - - TEST_PCHECK(CheckPtraceAttach(tracee_pid) == 0); - _exit(0); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; +SimpleSubprocess CreatePrctlSetPtracerSubprocess() { + return SimpleSubprocess("--ptrace_test_prctl_set_ptracer"); } -[[noreturn]] void RunPrctlSetPtracerAny(int fd) { - ScopedThread t([fd] { - // Perform prctl in a separate thread to verify that it is process-wide. - TEST_PCHECK(prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) == 0); - MaybeSave(); - // Indicate that the prctl has been set. - TEST_PCHECK(write(fd, "x", 1) == 1); - MaybeSave(); +[[noreturn]] static void RunPrctlSetPtracer(int sockfd) { + // Perform prctl in a separate thread to verify that it applies + // process-wide. + ScopedThread t([&] { + while (true) { + pid_t pid; + int rv = read(sockfd, &pid, sizeof(pid)); + if (rv == 0) { + _exit(0); + } + if (rv < 0) { + _exit(1); + } + int resp_errno = 0; + if (prctl(PR_SET_PTRACER, pid) < 0) { + resp_errno = errno; + } + TEST_PCHECK(write(sockfd, &resp_errno, sizeof(resp_errno)) == + sizeof(resp_errno)); + } }); while (true) { SleepSafe(absl::Seconds(1)); } } -TEST(PtraceTest, PrctlClearPtracer) { +TEST(PtraceTest, PrctlSetPtracer) { SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1); - AutoCapability cap(CAP_SYS_PTRACE, false); - - // Use sockets to synchronize between tracer and tracee. - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); - - // Allocate vector before forking (not async-signal-safe). - ExecveArray const owned_child_argv = { - "/proc/self/exe", "--ptrace_test_prctl_clear_ptracer", "--ptrace_test_fd", - std::to_string(sockets[0])}; - char* const* const child_argv = owned_child_argv.get(); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - // This test will create a new thread in the child process. - // pthread_create(2) isn't async-signal-safe, so we execve() first. - TEST_PCHECK(close(sockets[1]) == 0); - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - ASSERT_THAT(close(sockets[0]), SyscallSucceeds()); - - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - // Wait until tracee has called prctl. - char done; - TEST_PCHECK(read(sockets[1], &done, 1) == 1); - MaybeSave(); - - TEST_CHECK(CheckPtraceAttach(tracee_pid) == -1); - TEST_PCHECK(errno == EPERM); - _exit(0); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -[[noreturn]] void RunPrctlClearPtracer(int fd) { - ScopedThread t([fd] { - // Perform prctl in a separate thread to verify that it is process-wide. - TEST_PCHECK(prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) == 0); - MaybeSave(); - TEST_PCHECK(prctl(PR_SET_PTRACER, 0) == 0); - MaybeSave(); - // Indicate that the prctl has been set/cleared. - TEST_PCHECK(write(fd, "x", 1) == 1); - MaybeSave(); - }); - while (true) { - SleepSafe(absl::Seconds(1)); - } -} -TEST(PtraceTest, PrctlReplacePtracer) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1); AutoCapability cap(CAP_SYS_PTRACE, false); - pid_t const unused_pid = fork(); - if (unused_pid == 0) { - while (true) { - SleepSafe(absl::Seconds(1)); - } - } - ASSERT_THAT(unused_pid, SyscallSucceeds()); + // Ensure that initially, no tracer exception is set. + ASSERT_THAT(prctl(PR_SET_PTRACER, 0), SyscallSucceeds()); - // Use sockets to synchronize between tracer and tracee. - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); + SimpleSubprocess tracee = CreatePrctlSetPtracerSubprocess(); + SimpleSubprocess tracer = CreatePtraceAttacherSubprocess(); - // Allocate vector before forking (not async-signal-safe). - ExecveArray const owned_child_argv = { - "/proc/self/exe", - "--ptrace_test_prctl_replace_ptracer", - "--ptrace_test_prctl_replace_ptracer_tid", - std::to_string(unused_pid), - "--ptrace_test_fd", - std::to_string(sockets[0])}; - char* const* const child_argv = owned_child_argv.get(); + // By default, Yama should prevent tracer from tracing its parent (this + // process) or siblings (tracee). + EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(EPERM)); + EXPECT_THAT(tracer.Cmd(tracee.pid()), PosixErrorIs(EPERM)); - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - TEST_PCHECK(close(sockets[1]) == 0); - // This test will create a new thread in the child process. - // pthread_create(2) isn't async-signal-safe, so we execve() first. - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - ASSERT_THAT(close(sockets[0]), SyscallSucceeds()); + // If tracee invokes PR_SET_PTRACER on either tracer's pid, the pid of any of + // its ancestors (i.e. us), or PR_SET_PTRACER_ANY, then tracer can trace it + // (but not us). - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - // Wait until tracee has called prctl. - char done; - TEST_PCHECK(read(sockets[1], &done, 1) == 1); - MaybeSave(); + ASSERT_THAT(tracee.Cmd(tracer.pid()), PosixErrorIs(0)); + EXPECT_THAT(tracer.Cmd(tracee.pid()), PosixErrorIs(0)); + EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(EPERM)); - TEST_CHECK(CheckPtraceAttach(tracee_pid) == -1); - TEST_PCHECK(errno == EPERM); - _exit(0); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); + ASSERT_THAT(tracee.Cmd(gettid()), PosixErrorIs(0)); + EXPECT_THAT(tracer.Cmd(tracee.pid()), PosixErrorIs(0)); + EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(EPERM)); - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; + ASSERT_THAT(tracee.Cmd(static_cast<pid_t>(PR_SET_PTRACER_ANY)), + PosixErrorIs(0)); + EXPECT_THAT(tracer.Cmd(tracee.pid()), PosixErrorIs(0)); + EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(EPERM)); - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; + // If tracee invokes PR_SET_PTRACER with pid 0, then tracer can no longer + // trace it. + ASSERT_THAT(tracee.Cmd(0), PosixErrorIs(0)); + EXPECT_THAT(tracer.Cmd(tracee.pid()), PosixErrorIs(EPERM)); - // Clean up unused. - ASSERT_THAT(kill(unused_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(unused_pid, &status, 0), - SyscallSucceedsWithValue(unused_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} + // If we invoke PR_SET_PTRACER with tracer's pid, then it can trace us (but + // not our descendants). + ASSERT_THAT(prctl(PR_SET_PTRACER, tracer.pid()), SyscallSucceeds()); + EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(0)); + EXPECT_THAT(tracer.Cmd(tracee.pid()), PosixErrorIs(EPERM)); -[[noreturn]] void RunPrctlReplacePtracer(int new_tracer_pid, int fd) { - TEST_PCHECK(prctl(PR_SET_PTRACER, getppid()) == 0); - MaybeSave(); + // If we invoke PR_SET_PTRACER with pid 0, then tracer can no longer trace us. + ASSERT_THAT(prctl(PR_SET_PTRACER, 0), SyscallSucceeds()); + EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(EPERM)); - ScopedThread t([new_tracer_pid, fd] { - TEST_PCHECK(prctl(PR_SET_PTRACER, new_tracer_pid) == 0); - MaybeSave(); - // Indicate that the prctl has been set. - TEST_PCHECK(write(fd, "x", 1) == 1); - MaybeSave(); - }); - while (true) { - SleepSafe(absl::Seconds(1)); - } + // Another thread in our thread group can invoke PR_SET_PTRACER instead; its + // effect applies to the whole thread group. + pid_t const our_tid = gettid(); + ScopedThread([&] { + ASSERT_THAT(prctl(PR_SET_PTRACER, tracer.pid()), SyscallSucceeds()); + EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(0)); + EXPECT_THAT(tracer.Cmd(our_tid), PosixErrorIs(0)); + + ASSERT_THAT(prctl(PR_SET_PTRACER, 0), SyscallSucceeds()); + EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(EPERM)); + EXPECT_THAT(tracer.Cmd(our_tid), PosixErrorIs(EPERM)); + }).Join(); } // Tests that YAMA exceptions store tracees by thread group leader. Exceptions @@ -2342,21 +2255,12 @@ int main(int argc, char** argv) { gvisor::testing::RunTraceDescendantsAllowed(fd); } - if (absl::GetFlag(FLAGS_ptrace_test_prctl_set_ptracer_pid)) { - gvisor::testing::RunPrctlSetPtracerPID(fd); - } - - if (absl::GetFlag(FLAGS_ptrace_test_prctl_set_ptracer_any)) { - gvisor::testing::RunPrctlSetPtracerAny(fd); - } - - if (absl::GetFlag(FLAGS_ptrace_test_prctl_clear_ptracer)) { - gvisor::testing::RunPrctlClearPtracer(fd); + if (absl::GetFlag(FLAGS_ptrace_test_ptrace_attacher)) { + gvisor::testing::RunPtraceAttacher(fd); } - if (absl::GetFlag(FLAGS_ptrace_test_prctl_replace_ptracer)) { - gvisor::testing::RunPrctlReplacePtracer( - absl::GetFlag(FLAGS_ptrace_test_prctl_replace_ptracer_tid), fd); + if (absl::GetFlag(FLAGS_ptrace_test_prctl_set_ptracer)) { + gvisor::testing::RunPrctlSetPtracer(fd); } if (absl::GetFlag( diff --git a/test/syscalls/linux/socket.cc b/test/syscalls/linux/socket.cc index 7b966484d..2742d19be 100644 --- a/test/syscalls/linux/socket.cc +++ b/test/syscalls/linux/socket.cc @@ -119,6 +119,9 @@ TEST(SocketTest, UnixSCMRightsOnlyPassedOnce) { // Send more than what will fit inside the send/receive buffers, so that it is // split into multiple messages. constexpr int kBufSize = 0x100000; + // Heap allocation is async-signal-unsafe and thus cannot occur between fork() + // and execve(). + std::vector<char> buf(kBufSize); pid_t pid = fork(); if (pid == 0) { @@ -127,7 +130,6 @@ TEST(SocketTest, UnixSCMRightsOnlyPassedOnce) { // Construct a message with some control message. struct msghdr msg = {}; char control[CMSG_SPACE(sizeof(int))] = {}; - std::vector<char> buf(kBufSize); struct iovec iov = {}; msg.msg_control = control; msg.msg_controllen = sizeof(control); @@ -154,7 +156,6 @@ TEST(SocketTest, UnixSCMRightsOnlyPassedOnce) { struct msghdr msg = {}; char control[CMSG_SPACE(sizeof(int))] = {}; - std::vector<char> buf(kBufSize); struct iovec iov = {}; msg.msg_control = &control; msg.msg_controllen = sizeof(control); diff --git a/test/syscalls/linux/socket_bind_to_device_distribution.cc b/test/syscalls/linux/socket_bind_to_device_distribution.cc index 3b108cbd3..70b0b2742 100644 --- a/test/syscalls/linux/socket_bind_to_device_distribution.cc +++ b/test/syscalls/linux/socket_bind_to_device_distribution.cc @@ -77,34 +77,6 @@ class BindToDeviceDistributionTest } }; -PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr) { - switch (family) { - case AF_INET: - return static_cast<uint16_t>( - reinterpret_cast<sockaddr_in const*>(&addr)->sin_port); - case AF_INET6: - return static_cast<uint16_t>( - reinterpret_cast<sockaddr_in6 const*>(&addr)->sin6_port); - default: - return PosixError(EINVAL, - absl::StrCat("unknown socket family: ", family)); - } -} - -PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) { - switch (family) { - case AF_INET: - reinterpret_cast<sockaddr_in*>(addr)->sin_port = port; - return NoError(); - case AF_INET6: - reinterpret_cast<sockaddr_in6*>(addr)->sin6_port = port; - return NoError(); - default: - return PosixError(EINVAL, - absl::StrCat("unknown socket family: ", family)); - } -} - // Binds sockets to different devices and then creates many TCP connections. // Checks that the distribution of connections received on the sockets matches // the expectation. diff --git a/test/syscalls/linux/socket_generic_stress.cc b/test/syscalls/linux/socket_generic_stress.cc index c35aa2183..778c32a8e 100644 --- a/test/syscalls/linux/socket_generic_stress.cc +++ b/test/syscalls/linux/socket_generic_stress.cc @@ -37,49 +37,11 @@ namespace gvisor { namespace testing { -constexpr char kRangeFile[] = "/proc/sys/net/ipv4/ip_local_port_range"; - -PosixErrorOr<int> NumPorts() { - int min = 0; - int max = 1 << 16; - - // Read the ephemeral range from /proc. - ASSIGN_OR_RETURN_ERRNO(std::string rangefile, GetContents(kRangeFile)); - const std::string err_msg = - absl::StrFormat("%s has invalid content: %s", kRangeFile, rangefile); - if (rangefile.back() != '\n') { - return PosixError(EINVAL, err_msg); - } - rangefile.pop_back(); - std::vector<std::string> range = - absl::StrSplit(rangefile, absl::ByAnyChar("\t ")); - if (range.size() < 2 || !absl::SimpleAtoi(range.front(), &min) || - !absl::SimpleAtoi(range.back(), &max)) { - return PosixError(EINVAL, err_msg); - } - - // If we can open as writable, limit the range. - if (!access(kRangeFile, W_OK)) { - ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, - Open(kRangeFile, O_WRONLY | O_TRUNC, 0)); - max = min + 50; - const std::string small_range = absl::StrFormat("%d %d", min, max); - int n = write(fd.get(), small_range.c_str(), small_range.size()); - if (n < 0) { - return PosixError( - errno, - absl::StrFormat("write(%d [%s], \"%s\", %d)", fd.get(), kRangeFile, - small_range.c_str(), small_range.size())); - } - } - return max - min; -} - // Test fixture for tests that apply to pairs of connected sockets. using ConnectStressTest = SocketPairTest; TEST_P(ConnectStressTest, Reset) { - const int nports = ASSERT_NO_ERRNO_AND_VALUE(NumPorts()); + const int nports = ASSERT_NO_ERRNO_AND_VALUE(MaybeLimitEphemeralPorts()); for (int i = 0; i < nports * 2; i++) { const std::unique_ptr<SocketPair> sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); @@ -103,7 +65,7 @@ TEST_P(ConnectStressTest, Reset) { // Tests that opening too many connections -- without closing them -- does lead // to port exhaustion. TEST_P(ConnectStressTest, TooManyOpen) { - const int nports = ASSERT_NO_ERRNO_AND_VALUE(NumPorts()); + const int nports = ASSERT_NO_ERRNO_AND_VALUE(MaybeLimitEphemeralPorts()); int err_num = 0; std::vector<std::unique_ptr<SocketPair>> sockets = std::vector<std::unique_ptr<SocketPair>>(nports); @@ -164,7 +126,7 @@ class PersistentListenerConnectStressTest : public SocketPairTest { }; TEST_P(PersistentListenerConnectStressTest, ShutdownCloseFirst) { - const int nports = ASSERT_NO_ERRNO_AND_VALUE(NumPorts()); + const int nports = ASSERT_NO_ERRNO_AND_VALUE(MaybeLimitEphemeralPorts()); for (int i = 0; i < nports * 2; i++) { std::unique_ptr<SocketPair> sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketSleep()); @@ -185,7 +147,7 @@ TEST_P(PersistentListenerConnectStressTest, ShutdownCloseFirst) { } TEST_P(PersistentListenerConnectStressTest, ShutdownCloseSecond) { - const int nports = ASSERT_NO_ERRNO_AND_VALUE(NumPorts()); + const int nports = ASSERT_NO_ERRNO_AND_VALUE(MaybeLimitEphemeralPorts()); for (int i = 0; i < nports * 2; i++) { const std::unique_ptr<SocketPair> sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); @@ -206,7 +168,7 @@ TEST_P(PersistentListenerConnectStressTest, ShutdownCloseSecond) { } TEST_P(PersistentListenerConnectStressTest, Close) { - const int nports = ASSERT_NO_ERRNO_AND_VALUE(NumPorts()); + const int nports = ASSERT_NO_ERRNO_AND_VALUE(MaybeLimitEphemeralPorts()); for (int i = 0; i < nports * 2; i++) { std::unique_ptr<SocketPair> sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketSleep()); diff --git a/test/syscalls/linux/socket_generic_test_cases.cc b/test/syscalls/linux/socket_generic_test_cases.cc index 5c4cb6c35..fe5171bc8 100644 --- a/test/syscalls/linux/socket_generic_test_cases.cc +++ b/test/syscalls/linux/socket_generic_test_cases.cc @@ -14,6 +14,9 @@ #include "test/syscalls/linux/socket_generic.h" +#ifdef __linux__ +#include <linux/capability.h> +#endif // __linux__ #include <stdio.h> #include <sys/ioctl.h> #include <sys/socket.h> @@ -24,6 +27,7 @@ #include "absl/strings/string_view.h" #include "test/syscalls/linux/socket_test_util.h" #include "test/syscalls/linux/unix_domain_socket_test_util.h" +#include "test/util/capability_util.h" #include "test/util/test_util.h" // This file is a generic socket test file. It must be built with another file @@ -400,6 +404,46 @@ TEST_P(AllSocketPairTest, RcvBufSucceeds) { EXPECT_GT(size, 0); } +#ifdef __linux__ + +// Check that setting SO_RCVBUFFORCE above max is not clamped to the maximum +// receive buffer size. +TEST_P(AllSocketPairTest, SetSocketRecvBufForceAboveMax) { + std::unique_ptr<SocketPair> sockets = + ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + // Discover maxmimum buffer size by setting to a really large value. + constexpr int kRcvBufSz = 0xffffffff; + ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, + sizeof(kRcvBufSz)), + SyscallSucceeds()); + + int max = 0; + socklen_t max_len = sizeof(max); + ASSERT_THAT( + getsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVBUF, &max, &max_len), + SyscallSucceeds()); + + int above_max = max + 1; + int sso = setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVBUFFORCE, + &above_max, sizeof(above_max)); + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))) { + ASSERT_THAT(sso, SyscallFailsWithErrno(EPERM)); + return; + } + ASSERT_THAT(sso, SyscallSucceeds()); + + int val = 0; + socklen_t val_len = sizeof(val); + ASSERT_THAT( + getsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVBUF, &val, &val_len), + SyscallSucceeds()); + // The system doubles the passed-in maximum. + ASSERT_EQ(above_max * 2, val); +} + +#endif // __linux__ + TEST_P(AllSocketPairTest, GetSndBufSucceeds) { auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); int size = 0; diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc index 6b369d5b7..9ae0cc59d 100644 --- a/test/syscalls/linux/socket_inet_loopback.cc +++ b/test/syscalls/linux/socket_inet_loopback.cc @@ -34,6 +34,7 @@ #include "absl/time/clock.h" #include "absl/time/time.h" #include "test/syscalls/linux/ip_socket_test_util.h" +#include "test/syscalls/linux/socket_inet_loopback_test_params.h" #include "test/syscalls/linux/socket_test_util.h" #include "test/util/file_descriptor.h" #include "test/util/posix_error.h" @@ -48,45 +49,7 @@ namespace { using ::testing::Gt; -PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr) { - switch (family) { - case AF_INET: - return static_cast<uint16_t>( - reinterpret_cast<sockaddr_in const*>(&addr)->sin_port); - case AF_INET6: - return static_cast<uint16_t>( - reinterpret_cast<sockaddr_in6 const*>(&addr)->sin6_port); - default: - return PosixError(EINVAL, - absl::StrCat("unknown socket family: ", family)); - } -} - -PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) { - switch (family) { - case AF_INET: - reinterpret_cast<sockaddr_in*>(addr)->sin_port = port; - return NoError(); - case AF_INET6: - reinterpret_cast<sockaddr_in6*>(addr)->sin6_port = port; - return NoError(); - default: - return PosixError(EINVAL, - absl::StrCat("unknown socket family: ", family)); - } -} - -struct TestParam { - TestAddress listener; - TestAddress connector; -}; - -std::string DescribeTestParam(::testing::TestParamInfo<TestParam> const& info) { - return absl::StrCat("Listen", info.param.listener.description, "_Connect", - info.param.connector.description); -} - -using SocketInetLoopbackTest = ::testing::TestWithParam<TestParam>; +using SocketInetLoopbackTest = ::testing::TestWithParam<SocketInetTestParam>; TEST(BadSocketPairArgs, ValidateErrForBadCallsToSocketPair) { int fd[2] = {}; @@ -299,7 +262,7 @@ void tcpSimpleConnectTest(TestAddress const& listener, } TEST_P(SocketInetLoopbackTest, TCP) { - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -307,7 +270,7 @@ TEST_P(SocketInetLoopbackTest, TCP) { } TEST_P(SocketInetLoopbackTest, TCPListenUnbound) { - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -316,7 +279,7 @@ TEST_P(SocketInetLoopbackTest, TCPListenUnbound) { } TEST_P(SocketInetLoopbackTest, TCPListenShutdownListen) { - const auto& param = GetParam(); + SocketInetTestParam const& param = GetParam(); const TestAddress& listener = param.listener; const TestAddress& connector = param.connector; @@ -362,7 +325,7 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdownListen) { } TEST_P(SocketInetLoopbackTest, TCPListenShutdown) { - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -430,7 +393,7 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdown) { } TEST_P(SocketInetLoopbackTest, TCPListenClose) { - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -477,7 +440,7 @@ TEST_P(SocketInetLoopbackTest, TCPListenClose) { // Test the protocol state information returned by TCPINFO. TEST_P(SocketInetLoopbackTest, TCPInfoState) { - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -546,7 +509,7 @@ TEST_P(SocketInetLoopbackTest, TCPInfoState) { ASSERT_THAT(close(conn_fd.release()), SyscallSucceeds()); } -void TestHangupDuringConnect(const TestParam& param, +void TestHangupDuringConnect(const SocketInetTestParam& param, void (*hangup)(FileDescriptor&)) { TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -609,7 +572,7 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdownDuringConnect) { }); } -void TestListenHangupConnectingRead(const TestParam& param, +void TestListenHangupConnectingRead(const SocketInetTestParam& param, void (*hangup)(FileDescriptor&)) { constexpr int kTimeout = 10000; @@ -718,7 +681,7 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdownConnectingRead) { // Test close of a non-blocking connecting socket. TEST_P(SocketInetLoopbackTest, TCPNonBlockingConnectClose) { - TestParam const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -744,7 +707,7 @@ TEST_P(SocketInetLoopbackTest, TCPNonBlockingConnectClose) { // Try many iterations to catch a race with socket close and handshake // completion. - for (int i = 0; i < 1000; ++i) { + for (int i = 0; i < 100; ++i) { FileDescriptor client = ASSERT_NO_ERRNO_AND_VALUE( Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); ASSERT_THAT( @@ -793,7 +756,7 @@ TEST_P(SocketInetLoopbackTest, TCPNonBlockingConnectClose) { // queue because the queue is full are not correctly delivered after restore // causing the last accept to timeout on the restore. TEST_P(SocketInetLoopbackTest, TCPAcceptBacklogSizes) { - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -843,7 +806,7 @@ TEST_P(SocketInetLoopbackTest, TCPAcceptBacklogSizes) { // queue because the queue is full are not correctly delivered after restore // causing the last accept to timeout on the restore. TEST_P(SocketInetLoopbackTest, TCPBacklog) { - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -934,7 +897,7 @@ TEST_P(SocketInetLoopbackTest, TCPBacklog) { // queue because the queue is full are not correctly delivered after restore // causing the last accept to timeout on the restore. TEST_P(SocketInetLoopbackTest, TCPBacklogAcceptAll) { - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -1024,175 +987,12 @@ TEST_P(SocketInetLoopbackTest, TCPBacklogAcceptAll) { } } -// TCPFinWait2Test creates a pair of connected sockets then closes one end to -// trigger FIN_WAIT2 state for the closed endpoint. Then it binds the same local -// IP/port on a new socket and tries to connect. The connect should fail w/ -// an EADDRINUSE. Then we wait till the FIN_WAIT2 timeout is over and try the -// connect again with a new socket and this time it should succeed. -// -// TCP timers are not S/R today, this can cause this test to be flaky when run -// under random S/R due to timer being reset on a restore. -TEST_P(SocketInetLoopbackTest, TCPFinWait2Test) { - auto const& param = GetParam(); - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - // Create the listening socket. - const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT( - bind(listen_fd.get(), AsSockAddr(&listen_addr), listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(&listen_addr), &addrlen), - SyscallSucceeds()); - - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - - // Connect to the listening socket. - FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - // Lower FIN_WAIT2 state to 5 seconds for test. - constexpr int kTCPLingerTimeout = 5; - EXPECT_THAT(setsockopt(conn_fd.get(), IPPROTO_TCP, TCP_LINGER2, - &kTCPLingerTimeout, sizeof(kTCPLingerTimeout)), - SyscallSucceedsWithValue(0)); - - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr), - connector.addr_len), - SyscallSucceeds()); - - // Accept the connection. - auto accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); - - // Get the address/port bound by the connecting socket. - sockaddr_storage conn_bound_addr; - socklen_t conn_addrlen = connector.addr_len; - ASSERT_THAT( - getsockname(conn_fd.get(), AsSockAddr(&conn_bound_addr), &conn_addrlen), - SyscallSucceeds()); - - // close the connecting FD to trigger FIN_WAIT2 on the connected fd. - conn_fd.reset(); - - // Now bind and connect a new socket. - const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - // Disable cooperative saves after this point. As a save between the first - // bind/connect and the second one can cause the linger timeout timer to - // be restarted causing the final bind/connect to fail. - DisableSave ds; - - ASSERT_THAT(bind(conn_fd2.get(), AsSockAddr(&conn_bound_addr), conn_addrlen), - SyscallFailsWithErrno(EADDRINUSE)); - - // Sleep for a little over the linger timeout to reduce flakiness in - // save/restore tests. - absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 2)); - - ds.reset(); - - ASSERT_THAT( - RetryEINTR(connect)(conn_fd2.get(), AsSockAddr(&conn_addr), conn_addrlen), - SyscallSucceeds()); -} - -// TCPLinger2TimeoutAfterClose creates a pair of connected sockets -// then closes one end to trigger FIN_WAIT2 state for the closed endpont. -// It then sleeps for the TCP_LINGER2 timeout and verifies that bind/ -// connecting the same address succeeds. -// -// TCP timers are not S/R today, this can cause this test to be flaky when run -// under random S/R due to timer being reset on a restore. -TEST_P(SocketInetLoopbackTest, TCPLinger2TimeoutAfterClose) { - auto const& param = GetParam(); - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - // Create the listening socket. - const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT( - bind(listen_fd.get(), AsSockAddr(&listen_addr), listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(&listen_addr), &addrlen), - SyscallSucceeds()); - - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - - // Connect to the listening socket. - FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr), - connector.addr_len), - SyscallSucceeds()); - - // Accept the connection. - auto accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); - - // Get the address/port bound by the connecting socket. - sockaddr_storage conn_bound_addr; - socklen_t conn_addrlen = connector.addr_len; - ASSERT_THAT( - getsockname(conn_fd.get(), AsSockAddr(&conn_bound_addr), &conn_addrlen), - SyscallSucceeds()); - - // Disable cooperative saves after this point as TCP timers are not restored - // across a S/R. - { - DisableSave ds; - constexpr int kTCPLingerTimeout = 5; - EXPECT_THAT(setsockopt(conn_fd.get(), IPPROTO_TCP, TCP_LINGER2, - &kTCPLingerTimeout, sizeof(kTCPLingerTimeout)), - SyscallSucceedsWithValue(0)); - - // close the connecting FD to trigger FIN_WAIT2 on the connected fd. - conn_fd.reset(); - - absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 1)); - - // ds going out of scope will Re-enable S/R's since at this point the timer - // must have fired and cleaned up the endpoint. - } - - // Now bind and connect a new socket and verify that we can immediately - // rebind the address bound by the conn_fd as it never entered TIME_WAIT. - const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - ASSERT_THAT(bind(conn_fd2.get(), AsSockAddr(&conn_bound_addr), conn_addrlen), - SyscallSucceeds()); - ASSERT_THAT( - RetryEINTR(connect)(conn_fd2.get(), AsSockAddr(&conn_addr), conn_addrlen), - SyscallSucceeds()); -} - // TCPResetAfterClose creates a pair of connected sockets then closes // one end to trigger FIN_WAIT2 state for the closed endpoint verifies // that we generate RSTs for any new data after the socket is fully // closed. TEST_P(SocketInetLoopbackTest, TCPResetAfterClose) { - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -1252,198 +1052,8 @@ TEST_P(SocketInetLoopbackTest, TCPResetAfterClose) { SyscallSucceedsWithValue(0)); } -// setupTimeWaitClose sets up a socket endpoint in TIME_WAIT state. -// Callers can choose to perform active close on either ends of the connection -// and also specify if they want to enabled SO_REUSEADDR. -void setupTimeWaitClose(const TestAddress* listener, - const TestAddress* connector, bool reuse, - bool accept_close, sockaddr_storage* listen_addr, - sockaddr_storage* conn_bound_addr) { - // Create the listening socket. - FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener->family(), SOCK_STREAM, IPPROTO_TCP)); - if (reuse) { - ASSERT_THAT(setsockopt(listen_fd.get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - } - ASSERT_THAT( - bind(listen_fd.get(), AsSockAddr(listen_addr), listener->addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener->addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(listen_addr), &addrlen), - SyscallSucceeds()); - - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener->family(), *listen_addr)); - - // Connect to the listening socket. - FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector->family(), SOCK_STREAM, IPPROTO_TCP)); - - // We disable saves after this point as a S/R causes the netstack seed - // to be regenerated which changes what ports/ISN is picked for a given - // tuple (src ip,src port, dst ip, dst port). This can cause the final - // SYN to use a sequence number that looks like one from the current - // connection in TIME_WAIT and will not be accepted causing the test - // to timeout. - // - // TODO(gvisor.dev/issue/940): S/R portSeed/portHint - DisableSave ds; - - sockaddr_storage conn_addr = connector->addr; - ASSERT_NO_ERRNO(SetAddrPort(connector->family(), &conn_addr, port)); - ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr), - connector->addr_len), - SyscallSucceeds()); - - // Accept the connection. - auto accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); - - // Get the address/port bound by the connecting socket. - socklen_t conn_addrlen = connector->addr_len; - ASSERT_THAT( - getsockname(conn_fd.get(), AsSockAddr(conn_bound_addr), &conn_addrlen), - SyscallSucceeds()); - - FileDescriptor active_closefd, passive_closefd; - if (accept_close) { - active_closefd = std::move(accepted); - passive_closefd = std::move(conn_fd); - } else { - active_closefd = std::move(conn_fd); - passive_closefd = std::move(accepted); - } - - // shutdown to trigger TIME_WAIT. - ASSERT_THAT(shutdown(active_closefd.get(), SHUT_WR), SyscallSucceeds()); - { - constexpr int kTimeout = 10000; - pollfd pfd = { - .fd = passive_closefd.get(), - .events = POLLIN, - }; - ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); - ASSERT_EQ(pfd.revents, POLLIN); - } - ASSERT_THAT(shutdown(passive_closefd.get(), SHUT_WR), SyscallSucceeds()); - { - constexpr int kTimeout = 10000; - constexpr int16_t want_events = POLLHUP; - pollfd pfd = { - .fd = active_closefd.get(), - .events = want_events, - }; - ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); - } - - // This sleep is needed to reduce flake to ensure that the passive-close - // ensures the state transitions to CLOSE from LAST_ACK. - absl::SleepFor(absl::Seconds(1)); -} - -// These tests are disabled under random save as the the restore run -// results in the stack.Seed() being different which can cause -// sequence number of final connect to be one that is considered -// old and can cause the test to be flaky. -// -// Test re-binding of client and server bound addresses when the older -// connection is in TIME_WAIT. -TEST_P(SocketInetLoopbackTest, TCPPassiveCloseNoTimeWaitTest) { - auto const& param = GetParam(); - sockaddr_storage listen_addr, conn_bound_addr; - listen_addr = param.listener.addr; - setupTimeWaitClose(¶m.listener, ¶m.connector, false /*reuse*/, - true /*accept_close*/, &listen_addr, &conn_bound_addr); - - // Now bind a new socket and verify that we can immediately rebind the address - // bound by the conn_fd as it never entered TIME_WAIT. - const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr), - param.connector.addr_len), - SyscallSucceeds()); - - FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(param.listener.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT( - bind(listen_fd.get(), AsSockAddr(&listen_addr), param.listener.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); -} - -TEST_P(SocketInetLoopbackTest, TCPPassiveCloseNoTimeWaitReuseTest) { - auto const& param = GetParam(); - sockaddr_storage listen_addr, conn_bound_addr; - listen_addr = param.listener.addr; - setupTimeWaitClose(¶m.listener, ¶m.connector, true /*reuse*/, - true /*accept_close*/, &listen_addr, &conn_bound_addr); - - FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(param.listener.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT(setsockopt(listen_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT( - bind(listen_fd.get(), AsSockAddr(&listen_addr), param.listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); - - // Now bind and connect new socket and verify that we can immediately rebind - // the address bound by the conn_fd as it never entered TIME_WAIT. - const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT(setsockopt(conn_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr), - param.connector.addr_len), - SyscallSucceeds()); - - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(param.listener.family(), listen_addr)); - sockaddr_storage conn_addr = param.connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(param.connector.family(), &conn_addr, port)); - ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr), - param.connector.addr_len), - SyscallSucceeds()); -} - -TEST_P(SocketInetLoopbackTest, TCPActiveCloseTimeWaitTest) { - auto const& param = GetParam(); - sockaddr_storage listen_addr, conn_bound_addr; - listen_addr = param.listener.addr; - setupTimeWaitClose(¶m.listener, ¶m.connector, false /*reuse*/, - false /*accept_close*/, &listen_addr, &conn_bound_addr); - FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr), - param.connector.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); -} - -TEST_P(SocketInetLoopbackTest, TCPActiveCloseTimeWaitReuseTest) { - auto const& param = GetParam(); - sockaddr_storage listen_addr, conn_bound_addr; - listen_addr = param.listener.addr; - setupTimeWaitClose(¶m.listener, ¶m.connector, true /*reuse*/, - false /*accept_close*/, &listen_addr, &conn_bound_addr); - FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT(setsockopt(conn_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr), - param.connector.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); -} - TEST_P(SocketInetLoopbackTest, AcceptedInheritsTCPUserTimeout) { - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -1495,7 +1105,7 @@ TEST_P(SocketInetLoopbackTest, AcceptedInheritsTCPUserTimeout) { } TEST_P(SocketInetLoopbackTest, TCPAcceptAfterReset) { - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -1606,7 +1216,7 @@ TEST_P(SocketInetLoopbackTest, TCPDeferAccept) { // saved. Enable S/R issue is fixed. DisableSave ds; - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -1686,7 +1296,7 @@ TEST_P(SocketInetLoopbackTest, TCPDeferAcceptTimeout) { // saved. Enable S/R once issue is fixed. DisableSave ds; - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -1753,42 +1363,16 @@ TEST_P(SocketInetLoopbackTest, TCPDeferAcceptTimeout) { ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); } -INSTANTIATE_TEST_SUITE_P( - All, SocketInetLoopbackTest, - ::testing::Values( - // Listeners bound to IPv4 addresses refuse connections using IPv6 - // addresses. - TestParam{V4Any(), V4Any()}, TestParam{V4Any(), V4Loopback()}, - TestParam{V4Any(), V4MappedAny()}, - TestParam{V4Any(), V4MappedLoopback()}, - TestParam{V4Loopback(), V4Any()}, TestParam{V4Loopback(), V4Loopback()}, - TestParam{V4Loopback(), V4MappedLoopback()}, - TestParam{V4MappedAny(), V4Any()}, - TestParam{V4MappedAny(), V4Loopback()}, - TestParam{V4MappedAny(), V4MappedAny()}, - TestParam{V4MappedAny(), V4MappedLoopback()}, - TestParam{V4MappedLoopback(), V4Any()}, - TestParam{V4MappedLoopback(), V4Loopback()}, - TestParam{V4MappedLoopback(), V4MappedLoopback()}, - - // Listeners bound to IN6ADDR_ANY accept all connections. - TestParam{V6Any(), V4Any()}, TestParam{V6Any(), V4Loopback()}, - TestParam{V6Any(), V4MappedAny()}, - TestParam{V6Any(), V4MappedLoopback()}, TestParam{V6Any(), V6Any()}, - TestParam{V6Any(), V6Loopback()}, - - // Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4 - // addresses. - TestParam{V6Loopback(), V6Any()}, - TestParam{V6Loopback(), V6Loopback()}), - DescribeTestParam); +INSTANTIATE_TEST_SUITE_P(All, SocketInetLoopbackTest, + SocketInetLoopbackTestValues(), + DescribeSocketInetTestParam); -using SocketInetReusePortTest = ::testing::TestWithParam<TestParam>; +using SocketInetReusePortTest = ::testing::TestWithParam<SocketInetTestParam>; // TODO(gvisor.dev/issue/940): Remove when portHint/stack.Seed is // saved/restored. TEST_P(SocketInetReusePortTest, TcpPortReuseMultiThread) { - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -1898,7 +1482,7 @@ TEST_P(SocketInetReusePortTest, TcpPortReuseMultiThread) { } TEST_P(SocketInetReusePortTest, UdpPortReuseMultiThread) { - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -2009,7 +1593,7 @@ TEST_P(SocketInetReusePortTest, UdpPortReuseMultiThread) { } TEST_P(SocketInetReusePortTest, UdpPortReuseMultiThreadShort) { - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -2117,32 +1701,23 @@ INSTANTIATE_TEST_SUITE_P( ::testing::Values( // Listeners bound to IPv4 addresses refuse connections using IPv6 // addresses. - TestParam{V4Any(), V4Loopback()}, - TestParam{V4Loopback(), V4MappedLoopback()}, + SocketInetTestParam{V4Any(), V4Loopback()}, + SocketInetTestParam{V4Loopback(), V4MappedLoopback()}, // Listeners bound to IN6ADDR_ANY accept all connections. - TestParam{V6Any(), V4Loopback()}, TestParam{V6Any(), V6Loopback()}, + SocketInetTestParam{V6Any(), V4Loopback()}, + SocketInetTestParam{V6Any(), V6Loopback()}, // Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4 // addresses. - TestParam{V6Loopback(), V6Loopback()}), - DescribeTestParam); - -struct ProtocolTestParam { - std::string description; - int type; -}; - -std::string DescribeProtocolTestParam( - ::testing::TestParamInfo<ProtocolTestParam> const& info) { - return info.param.description; -} + SocketInetTestParam{V6Loopback(), V6Loopback()}), + DescribeSocketInetTestParam); using SocketMultiProtocolInetLoopbackTest = ::testing::TestWithParam<ProtocolTestParam>; TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedLoopbackOnlyReservesV4) { - auto const& param = GetParam(); + ProtocolTestParam const& param = GetParam(); for (int i = 0; true; i++) { // Bind the v4 loopback on a dual stack socket. @@ -2191,7 +1766,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedLoopbackOnlyReservesV4) { } TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedAnyOnlyReservesV4) { - auto const& param = GetParam(); + ProtocolTestParam const& param = GetParam(); for (int i = 0; true; i++) { // Bind the v4 any on a dual stack socket. @@ -2240,7 +1815,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedAnyOnlyReservesV4) { } TEST_P(SocketMultiProtocolInetLoopbackTest, DualStackV6AnyReservesEverything) { - auto const& param = GetParam(); + ProtocolTestParam const& param = GetParam(); // Bind the v6 any on a dual stack socket. TestAddress const& test_addr_dual = V6Any(); @@ -2303,7 +1878,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, DualStackV6AnyReservesEverything) { TEST_P(SocketMultiProtocolInetLoopbackTest, DualStackV6AnyReuseAddrDoesNotReserveV4Any) { - auto const& param = GetParam(); + ProtocolTestParam const& param = GetParam(); // Bind the v6 any on a dual stack socket. TestAddress const& test_addr_dual = V6Any(); @@ -2340,7 +1915,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, TEST_P(SocketMultiProtocolInetLoopbackTest, DualStackV6AnyReuseAddrListenReservesV4Any) { - auto const& param = GetParam(); + ProtocolTestParam const& param = GetParam(); // Only TCP sockets are supported. SKIP_IF((param.type & SOCK_STREAM) == 0); @@ -2383,7 +1958,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, TEST_P(SocketMultiProtocolInetLoopbackTest, DualStackV6AnyWithListenReservesEverything) { - auto const& param = GetParam(); + ProtocolTestParam const& param = GetParam(); // Only TCP sockets are supported. SKIP_IF((param.type & SOCK_STREAM) == 0); @@ -2450,7 +2025,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, } TEST_P(SocketMultiProtocolInetLoopbackTest, V6OnlyV6AnyReservesV6) { - auto const& param = GetParam(); + ProtocolTestParam const& param = GetParam(); for (int i = 0; true; i++) { // Bind the v6 any on a v6-only socket. @@ -2503,7 +2078,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V6OnlyV6AnyReservesV6) { } TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReserved) { - auto const& param = GetParam(); + ProtocolTestParam const& param = GetParam(); for (int i = 0; true; i++) { // Bind the v6 loopback on a dual stack socket. @@ -2583,66 +2158,8 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReserved) { } } -TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReservedReuseAddr) { - auto const& param = GetParam(); - - // Bind the v6 loopback on a dual stack socket. - TestAddress const& test_addr = V6Loopback(); - sockaddr_storage bound_addr = test_addr.addr; - const FileDescriptor bound_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(bind(bound_fd.get(), AsSockAddr(&bound_addr), test_addr.addr_len), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Listen iff TCP. - if (param.type == SOCK_STREAM) { - ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); - } - - // Get the port that we bound. - socklen_t bound_addr_len = test_addr.addr_len; - ASSERT_THAT( - getsockname(bound_fd.get(), AsSockAddr(&bound_addr), &bound_addr_len), - SyscallSucceeds()); - - // Connect to bind an ephemeral port. - const FileDescriptor connected_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), AsSockAddr(&bound_addr), - bound_addr_len), - SyscallSucceeds()); - - // Get the ephemeral port. - sockaddr_storage connected_addr = {}; - socklen_t connected_addr_len = sizeof(connected_addr); - ASSERT_THAT(getsockname(connected_fd.get(), AsSockAddr(&connected_addr), - &connected_addr_len), - SyscallSucceeds()); - uint16_t const ephemeral_port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); - - // Verify that we actually got an ephemeral port. - ASSERT_NE(ephemeral_port, 0); - - // Verify that the ephemeral port is not reserved. - const FileDescriptor checking_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - EXPECT_THAT( - bind(checking_fd.get(), AsSockAddr(&connected_addr), connected_addr_len), - SyscallSucceeds()); -} - TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedEphemeralPortReserved) { - auto const& param = GetParam(); + ProtocolTestParam const& param = GetParam(); for (int i = 0; true; i++) { // Bind the v4 loopback on a dual stack socket. @@ -2754,68 +2271,8 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedEphemeralPortReserved) { } } -TEST_P(SocketMultiProtocolInetLoopbackTest, - V4MappedEphemeralPortReservedResueAddr) { - auto const& param = GetParam(); - - // Bind the v4 loopback on a dual stack socket. - TestAddress const& test_addr = V4MappedLoopback(); - sockaddr_storage bound_addr = test_addr.addr; - const FileDescriptor bound_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(bind(bound_fd.get(), AsSockAddr(&bound_addr), test_addr.addr_len), - SyscallSucceeds()); - - ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Listen iff TCP. - if (param.type == SOCK_STREAM) { - ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); - } - - // Get the port that we bound. - socklen_t bound_addr_len = test_addr.addr_len; - ASSERT_THAT( - getsockname(bound_fd.get(), AsSockAddr(&bound_addr), &bound_addr_len), - SyscallSucceeds()); - - // Connect to bind an ephemeral port. - const FileDescriptor connected_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), AsSockAddr(&bound_addr), - bound_addr_len), - SyscallSucceeds()); - - // Get the ephemeral port. - sockaddr_storage connected_addr = {}; - socklen_t connected_addr_len = sizeof(connected_addr); - ASSERT_THAT(getsockname(connected_fd.get(), AsSockAddr(&connected_addr), - &connected_addr_len), - SyscallSucceeds()); - uint16_t const ephemeral_port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); - - // Verify that we actually got an ephemeral port. - ASSERT_NE(ephemeral_port, 0); - - // Verify that the ephemeral port is not reserved. - const FileDescriptor checking_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - EXPECT_THAT( - bind(checking_fd.get(), AsSockAddr(&connected_addr), connected_addr_len), - SyscallSucceeds()); -} - TEST_P(SocketMultiProtocolInetLoopbackTest, V4EphemeralPortReserved) { - auto const& param = GetParam(); + ProtocolTestParam const& param = GetParam(); for (int i = 0; true; i++) { // Bind the v4 loopback on a v4 socket. @@ -2928,71 +2385,9 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V4EphemeralPortReserved) { } } -TEST_P(SocketMultiProtocolInetLoopbackTest, V4EphemeralPortReservedReuseAddr) { - auto const& param = GetParam(); - - // Bind the v4 loopback on a v4 socket. - TestAddress const& test_addr = V4Loopback(); - sockaddr_storage bound_addr = test_addr.addr; - const FileDescriptor bound_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - - ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - ASSERT_THAT(bind(bound_fd.get(), AsSockAddr(&bound_addr), test_addr.addr_len), - SyscallSucceeds()); - - // Listen iff TCP. - if (param.type == SOCK_STREAM) { - ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); - } - - // Get the port that we bound. - socklen_t bound_addr_len = test_addr.addr_len; - ASSERT_THAT( - getsockname(bound_fd.get(), AsSockAddr(&bound_addr), &bound_addr_len), - SyscallSucceeds()); - - // Connect to bind an ephemeral port. - const FileDescriptor connected_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - - ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - - ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), AsSockAddr(&bound_addr), - bound_addr_len), - SyscallSucceeds()); - - // Get the ephemeral port. - sockaddr_storage connected_addr = {}; - socklen_t connected_addr_len = sizeof(connected_addr); - ASSERT_THAT(getsockname(connected_fd.get(), AsSockAddr(&connected_addr), - &connected_addr_len), - SyscallSucceeds()); - uint16_t const ephemeral_port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); - - // Verify that we actually got an ephemeral port. - ASSERT_NE(ephemeral_port, 0); - - // Verify that the ephemeral port is not reserved. - const FileDescriptor checking_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - EXPECT_THAT( - bind(checking_fd.get(), AsSockAddr(&connected_addr), connected_addr_len), - SyscallSucceeds()); -} - TEST_P(SocketMultiProtocolInetLoopbackTest, MultipleBindsAllowedNoListeningReuseAddr) { - const auto& param = GetParam(); + ProtocolTestParam const& param = GetParam(); // UDP sockets are allowed to bind/listen on the port w/ SO_REUSEADDR, for TCP // this is only permitted if there is no other listening socket. SKIP_IF(param.type != SOCK_STREAM); @@ -3027,7 +2422,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, } TEST_P(SocketMultiProtocolInetLoopbackTest, PortReuseTwoSockets) { - auto const& param = GetParam(); + ProtocolTestParam const& param = GetParam(); TestAddress const& test_addr = V4Loopback(); sockaddr_storage addr = test_addr.addr; @@ -3080,7 +2475,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, PortReuseTwoSockets) { // closed, we can bind a different socket to the same address without needing // REUSEPORT. TEST_P(SocketMultiProtocolInetLoopbackTest, NoReusePortFollowingReusePort) { - auto const& param = GetParam(); + ProtocolTestParam const& param = GetParam(); TestAddress const& test_addr = V4Loopback(); sockaddr_storage addr = test_addr.addr; @@ -3107,11 +2502,8 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, NoReusePortFollowingReusePort) { ASSERT_THAT(bind(fd, AsSockAddr(&addr), addrlen), SyscallSucceeds()); } -INSTANTIATE_TEST_SUITE_P( - AllFamilies, SocketMultiProtocolInetLoopbackTest, - ::testing::Values(ProtocolTestParam{"TCP", SOCK_STREAM}, - ProtocolTestParam{"UDP", SOCK_DGRAM}), - DescribeProtocolTestParam); +INSTANTIATE_TEST_SUITE_P(AllFamilies, SocketMultiProtocolInetLoopbackTest, + ProtocolTestValues(), DescribeProtocolTestParam); } // namespace diff --git a/test/syscalls/linux/socket_inet_loopback_isolated.cc b/test/syscalls/linux/socket_inet_loopback_isolated.cc new file mode 100644 index 000000000..ab2259b55 --- /dev/null +++ b/test/syscalls/linux/socket_inet_loopback_isolated.cc @@ -0,0 +1,489 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <netinet/tcp.h> + +#include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "test/syscalls/linux/socket_inet_loopback_test_params.h" +#include "test/syscalls/linux/socket_test_util.h" +#include "test/util/test_util.h" + +// Unit tests in this file will run in their own network namespace. + +namespace gvisor { +namespace testing { + +namespace { + +using SocketInetLoopbackIsolatedTest = + ::testing::TestWithParam<SocketInetTestParam>; + +TEST_P(SocketInetLoopbackIsolatedTest, TCPActiveCloseTimeWaitTest) { + SocketInetTestParam const& param = GetParam(); + sockaddr_storage listen_addr, conn_bound_addr; + listen_addr = param.listener.addr; + SetupTimeWaitClose(¶m.listener, ¶m.connector, false /*reuse*/, + false /*accept_close*/, &listen_addr, &conn_bound_addr); + FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( + Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP)); + + ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr), + param.connector.addr_len), + SyscallFailsWithErrno(EADDRINUSE)); +} + +TEST_P(SocketInetLoopbackIsolatedTest, TCPActiveCloseTimeWaitReuseTest) { + SocketInetTestParam const& param = GetParam(); + sockaddr_storage listen_addr, conn_bound_addr; + listen_addr = param.listener.addr; + SetupTimeWaitClose(¶m.listener, ¶m.connector, true /*reuse*/, + false /*accept_close*/, &listen_addr, &conn_bound_addr); + FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( + Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP)); + ASSERT_THAT(setsockopt(conn_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceeds()); + ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr), + param.connector.addr_len), + SyscallFailsWithErrno(EADDRINUSE)); +} + +// These tests are disabled under random save as the restore run +// results in the stack.Seed() being different which can cause +// sequence number of final connect to be one that is considered +// old and can cause the test to be flaky. +// +// Test re-binding of client and server bound addresses when the older +// connection is in TIME_WAIT. +TEST_P(SocketInetLoopbackIsolatedTest, TCPPassiveCloseNoTimeWaitTest) { + SocketInetTestParam const& param = GetParam(); + sockaddr_storage listen_addr, conn_bound_addr; + listen_addr = param.listener.addr; + SetupTimeWaitClose(¶m.listener, ¶m.connector, false /*reuse*/, + true /*accept_close*/, &listen_addr, &conn_bound_addr); + + // Now bind a new socket and verify that we can immediately rebind the address + // bound by the conn_fd as it never entered TIME_WAIT. + const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( + Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP)); + ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr), + param.connector.addr_len), + SyscallSucceeds()); + + FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( + Socket(param.listener.family(), SOCK_STREAM, IPPROTO_TCP)); + ASSERT_THAT( + bind(listen_fd.get(), AsSockAddr(&listen_addr), param.listener.addr_len), + SyscallFailsWithErrno(EADDRINUSE)); +} + +TEST_P(SocketInetLoopbackIsolatedTest, TCPPassiveCloseNoTimeWaitReuseTest) { + SocketInetTestParam const& param = GetParam(); + sockaddr_storage listen_addr, conn_bound_addr; + listen_addr = param.listener.addr; + SetupTimeWaitClose(¶m.listener, ¶m.connector, true /*reuse*/, + true /*accept_close*/, &listen_addr, &conn_bound_addr); + + FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( + Socket(param.listener.family(), SOCK_STREAM, IPPROTO_TCP)); + ASSERT_THAT(setsockopt(listen_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceeds()); + ASSERT_THAT( + bind(listen_fd.get(), AsSockAddr(&listen_addr), param.listener.addr_len), + SyscallSucceeds()); + ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); + + // Now bind and connect new socket and verify that we can immediately rebind + // the address bound by the conn_fd as it never entered TIME_WAIT. + const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( + Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP)); + ASSERT_THAT(setsockopt(conn_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceeds()); + ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr), + param.connector.addr_len), + SyscallSucceeds()); + + uint16_t const port = + ASSERT_NO_ERRNO_AND_VALUE(AddrPort(param.listener.family(), listen_addr)); + sockaddr_storage conn_addr = param.connector.addr; + ASSERT_NO_ERRNO(SetAddrPort(param.connector.family(), &conn_addr, port)); + ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr), + param.connector.addr_len), + SyscallSucceeds()); +} + +// TCPFinWait2Test creates a pair of connected sockets then closes one end to +// trigger FIN_WAIT2 state for the closed endpoint. Then it binds the same local +// IP/port on a new socket and tries to connect. The connect should fail w/ +// an EADDRINUSE. Then we wait till the FIN_WAIT2 timeout is over and try the +// connect again with a new socket and this time it should succeed. +// +// TCP timers are not S/R today, this can cause this test to be flaky when run +// under random S/R due to timer being reset on a restore. +TEST_P(SocketInetLoopbackIsolatedTest, TCPFinWait2Test) { + SocketInetTestParam const& param = GetParam(); + TestAddress const& listener = param.listener; + TestAddress const& connector = param.connector; + + // Create the listening socket. + const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( + Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); + sockaddr_storage listen_addr = listener.addr; + ASSERT_THAT( + bind(listen_fd.get(), AsSockAddr(&listen_addr), listener.addr_len), + SyscallSucceeds()); + ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); + + // Get the port bound by the listening socket. + socklen_t addrlen = listener.addr_len; + ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(&listen_addr), &addrlen), + SyscallSucceeds()); + + uint16_t const port = + ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); + + // Connect to the listening socket. + FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( + Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); + + // Lower FIN_WAIT2 state to 5 seconds for test. + constexpr int kTCPLingerTimeout = 5; + EXPECT_THAT(setsockopt(conn_fd.get(), IPPROTO_TCP, TCP_LINGER2, + &kTCPLingerTimeout, sizeof(kTCPLingerTimeout)), + SyscallSucceedsWithValue(0)); + + sockaddr_storage conn_addr = connector.addr; + ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); + ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr), + connector.addr_len), + SyscallSucceeds()); + + // Accept the connection. + auto accepted = + ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); + + // Get the address/port bound by the connecting socket. + sockaddr_storage conn_bound_addr; + socklen_t conn_addrlen = connector.addr_len; + ASSERT_THAT( + getsockname(conn_fd.get(), AsSockAddr(&conn_bound_addr), &conn_addrlen), + SyscallSucceeds()); + + // close the connecting FD to trigger FIN_WAIT2 on the connected fd. + conn_fd.reset(); + + // Now bind and connect a new socket. + const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE( + Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); + + // Disable cooperative saves after this point. As a save between the first + // bind/connect and the second one can cause the linger timeout timer to + // be restarted causing the final bind/connect to fail. + DisableSave ds; + + ASSERT_THAT(bind(conn_fd2.get(), AsSockAddr(&conn_bound_addr), conn_addrlen), + SyscallFailsWithErrno(EADDRINUSE)); + + // Sleep for a little over the linger timeout to reduce flakiness in + // save/restore tests. + absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 2)); + + ds.reset(); + + ASSERT_THAT( + RetryEINTR(connect)(conn_fd2.get(), AsSockAddr(&conn_addr), conn_addrlen), + SyscallSucceeds()); +} + +// TCPLinger2TimeoutAfterClose creates a pair of connected sockets +// then closes one end to trigger FIN_WAIT2 state for the closed endpoint. +// It then sleeps for the TCP_LINGER2 timeout and verifies that bind/ +// connecting the same address succeeds. +// +// TCP timers are not S/R today, this can cause this test to be flaky when run +// under random S/R due to timer being reset on a restore. +TEST_P(SocketInetLoopbackIsolatedTest, TCPLinger2TimeoutAfterClose) { + SocketInetTestParam const& param = GetParam(); + TestAddress const& listener = param.listener; + TestAddress const& connector = param.connector; + + // Create the listening socket. + const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( + Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); + sockaddr_storage listen_addr = listener.addr; + ASSERT_THAT( + bind(listen_fd.get(), AsSockAddr(&listen_addr), listener.addr_len), + SyscallSucceeds()); + ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); + + // Get the port bound by the listening socket. + socklen_t addrlen = listener.addr_len; + ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(&listen_addr), &addrlen), + SyscallSucceeds()); + + uint16_t const port = + ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); + + // Connect to the listening socket. + FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( + Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); + + sockaddr_storage conn_addr = connector.addr; + ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); + ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr), + connector.addr_len), + SyscallSucceeds()); + + // Accept the connection. + auto accepted = + ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); + + // Get the address/port bound by the connecting socket. + sockaddr_storage conn_bound_addr; + socklen_t conn_addrlen = connector.addr_len; + ASSERT_THAT( + getsockname(conn_fd.get(), AsSockAddr(&conn_bound_addr), &conn_addrlen), + SyscallSucceeds()); + + // Disable cooperative saves after this point as TCP timers are not restored + // across a S/R. + { + DisableSave ds; + constexpr int kTCPLingerTimeout = 5; + EXPECT_THAT(setsockopt(conn_fd.get(), IPPROTO_TCP, TCP_LINGER2, + &kTCPLingerTimeout, sizeof(kTCPLingerTimeout)), + SyscallSucceedsWithValue(0)); + + // close the connecting FD to trigger FIN_WAIT2 on the connected fd. + conn_fd.reset(); + + absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 1)); + + // ds going out of scope will Re-enable S/R's since at this point the timer + // must have fired and cleaned up the endpoint. + } + + // Now bind and connect a new socket and verify that we can immediately + // rebind the address bound by the conn_fd as it never entered TIME_WAIT. + const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE( + Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); + + ASSERT_THAT(bind(conn_fd2.get(), AsSockAddr(&conn_bound_addr), conn_addrlen), + SyscallSucceeds()); + ASSERT_THAT( + RetryEINTR(connect)(conn_fd2.get(), AsSockAddr(&conn_addr), conn_addrlen), + SyscallSucceeds()); +} + +INSTANTIATE_TEST_SUITE_P(All, SocketInetLoopbackIsolatedTest, + SocketInetLoopbackTestValues(), + DescribeSocketInetTestParam); + +using SocketMultiProtocolInetLoopbackIsolatedTest = + ::testing::TestWithParam<ProtocolTestParam>; + +TEST_P(SocketMultiProtocolInetLoopbackIsolatedTest, + V4EphemeralPortReservedReuseAddr) { + ProtocolTestParam const& param = GetParam(); + + // Bind the v4 loopback on a v4 socket. + TestAddress const& test_addr = V4Loopback(); + sockaddr_storage bound_addr = test_addr.addr; + const FileDescriptor bound_fd = + ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); + + ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceeds()); + + ASSERT_THAT(bind(bound_fd.get(), AsSockAddr(&bound_addr), test_addr.addr_len), + SyscallSucceeds()); + + // Listen iff TCP. + if (param.type == SOCK_STREAM) { + ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); + } + + // Get the port that we bound. + socklen_t bound_addr_len = test_addr.addr_len; + ASSERT_THAT( + getsockname(bound_fd.get(), AsSockAddr(&bound_addr), &bound_addr_len), + SyscallSucceeds()); + + // Connect to bind an ephemeral port. + const FileDescriptor connected_fd = + ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); + + ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR, + &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + + ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), AsSockAddr(&bound_addr), + bound_addr_len), + SyscallSucceeds()); + + // Get the ephemeral port. + sockaddr_storage connected_addr = {}; + socklen_t connected_addr_len = sizeof(connected_addr); + ASSERT_THAT(getsockname(connected_fd.get(), AsSockAddr(&connected_addr), + &connected_addr_len), + SyscallSucceeds()); + uint16_t const ephemeral_port = + ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); + + // Verify that we actually got an ephemeral port. + ASSERT_NE(ephemeral_port, 0); + + // Verify that the ephemeral port is not reserved. + const FileDescriptor checking_fd = + ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); + ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR, + &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + EXPECT_THAT( + bind(checking_fd.get(), AsSockAddr(&connected_addr), connected_addr_len), + SyscallSucceeds()); +} + +TEST_P(SocketMultiProtocolInetLoopbackIsolatedTest, + V4MappedEphemeralPortReservedReuseAddr) { + ProtocolTestParam const& param = GetParam(); + + // Bind the v4 loopback on a dual stack socket. + TestAddress const& test_addr = V4MappedLoopback(); + sockaddr_storage bound_addr = test_addr.addr; + const FileDescriptor bound_fd = + ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); + ASSERT_THAT(bind(bound_fd.get(), AsSockAddr(&bound_addr), test_addr.addr_len), + SyscallSucceeds()); + + ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceeds()); + + // Listen iff TCP. + if (param.type == SOCK_STREAM) { + ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); + } + + // Get the port that we bound. + socklen_t bound_addr_len = test_addr.addr_len; + ASSERT_THAT( + getsockname(bound_fd.get(), AsSockAddr(&bound_addr), &bound_addr_len), + SyscallSucceeds()); + + // Connect to bind an ephemeral port. + const FileDescriptor connected_fd = + ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); + ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR, + &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), AsSockAddr(&bound_addr), + bound_addr_len), + SyscallSucceeds()); + + // Get the ephemeral port. + sockaddr_storage connected_addr = {}; + socklen_t connected_addr_len = sizeof(connected_addr); + ASSERT_THAT(getsockname(connected_fd.get(), AsSockAddr(&connected_addr), + &connected_addr_len), + SyscallSucceeds()); + uint16_t const ephemeral_port = + ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); + + // Verify that we actually got an ephemeral port. + ASSERT_NE(ephemeral_port, 0); + + // Verify that the ephemeral port is not reserved. + const FileDescriptor checking_fd = + ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); + ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR, + &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + EXPECT_THAT( + bind(checking_fd.get(), AsSockAddr(&connected_addr), connected_addr_len), + SyscallSucceeds()); +} + +TEST_P(SocketMultiProtocolInetLoopbackIsolatedTest, + V6EphemeralPortReservedReuseAddr) { + ProtocolTestParam const& param = GetParam(); + + // Bind the v6 loopback on a dual stack socket. + TestAddress const& test_addr = V6Loopback(); + sockaddr_storage bound_addr = test_addr.addr; + const FileDescriptor bound_fd = + ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); + ASSERT_THAT(bind(bound_fd.get(), AsSockAddr(&bound_addr), test_addr.addr_len), + SyscallSucceeds()); + ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceeds()); + + // Listen iff TCP. + if (param.type == SOCK_STREAM) { + ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); + } + + // Get the port that we bound. + socklen_t bound_addr_len = test_addr.addr_len; + ASSERT_THAT( + getsockname(bound_fd.get(), AsSockAddr(&bound_addr), &bound_addr_len), + SyscallSucceeds()); + + // Connect to bind an ephemeral port. + const FileDescriptor connected_fd = + ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); + ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR, + &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), AsSockAddr(&bound_addr), + bound_addr_len), + SyscallSucceeds()); + + // Get the ephemeral port. + sockaddr_storage connected_addr = {}; + socklen_t connected_addr_len = sizeof(connected_addr); + ASSERT_THAT(getsockname(connected_fd.get(), AsSockAddr(&connected_addr), + &connected_addr_len), + SyscallSucceeds()); + uint16_t const ephemeral_port = + ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); + + // Verify that we actually got an ephemeral port. + ASSERT_NE(ephemeral_port, 0); + + // Verify that the ephemeral port is not reserved. + const FileDescriptor checking_fd = + ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); + ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR, + &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + EXPECT_THAT( + bind(checking_fd.get(), AsSockAddr(&connected_addr), connected_addr_len), + SyscallSucceeds()); +} + +INSTANTIATE_TEST_SUITE_P(AllFamilies, + SocketMultiProtocolInetLoopbackIsolatedTest, + ProtocolTestValues(), DescribeProtocolTestParam); + +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_inet_loopback_nogotsan.cc b/test/syscalls/linux/socket_inet_loopback_nogotsan.cc index 601ae107b..cc2773af1 100644 --- a/test/syscalls/linux/socket_inet_loopback_nogotsan.cc +++ b/test/syscalls/linux/socket_inet_loopback_nogotsan.cc @@ -27,6 +27,7 @@ #include "gtest/gtest.h" #include "absl/strings/str_cat.h" #include "test/syscalls/linux/ip_socket_test_util.h" +#include "test/syscalls/linux/socket_inet_loopback_test_params.h" #include "test/syscalls/linux/socket_test_util.h" #include "test/util/file_descriptor.h" #include "test/util/posix_error.h" @@ -38,47 +39,7 @@ namespace testing { namespace { -using ::testing::Gt; - -PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr) { - switch (family) { - case AF_INET: - return static_cast<uint16_t>( - reinterpret_cast<sockaddr_in const*>(&addr)->sin_port); - case AF_INET6: - return static_cast<uint16_t>( - reinterpret_cast<sockaddr_in6 const*>(&addr)->sin6_port); - default: - return PosixError(EINVAL, - absl::StrCat("unknown socket family: ", family)); - } -} - -PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) { - switch (family) { - case AF_INET: - reinterpret_cast<sockaddr_in*>(addr)->sin_port = port; - return NoError(); - case AF_INET6: - reinterpret_cast<sockaddr_in6*>(addr)->sin6_port = port; - return NoError(); - default: - return PosixError(EINVAL, - absl::StrCat("unknown socket family: ", family)); - } -} - -struct TestParam { - TestAddress listener; - TestAddress connector; -}; - -std::string DescribeTestParam(::testing::TestParamInfo<TestParam> const& info) { - return absl::StrCat("Listen", info.param.listener.description, "_Connect", - info.param.connector.description); -} - -using SocketInetLoopbackTest = ::testing::TestWithParam<TestParam>; +using SocketInetLoopbackTest = ::testing::TestWithParam<SocketInetTestParam>; // This test verifies that connect returns EADDRNOTAVAIL if all local ephemeral // ports are already in use for a given destination ip/port. @@ -87,7 +48,7 @@ using SocketInetLoopbackTest = ::testing::TestWithParam<TestParam>; // // FIXME(b/162475855): This test is failing reliably. TEST_P(SocketInetLoopbackTest, DISABLED_TestTCPPortExhaustion) { - auto const& param = GetParam(); + SocketInetTestParam const& param = GetParam(); TestAddress const& listener = param.listener; TestAddress const& connector = param.connector; @@ -136,59 +97,32 @@ TEST_P(SocketInetLoopbackTest, DISABLED_TestTCPPortExhaustion) { } } -INSTANTIATE_TEST_SUITE_P( - All, SocketInetLoopbackTest, - ::testing::Values( - // Listeners bound to IPv4 addresses refuse connections using IPv6 - // addresses. - TestParam{V4Any(), V4Any()}, TestParam{V4Any(), V4Loopback()}, - TestParam{V4Any(), V4MappedAny()}, - TestParam{V4Any(), V4MappedLoopback()}, - TestParam{V4Loopback(), V4Any()}, TestParam{V4Loopback(), V4Loopback()}, - TestParam{V4Loopback(), V4MappedLoopback()}, - TestParam{V4MappedAny(), V4Any()}, - TestParam{V4MappedAny(), V4Loopback()}, - TestParam{V4MappedAny(), V4MappedAny()}, - TestParam{V4MappedAny(), V4MappedLoopback()}, - TestParam{V4MappedLoopback(), V4Any()}, - TestParam{V4MappedLoopback(), V4Loopback()}, - TestParam{V4MappedLoopback(), V4MappedLoopback()}, - - // Listeners bound to IN6ADDR_ANY accept all connections. - TestParam{V6Any(), V4Any()}, TestParam{V6Any(), V4Loopback()}, - TestParam{V6Any(), V4MappedAny()}, - TestParam{V6Any(), V4MappedLoopback()}, TestParam{V6Any(), V6Any()}, - TestParam{V6Any(), V6Loopback()}, - - // Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4 - // addresses. - TestParam{V6Loopback(), V6Any()}, - TestParam{V6Loopback(), V6Loopback()}), - DescribeTestParam); - -struct ProtocolTestParam { - std::string description; - int type; -}; - -std::string DescribeProtocolTestParam( - ::testing::TestParamInfo<ProtocolTestParam> const& info) { - return info.param.description; -} +INSTANTIATE_TEST_SUITE_P(All, SocketInetLoopbackTest, + SocketInetLoopbackTestValues(), + DescribeSocketInetTestParam); using SocketMultiProtocolInetLoopbackTest = ::testing::TestWithParam<ProtocolTestParam>; -TEST_P(SocketMultiProtocolInetLoopbackTest, BindAvoidsListeningPortsReuseAddr) { - const auto& param = GetParam(); - // UDP sockets are allowed to bind/listen on the port w/ SO_REUSEADDR, for TCP - // this is only permitted if there is no other listening socket. +TEST_P(SocketMultiProtocolInetLoopbackTest, + TCPBindAvoidsOtherBoundPortsReuseAddr) { + ProtocolTestParam const& param = GetParam(); + // UDP sockets are allowed to bind/listen on an already bound port w/ + // SO_REUSEADDR even when requesting a port from the kernel. In case of TCP + // rebinding is only permitted when SO_REUSEADDR is set and an explicit port + // is specified. When a zero port is specified to the bind() call then an + // already bound port will not be picked. SKIP_IF(param.type != SOCK_STREAM); DisableSave ds; // Too many syscalls. // A map of port to file descriptor binding the port. - std::map<uint16_t, FileDescriptor> listen_sockets; + std::map<uint16_t, FileDescriptor> bound_sockets; + + // Reduce number of ephemeral ports if permitted to reduce running time of + // the test. + [[maybe_unused]] const int nports = + ASSERT_NO_ERRNO_AND_VALUE(MaybeLimitEphemeralPorts()); // Exhaust all ephemeral ports. while (true) { @@ -214,19 +148,63 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, BindAvoidsListeningPortsReuseAddr) { SyscallSucceeds()); uint16_t port = reinterpret_cast<sockaddr_in*>(&bound_addr)->sin_port; - // Newly bound port should not already be in use by a listening socket. - ASSERT_EQ(listen_sockets.find(port), listen_sockets.end()); - auto fd = bound_fd.get(); - listen_sockets.insert(std::make_pair(port, std::move(bound_fd))); - ASSERT_THAT(listen(fd, SOMAXCONN), SyscallSucceeds()); + auto [iter, inserted] = bound_sockets.emplace(port, std::move(bound_fd)); + ASSERT_TRUE(inserted); + } +} + +TEST_P(SocketMultiProtocolInetLoopbackTest, + UDPBindMayBindOtherBoundPortsReuseAddr) { + ProtocolTestParam const& param = GetParam(); + // UDP sockets are allowed to bind/listen on an already bound port w/ + // SO_REUSEADDR even when requesting a port from the kernel. + SKIP_IF(param.type != SOCK_DGRAM); + + DisableSave ds; // Too many syscalls. + + // A map of port to file descriptor binding the port. + std::map<uint16_t, FileDescriptor> bound_sockets; + + // Reduce number of ephemeral ports if permitted to reduce running time of + // the test. + [[maybe_unused]] const int nports = + ASSERT_NO_ERRNO_AND_VALUE(MaybeLimitEphemeralPorts()); + + // Exhaust all ephemeral ports. + bool duplicate_binding = false; + while (true) { + // Bind the v4 loopback on a v4 socket. + TestAddress const& test_addr = V4Loopback(); + sockaddr_storage bound_addr = test_addr.addr; + FileDescriptor bound_fd = + ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); + + ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, + &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + + ASSERT_THAT( + bind(bound_fd.get(), AsSockAddr(&bound_addr), test_addr.addr_len), + SyscallSucceeds()); + + // Get the port that we bound. + socklen_t bound_addr_len = test_addr.addr_len; + ASSERT_THAT( + getsockname(bound_fd.get(), AsSockAddr(&bound_addr), &bound_addr_len), + SyscallSucceeds()); + uint16_t port = reinterpret_cast<sockaddr_in*>(&bound_addr)->sin_port; + + auto [iter, inserted] = bound_sockets.emplace(port, std::move(bound_fd)); + if (!inserted) { + duplicate_binding = true; + break; + } } + ASSERT_TRUE(duplicate_binding); } -INSTANTIATE_TEST_SUITE_P( - AllFamilies, SocketMultiProtocolInetLoopbackTest, - ::testing::Values(ProtocolTestParam{"TCP", SOCK_STREAM}, - ProtocolTestParam{"UDP", SOCK_DGRAM}), - DescribeProtocolTestParam); +INSTANTIATE_TEST_SUITE_P(AllFamilies, SocketMultiProtocolInetLoopbackTest, + ProtocolTestValues(), DescribeProtocolTestParam); } // namespace diff --git a/test/syscalls/linux/socket_inet_loopback_test_params.h b/test/syscalls/linux/socket_inet_loopback_test_params.h new file mode 100644 index 000000000..42b48eb8a --- /dev/null +++ b/test/syscalls/linux/socket_inet_loopback_test_params.h @@ -0,0 +1,86 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_INET_LOOPBACK_TEST_PARAMS_H_ +#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_INET_LOOPBACK_TEST_PARAMS_H_ + +#include "gtest/gtest.h" +#include "test/syscalls/linux/socket_test_util.h" + +namespace gvisor { +namespace testing { + +struct SocketInetTestParam { + TestAddress listener; + TestAddress connector; +}; + +inline std::string DescribeSocketInetTestParam( + ::testing::TestParamInfo<SocketInetTestParam> const& info) { + return absl::StrCat("Listen", info.param.listener.description, "_Connect", + info.param.connector.description); +} + +inline auto SocketInetLoopbackTestValues() { + return ::testing::Values( + // Listeners bound to IPv4 addresses refuse connections using IPv6 + // addresses. + SocketInetTestParam{V4Any(), V4Any()}, + SocketInetTestParam{V4Any(), V4Loopback()}, + SocketInetTestParam{V4Any(), V4MappedAny()}, + SocketInetTestParam{V4Any(), V4MappedLoopback()}, + SocketInetTestParam{V4Loopback(), V4Any()}, + SocketInetTestParam{V4Loopback(), V4Loopback()}, + SocketInetTestParam{V4Loopback(), V4MappedLoopback()}, + SocketInetTestParam{V4MappedAny(), V4Any()}, + SocketInetTestParam{V4MappedAny(), V4Loopback()}, + SocketInetTestParam{V4MappedAny(), V4MappedAny()}, + SocketInetTestParam{V4MappedAny(), V4MappedLoopback()}, + SocketInetTestParam{V4MappedLoopback(), V4Any()}, + SocketInetTestParam{V4MappedLoopback(), V4Loopback()}, + SocketInetTestParam{V4MappedLoopback(), V4MappedLoopback()}, + + // Listeners bound to IN6ADDR_ANY accept all connections. + SocketInetTestParam{V6Any(), V4Any()}, + SocketInetTestParam{V6Any(), V4Loopback()}, + SocketInetTestParam{V6Any(), V4MappedAny()}, + SocketInetTestParam{V6Any(), V4MappedLoopback()}, + SocketInetTestParam{V6Any(), V6Any()}, + SocketInetTestParam{V6Any(), V6Loopback()}, + + // Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4 + // addresses. + SocketInetTestParam{V6Loopback(), V6Any()}, + SocketInetTestParam{V6Loopback(), V6Loopback()}); +} + +struct ProtocolTestParam { + std::string description; + int type; +}; + +inline std::string DescribeProtocolTestParam( + ::testing::TestParamInfo<ProtocolTestParam> const& info) { + return info.param.description; +} + +inline auto ProtocolTestValues() { + return ::testing::Values(ProtocolTestParam{"TCP", SOCK_STREAM}, + ProtocolTestParam{"UDP", SOCK_DGRAM}); +} + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_INET_LOOPBACK_TEST_PARAMS_H_ diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_loopback_nogotsan.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_loopback_nogotsan.cc index 7ca6d52e4..a2c6d4491 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_loopback_nogotsan.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_loopback_nogotsan.cc @@ -31,7 +31,7 @@ using IPv4UDPUnboundSocketNogotsanTest = SimpleSocketTest; // We disable S/R because this test creates a large number of sockets. TEST_P(IPv4UDPUnboundSocketNogotsanTest, UDPConnectPortExhaustion) { auto receiver1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - constexpr int kClients = 65536; + const int kClients = ASSERT_NO_ERRNO_AND_VALUE(MaybeLimitEphemeralPorts()); // Bind the first socket to the loopback and take note of the selected port. auto addr = V4Loopback(); ASSERT_THAT(bind(receiver1->get(), AsSockAddr(&addr.addr), addr.addr_len), @@ -61,7 +61,7 @@ TEST_P(IPv4UDPUnboundSocketNogotsanTest, UDPConnectPortExhaustion) { // We disable S/R because this test creates a large number of sockets. TEST_P(IPv4UDPUnboundSocketNogotsanTest, UDPBindPortExhaustion) { auto receiver1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - constexpr int kClients = 65536; + const int kClients = ASSERT_NO_ERRNO_AND_VALUE(MaybeLimitEphemeralPorts()); auto addr = V4Loopback(); // Disable cooperative S/R as we are making too many syscalls. DisableSave ds; diff --git a/test/syscalls/linux/socket_netdevice.cc b/test/syscalls/linux/socket_netdevice.cc index 5f8d7f981..8d214a2b7 100644 --- a/test/syscalls/linux/socket_netdevice.cc +++ b/test/syscalls/linux/socket_netdevice.cc @@ -37,6 +37,7 @@ using ::testing::AnyOf; using ::testing::Eq; TEST(NetdeviceTest, Loopback) { + SKIP_IF(IsRunningWithHostinet()); FileDescriptor sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); @@ -60,6 +61,7 @@ TEST(NetdeviceTest, Loopback) { } TEST(NetdeviceTest, Netmask) { + SKIP_IF(IsRunningWithHostinet()); // We need an interface index to identify the loopback device. FileDescriptor sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); @@ -135,6 +137,7 @@ TEST(NetdeviceTest, Netmask) { } TEST(NetdeviceTest, InterfaceName) { + SKIP_IF(IsRunningWithHostinet()); FileDescriptor sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); @@ -168,6 +171,7 @@ TEST(NetdeviceTest, InterfaceFlags) { } TEST(NetdeviceTest, InterfaceMTU) { + SKIP_IF(IsRunningWithHostinet()); FileDescriptor sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); @@ -181,6 +185,7 @@ TEST(NetdeviceTest, InterfaceMTU) { } TEST(NetdeviceTest, EthtoolGetTSInfo) { + SKIP_IF(IsRunningWithHostinet()); FileDescriptor sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); diff --git a/test/syscalls/linux/socket_test_util.cc b/test/syscalls/linux/socket_test_util.cc index 83c33ec8d..c1cded834 100644 --- a/test/syscalls/linux/socket_test_util.cc +++ b/test/syscalls/linux/socket_test_util.cc @@ -24,6 +24,7 @@ #include "gtest/gtest.h" #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" #include "absl/time/clock.h" #include "absl/types/optional.h" #include "test/util/file_descriptor.h" @@ -948,5 +949,169 @@ uint16_t ICMPChecksum(struct icmphdr icmphdr, const char* payload, return csum; } +PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr) { + switch (family) { + case AF_INET: + return static_cast<uint16_t>( + reinterpret_cast<sockaddr_in const*>(&addr)->sin_port); + case AF_INET6: + return static_cast<uint16_t>( + reinterpret_cast<sockaddr_in6 const*>(&addr)->sin6_port); + default: + return PosixError(EINVAL, + absl::StrCat("unknown socket family: ", family)); + } +} + +PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) { + switch (family) { + case AF_INET: + reinterpret_cast<sockaddr_in*>(addr)->sin_port = port; + return NoError(); + case AF_INET6: + reinterpret_cast<sockaddr_in6*>(addr)->sin6_port = port; + return NoError(); + default: + return PosixError(EINVAL, + absl::StrCat("unknown socket family: ", family)); + } +} + +void SetupTimeWaitClose(const TestAddress* listener, + const TestAddress* connector, bool reuse, + bool accept_close, sockaddr_storage* listen_addr, + sockaddr_storage* conn_bound_addr) { + // Create the listening socket. + FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( + Socket(listener->family(), SOCK_STREAM, IPPROTO_TCP)); + if (reuse) { + ASSERT_THAT(setsockopt(listen_fd.get(), SOL_SOCKET, SO_REUSEADDR, + &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + } + ASSERT_THAT( + bind(listen_fd.get(), AsSockAddr(listen_addr), listener->addr_len), + SyscallSucceeds()); + ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); + + // Get the port bound by the listening socket. + socklen_t addrlen = listener->addr_len; + ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(listen_addr), &addrlen), + SyscallSucceeds()); + + uint16_t const port = + ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener->family(), *listen_addr)); + + // Connect to the listening socket. + FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( + Socket(connector->family(), SOCK_STREAM, IPPROTO_TCP)); + + // We disable saves after this point as a S/R causes the netstack seed + // to be regenerated which changes what ports/ISN is picked for a given + // tuple (src ip,src port, dst ip, dst port). This can cause the final + // SYN to use a sequence number that looks like one from the current + // connection in TIME_WAIT and will not be accepted causing the test + // to timeout. + // + // TODO(gvisor.dev/issue/940): S/R portSeed/portHint + DisableSave ds; + + sockaddr_storage conn_addr = connector->addr; + ASSERT_NO_ERRNO(SetAddrPort(connector->family(), &conn_addr, port)); + ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr), + connector->addr_len), + SyscallSucceeds()); + + // Accept the connection. + auto accepted = + ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); + + // Get the address/port bound by the connecting socket. + socklen_t conn_addrlen = connector->addr_len; + ASSERT_THAT( + getsockname(conn_fd.get(), AsSockAddr(conn_bound_addr), &conn_addrlen), + SyscallSucceeds()); + + FileDescriptor active_closefd, passive_closefd; + if (accept_close) { + active_closefd = std::move(accepted); + passive_closefd = std::move(conn_fd); + } else { + active_closefd = std::move(conn_fd); + passive_closefd = std::move(accepted); + } + + // shutdown to trigger TIME_WAIT. + ASSERT_THAT(shutdown(active_closefd.get(), SHUT_WR), SyscallSucceeds()); + { + constexpr int kTimeout = 10000; + pollfd pfd = { + .fd = passive_closefd.get(), + .events = POLLIN, + }; + ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); + ASSERT_EQ(pfd.revents, POLLIN); + } + ASSERT_THAT(shutdown(passive_closefd.get(), SHUT_WR), SyscallSucceeds()); + { + constexpr int kTimeout = 10000; + constexpr int16_t want_events = POLLHUP; + pollfd pfd = { + .fd = active_closefd.get(), + .events = want_events, + }; + ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); + } + + // This sleep is needed to reduce flake to ensure that the passive-close + // ensures the state transitions to CLOSE from LAST_ACK. + absl::SleepFor(absl::Seconds(1)); +} + +constexpr char kRangeFile[] = "/proc/sys/net/ipv4/ip_local_port_range"; + +PosixErrorOr<int> MaybeLimitEphemeralPorts() { + int min = 0; + int max = 1 << 16; + + // Read the ephemeral range from /proc. + ASSIGN_OR_RETURN_ERRNO(std::string rangefile, GetContents(kRangeFile)); + const std::string err_msg = + absl::StrFormat("%s has invalid content: %s", kRangeFile, rangefile); + if (rangefile.back() != '\n') { + return PosixError(EINVAL, err_msg); + } + rangefile.pop_back(); + std::vector<std::string> range = + absl::StrSplit(rangefile, absl::ByAnyChar("\t ")); + if (range.size() < 2 || !absl::SimpleAtoi(range.front(), &min) || + !absl::SimpleAtoi(range.back(), &max)) { + return PosixError(EINVAL, err_msg); + } + + // If we can open as writable, limit the range. + if (!access(kRangeFile, W_OK)) { + ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, + Open(kRangeFile, O_WRONLY | O_TRUNC, 0)); + int newMax = min + 50; + const std::string small_range = absl::StrFormat("%d %d", min, newMax); + int n = write(fd.get(), small_range.c_str(), small_range.size()); + if (n < 0) { + // Hostinet doesn't allow modifying the host port range. And if we're root + // (as we are in some tests), access and open will succeed even if the + // file mode is readonly. + if (errno != EACCES) { + return PosixError( + errno, + absl::StrFormat("write(%d [%s], \"%s\", %d)", fd.get(), kRangeFile, + small_range.c_str(), small_range.size())); + } + } else { + max = newMax; + } + } + return max - min; +} + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_test_util.h b/test/syscalls/linux/socket_test_util.h index 76dc090e0..0e2be63cc 100644 --- a/test/syscalls/linux/socket_test_util.h +++ b/test/syscalls/linux/socket_test_util.h @@ -564,6 +564,22 @@ inline sockaddr* AsSockAddr(sockaddr_un* s) { return reinterpret_cast<sockaddr*>(s); } +PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr); + +PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port); + +// setupTimeWaitClose sets up a socket endpoint in TIME_WAIT state. +// Callers can choose to perform active close on either ends of the connection +// and also specify if they want to enabled SO_REUSEADDR. +void SetupTimeWaitClose(const TestAddress* listener, + const TestAddress* connector, bool reuse, + bool accept_close, sockaddr_storage* listen_addr, + sockaddr_storage* conn_bound_addr); + +// MaybeLimitEphemeralPorts attempts to reduce the number of ephemeral ports and +// returns the number of ephemeral ports. +PosixErrorOr<int> MaybeLimitEphemeralPorts(); + namespace internal { PosixErrorOr<int> TryPortAvailable(int port, AddressFamily family, SocketType type, bool reuse_addr); diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc index 5bfdecc79..183819faf 100644 --- a/test/syscalls/linux/tcp_socket.cc +++ b/test/syscalls/linux/tcp_socket.cc @@ -1182,6 +1182,62 @@ TEST_P(SimpleTcpSocketTest, SelfConnectSend) { EXPECT_THAT(shutdown(s.get(), SHUT_WR), SyscallSucceedsWithValue(0)); } +TEST_P(SimpleTcpSocketTest, SelfConnectSendShutdownWrite) { + // Initialize address to the loopback one. + sockaddr_storage addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); + socklen_t addrlen = sizeof(addr); + + const FileDescriptor s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); + + ASSERT_THAT(bind(s.get(), AsSockAddr(&addr), addrlen), SyscallSucceeds()); + // Get the bound port. + ASSERT_THAT(getsockname(s.get(), AsSockAddr(&addr), &addrlen), + SyscallSucceeds()); + ASSERT_THAT(RetryEINTR(connect)(s.get(), AsSockAddr(&addr), addrlen), + SyscallSucceeds()); + + // Write enough data to fill send and receive buffers. + size_t write_size = 24 << 20; // 24 MiB. + std::vector<char> writebuf(write_size); + + ScopedThread t([&s]() { + absl::SleepFor(absl::Milliseconds(250)); + ASSERT_THAT(shutdown(s.get(), SHUT_WR), SyscallSucceeds()); + }); + + // Try to send the whole thing. + int n; + ASSERT_THAT(n = SendFd(s.get(), writebuf.data(), writebuf.size(), 0), + SyscallFailsWithErrno(EPIPE)); +} + +TEST_P(SimpleTcpSocketTest, SelfConnectRecvShutdownRead) { + // Initialize address to the loopback one. + sockaddr_storage addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); + socklen_t addrlen = sizeof(addr); + + const FileDescriptor s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); + + ASSERT_THAT(bind(s.get(), AsSockAddr(&addr), addrlen), SyscallSucceeds()); + // Get the bound port. + ASSERT_THAT(getsockname(s.get(), AsSockAddr(&addr), &addrlen), + SyscallSucceeds()); + ASSERT_THAT(RetryEINTR(connect)(s.get(), AsSockAddr(&addr), addrlen), + SyscallSucceeds()); + + ScopedThread t([&s]() { + absl::SleepFor(absl::Milliseconds(250)); + ASSERT_THAT(shutdown(s.get(), SHUT_RD), SyscallSucceeds()); + }); + + char buf[1]; + EXPECT_THAT(recv(s.get(), buf, 0, 0), SyscallSucceedsWithValue(0)); +} + void NonBlockingConnect(int family, int16_t pollMask) { const FileDescriptor listener = ASSERT_NO_ERRNO_AND_VALUE(Socket(family, SOCK_STREAM, IPPROTO_TCP)); diff --git a/test/syscalls/linux/tuntap.cc b/test/syscalls/linux/tuntap.cc index 279fe342c..1c74b9724 100644 --- a/test/syscalls/linux/tuntap.cc +++ b/test/syscalls/linux/tuntap.cc @@ -24,6 +24,8 @@ #include <sys/socket.h> #include <sys/types.h> +#include <cstddef> + #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/strings/ascii.h" @@ -44,6 +46,7 @@ constexpr int kIPLen = 4; constexpr const char kDevNetTun[] = "/dev/net/tun"; constexpr const char kTapName[] = "tap0"; +constexpr const char kTunName[] = "tun0"; #define kTapIPAddr htonl(0x0a000001) /* Inet 10.0.0.1 */ #define kTapPeerIPAddr htonl(0x0a000002) /* Inet 10.0.0.2 */ @@ -413,6 +416,47 @@ TEST_F(TuntapTest, SendUdpTriggersArpResolution) { } } +TEST_F(TuntapTest, TUNNoPacketInfo) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); + + // Interface creation. + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(kDevNetTun, O_RDWR)); + + struct ifreq ifr_set = {}; + ifr_set.ifr_flags = IFF_TUN | IFF_NO_PI; + strncpy(ifr_set.ifr_name, kTunName, IFNAMSIZ); + EXPECT_THAT(ioctl(fd.get(), TUNSETIFF, &ifr_set), SyscallSucceeds()); + + // Interface setup. + auto link = ASSERT_NO_ERRNO_AND_VALUE(GetLinkByName(kTunName)); + const struct in_addr dev_ipv4_addr = {.s_addr = kTapIPAddr}; + EXPECT_NO_ERRNO(LinkAddLocalAddr(link.index, AF_INET, 24, &dev_ipv4_addr, + sizeof(dev_ipv4_addr))); + + ping_pkt ping_req = + CreatePingPacket(kMacB, kTapPeerIPAddr, kMacA, kTapIPAddr); + size_t packet_size = sizeof(ping_req) - offsetof(ping_pkt, ip); + + // Send ICMP query + EXPECT_THAT(write(fd.get(), &ping_req.ip, packet_size), + SyscallSucceedsWithValue(packet_size)); + + // Receive loop to process inbound packets. + while (1) { + ping_pkt ping_resp = {}; + EXPECT_THAT(read(fd.get(), &ping_resp.ip, packet_size), + SyscallSucceedsWithValue(packet_size)); + + // Process ping response packet. + if (!memcmp(&ping_resp.ip.saddr, &ping_req.ip.daddr, kIPLen) && + !memcmp(&ping_resp.ip.daddr, &ping_req.ip.saddr, kIPLen) && + ping_resp.icmp.type == 0 && ping_resp.icmp.code == 0) { + // Ends and passes the test. + break; + } + } +} + // TCPBlockingConnectFailsArpResolution tests for TCP connect to fail on link // address resolution failure to a routable, but non existent peer. TEST_F(TuntapTest, TCPBlockingConnectFailsArpResolution) { diff --git a/test/syscalls/linux/verity_getdents.cc b/test/syscalls/linux/verity_getdents.cc index 093595dd3..2eafc3dd3 100644 --- a/test/syscalls/linux/verity_getdents.cc +++ b/test/syscalls/linux/verity_getdents.cc @@ -58,16 +58,16 @@ class GetDentsTest : public ::testing::Test { }; TEST_F(GetDentsTest, GetDents) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); std::vector<std::string> expect = {".", "..", filename_}; EXPECT_NO_ERRNO(DirContains(verity_dir, expect, /*exclude=*/{})); } TEST_F(GetDentsTest, Deleted) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); EXPECT_THAT(unlink(JoinPath(tmpfs_dir_.path(), filename_).c_str()), SyscallSucceeds()); @@ -77,8 +77,8 @@ TEST_F(GetDentsTest, Deleted) { } TEST_F(GetDentsTest, Renamed) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); std::string new_file_name = "renamed-" + filename_; EXPECT_THAT(rename(JoinPath(tmpfs_dir_.path(), filename_).c_str(), diff --git a/test/syscalls/linux/verity_ioctl.cc b/test/syscalls/linux/verity_ioctl.cc index be91b23d0..e7e4fa64b 100644 --- a/test/syscalls/linux/verity_ioctl.cc +++ b/test/syscalls/linux/verity_ioctl.cc @@ -105,8 +105,8 @@ TEST_F(IoctlTest, Measure) { } TEST_F(IoctlTest, Mount) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Make sure the file can be open and read in the mounted verity fs. auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( @@ -117,8 +117,8 @@ TEST_F(IoctlTest, Mount) { } TEST_F(IoctlTest, NonExistingFile) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Confirm that opening a non-existing file in the verity-enabled directory // triggers the expected error instead of verification failure. @@ -128,8 +128,8 @@ TEST_F(IoctlTest, NonExistingFile) { } TEST_F(IoctlTest, ModifiedFile) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Modify the file and check verification failure upon reading from it. auto const fd = ASSERT_NO_ERRNO_AND_VALUE( @@ -143,8 +143,8 @@ TEST_F(IoctlTest, ModifiedFile) { } TEST_F(IoctlTest, ModifiedMerkle) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Modify the Merkle file and check verification failure upon opening the // corresponding file. @@ -158,8 +158,8 @@ TEST_F(IoctlTest, ModifiedMerkle) { } TEST_F(IoctlTest, ModifiedDirMerkle) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Modify the Merkle file for the parent directory and check verification // failure upon opening the corresponding file. @@ -173,8 +173,8 @@ TEST_F(IoctlTest, ModifiedDirMerkle) { } TEST_F(IoctlTest, Stat) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); struct stat st; EXPECT_THAT(stat(JoinPath(verity_dir, filename_).c_str(), &st), @@ -182,8 +182,8 @@ TEST_F(IoctlTest, Stat) { } TEST_F(IoctlTest, ModifiedStat) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); EXPECT_THAT(chmod(JoinPath(tmpfs_dir_.path(), filename_).c_str(), 0644), SyscallSucceeds()); @@ -193,8 +193,8 @@ TEST_F(IoctlTest, ModifiedStat) { } TEST_F(IoctlTest, DeleteFile) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); EXPECT_THAT(unlink(JoinPath(tmpfs_dir_.path(), filename_).c_str()), SyscallSucceeds()); @@ -203,8 +203,8 @@ TEST_F(IoctlTest, DeleteFile) { } TEST_F(IoctlTest, DeleteMerkle) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); EXPECT_THAT( unlink(MerklePath(JoinPath(tmpfs_dir_.path(), filename_)).c_str()), @@ -214,8 +214,8 @@ TEST_F(IoctlTest, DeleteMerkle) { } TEST_F(IoctlTest, RenameFile) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); std::string new_file_name = "renamed-" + filename_; EXPECT_THAT(rename(JoinPath(tmpfs_dir_.path(), filename_).c_str(), @@ -226,8 +226,8 @@ TEST_F(IoctlTest, RenameFile) { } TEST_F(IoctlTest, RenameMerkle) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); std::string new_file_name = "renamed-" + filename_; EXPECT_THAT( diff --git a/test/syscalls/linux/verity_mmap.cc b/test/syscalls/linux/verity_mmap.cc index dde74cc91..09ced6eb3 100644 --- a/test/syscalls/linux/verity_mmap.cc +++ b/test/syscalls/linux/verity_mmap.cc @@ -57,8 +57,8 @@ class MmapTest : public ::testing::Test { }; TEST_F(MmapTest, MmapRead) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Make sure the file can be open and mmapped in the mounted verity fs. auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( @@ -71,8 +71,8 @@ TEST_F(MmapTest, MmapRead) { } TEST_F(MmapTest, ModifiedBeforeMmap) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Modify the file and check verification failure upon mmapping. auto const fd = ASSERT_NO_ERRNO_AND_VALUE( @@ -90,8 +90,8 @@ TEST_F(MmapTest, ModifiedBeforeMmap) { } TEST_F(MmapTest, ModifiedAfterMmap) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777)); @@ -126,8 +126,8 @@ INSTANTIATE_TEST_SUITE_P( ::testing::ValuesIn({MAP_SHARED, MAP_PRIVATE}))); TEST_P(MmapParamTest, Mmap) { - std::string verity_dir = - ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_)); + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); // Make sure the file can be open and mmapped in the mounted verity fs. auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( diff --git a/test/syscalls/linux/verity_symlink.cc b/test/syscalls/linux/verity_symlink.cc new file mode 100644 index 000000000..bbf5375cb --- /dev/null +++ b/test/syscalls/linux/verity_symlink.cc @@ -0,0 +1,117 @@ +// Copyright 2021 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <stdint.h> +#include <stdlib.h> +#include <sys/mount.h> +#include <sys/stat.h> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "test/util/capability_util.h" +#include "test/util/fs_util.h" +#include "test/util/mount_util.h" +#include "test/util/temp_path.h" +#include "test/util/test_util.h" +#include "test/util/verity_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +const char kSymlink[] = "verity_symlink"; + +class SymlinkTest : public ::testing::Test { + protected: + void SetUp() override { + // Verity is implemented in VFS2. + SKIP_IF(IsRunningWithVFS1()); + + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + // Mount a tmpfs file system, to be wrapped by a verity fs. + tmpfs_dir_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + ASSERT_THAT(mount("", tmpfs_dir_.path().c_str(), "tmpfs", 0, ""), + SyscallSucceeds()); + + // Create a new file in the tmpfs mount. + file_ = ASSERT_NO_ERRNO_AND_VALUE( + TempPath::CreateFileWith(tmpfs_dir_.path(), kContents, 0777)); + filename_ = Basename(file_.path()); + + // Create a symlink to the file. + ASSERT_THAT(symlink(file_.path().c_str(), + JoinPath(tmpfs_dir_.path(), kSymlink).c_str()), + SyscallSucceeds()); + } + + TempPath tmpfs_dir_; + TempPath file_; + std::string filename_; +}; + +TEST_F(SymlinkTest, Success) { + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, + {EnableTarget(kSymlink, O_RDONLY | O_NOFOLLOW)})); + + char buf[256]; + EXPECT_THAT( + readlink(JoinPath(verity_dir, kSymlink).c_str(), buf, sizeof(buf)), + SyscallSucceeds()); + auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( + Open(JoinPath(verity_dir, kSymlink).c_str(), O_RDONLY, 0777)); + EXPECT_THAT(ReadFd(verity_fd.get(), buf, sizeof(kContents)), + SyscallSucceeds()); +} + +TEST_F(SymlinkTest, DeleteLink) { + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, + {EnableTarget(kSymlink, O_RDONLY | O_NOFOLLOW)})); + + ASSERT_THAT(unlink(JoinPath(tmpfs_dir_.path(), kSymlink).c_str()), + SyscallSucceeds()); + char buf[256]; + EXPECT_THAT( + readlink(JoinPath(verity_dir, kSymlink).c_str(), buf, sizeof(buf)), + SyscallFailsWithErrno(EIO)); + EXPECT_THAT(open(JoinPath(verity_dir, kSymlink).c_str(), O_RDONLY, 0777), + SyscallFailsWithErrno(EIO)); +} + +TEST_F(SymlinkTest, ModifyLink) { + std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( + MountVerity(tmpfs_dir_.path(), filename_, + {EnableTarget(kSymlink, O_RDONLY | O_NOFOLLOW)})); + + ASSERT_THAT(unlink(JoinPath(tmpfs_dir_.path(), kSymlink).c_str()), + SyscallSucceeds()); + + std::string newlink = "newlink"; + ASSERT_THAT(symlink(JoinPath(tmpfs_dir_.path(), newlink).c_str(), + JoinPath(tmpfs_dir_.path(), kSymlink).c_str()), + SyscallSucceeds()); + char buf[256]; + EXPECT_THAT( + readlink(JoinPath(verity_dir, kSymlink).c_str(), buf, sizeof(buf)), + SyscallFailsWithErrno(EIO)); + EXPECT_THAT(open(JoinPath(verity_dir, kSymlink).c_str(), O_RDONLY, 0777), + SyscallFailsWithErrno(EIO)); +} + +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/util/capability_util.cc b/test/util/capability_util.cc index a1b994c45..3bf218128 100644 --- a/test/util/capability_util.cc +++ b/test/util/capability_util.cc @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#ifdef __linux__ + #include "test/util/capability_util.h" #include <linux/capability.h> @@ -79,3 +81,5 @@ PosixErrorOr<bool> CanCreateUserNamespace() { } // namespace testing } // namespace gvisor + +#endif // __linux__ diff --git a/test/util/capability_util.h b/test/util/capability_util.h index f2c370125..c4b0feade 100644 --- a/test/util/capability_util.h +++ b/test/util/capability_util.h @@ -17,6 +17,8 @@ #ifndef GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_ #define GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_ +#ifdef __linux__ + #include <errno.h> #include <linux/capability.h> #include <sys/syscall.h> @@ -120,4 +122,7 @@ class AutoCapability { } // namespace testing } // namespace gvisor + +#endif // __linux__ + #endif // GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_ diff --git a/test/util/posix_error.h b/test/util/posix_error.h index 9ca09b77c..40853cb21 100644 --- a/test/util/posix_error.h +++ b/test/util/posix_error.h @@ -385,7 +385,7 @@ class PosixErrorIsMatcher { }; // Returns a gMock matcher that matches a PosixError or PosixErrorOr<> whose -// whose error code matches code_matcher, and whose error message matches +// error code matches code_matcher, and whose error message matches // message_matcher. template <typename ErrorCodeMatcher> PosixErrorIsMatcher PosixErrorIs( @@ -395,6 +395,14 @@ PosixErrorIsMatcher PosixErrorIs( std::move(message_matcher)); } +// Returns a gMock matcher that matches a PosixError or PosixErrorOr<> whose +// error code matches code_matcher. +template <typename ErrorCodeMatcher> +PosixErrorIsMatcher PosixErrorIs(ErrorCodeMatcher&& code_matcher) { + return PosixErrorIsMatcher(std::forward<ErrorCodeMatcher>(code_matcher), + ::testing::_); +} + // Returns a gMock matcher that matches a PosixErrorOr<> which is ok() and // value matches the inner matcher. template <typename InnerMatcher> diff --git a/test/util/test_util_impl.cc b/test/util/test_util_impl.cc index 7e1ad9e66..6b6826898 100644 --- a/test/util/test_util_impl.cc +++ b/test/util/test_util_impl.cc @@ -20,6 +20,7 @@ #include "benchmark/benchmark.h" #include "test/util/logging.h" +extern bool FLAGS_gtest_list_tests; extern bool FLAGS_benchmark_list_tests; extern std::string FLAGS_benchmark_filter; @@ -40,12 +41,18 @@ void TestInit(int* argc, char*** argv) { } int RunAllTests() { - if (FLAGS_benchmark_list_tests || FLAGS_benchmark_filter != ".") { + if (::testing::FLAGS_gtest_list_tests) { + return RUN_ALL_TESTS(); + } + if (FLAGS_benchmark_list_tests) { benchmark::RunSpecifiedBenchmarks(); return 0; - } else { - return RUN_ALL_TESTS(); } + + // Run selected tests & benchmarks. + int rc = RUN_ALL_TESTS(); + benchmark::RunSpecifiedBenchmarks(); + return rc; } } // namespace testing diff --git a/test/util/verity_util.cc b/test/util/verity_util.cc index f1b4c251b..501d7c2cf 100644 --- a/test/util/verity_util.cc +++ b/test/util/verity_util.cc @@ -55,17 +55,26 @@ PosixError FlipRandomBit(int fd, int size) { } PosixErrorOr<std::string> MountVerity(std::string tmpfs_dir, - std::string filename) { + std::string filename, + std::vector<EnableTarget> targets) { // Mount a verity fs on the existing tmpfs mount. std::string mount_opts = "lower_path=" + tmpfs_dir; ASSIGN_OR_RETURN_ERRNO(TempPath verity_dir, TempPath::CreateDir()); RETURN_ERROR_IF_SYSCALL_FAIL( mount("", verity_dir.path().c_str(), "verity", 0, mount_opts.c_str())); - // Enable both the file and the directory. + // Enable the file, symlink(if provided) and the directory. ASSIGN_OR_RETURN_ERRNO( auto fd, Open(JoinPath(verity_dir.path(), filename), O_RDONLY, 0777)); RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(fd.get(), FS_IOC_ENABLE_VERITY)); + + for (const EnableTarget& target : targets) { + ASSIGN_OR_RETURN_ERRNO( + auto target_fd, + Open(JoinPath(verity_dir.path(), target.path), target.flags, 0777)); + RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(target_fd.get(), FS_IOC_ENABLE_VERITY)); + } + ASSIGN_OR_RETURN_ERRNO(auto dir_fd, Open(verity_dir.path(), O_RDONLY, 0777)); RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(dir_fd.get(), FS_IOC_ENABLE_VERITY)); diff --git a/test/util/verity_util.h b/test/util/verity_util.h index 18743ecd6..44863f322 100644 --- a/test/util/verity_util.h +++ b/test/util/verity_util.h @@ -17,6 +17,8 @@ #include <stdint.h> +#include <vector> + #include "test/util/posix_error.h" namespace gvisor { @@ -44,6 +46,13 @@ struct fsverity_digest { unsigned char digest[]; }; +struct EnableTarget { + std::string path; + int flags; + + EnableTarget(std::string path, int flags) : path(path), flags(flags) {} +}; + constexpr int kMaxDigestSize = 64; constexpr int kDefaultDigestSize = 32; constexpr char kContents[] = "foobarbaz"; @@ -67,7 +76,8 @@ PosixError FlipRandomBit(int fd, int size); // Mount a verity on the tmpfs and enable both the file and the direcotry. Then // mount a new verity with measured root hash. PosixErrorOr<std::string> MountVerity(std::string tmpfs_dir, - std::string filename); + std::string filename, + std::vector<EnableTarget> targets); } // namespace testing } // namespace gvisor |