summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/benchmarks/fs/BUILD2
-rw-r--r--test/benchmarks/fs/bazel_test.go23
-rw-r--r--test/benchmarks/fs/fio_test.go20
-rw-r--r--test/benchmarks/harness/BUILD1
-rw-r--r--test/benchmarks/harness/util.go34
-rw-r--r--test/packetimpact/runner/defs.bzl12
-rw-r--r--test/packetimpact/runner/dut.go22
-rw-r--r--test/packetimpact/tests/BUILD22
-rw-r--r--test/packetimpact/tests/tcp_outside_the_window_closing_test.go86
-rw-r--r--test/packetimpact/tests/tcp_outside_the_window_test.go72
-rw-r--r--test/packetimpact/tests/tcp_unacc_seq_ack_closing_test.go94
-rw-r--r--test/packetimpact/tests/tcp_unacc_seq_ack_test.go63
-rw-r--r--test/syscalls/linux/BUILD4
-rw-r--r--test/syscalls/linux/read.cc40
-rw-r--r--test/syscalls/linux/setgid.cc95
-rw-r--r--test/syscalls/linux/socket_inet_loopback.cc111
-rw-r--r--test/syscalls/linux/write.cc78
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