diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/benchmarks/fs/BUILD | 2 | ||||
-rw-r--r-- | test/benchmarks/fs/bazel_test.go | 23 | ||||
-rw-r--r-- | test/benchmarks/fs/fio_test.go | 20 | ||||
-rw-r--r-- | test/benchmarks/harness/BUILD | 1 | ||||
-rw-r--r-- | test/benchmarks/harness/util.go | 34 | ||||
-rw-r--r-- | test/packetimpact/runner/defs.bzl | 12 | ||||
-rw-r--r-- | test/packetimpact/runner/dut.go | 22 | ||||
-rw-r--r-- | test/packetimpact/tests/BUILD | 22 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_outside_the_window_closing_test.go | 86 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_outside_the_window_test.go | 72 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_unacc_seq_ack_closing_test.go | 94 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_unacc_seq_ack_test.go | 63 | ||||
-rw-r--r-- | test/syscalls/linux/BUILD | 4 | ||||
-rw-r--r-- | test/syscalls/linux/read.cc | 40 | ||||
-rw-r--r-- | test/syscalls/linux/setgid.cc | 95 | ||||
-rw-r--r-- | test/syscalls/linux/socket_inet_loopback.cc | 111 | ||||
-rw-r--r-- | test/syscalls/linux/write.cc | 78 |
17 files changed, 495 insertions, 284 deletions
diff --git a/test/benchmarks/fs/BUILD b/test/benchmarks/fs/BUILD index c94caab60..dc82e63b2 100644 --- a/test/benchmarks/fs/BUILD +++ b/test/benchmarks/fs/BUILD @@ -8,6 +8,7 @@ benchmark_test( srcs = ["bazel_test.go"], visibility = ["//:sandbox"], deps = [ + "//pkg/cleanup", "//pkg/test/dockerutil", "//test/benchmarks/harness", "//test/benchmarks/tools", @@ -21,6 +22,7 @@ benchmark_test( srcs = ["fio_test.go"], visibility = ["//:sandbox"], deps = [ + "//pkg/cleanup", "//pkg/test/dockerutil", "//test/benchmarks/harness", "//test/benchmarks/tools", diff --git a/test/benchmarks/fs/bazel_test.go b/test/benchmarks/fs/bazel_test.go index 7ced963f6..797b1952d 100644 --- a/test/benchmarks/fs/bazel_test.go +++ b/test/benchmarks/fs/bazel_test.go @@ -20,6 +20,7 @@ import ( "strings" "testing" + "gvisor.dev/gvisor/pkg/cleanup" "gvisor.dev/gvisor/pkg/test/dockerutil" "gvisor.dev/gvisor/test/benchmarks/harness" "gvisor.dev/gvisor/test/benchmarks/tools" @@ -28,8 +29,8 @@ import ( // Dimensions here are clean/dirty cache (do or don't drop caches) // and if the mount on which we are compiling is a tmpfs/bind mount. type benchmark struct { - clearCache bool // clearCache drops caches before running. - fstype string // type of filesystem to use. + clearCache bool // clearCache drops caches before running. + fstype harness.FileSystemType // type of filesystem to use. } // Note: CleanCache versions of this test require running with root permissions. @@ -48,12 +49,12 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) { // Get a machine from the Harness on which to run. machine, err := harness.GetMachine() if err != nil { - b.Fatalf("failed to get machine: %v", err) + b.Fatalf("Failed to get machine: %v", err) } defer machine.CleanUp() benchmarks := make([]benchmark, 0, 6) - for _, filesys := range []string{harness.BindFS, harness.TmpFS, harness.RootFS} { + for _, filesys := range []harness.FileSystemType{harness.BindFS, harness.TmpFS, harness.RootFS} { benchmarks = append(benchmarks, benchmark{ clearCache: true, fstype: filesys, @@ -75,7 +76,7 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) { filesystem := tools.Parameter{ Name: "filesystem", - Value: bm.fstype, + Value: string(bm.fstype), } name, err := tools.ParametersToName(pageCache, filesystem) if err != nil { @@ -86,13 +87,14 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) { // Grab a container. ctx := context.Background() container := machine.GetContainer(ctx, b) - defer container.CleanUp(ctx) - - mts, prefix, cleanup, err := harness.MakeMount(machine, bm.fstype) + cu := cleanup.Make(func() { + container.CleanUp(ctx) + }) + defer cu.Clean() + mts, prefix, err := harness.MakeMount(machine, bm.fstype, &cu) if err != nil { b.Fatalf("Failed to make mount: %v", err) } - defer cleanup() runOpts := dockerutil.RunOpts{ Image: image, @@ -104,8 +106,9 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) { b.Fatalf("run failed with: %v", err) } + cpCmd := fmt.Sprintf("mkdir -p %s && cp -r %s %s/.", prefix, workdir, prefix) if out, err := container.Exec(ctx, dockerutil.ExecOpts{}, - "cp", "-rf", workdir, prefix+"/."); err != nil { + "/bin/sh", "-c", cpCmd); err != nil { b.Fatalf("failed to copy directory: %v (%s)", err, out) } diff --git a/test/benchmarks/fs/fio_test.go b/test/benchmarks/fs/fio_test.go index f783a2b33..1482466f4 100644 --- a/test/benchmarks/fs/fio_test.go +++ b/test/benchmarks/fs/fio_test.go @@ -21,6 +21,7 @@ import ( "strings" "testing" + "gvisor.dev/gvisor/pkg/cleanup" "gvisor.dev/gvisor/pkg/test/dockerutil" "gvisor.dev/gvisor/test/benchmarks/harness" "gvisor.dev/gvisor/test/benchmarks/tools" @@ -69,7 +70,7 @@ func BenchmarkFio(b *testing.B) { } defer machine.CleanUp() - for _, fsType := range []string{harness.BindFS, harness.TmpFS, harness.RootFS} { + for _, fsType := range []harness.FileSystemType{harness.BindFS, harness.TmpFS, harness.RootFS} { for _, tc := range testCases { operation := tools.Parameter{ Name: "operation", @@ -81,7 +82,7 @@ func BenchmarkFio(b *testing.B) { } filesystem := tools.Parameter{ Name: "filesystem", - Value: fsType, + Value: string(fsType), } name, err := tools.ParametersToName(operation, blockSize, filesystem) if err != nil { @@ -90,15 +91,18 @@ func BenchmarkFio(b *testing.B) { b.Run(name, func(b *testing.B) { b.StopTimer() tc.Size = b.N + ctx := context.Background() container := machine.GetContainer(ctx, b) - defer container.CleanUp(ctx) + cu := cleanup.Make(func() { + container.CleanUp(ctx) + }) + defer cu.Clean() - mnts, outdir, mountCleanup, err := harness.MakeMount(machine, fsType) + mnts, outdir, err := harness.MakeMount(machine, fsType, &cu) if err != nil { b.Fatalf("failed to make mount: %v", err) } - defer mountCleanup() // Start the container with the mount. if err := container.Spawn( @@ -112,6 +116,11 @@ func BenchmarkFio(b *testing.B) { b.Fatalf("failed to start fio container with: %v", err) } + if out, err := container.Exec(ctx, dockerutil.ExecOpts{}, + "mkdir", "-p", outdir); err != nil { + b.Fatalf("failed to copy directory: %v (%s)", err, out) + } + // Directory and filename inside container where fio will read/write. outfile := filepath.Join(outdir, "test.txt") @@ -130,7 +139,6 @@ func BenchmarkFio(b *testing.B) { } cmd := tc.MakeCmd(outfile) - if err := harness.DropCaches(machine); err != nil { b.Fatalf("failed to drop caches: %v", err) } diff --git a/test/benchmarks/harness/BUILD b/test/benchmarks/harness/BUILD index 116610938..367316661 100644 --- a/test/benchmarks/harness/BUILD +++ b/test/benchmarks/harness/BUILD @@ -12,6 +12,7 @@ go_library( ], visibility = ["//:sandbox"], deps = [ + "//pkg/cleanup", "//pkg/test/dockerutil", "//pkg/test/testutil", "@com_github_docker_docker//api/types/mount:go_default_library", diff --git a/test/benchmarks/harness/util.go b/test/benchmarks/harness/util.go index 36abe1069..f7e569751 100644 --- a/test/benchmarks/harness/util.go +++ b/test/benchmarks/harness/util.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/docker/docker/api/types/mount" + "gvisor.dev/gvisor/pkg/cleanup" "gvisor.dev/gvisor/pkg/test/dockerutil" "gvisor.dev/gvisor/pkg/test/testutil" ) @@ -58,52 +59,55 @@ func DebugLog(b *testing.B, msg string, args ...interface{}) { } } +// FileSystemType represents a type container mount. +type FileSystemType string + const ( // BindFS indicates a bind mount should be created. - BindFS = "bindfs" + BindFS FileSystemType = "bindfs" // TmpFS indicates a tmpfs mount should be created. - TmpFS = "tmpfs" + TmpFS FileSystemType = "tmpfs" // RootFS indicates no mount should be created and the root mount should be used. - RootFS = "rootfs" + RootFS FileSystemType = "rootfs" ) // MakeMount makes a mount and cleanup based on the requested type. Bind // and volume mounts are backed by a temp directory made with mktemp. // tmpfs mounts require no such backing and are just made. // rootfs mounts do not make a mount, but instead return a target direectory at root. -// It is up to the caller to call the returned cleanup. -func MakeMount(machine Machine, fsType string) ([]mount.Mount, string, func(), error) { +// It is up to the caller to call Clean on the passed *cleanup.Cleanup +func MakeMount(machine Machine, fsType FileSystemType, cu *cleanup.Cleanup) ([]mount.Mount, string, error) { mounts := make([]mount.Mount, 0, 1) + target := "/data" switch fsType { case BindFS: dir, err := machine.RunCommand("mktemp", "-d") if err != nil { - return mounts, "", func() {}, fmt.Errorf("failed to create tempdir: %v", err) + return mounts, "", fmt.Errorf("failed to create tempdir: %v", err) } dir = strings.TrimSuffix(dir, "\n") - + cu.Add(func() { + machine.RunCommand("rm", "-rf", dir) + }) out, err := machine.RunCommand("chmod", "777", dir) if err != nil { - machine.RunCommand("rm", "-rf", dir) - return mounts, "", func() {}, fmt.Errorf("failed modify directory: %v %s", err, out) + return mounts, "", fmt.Errorf("failed modify directory: %v %s", err, out) } - target := "/data" mounts = append(mounts, mount.Mount{ Target: target, Source: dir, Type: mount.TypeBind, }) - return mounts, target, func() { machine.RunCommand("rm", "-rf", dir) }, nil + return mounts, target, nil case RootFS: - return mounts, "/", func() {}, nil + return mounts, target, nil case TmpFS: - target := "/data" mounts = append(mounts, mount.Mount{ Target: target, Type: mount.TypeTmpfs, }) - return mounts, target, func() {}, nil + return mounts, target, nil default: - return mounts, "", func() {}, fmt.Errorf("illegal mount type not supported: %v", fsType) + return mounts, "", fmt.Errorf("illegal mount type not supported: %v", fsType) } } diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl index 567f64c41..34e83ec49 100644 --- a/test/packetimpact/runner/defs.bzl +++ b/test/packetimpact/runner/defs.bzl @@ -203,11 +203,6 @@ ALL_TESTS = [ name = "tcp_outside_the_window", ), PacketimpactTestInfo( - name = "tcp_outside_the_window_closing", - # TODO(b/181625316): Fix netstack then merge into tcp_outside_the_window. - expect_netstack_failure = True, - ), - PacketimpactTestInfo( name = "tcp_noaccept_close_rst", ), PacketimpactTestInfo( @@ -217,11 +212,6 @@ ALL_TESTS = [ name = "tcp_unacc_seq_ack", ), PacketimpactTestInfo( - name = "tcp_unacc_seq_ack_closing", - # TODO(b/181625316): Fix netstack then merge into tcp_unacc_seq_ack. - expect_netstack_failure = True, - ), - PacketimpactTestInfo( name = "tcp_paws_mechanism", # TODO(b/156682000): Fix netstack then remove the line below. expect_netstack_failure = True, @@ -289,8 +279,6 @@ ALL_TESTS = [ ), PacketimpactTestInfo( name = "tcp_fin_retransmission", - # TODO(b/181625316): Fix netstack then remove the line below. - expect_netstack_failure = True, ), ] diff --git a/test/packetimpact/runner/dut.go b/test/packetimpact/runner/dut.go index 1064ca976..b271bd47e 100644 --- a/test/packetimpact/runner/dut.go +++ b/test/packetimpact/runner/dut.go @@ -137,7 +137,7 @@ func setUpDUT(ctx context.Context, t *testing.T, id int, mkDevice func(*dockerut dn := dn t.Cleanup(func() { if err := dn.Cleanup(ctx); err != nil { - t.Errorf("unable to cleanup container %s: %s", dn.Name, err) + t.Errorf("failed to cleanup network %s: %s", dn.Name, err) } }) // Sanity check. @@ -151,13 +151,15 @@ func setUpDUT(ctx context.Context, t *testing.T, id int, mkDevice func(*dockerut info.testNet = testNet // Create the Docker container for the DUT. - var dut DUT + makeContainer := dockerutil.MakeContainer if native { - dut = mkDevice(dockerutil.MakeNativeContainer(ctx, logger(fmt.Sprintf("dut-%d", id)))) - } else { - dut = mkDevice(dockerutil.MakeContainer(ctx, logger(fmt.Sprintf("dut-%d", id)))) + makeContainer = dockerutil.MakeNativeContainer } - info.dut = dut + dutContainer := makeContainer(ctx, logger(fmt.Sprintf("dut-%d", id))) + t.Cleanup(func() { + dutContainer.CleanUp(ctx) + }) + info.dut = mkDevice(dutContainer) runOpts := dockerutil.RunOpts{ Image: "packetimpact", @@ -168,7 +170,7 @@ func setUpDUT(ctx context.Context, t *testing.T, id int, mkDevice func(*dockerut } ipv4PrefixLength, _ := testNet.Subnet.Mask.Size() - remoteIPv6, remoteMAC, dutDeviceID, dutTestNetDev, err := dut.Prepare(ctx, t, runOpts, ctrlNet, testNet) + remoteIPv6, remoteMAC, dutDeviceID, dutTestNetDev, err := info.dut.Prepare(ctx, t, runOpts, ctrlNet, testNet) if err != nil { return dutInfo{}, err } @@ -183,7 +185,7 @@ func setUpDUT(ctx context.Context, t *testing.T, id int, mkDevice func(*dockerut POSIXServerIP: AddressInSubnet(DUTAddr, *ctrlNet.Subnet), POSIXServerPort: CtrlPort, } - info.uname, err = dut.Uname(ctx) + info.uname, err = info.dut.Uname(ctx) if err != nil { return dutInfo{}, fmt.Errorf("failed to get uname information on DUT: %w", err) } @@ -231,6 +233,9 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co // Create the Docker container for the testbench. testbenchContainer := dockerutil.MakeNativeContainer(ctx, logger("testbench")) + t.Cleanup(func() { + testbenchContainer.CleanUp(ctx) + }) runOpts := dockerutil.RunOpts{ Image: "packetimpact", @@ -598,7 +603,6 @@ func createDockerNetwork(ctx context.Context, n *dockerutil.Network) error { func StartContainer(ctx context.Context, runOpts dockerutil.RunOpts, c *dockerutil.Container, containerAddr net.IP, ns []*dockerutil.Network, sysctls map[string]string, cmd ...string) error { conf, hostconf, netconf := c.ConfigsFrom(runOpts, cmd...) _ = netconf - hostconf.AutoRemove = true hostconf.Sysctls = map[string]string{"net.ipv6.conf.all.disable_ipv6": "0"} for k, v := range sysctls { hostconf.Sysctls[k] = v diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD index d5cb0ae06..c0deb33e5 100644 --- a/test/packetimpact/tests/BUILD +++ b/test/packetimpact/tests/BUILD @@ -124,17 +124,6 @@ packetimpact_testbench( ) packetimpact_testbench( - name = "tcp_outside_the_window_closing", - srcs = ["tcp_outside_the_window_closing_test.go"], - deps = [ - "//pkg/tcpip/header", - "//pkg/tcpip/seqnum", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( name = "tcp_noaccept_close_rst", srcs = ["tcp_noaccept_close_rst_test.go"], deps = [ @@ -166,17 +155,6 @@ packetimpact_testbench( ) packetimpact_testbench( - name = "tcp_unacc_seq_ack_closing", - srcs = ["tcp_unacc_seq_ack_closing_test.go"], - deps = [ - "//pkg/tcpip/header", - "//pkg/tcpip/seqnum", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( name = "tcp_paws_mechanism", srcs = ["tcp_paws_mechanism_test.go"], deps = [ diff --git a/test/packetimpact/tests/tcp_outside_the_window_closing_test.go b/test/packetimpact/tests/tcp_outside_the_window_closing_test.go deleted file mode 100644 index 1097746c7..000000000 --- a/test/packetimpact/tests/tcp_outside_the_window_closing_test.go +++ /dev/null @@ -1,86 +0,0 @@ -// 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. - -package tcp_outside_the_window_closing_test - -import ( - "flag" - "fmt" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/seqnum" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestAckOTWSeqInClosing tests that the DUT should send an ACK with -// the right ACK number when receiving a packet with OTW Seq number -// in CLOSING state. https://tools.ietf.org/html/rfc793#page-69 -func TestAckOTWSeqInClosing(t *testing.T) { - for seqNumOffset := seqnum.Size(0); seqNumOffset < 3; seqNumOffset++ { - for _, tt := range []struct { - description string - flags header.TCPFlags - payloads testbench.Layers - }{ - {"SYN", header.TCPFlagSyn, nil}, - {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil}, - {"ACK", header.TCPFlagAck, nil}, - {"FINACK", header.TCPFlagFin | header.TCPFlagAck, nil}, - {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("abc123")}}}, - } { - t.Run(fmt.Sprintf("%s%d", tt.description, seqNumOffset), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - defer dut.Close(t, acceptFD) - - dut.Shutdown(t, acceptFD, unix.SHUT_WR) - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected FINACK from DUT, but got none: %s", err) - } - - // Do not ack the FIN from DUT so that the TCP state on DUT is CLOSING instead of CLOSED. - seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1) - conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected an ACK to our FIN, but got none: %s", err) - } - - windowSize := seqnum.Size(*conn.SynAck(t).WindowSize) + seqNumOffset - conn.SendFrameStateless(t, conn.CreateFrame(t, testbench.Layers{&testbench.TCP{ - SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))), - AckNum: seqNumForTheirFIN, - Flags: testbench.TCPFlags(tt.flags), - }}, tt.payloads...)) - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected an ACK but got none: %s", err) - } - }) - } - } -} diff --git a/test/packetimpact/tests/tcp_outside_the_window_test.go b/test/packetimpact/tests/tcp_outside_the_window_test.go index 7cd7ff703..0523887d9 100644 --- a/test/packetimpact/tests/tcp_outside_the_window_test.go +++ b/test/packetimpact/tests/tcp_outside_the_window_test.go @@ -108,3 +108,75 @@ func TestTCPOutsideTheWindow(t *testing.T) { }) } } + +// TestAckOTWSeqInClosing tests that the DUT should send an ACK with +// the right ACK number when receiving a packet with OTW Seq number +// in CLOSING state. https://tools.ietf.org/html/rfc793#page-69 +func TestAckOTWSeqInClosing(t *testing.T) { + for _, tt := range []struct { + description string + flags header.TCPFlags + payloads testbench.Layers + seqNumOffset seqnum.Size + expectACK bool + }{ + {"SYN", header.TCPFlagSyn, nil, 0, true}, + {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 0, true}, + {"ACK", header.TCPFlagAck, nil, 0, false}, + {"FINACK", header.TCPFlagFin | header.TCPFlagAck, nil, 0, false}, + {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("Sample Data")}}, 0, false}, + + {"SYN", header.TCPFlagSyn, nil, 1, true}, + {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 1, true}, + {"ACK", header.TCPFlagAck, nil, 1, true}, + {"FINACK", header.TCPFlagFin | header.TCPFlagAck, nil, 1, true}, + {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("Sample Data")}}, 1, true}, + + {"SYN", header.TCPFlagSyn, nil, 2, true}, + {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 2, true}, + {"ACK", header.TCPFlagAck, nil, 2, true}, + {"FINACK", header.TCPFlagFin | header.TCPFlagAck, nil, 2, true}, + {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("Sample Data")}}, 2, true}, + } { + t.Run(fmt.Sprintf("%s%d", tt.description, tt.seqNumOffset), func(t *testing.T) { + dut := testbench.NewDUT(t) + listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) + defer dut.Close(t, listenFD) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + defer conn.Close(t) + conn.Connect(t) + acceptFD, _ := dut.Accept(t, listenFD) + defer dut.Close(t, acceptFD) + + dut.Shutdown(t, acceptFD, unix.SHUT_WR) + + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { + t.Fatalf("expected FINACK from DUT, but got none: %s", err) + } + + // Do not ack the FIN from DUT so that the TCP state on DUT is CLOSING instead of CLOSED. + seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1) + conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) + + gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) + if err != nil { + t.Fatalf("expected an ACK to our FIN, but got none: %s", err) + } + + windowSize := seqnum.Size(*gotTCP.WindowSize) + tt.seqNumOffset + conn.SendFrameStateless(t, conn.CreateFrame(t, testbench.Layers{&testbench.TCP{ + SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))), + AckNum: seqNumForTheirFIN, + Flags: testbench.TCPFlags(tt.flags), + }}, tt.payloads...)) + + gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) + if tt.expectACK && err != nil { + t.Errorf("expected an ACK but got none: %s", err) + } + if !tt.expectACK && gotACK != nil { + t.Errorf("expected no ACK but got one: %s", gotACK) + } + }) + } +} diff --git a/test/packetimpact/tests/tcp_unacc_seq_ack_closing_test.go b/test/packetimpact/tests/tcp_unacc_seq_ack_closing_test.go deleted file mode 100644 index a208210ac..000000000 --- a/test/packetimpact/tests/tcp_unacc_seq_ack_closing_test.go +++ /dev/null @@ -1,94 +0,0 @@ -// 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. - -package tcp_unacc_seq_ack_closing_test - -import ( - "flag" - "fmt" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/seqnum" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestSimultaneousCloseUnaccSeqAck(t *testing.T) { - for _, tt := range []struct { - description string - makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP - seqNumOffset seqnum.Size - expectAck bool - }{ - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, expectAck: true}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, expectAck: true}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, expectAck: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, expectAck: false}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, expectAck: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, expectAck: true}, - } { - t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - - // Trigger active close. - dut.Shutdown(t, acceptFD, unix.SHUT_WR) - - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected a FIN: %s", err) - } - // Do not ack the FIN from DUT so that we get to CLOSING. - seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1) - conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected an ACK to our FIN, but got none: %s", err) - } - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - origSeq := uint32(*conn.LocalSeqNum(t)) - // Send a segment with OTW Seq / unacc ACK. - tcp := tt.makeTestingTCP(t, &conn, tt.seqNumOffset, seqnum.Size(*gotTCP.WindowSize)) - if tt.description == "OTWSeq" { - // If we generate an OTW Seq segment, make sure we don't acknowledge their FIN so that - // we stay in CLOSING. - tcp.AckNum = seqNumForTheirFIN - } - conn.Send(t, tcp, samplePayload) - - got, err := conn.Expect(t, testbench.TCP{AckNum: testbench.Uint32(origSeq), Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if tt.expectAck && err != nil { - t.Errorf("expected an ack in CLOSING state, but got none: %s", err) - } - if !tt.expectAck && got != nil { - t.Errorf("expected no ack in CLOSING state, but got one: %s", got) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_unacc_seq_ack_test.go b/test/packetimpact/tests/tcp_unacc_seq_ack_test.go index ce0a26171..389bfc629 100644 --- a/test/packetimpact/tests/tcp_unacc_seq_ack_test.go +++ b/test/packetimpact/tests/tcp_unacc_seq_ack_test.go @@ -209,3 +209,66 @@ func TestActiveCloseUnaccpSeqAck(t *testing.T) { }) } } + +func TestSimultaneousCloseUnaccSeqAck(t *testing.T) { + for _, tt := range []struct { + description string + makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP + seqNumOffset seqnum.Size + expectAck bool + }{ + {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, expectAck: false}, + {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, expectAck: true}, + {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, expectAck: true}, + {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, expectAck: false}, + {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, expectAck: true}, + {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, expectAck: true}, + } { + t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { + dut := testbench.NewDUT(t) + listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) + defer dut.Close(t, listenFD) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + defer conn.Close(t) + + conn.Connect(t) + acceptFD, _ := dut.Accept(t, listenFD) + + // Trigger active close. + dut.Shutdown(t, acceptFD, unix.SHUT_WR) + + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { + t.Fatalf("expected a FIN: %s", err) + } + // Do not ack the FIN from DUT so that we get to CLOSING. + seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1) + conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) + + gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) + if err != nil { + t.Errorf("expected an ACK to our FIN, but got none: %s", err) + } + + sampleData := []byte("Sample Data") + samplePayload := &testbench.Payload{Bytes: sampleData} + + origSeq := uint32(*conn.LocalSeqNum(t)) + // Send a segment with OTW Seq / unacc ACK. + tcp := tt.makeTestingTCP(t, &conn, tt.seqNumOffset, seqnum.Size(*gotTCP.WindowSize)) + if tt.description == "OTWSeq" { + // If we generate an OTW Seq segment, make sure we don't acknowledge their FIN so that + // we stay in CLOSING. + tcp.AckNum = seqNumForTheirFIN + } + conn.Send(t, tcp, samplePayload) + + got, err := conn.Expect(t, testbench.TCP{AckNum: testbench.Uint32(origSeq), Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) + if tt.expectAck && err != nil { + t.Errorf("expected an ack in CLOSING state, but got none: %s", err) + } + if !tt.expectAck && got != nil { + t.Errorf("expected no ack in CLOSING state, but got one: %s", got) + } + }) + } +} diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 4509b5e55..043ada583 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -1922,7 +1922,9 @@ cc_binary( linkstatic = 1, deps = [ "//test/util:file_descriptor", + "@com_google_absl//absl/base:core_headers", gtest, + "//test/util:cleanup", "//test/util:temp_path", "//test/util:test_main", "//test/util:test_util", @@ -2162,6 +2164,7 @@ cc_binary( "//test/util:temp_path", "//test/util:test_main", "//test/util:test_util", + "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/strings", gtest, ], @@ -3990,6 +3993,7 @@ cc_binary( linkstatic = 1, deps = [ "//test/util:cleanup", + "@com_google_absl//absl/base:core_headers", gtest, "//test/util:temp_path", "//test/util:test_main", diff --git a/test/syscalls/linux/read.cc b/test/syscalls/linux/read.cc index 98d5e432d..087262535 100644 --- a/test/syscalls/linux/read.cc +++ b/test/syscalls/linux/read.cc @@ -13,11 +13,14 @@ // limitations under the License. #include <fcntl.h> +#include <sys/mman.h> #include <unistd.h> #include <vector> #include "gtest/gtest.h" +#include "absl/base/macros.h" +#include "test/util/cleanup.h" #include "test/util/file_descriptor.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" @@ -121,6 +124,43 @@ TEST_F(ReadTest, ReadWithOpath) { EXPECT_THAT(ReadFd(fd.get(), buf.data(), 1), SyscallFailsWithErrno(EBADF)); } +// Test that partial writes that hit SIGSEGV are correctly handled and return +// partial write. +TEST_F(ReadTest, PartialReadSIGSEGV) { + // Allocate 2 pages and remove permission from the second. + const size_t size = 2 * kPageSize; + void* addr = + mmap(0, size, PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + ASSERT_NE(addr, MAP_FAILED); + auto cleanup = Cleanup( + [addr, size] { EXPECT_THAT(munmap(addr, size), SyscallSucceeds()); }); + + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(name_.c_str(), O_RDWR, 0666)); + for (size_t i = 0; i < 2; i++) { + EXPECT_THAT(pwrite(fd.get(), addr, size, 0), + SyscallSucceedsWithValue(size)); + } + + void* badAddr = reinterpret_cast<char*>(addr) + kPageSize; + ASSERT_THAT(mprotect(badAddr, kPageSize, PROT_NONE), SyscallSucceeds()); + + // Attempt to read to both pages. Create a non-contiguous iovec pair to + // ensure operation is done in 2 steps. + struct iovec iov[] = { + { + .iov_base = addr, + .iov_len = kPageSize, + }, + { + .iov_base = addr, + .iov_len = size, + }, + }; + EXPECT_THAT(preadv(fd.get(), iov, ABSL_ARRAYSIZE(iov), 0), + SyscallSucceedsWithValue(size)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/setgid.cc b/test/syscalls/linux/setgid.cc index cd030b094..98f8f3dfe 100644 --- a/test/syscalls/linux/setgid.cc +++ b/test/syscalls/linux/setgid.cc @@ -17,6 +17,7 @@ #include <unistd.h> #include "gtest/gtest.h" +#include "absl/flags/flag.h" #include "test/util/capability_util.h" #include "test/util/cleanup.h" #include "test/util/fs_util.h" @@ -24,6 +25,11 @@ #include "test/util/temp_path.h" #include "test/util/test_util.h" +ABSL_FLAG(std::vector<std::string>, groups, std::vector<std::string>({}), + "groups the test can use"); + +constexpr gid_t kNobody = 65534; + namespace gvisor { namespace testing { @@ -46,6 +52,18 @@ PosixErrorOr<Cleanup> Setegid(gid_t egid) { // Returns a pair of groups that the user is a member of. PosixErrorOr<std::pair<gid_t, gid_t>> Groups() { + // Were we explicitly passed GIDs? + std::vector<std::string> flagged_groups = absl::GetFlag(FLAGS_groups); + if (flagged_groups.size() >= 2) { + int group1; + int group2; + if (!absl::SimpleAtoi(flagged_groups[0], &group1) || + !absl::SimpleAtoi(flagged_groups[1], &group2)) { + return PosixError(EINVAL, "failed converting group flags to ints"); + } + return std::pair<gid_t, gid_t>(group1, group2); + } + // See whether the user is a member of at least 2 groups. std::vector<gid_t> groups(64); for (; groups.size() <= NGROUPS_MAX; groups.resize(groups.size() * 2)) { @@ -58,26 +76,47 @@ PosixErrorOr<std::pair<gid_t, gid_t>> Groups() { return PosixError(errno, absl::StrFormat("getgroups(%d, %p)", groups.size(), groups.data())); } - if (ngroups >= 2) { - return std::pair<gid_t, gid_t>(groups[0], groups[1]); + + if (ngroups < 2) { + // There aren't enough groups. + break; + } + + // TODO(b/181878080): Read /proc/sys/fs/overflowgid once it is supported in + // gVisor. + if (groups[0] == kNobody || groups[1] == kNobody) { + // These groups aren't mapped into our user namespace, so we can't use + // them. + break; } - // There aren't enough groups. - break; + return std::pair<gid_t, gid_t>(groups[0], groups[1]); } - // If we're root in the root user namespace, we can set our GID to whatever we - // want. Try that before giving up. - constexpr gid_t kGID1 = 1111; - constexpr gid_t kGID2 = 2222; - auto cleanup1 = Setegid(kGID1); + // If we're running in gVisor and are root in the root user namespace, we can + // set our GID to whatever we want. Try that before giving up. + // + // This won't work in native tests, as despite having CAP_SETGID, the gofer + // process will be sandboxed and unable to change file GIDs. + if (!IsRunningOnGvisor()) { + return PosixError(EPERM, "no valid groups for native testing"); + } + PosixErrorOr<bool> capable = HaveCapability(CAP_SETGID); + if (!capable.ok()) { + return capable.error(); + } + if (!capable.ValueOrDie()) { + return PosixError(EPERM, "missing CAP_SETGID"); + } + gid_t gid = getegid(); + auto cleanup1 = Setegid(gid); if (!cleanup1.ok()) { return cleanup1.error(); } - auto cleanup2 = Setegid(kGID2); + auto cleanup2 = Setegid(kNobody); if (!cleanup2.ok()) { return cleanup2.error(); } - return std::pair<gid_t, gid_t>(kGID1, kGID2); + return std::pair<gid_t, gid_t>(gid, kNobody); } class SetgidDirTest : public ::testing::Test { @@ -85,17 +124,21 @@ class SetgidDirTest : public ::testing::Test { void SetUp() override { original_gid_ = getegid(); - // TODO(b/175325250): Enable when setgid directories are supported. SKIP_IF(IsRunningWithVFS1()); - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETGID))); + // If we can't find two usable groups, we're in an unsupporting environment. + // Skip the test. + PosixErrorOr<std::pair<gid_t, gid_t>> groups = Groups(); + SKIP_IF(!groups.ok()); + groups_ = groups.ValueOrDie(); + + auto cleanup = Setegid(groups_.first); temp_dir_ = ASSERT_NO_ERRNO_AND_VALUE( TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0777 /* mode */)); - groups_ = ASSERT_NO_ERRNO_AND_VALUE(Groups()); } void TearDown() override { - ASSERT_THAT(setegid(original_gid_), SyscallSucceeds()); + EXPECT_THAT(setegid(original_gid_), SyscallSucceeds()); } void MkdirAsGid(gid_t gid, const std::string& path, mode_t mode) { @@ -131,7 +174,7 @@ TEST_F(SetgidDirTest, Control) { ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, 0777)); // Set group to G2, create a file in g1owned, and confirm that G2 owns it. - ASSERT_THAT(setegid(groups_.second), SyscallSucceeds()); + auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(Setegid(groups_.second)); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( Open(JoinPath(g1owned, "g2owned").c_str(), O_CREAT | O_RDWR, 0777)); struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); @@ -146,7 +189,7 @@ TEST_F(SetgidDirTest, CreateFile) { ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeSgid), SyscallSucceeds()); // Set group to G2, create a file, and confirm that G1 owns it. - ASSERT_THAT(setegid(groups_.second), SyscallSucceeds()); + auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(Setegid(groups_.second)); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( Open(JoinPath(g1owned, "g2created").c_str(), O_CREAT | O_RDWR, 0666)); struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); @@ -194,7 +237,7 @@ TEST_F(SetgidDirTest, OldFile) { ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeNoSgid), SyscallSucceeds()); // Set group to G2, create a file, confirm that G2 owns it. - ASSERT_THAT(setegid(groups_.second), SyscallSucceeds()); + auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(Setegid(groups_.second)); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( Open(JoinPath(g1owned, "g2created").c_str(), O_CREAT | O_RDWR, 0666)); struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); @@ -217,7 +260,7 @@ TEST_F(SetgidDirTest, OldDir) { ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeNoSgid), SyscallSucceeds()); // Set group to G2, create a directory, confirm that G2 owns it. - ASSERT_THAT(setegid(groups_.second), SyscallSucceeds()); + auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(Setegid(groups_.second)); auto g2created = JoinPath(g1owned, "g2created"); ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.second, g2created, 0666)); struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g2created)); @@ -306,6 +349,10 @@ class FileModeTest : public ::testing::TestWithParam<FileModeTestcase> {}; TEST_P(FileModeTest, WriteToFile) { SKIP_IF(IsRunningWithVFS1()); + PosixErrorOr<std::pair<gid_t, gid_t>> groups = Groups(); + SKIP_IF(!groups.ok()); + + auto cleanup = Setegid(groups.ValueOrDie().first); auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE( TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0777 /* mode */)); auto path = JoinPath(temp_dir.path(), GetParam().name); @@ -329,26 +376,28 @@ TEST_P(FileModeTest, WriteToFile) { TEST_P(FileModeTest, TruncateFile) { SKIP_IF(IsRunningWithVFS1()); + PosixErrorOr<std::pair<gid_t, gid_t>> groups = Groups(); + SKIP_IF(!groups.ok()); + + auto cleanup = Setegid(groups.ValueOrDie().first); auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE( TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0777 /* mode */)); auto path = JoinPath(temp_dir.path(), GetParam().name); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.c_str(), O_CREAT | O_RDWR, 0666)); - ASSERT_THAT(fchmod(fd.get(), GetParam().mode), SyscallSucceeds()); - struct stat stats; - ASSERT_THAT(fstat(fd.get(), &stats), SyscallSucceeds()); - EXPECT_EQ(stats.st_mode & kDirmodeMask, GetParam().mode); // Write something to the file, as truncating an empty file is a no-op. constexpr char c = 'M'; ASSERT_THAT(write(fd.get(), &c, sizeof(c)), SyscallSucceedsWithValue(sizeof(c))); + ASSERT_THAT(fchmod(fd.get(), GetParam().mode), SyscallSucceeds()); // For security reasons, truncating the file clears the SUID bit, and clears // the SGID bit when the group executable bit is unset (which is not a true // SGID binary). ASSERT_THAT(ftruncate(fd.get(), 0), SyscallSucceeds()); + struct stat stats; ASSERT_THAT(fstat(fd.get(), &stats), SyscallSucceeds()); EXPECT_EQ(stats.st_mode & kDirmodeMask, GetParam().result_mode); } diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc index 54b45b075..597b5bcb1 100644 --- a/test/syscalls/linux/socket_inet_loopback.cc +++ b/test/syscalls/linux/socket_inet_loopback.cc @@ -490,7 +490,11 @@ void TestListenWhileConnect(const TestParam& param, TestAddress const& connector = param.connector; constexpr int kBacklog = 2; - constexpr int kClients = kBacklog + 1; + // Linux completes one more connection than the listen backlog argument. + // To ensure that there is at least one client connection that stays in + // connecting state, keep 2 more client connections than the listen backlog. + // gVisor differs in this behavior though, gvisor.dev/issue/3153. + constexpr int kClients = kBacklog + 2; // Create the listening socket. FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( @@ -527,7 +531,7 @@ void TestListenWhileConnect(const TestParam& param, for (auto& client : clients) { constexpr int kTimeout = 10000; - struct pollfd pfd = { + pollfd pfd = { .fd = client.get(), .events = POLLIN, }; @@ -543,6 +547,10 @@ void TestListenWhileConnect(const TestParam& param, ASSERT_THAT(read(client.get(), &c, sizeof(c)), AnyOf(SyscallFailsWithErrno(ECONNRESET), SyscallFailsWithErrno(ECONNREFUSED))); + // The last client connection would be in connecting (SYN_SENT) state. + if (client.get() == clients[kClients - 1].get()) { + ASSERT_EQ(errno, ECONNREFUSED) << strerror(errno); + } } } @@ -598,7 +606,7 @@ TEST_P(SocketInetLoopbackTest, TCPbacklog_NoRandomSave) { connector.addr_len); if (ret != 0) { EXPECT_THAT(ret, SyscallFailsWithErrno(EINPROGRESS)); - struct pollfd pfd = { + pollfd pfd = { .fd = conn_fd.get(), .events = POLLOUT, }; @@ -623,6 +631,95 @@ TEST_P(SocketInetLoopbackTest, TCPbacklog_NoRandomSave) { } } +// Test if the stack completes atmost listen backlog number of client +// connections. It exercises the path of the stack that enqueues completed +// connections to accept queue vs new incoming SYNs. +TEST_P(SocketInetLoopbackTest, TCPConnectBacklog_NoRandomSave) { + const auto& param = GetParam(); + const TestAddress& listener = param.listener; + const TestAddress& connector = param.connector; + + constexpr int kBacklog = 1; + // Keep the number of client connections more than the listen backlog. + // Linux completes one more connection than the listen backlog argument. + // gVisor differs in this behavior though, gvisor.dev/issue/3153. + int kClients = kBacklog + 2; + if (IsRunningOnGvisor()) { + kClients--; + } + + // Run the following test for few iterations to test race between accept queue + // getting filled with incoming SYNs. + for (int num = 0; num < 10; num++) { + 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(), reinterpret_cast<sockaddr*>(&listen_addr), + listener.addr_len), + SyscallSucceeds()); + ASSERT_THAT(listen(listen_fd.get(), kBacklog), SyscallSucceeds()); + + socklen_t addrlen = listener.addr_len; + ASSERT_THAT( + getsockname(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), + &addrlen), + SyscallSucceeds()); + uint16_t const port = + ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); + sockaddr_storage conn_addr = connector.addr; + ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); + + std::vector<FileDescriptor> clients; + // Issue multiple non-blocking client connects. + for (int i = 0; i < kClients; i++) { + FileDescriptor client = ASSERT_NO_ERRNO_AND_VALUE( + Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); + int ret = connect(client.get(), reinterpret_cast<sockaddr*>(&conn_addr), + connector.addr_len); + if (ret != 0) { + EXPECT_THAT(ret, SyscallFailsWithErrno(EINPROGRESS)); + } + clients.push_back(std::move(client)); + } + + // Now that client connects are issued, wait for the accept queue to get + // filled and ensure no new client connection is completed. + for (int i = 0; i < kClients; i++) { + pollfd pfd = { + .fd = clients[i].get(), + .events = POLLOUT, + }; + if (i < kClients - 1) { + // Poll for client side connection completions with a large timeout. + // We cannot poll on the listener side without calling accept as poll + // stays level triggered with non-zero accept queue length. + // + // Client side poll would not guarantee that the completed connection + // has been enqueued in to the acccept queue, but the fact that the + // listener ACKd the SYN, means that it cannot complete any new incoming + // SYNs when it has already ACKd for > backlog number of SYNs. + ASSERT_THAT(poll(&pfd, 1, 10000), SyscallSucceedsWithValue(1)) + << "num=" << num << " i=" << i << " kClients=" << kClients; + ASSERT_EQ(pfd.revents, POLLOUT) << "num=" << num << " i=" << i; + } else { + // Now that we expect accept queue filled up, ensure that the last + // client connection never completes with a smaller poll timeout. + ASSERT_THAT(poll(&pfd, 1, 1000), SyscallSucceedsWithValue(0)) + << "num=" << num << " i=" << i; + } + + ASSERT_THAT(close(clients[i].release()), SyscallSucceedsWithValue(0)) + << "num=" << num << " i=" << i; + } + clients.clear(); + // We close the listening side and open a new listener. We could instead + // drain the accept queue by calling accept() and reuse the listener, but + // that is racy as the retransmitted SYNs could get ACKd as we make room in + // the accept queue. + ASSERT_THAT(close(listen_fd.release()), SyscallSucceedsWithValue(0)); + } +} + // 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/ @@ -937,7 +1034,7 @@ void setupTimeWaitClose(const TestAddress* listener, ASSERT_THAT(shutdown(active_closefd.get(), SHUT_WR), SyscallSucceeds()); { constexpr int kTimeout = 10000; - struct pollfd pfd = { + pollfd pfd = { .fd = passive_closefd.get(), .events = POLLIN, }; @@ -948,7 +1045,7 @@ void setupTimeWaitClose(const TestAddress* listener, { constexpr int kTimeout = 10000; constexpr int16_t want_events = POLLHUP; - struct pollfd pfd = { + pollfd pfd = { .fd = active_closefd.get(), .events = want_events, }; @@ -1181,7 +1278,7 @@ TEST_P(SocketInetLoopbackTest, TCPAcceptAfterReset) { // Wait for accept_fd to process the RST. constexpr int kTimeout = 10000; - struct pollfd pfd = { + pollfd pfd = { .fd = accept_fd.get(), .events = POLLIN, }; @@ -1705,7 +1802,7 @@ TEST_P(SocketInetReusePortTest, UdpPortReuseMultiThreadShort_NoRandomSave) { SyscallSucceedsWithValue(sizeof(i))); } - struct pollfd pollfds[kThreadCount]; + pollfd pollfds[kThreadCount]; for (int i = 0; i < kThreadCount; i++) { pollfds[i].fd = listener_fds[i].get(); pollfds[i].events = POLLIN; diff --git a/test/syscalls/linux/write.cc b/test/syscalls/linux/write.cc index 740992d0a..3373ba72b 100644 --- a/test/syscalls/linux/write.cc +++ b/test/syscalls/linux/write.cc @@ -15,6 +15,7 @@ #include <errno.h> #include <fcntl.h> #include <signal.h> +#include <sys/mman.h> #include <sys/resource.h> #include <sys/stat.h> #include <sys/types.h> @@ -23,6 +24,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/macros.h" #include "test/util/cleanup.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" @@ -256,6 +258,82 @@ TEST_F(WriteTest, PwriteWithOpath) { SyscallFailsWithErrno(EBADF)); } +// Test that partial writes that hit SIGSEGV are correctly handled and return +// partial write. +TEST_F(WriteTest, PartialWriteSIGSEGV) { + // Allocate 2 pages and remove permission from the second. + const size_t size = 2 * kPageSize; + void* addr = mmap(0, size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + ASSERT_NE(addr, MAP_FAILED); + auto cleanup = Cleanup( + [addr, size] { EXPECT_THAT(munmap(addr, size), SyscallSucceeds()); }); + + void* badAddr = reinterpret_cast<char*>(addr) + kPageSize; + ASSERT_THAT(mprotect(badAddr, kPageSize, PROT_NONE), SyscallSucceeds()); + + TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_WRONLY)); + + // Attempt to write both pages to the file. Create a non-contiguous iovec pair + // to ensure operation is done in 2 steps. + struct iovec iov[] = { + { + .iov_base = addr, + .iov_len = kPageSize, + }, + { + .iov_base = addr, + .iov_len = size, + }, + }; + // Write should succeed for the first iovec and half of the second (=2 pages). + EXPECT_THAT(pwritev(fd.get(), iov, ABSL_ARRAYSIZE(iov), 0), + SyscallSucceedsWithValue(2 * kPageSize)); +} + +// Test that partial writes that hit SIGBUS are correctly handled and return +// partial write. +TEST_F(WriteTest, PartialWriteSIGBUS) { + SKIP_IF(getenv("GVISOR_GOFER_UNCACHED")); // Can't mmap from uncached files. + + TempPath mapfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd_map = + ASSERT_NO_ERRNO_AND_VALUE(Open(mapfile.path().c_str(), O_RDWR)); + + // Let the first page be read to force a partial write. + ASSERT_THAT(ftruncate(fd_map.get(), kPageSize), SyscallSucceeds()); + + // Map 2 pages, one of which is not allocated in the backing file. Reading + // from it will trigger a SIGBUS. + const size_t size = 2 * kPageSize; + void* addr = + mmap(NULL, size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd_map.get(), 0); + ASSERT_NE(addr, MAP_FAILED); + auto cleanup = Cleanup( + [addr, size] { EXPECT_THAT(munmap(addr, size), SyscallSucceeds()); }); + + TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_WRONLY)); + + // Attempt to write both pages to the file. Create a non-contiguous iovec pair + // to ensure operation is done in 2 steps. + struct iovec iov[] = { + { + .iov_base = addr, + .iov_len = kPageSize, + }, + { + .iov_base = addr, + .iov_len = size, + }, + }; + // Write should succeed for the first iovec and half of the second (=2 pages). + ASSERT_THAT(pwritev(fd.get(), iov, ABSL_ARRAYSIZE(iov), 0), + SyscallSucceedsWithValue(2 * kPageSize)); +} + } // namespace } // namespace testing |