summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/packetimpact/runner/defs.bzl3
-rw-r--r--test/packetimpact/testbench/testbench.go5
-rw-r--r--test/packetimpact/tests/BUILD10
-rw-r--r--test/packetimpact/tests/tcp_connect_icmp_error_test.go104
-rw-r--r--test/packetimpact/tests/tcp_zero_receive_window_test.go131
-rw-r--r--test/perf/BUILD2
-rw-r--r--test/root/cgroup_test.go16
-rw-r--r--test/runner/defs.bzl6
-rw-r--r--test/runner/gtest/gtest.go50
-rw-r--r--test/runner/runner.go210
-rw-r--r--test/syscalls/BUILD4
-rw-r--r--test/syscalls/linux/32bit.cc4
-rw-r--r--test/syscalls/linux/BUILD20
-rw-r--r--test/syscalls/linux/cgroup.cc52
-rw-r--r--test/syscalls/linux/chdir.cc4
-rw-r--r--test/syscalls/linux/chmod.cc34
-rw-r--r--test/syscalls/linux/chown.cc8
-rw-r--r--test/syscalls/linux/epoll.cc53
-rw-r--r--test/syscalls/linux/fchdir.cc4
-rw-r--r--test/syscalls/linux/fcntl.cc4
-rw-r--r--test/syscalls/linux/flock.cc4
-rw-r--r--test/syscalls/linux/memory_accounting.cc4
-rw-r--r--test/syscalls/linux/mkdir.cc8
-rw-r--r--test/syscalls/linux/mlock.cc24
-rw-r--r--test/syscalls/linux/mount.cc7
-rw-r--r--test/syscalls/linux/open.cc10
-rw-r--r--test/syscalls/linux/open_create.cc15
-rw-r--r--test/syscalls/linux/prctl.cc6
-rw-r--r--test/syscalls/linux/proc.cc23
-rw-r--r--test/syscalls/linux/ptrace.cc28
-rw-r--r--test/syscalls/linux/pty.cc11
-rw-r--r--test/syscalls/linux/raw_socket_hdrincl.cc6
-rw-r--r--test/syscalls/linux/rename.cc16
-rw-r--r--test/syscalls/linux/rlimits.cc4
-rw-r--r--test/syscalls/linux/semaphore.cc12
-rw-r--r--test/syscalls/linux/socket_capability.cc2
-rw-r--r--test/syscalls/linux/socket_ip_tcp_generic.cc2
-rw-r--r--test/syscalls/linux/sticky.cc8
-rw-r--r--test/syscalls/linux/symlink.cc12
-rw-r--r--test/syscalls/linux/tcp_socket.cc8
-rw-r--r--test/syscalls/linux/truncate.cc4
-rw-r--r--test/syscalls/linux/tuntap.cc15
-rw-r--r--test/syscalls/linux/uname.cc4
-rw-r--r--test/syscalls/linux/unlink.cc8
-rw-r--r--test/syscalls/linux/utimes.cc3
-rw-r--r--test/syscalls/linux/verity_ioctl.cc103
-rw-r--r--test/syscalls/linux/verity_mmap.cc158
-rw-r--r--test/syscalls/linux/xattr.cc14
-rw-r--r--test/util/BUILD25
-rw-r--r--test/util/cgroup_util.cc14
-rw-r--r--test/util/cgroup_util.h2
-rw-r--r--test/util/mount_util.cc32
-rw-r--r--test/util/mount_util.h10
-rw-r--r--test/util/mount_util_test.cc47
-rw-r--r--test/util/test_util.h9
-rw-r--r--test/util/verity_util.cc93
-rw-r--r--test/util/verity_util.h75
57 files changed, 1106 insertions, 444 deletions
diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl
index 634c15727..afe73a69a 100644
--- a/test/packetimpact/runner/defs.bzl
+++ b/test/packetimpact/runner/defs.bzl
@@ -252,6 +252,9 @@ ALL_TESTS = [
name = "tcp_syncookie",
),
PacketimpactTestInfo(
+ name = "tcp_connect_icmp_error",
+ ),
+ PacketimpactTestInfo(
name = "icmpv6_param_problem",
),
PacketimpactTestInfo(
diff --git a/test/packetimpact/testbench/testbench.go b/test/packetimpact/testbench/testbench.go
index a73c07e64..37d02365a 100644
--- a/test/packetimpact/testbench/testbench.go
+++ b/test/packetimpact/testbench/testbench.go
@@ -57,6 +57,11 @@ type DUTUname struct {
OperatingSystem string
}
+// IsLinux returns true if we are running natively on Linux.
+func (n *DUTUname) IsLinux() bool {
+ return Native && n.OperatingSystem == "GNU/Linux"
+}
+
// DUTTestNet describes the test network setup on dut and how the testbench
// should connect with an existing DUT.
type DUTTestNet struct {
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
index e015c1f0e..c4fe293e0 100644
--- a/test/packetimpact/tests/BUILD
+++ b/test/packetimpact/tests/BUILD
@@ -405,6 +405,16 @@ packetimpact_testbench(
],
)
+packetimpact_testbench(
+ name = "tcp_connect_icmp_error",
+ srcs = ["tcp_connect_icmp_error_test.go"],
+ deps = [
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
validate_all_tests()
[packetimpact_go_test(
diff --git a/test/packetimpact/tests/tcp_connect_icmp_error_test.go b/test/packetimpact/tests/tcp_connect_icmp_error_test.go
new file mode 100644
index 000000000..79bfe9eb7
--- /dev/null
+++ b/test/packetimpact/tests/tcp_connect_icmp_error_test.go
@@ -0,0 +1,104 @@
+// 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_connect_icmp_error_test
+
+import (
+ "context"
+ "flag"
+ "sync"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.Initialize(flag.CommandLine)
+}
+
+func sendICMPError(t *testing.T, conn *testbench.TCPIPv4, tcp *testbench.TCP) {
+ t.Helper()
+
+ layers := conn.CreateFrame(t, nil)
+ layers = layers[:len(layers)-1]
+ ip, ok := tcp.Prev().(*testbench.IPv4)
+ if !ok {
+ t.Fatalf("expected %s to be IPv4", tcp.Prev())
+ }
+ icmpErr := &testbench.ICMPv4{
+ Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable),
+ Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable)}
+
+ layers = append(layers, icmpErr, ip, tcp)
+ conn.SendFrameStateless(t, layers)
+}
+
+// TestTCPConnectICMPError tests for the handshake to fail and the socket state
+// cleaned up on receiving an ICMP error.
+func TestTCPConnectICMPError(t *testing.T) {
+ dut := testbench.NewDUT(t)
+
+ clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4)
+ port := uint16(9001)
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port})
+ defer conn.Close(t)
+ sa := unix.SockaddrInet4{Port: int(port)}
+ copy(sa.Addr[:], dut.Net.LocalIPv4)
+ // Bring the dut to SYN-SENT state with a non-blocking connect.
+ dut.Connect(t, clientFD, &sa)
+ tcp, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second)
+ if err != nil {
+ t.Fatalf("expected SYN, %s", err)
+ }
+
+ done := make(chan bool)
+ defer close(done)
+ var wg sync.WaitGroup
+ defer wg.Wait()
+ wg.Add(1)
+ var block sync.WaitGroup
+ block.Add(1)
+ go func() {
+ defer wg.Done()
+ _, cancel := context.WithTimeout(context.Background(), time.Second*3)
+ defer cancel()
+
+ block.Done()
+ for {
+ select {
+ case <-done:
+ return
+ default:
+ if errno := dut.GetSockOptInt(t, clientFD, unix.SOL_SOCKET, unix.SO_ERROR); errno != 0 {
+ return
+ }
+ }
+ }
+ }()
+ block.Wait()
+
+ sendICMPError(t, &conn, tcp)
+
+ dut.PollOne(t, clientFD, unix.POLLHUP, time.Second)
+
+ conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
+ // The DUT should reply with RST to our ACK as the state should have
+ // transitioned to CLOSED because of handshake error.
+ if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, time.Second); err != nil {
+ t.Fatalf("expected RST, %s", err)
+ }
+}
diff --git a/test/packetimpact/tests/tcp_zero_receive_window_test.go b/test/packetimpact/tests/tcp_zero_receive_window_test.go
index d73495454..bd33a2a03 100644
--- a/test/packetimpact/tests/tcp_zero_receive_window_test.go
+++ b/test/packetimpact/tests/tcp_zero_receive_window_test.go
@@ -45,37 +45,114 @@ func TestZeroReceiveWindow(t *testing.T) {
dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
- samplePayload := &testbench.Payload{Bytes: testbench.GenerateRandomPayload(t, payloadLen)}
- // Expect the DUT to eventually advertise zero receive window.
- // The test would timeout otherwise.
- for readOnce := false; ; {
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
- gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
- if err != nil {
- t.Fatalf("expected packet was not received: %s", err)
- }
- // Read once to trigger the subsequent window update from the
- // DUT to grow the right edge of the receive window from what
- // was advertised in the SYN-ACK. This ensures that we test
- // for the full default buffer size (1MB on gVisor at the time
- // of writing this comment), thus testing for cases when the
- // scaled receive window size ends up > 65535 (0xffff).
- if !readOnce {
- if got := dut.Recv(t, acceptFd, int32(payloadLen), 0); len(got) != payloadLen {
- t.Fatalf("got dut.Recv(t, %d, %d, 0) = %d, want %d", acceptFd, payloadLen, len(got), payloadLen)
- }
- readOnce = true
- }
- windowSize := *gotTCP.WindowSize
- t.Logf("got window size = %d", windowSize)
- if windowSize == 0 {
- break
- }
- }
+ fillRecvBuffer(t, &conn, &dut, acceptFd, payloadLen)
})
}
}
+func fillRecvBuffer(t *testing.T, conn *testbench.TCPIPv4, dut *testbench.DUT, acceptFd int32, payloadLen int) {
+ // Expect the DUT to eventually advertise zero receive window.
+ // The test would timeout otherwise.
+ for readOnce := false; ; {
+ samplePayload := &testbench.Payload{Bytes: testbench.GenerateRandomPayload(t, payloadLen)}
+ conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
+ gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
+ if err != nil {
+ t.Fatalf("expected packet was not received: %s", err)
+ }
+ // Read once to trigger the subsequent window update from the
+ // DUT to grow the right edge of the receive window from what
+ // was advertised in the SYN-ACK. This ensures that we test
+ // for the full default buffer size (1MB on gVisor at the time
+ // of writing this comment), thus testing for cases when the
+ // scaled receive window size ends up > 65535 (0xffff).
+ if !readOnce {
+ if got := dut.Recv(t, acceptFd, int32(payloadLen), 0); len(got) != payloadLen {
+ t.Fatalf("got dut.Recv(t, %d, %d, 0) = %d, want %d", acceptFd, payloadLen, len(got), payloadLen)
+ }
+ readOnce = true
+ }
+ windowSize := *gotTCP.WindowSize
+ t.Logf("got window size = %d", windowSize)
+ if windowSize == 0 {
+ break
+ }
+ if payloadLen > int(windowSize) {
+ payloadLen = int(windowSize)
+ }
+ }
+}
+
+func TestZeroToNonZeroWindowUpdate(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.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)})
+ synAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second)
+ if err != nil {
+ t.Fatalf("didn't get synack during handshake: %s", err)
+ }
+ conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
+
+ acceptFd, _ := dut.Accept(t, listenFd)
+ defer dut.Close(t, acceptFd)
+
+ dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
+
+ mss := header.ParseSynOptions(synAck.Options, true).MSS
+ fillRecvBuffer(t, &conn, &dut, acceptFd, int(mss))
+
+ // Read < mss worth of data from the receive buffer and expect the DUT to
+ // not send a non-zero window update.
+ payloadLen := mss - 1
+ if got := dut.Recv(t, acceptFd, int32(payloadLen), 0); len(got) != int(payloadLen) {
+ t.Fatalf("got dut.Recv(t, %d, %d, 0) = %d, want %d", acceptFd, payloadLen, len(got), payloadLen)
+ }
+ // Send a zero-window-probe to force an ACK from the receiver with any
+ // window updates.
+ conn.Send(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(*conn.LocalSeqNum(t) - 1)), Flags: testbench.TCPFlags(header.TCPFlagAck)})
+ gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
+ if err != nil {
+ t.Fatalf("expected packet was not received: %s", err)
+ }
+ if windowSize := *gotTCP.WindowSize; windowSize != 0 {
+ t.Fatalf("got non zero window = %d", windowSize)
+ }
+
+ // Now, ensure that the DUT eventually sends non-zero window update.
+ seqNum := testbench.Uint32(uint32(*conn.LocalSeqNum(t) - 1))
+ ackNum := testbench.Uint32(uint32(*conn.LocalSeqNum(t)))
+ recvCheckWindowUpdate := func(readLen int) uint16 {
+ if got := dut.Recv(t, acceptFd, int32(readLen), 0); len(got) != readLen {
+ t.Fatalf("got dut.Recv(t, %d, %d, 0) = %d, want %d", acceptFd, readLen, len(got), readLen)
+ }
+ conn.Send(t, testbench.TCP{SeqNum: seqNum, Flags: testbench.TCPFlags(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: make([]byte, 1)})
+ gotTCP, err := conn.Expect(t, testbench.TCP{AckNum: ackNum, Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second)
+ if err != nil {
+ t.Fatalf("expected packet was not received: %s", err)
+ }
+ return *gotTCP.WindowSize
+ }
+
+ if !dut.Uname.IsLinux() {
+ if win := recvCheckWindowUpdate(1); win == 0 {
+ t.Fatal("expected non-zero window update")
+ }
+ } else {
+ // Linux stack takes additional socket reads to send out window update,
+ // its a function of sysctl_tcp_rmem among other things.
+ // https://github.com/torvalds/linux/blob/7acac4b3196/net/ipv4/tcp_input.c#L687
+ for {
+ if win := recvCheckWindowUpdate(int(payloadLen)); win != 0 {
+ break
+ }
+ }
+ }
+}
+
// TestNonZeroReceiveWindow tests for the DUT to never send a zero receive
// window when the data is being read from the socket buffer.
func TestNonZeroReceiveWindow(t *testing.T) {
diff --git a/test/perf/BUILD b/test/perf/BUILD
index 71982fc4d..75b5003e2 100644
--- a/test/perf/BUILD
+++ b/test/perf/BUILD
@@ -1,3 +1,4 @@
+load("//tools:defs.bzl", "more_shards")
load("//test/runner:defs.bzl", "syscall_test")
package(licenses = ["notice"])
@@ -37,6 +38,7 @@ syscall_test(
syscall_test(
size = "large",
debug = False,
+ shard_count = more_shards,
tags = ["nogotsan"],
test = "//test/perf/linux:getdents_benchmark",
)
diff --git a/test/root/cgroup_test.go b/test/root/cgroup_test.go
index a74d6b1c1..39e838582 100644
--- a/test/root/cgroup_test.go
+++ b/test/root/cgroup_test.go
@@ -308,8 +308,8 @@ func TestCgroup(t *testing.T) {
}
}
-// TestCgroupParent sets the "CgroupParent" option and checks that the child and parent's
-// cgroups are created correctly relative to each other.
+// TestCgroupParent sets the "CgroupParent" option and checks that the child and
+// parent's cgroups are created correctly relative to each other.
func TestCgroupParent(t *testing.T) {
ctx := context.Background()
d := dockerutil.MakeContainer(ctx, t)
@@ -343,15 +343,19 @@ func TestCgroupParent(t *testing.T) {
// Finds cgroup for the sandbox's parent process to check that cgroup is
// created in the right location relative to the parent.
cmd := fmt.Sprintf("grep PPid: /proc/%d/status | sed 's/PPid:\\s//'", pid)
- ppid, err := exec.Command("bash", "-c", cmd).CombinedOutput()
+ ppidStr, err := exec.Command("bash", "-c", cmd).CombinedOutput()
if err != nil {
t.Fatalf("Executing %q: %v", cmd, err)
}
- cgroups, err := cgroup.LoadPaths(strings.TrimSpace(string(ppid)))
+ ppid, err := strconv.Atoi(strings.TrimSpace(string(ppidStr)))
if err != nil {
- t.Fatalf("cgroup.LoadPath(%s): %v", ppid, err)
+ t.Fatalf("invalid PID (%s): %v", ppidStr, err)
}
- path := filepath.Join("/sys/fs/cgroup/memory", cgroups["memory"], parent, gid, "cgroup.procs")
+ cgroups, err := cgroup.NewFromPid(ppid)
+ if err != nil {
+ t.Fatalf("cgroup.NewFromPid(%d): %v", ppid, err)
+ }
+ path := filepath.Join(cgroups.MakePath("cpuacct"), parent, gid, "cgroup.procs")
if err := verifyPid(pid, path); err != nil {
t.Errorf("cgroup control %q processes: %v", "memory", err)
}
diff --git a/test/runner/defs.bzl b/test/runner/defs.bzl
index 2a0ef2cec..416f51935 100644
--- a/test/runner/defs.bzl
+++ b/test/runner/defs.bzl
@@ -88,6 +88,12 @@ def _syscall_test(
tags = list(tags)
tags += [full_platform, "file_" + file_access]
+ # Hash this target into one of 15 buckets. This can be used to
+ # randomly split targets between different workflows.
+ hash15 = hash(native.package_name() + name) % 15
+ tags.append("hash15:" + str(hash15))
+ tags.append("hash15")
+
# Disable off-host networking.
tags.append("requires-net:loopback")
tags.append("requires-net:ipv4")
diff --git a/test/runner/gtest/gtest.go b/test/runner/gtest/gtest.go
index 2ad5f58ef..38e57d62f 100644
--- a/test/runner/gtest/gtest.go
+++ b/test/runner/gtest/gtest.go
@@ -35,39 +35,6 @@ var (
filterBenchmarkFlag = "--benchmark_filter"
)
-// BuildTestArgs builds arguments to be passed to the test binary to execute
-// only the test cases in `indices`.
-func BuildTestArgs(indices []int, testCases []TestCase) []string {
- var testFilter, benchFilter string
- for _, tci := range indices {
- tc := testCases[tci]
- if tc.all {
- // No argument will make all tests run.
- return nil
- }
- if tc.benchmark {
- if len(benchFilter) > 0 {
- benchFilter += "|"
- }
- benchFilter += "^" + tc.Name + "$"
- } else {
- if len(testFilter) > 0 {
- testFilter += ":"
- }
- testFilter += tc.FullName()
- }
- }
-
- var args []string
- if len(testFilter) > 0 {
- args = append(args, fmt.Sprintf("%s=%s", filterTestFlag, testFilter))
- }
- if len(benchFilter) > 0 {
- args = append(args, fmt.Sprintf("%s=%s", filterBenchmarkFlag, benchFilter))
- }
- return args
-}
-
// TestCase is a single gtest test case.
type TestCase struct {
// Suite is the suite for this test.
@@ -92,6 +59,22 @@ func (tc TestCase) FullName() string {
return fmt.Sprintf("%s.%s", tc.Suite, tc.Name)
}
+// Args returns arguments to be passed when invoking the test.
+func (tc TestCase) Args() []string {
+ if tc.all {
+ return []string{} // No arguments.
+ }
+ if tc.benchmark {
+ return []string{
+ fmt.Sprintf("%s=^%s$", filterBenchmarkFlag, tc.Name),
+ fmt.Sprintf("%s=", filterTestFlag),
+ }
+ }
+ return []string{
+ fmt.Sprintf("%s=%s", filterTestFlag, tc.FullName()),
+ }
+}
+
// ParseTestCases calls a gtest test binary to list its test and returns a
// slice with the name and suite of each test.
//
@@ -107,7 +90,6 @@ func ParseTestCases(testBin string, benchmarks bool, extraArgs ...string) ([]Tes
// We failed to list tests with the given flags. Just
// return something that will run the binary with no
// flags, which should execute all tests.
- fmt.Printf("failed to get test list: %v\n", err)
return []TestCase{
{
Suite: "Default",
diff --git a/test/runner/runner.go b/test/runner/runner.go
index d314a5036..7e8e88ba2 100644
--- a/test/runner/runner.go
+++ b/test/runner/runner.go
@@ -26,6 +26,7 @@ import (
"path/filepath"
"strings"
"syscall"
+ "testing"
"time"
specs "github.com/opencontainers/runtime-spec/specs-go"
@@ -56,82 +57,13 @@ var (
leakCheck = flag.Bool("leak-check", false, "check for reference leaks")
)
-func main() {
- flag.Parse()
- if flag.NArg() != 1 {
- fatalf("test must be provided")
- }
-
- log.SetLevel(log.Info)
- if *debug {
- log.SetLevel(log.Debug)
- }
-
- if *platform != "native" && *runscPath == "" {
- if err := testutil.ConfigureExePath(); err != nil {
- panic(err.Error())
- }
- *runscPath = specutils.ExePath
- }
-
- // Make sure stdout and stderr are opened with O_APPEND, otherwise logs
- // from outside the sandbox can (and will) stomp on logs from inside
- // the sandbox.
- for _, f := range []*os.File{os.Stdout, os.Stderr} {
- flags, err := unix.FcntlInt(f.Fd(), unix.F_GETFL, 0)
- if err != nil {
- fatalf("error getting file flags for %v: %v", f, err)
- }
- if flags&unix.O_APPEND == 0 {
- flags |= unix.O_APPEND
- if _, err := unix.FcntlInt(f.Fd(), unix.F_SETFL, flags); err != nil {
- fatalf("error setting file flags for %v: %v", f, err)
- }
- }
- }
-
- // Resolve the absolute path for the binary.
- testBin, err := filepath.Abs(flag.Args()[0])
- if err != nil {
- fatalf("Abs(%q) failed: %v", flag.Args()[0], err)
- }
-
- // Get all test cases in each binary.
- testCases, err := gtest.ParseTestCases(testBin, true)
- if err != nil {
- fatalf("ParseTestCases(%q) failed: %v", testBin, err)
- }
-
- // Get subset of tests corresponding to shard.
- indices, err := testutil.TestIndicesForShard(len(testCases))
- if err != nil {
- fatalf("TestsForShard() failed: %v", err)
- }
- if len(indices) == 0 {
- log.Warningf("No tests to run in this shard")
- return
- }
- args := gtest.BuildTestArgs(indices, testCases)
-
- switch *platform {
- case "native":
- if err := runTestCaseNative(testBin, args); err != nil {
- fatalf(err.Error())
- }
- default:
- if err := runTestCaseRunsc(testBin, args); err != nil {
- fatalf(err.Error())
- }
- }
-}
-
// runTestCaseNative runs the test case directly on the host machine.
-func runTestCaseNative(testBin string, args []string) error {
+func runTestCaseNative(testBin string, tc gtest.TestCase, t *testing.T) {
// These tests might be running in parallel, so make sure they have a
// unique test temp dir.
tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "")
if err != nil {
- return fmt.Errorf("could not create temp dir: %v", err)
+ t.Fatalf("could not create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
@@ -152,12 +84,12 @@ func runTestCaseNative(testBin string, args []string) error {
}
// Remove shard env variables so that the gunit binary does not try to
// interpret them.
- env = filterEnv(env, "TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS")
+ env = filterEnv(env, []string{"TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS"})
if *addUDSTree {
socketDir, cleanup, err := uds.CreateSocketTree("/tmp")
if err != nil {
- return fmt.Errorf("failed to create socket tree: %v", err)
+ t.Fatalf("failed to create socket tree: %v", err)
}
defer cleanup()
@@ -167,7 +99,7 @@ func runTestCaseNative(testBin string, args []string) error {
env = append(env, "TEST_UDS_ATTACH_TREE="+socketDir)
}
- cmd := exec.Command(testBin, args...)
+ cmd := exec.Command(testBin, tc.Args()...)
cmd.Env = env
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -183,9 +115,8 @@ func runTestCaseNative(testBin string, args []string) error {
if err := cmd.Run(); err != nil {
ws := err.(*exec.ExitError).Sys().(syscall.WaitStatus)
- return fmt.Errorf("test exited with status %d, want 0", ws.ExitStatus())
+ t.Errorf("test %q exited with status %d, want 0", tc.FullName(), ws.ExitStatus())
}
- return nil
}
// runRunsc runs spec in runsc in a standard test configuration.
@@ -193,7 +124,7 @@ func runTestCaseNative(testBin string, args []string) error {
// runsc logs will be saved to a path in TEST_UNDECLARED_OUTPUTS_DIR.
//
// Returns an error if the sandboxed application exits non-zero.
-func runRunsc(spec *specs.Spec) error {
+func runRunsc(tc gtest.TestCase, spec *specs.Spec) error {
bundleDir, cleanup, err := testutil.SetupBundleDir(spec)
if err != nil {
return fmt.Errorf("SetupBundleDir failed: %v", err)
@@ -206,8 +137,9 @@ func runRunsc(spec *specs.Spec) error {
}
defer cleanup()
+ name := tc.FullName()
id := testutil.RandomContainerID()
- log.Infof("Running test in container %q", id)
+ log.Infof("Running test %q in container %q", name, id)
specutils.LogSpec(spec)
args := []string{
@@ -243,8 +175,13 @@ func runRunsc(spec *specs.Spec) error {
args = append(args, "-ref-leak-mode=log-names")
}
- testLogDir := os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR")
- if len(testLogDir) > 0 {
+ testLogDir := ""
+ if undeclaredOutputsDir, ok := unix.Getenv("TEST_UNDECLARED_OUTPUTS_DIR"); ok {
+ // Create log directory dedicated for this test.
+ testLogDir = filepath.Join(undeclaredOutputsDir, strings.Replace(name, "/", "_", -1))
+ if err := os.MkdirAll(testLogDir, 0755); err != nil {
+ return fmt.Errorf("could not create test dir: %v", err)
+ }
debugLogDir, err := ioutil.TempDir(testLogDir, "runsc")
if err != nil {
return fmt.Errorf("could not create temp dir: %v", err)
@@ -290,7 +227,7 @@ func runRunsc(spec *specs.Spec) error {
if !ok {
return
}
- log.Warningf("Got signal: %v", s)
+ log.Warningf("%s: Got signal: %v", name, s)
done := make(chan bool, 1)
dArgs := append([]string{}, args...)
dArgs = append(dArgs, "-alsologtostderr=true", "debug", "--stacks", id)
@@ -323,7 +260,7 @@ func runRunsc(spec *specs.Spec) error {
if err == nil && len(testLogDir) > 0 {
// If the test passed, then we erase the log directory. This speeds up
// uploading logs in continuous integration & saves on disk space.
- _ = os.RemoveAll(testLogDir)
+ os.RemoveAll(testLogDir)
}
return err
@@ -378,10 +315,10 @@ func setupUDSTree(spec *specs.Spec) (cleanup func(), err error) {
}
// runsTestCaseRunsc runs the test case in runsc.
-func runTestCaseRunsc(testBin string, args []string) error {
+func runTestCaseRunsc(testBin string, tc gtest.TestCase, t *testing.T) {
// Run a new container with the test executable and filter for the
// given test suite and name.
- spec := testutil.NewSpecWithArgs(append([]string{testBin}, args...)...)
+ spec := testutil.NewSpecWithArgs(append([]string{testBin}, tc.Args()...)...)
// Mark the root as writeable, as some tests attempt to
// write to the rootfs, and expect EACCES, not EROFS.
@@ -407,12 +344,12 @@ func runTestCaseRunsc(testBin string, args []string) error {
// users, so make sure it is world-accessible.
tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "")
if err != nil {
- return fmt.Errorf("could not create temp dir: %v", err)
+ t.Fatalf("could not create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
if err := os.Chmod(tmpDir, 0777); err != nil {
- return fmt.Errorf("could not chmod temp dir: %v", err)
+ t.Fatalf("could not chmod temp dir: %v", err)
}
// "/tmp" is not replaced with a tmpfs mount inside the sandbox
@@ -432,12 +369,13 @@ func runTestCaseRunsc(testBin string, args []string) error {
// Set environment variables that indicate we are running in gVisor with
// the given platform, network, and filesystem stack.
- env := []string{"TEST_ON_GVISOR=" + *platform, "GVISOR_NETWORK=" + *network}
- env = append(env, os.Environ()...)
- const vfsVar = "GVISOR_VFS"
+ platformVar := "TEST_ON_GVISOR"
+ networkVar := "GVISOR_NETWORK"
+ env := append(os.Environ(), platformVar+"="+*platform, networkVar+"="+*network)
+ vfsVar := "GVISOR_VFS"
if *vfs2 {
env = append(env, vfsVar+"=VFS2")
- const fuseVar = "FUSE_ENABLED"
+ fuseVar := "FUSE_ENABLED"
if *fuse {
env = append(env, fuseVar+"=TRUE")
} else {
@@ -449,11 +387,11 @@ func runTestCaseRunsc(testBin string, args []string) error {
// Remove shard env variables so that the gunit binary does not try to
// interpret them.
- env = filterEnv(env, "TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS")
+ env = filterEnv(env, []string{"TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS"})
// Set TEST_TMPDIR to /tmp, as some of the syscall tests require it to
// be backed by tmpfs.
- env = filterEnv(env, "TEST_TMPDIR")
+ env = filterEnv(env, []string{"TEST_TMPDIR"})
env = append(env, fmt.Sprintf("TEST_TMPDIR=%s", testTmpDir))
spec.Process.Env = env
@@ -461,19 +399,18 @@ func runTestCaseRunsc(testBin string, args []string) error {
if *addUDSTree {
cleanup, err := setupUDSTree(spec)
if err != nil {
- return fmt.Errorf("error creating UDS tree: %v", err)
+ t.Fatalf("error creating UDS tree: %v", err)
}
defer cleanup()
}
- if err := runRunsc(spec); err != nil {
- return fmt.Errorf("test failed with error %v, want nil", err)
+ if err := runRunsc(tc, spec); err != nil {
+ t.Errorf("test %q failed with error %v, want nil", tc.FullName(), err)
}
- return nil
}
// filterEnv returns an environment with the excluded variables removed.
-func filterEnv(env []string, exclude ...string) []string {
+func filterEnv(env, exclude []string) []string {
var out []string
for _, kv := range env {
ok := true
@@ -494,3 +431,82 @@ func fatalf(s string, args ...interface{}) {
fmt.Fprintf(os.Stderr, s+"\n", args...)
os.Exit(1)
}
+
+func matchString(a, b string) (bool, error) {
+ return a == b, nil
+}
+
+func main() {
+ flag.Parse()
+ if flag.NArg() != 1 {
+ fatalf("test must be provided")
+ }
+ testBin := flag.Args()[0] // Only argument.
+
+ log.SetLevel(log.Info)
+ if *debug {
+ log.SetLevel(log.Debug)
+ }
+
+ if *platform != "native" && *runscPath == "" {
+ if err := testutil.ConfigureExePath(); err != nil {
+ panic(err.Error())
+ }
+ *runscPath = specutils.ExePath
+ }
+
+ // Make sure stdout and stderr are opened with O_APPEND, otherwise logs
+ // from outside the sandbox can (and will) stomp on logs from inside
+ // the sandbox.
+ for _, f := range []*os.File{os.Stdout, os.Stderr} {
+ flags, err := unix.FcntlInt(f.Fd(), unix.F_GETFL, 0)
+ if err != nil {
+ fatalf("error getting file flags for %v: %v", f, err)
+ }
+ if flags&unix.O_APPEND == 0 {
+ flags |= unix.O_APPEND
+ if _, err := unix.FcntlInt(f.Fd(), unix.F_SETFL, flags); err != nil {
+ fatalf("error setting file flags for %v: %v", f, err)
+ }
+ }
+ }
+
+ // Get all test cases in each binary.
+ testCases, err := gtest.ParseTestCases(testBin, true)
+ if err != nil {
+ fatalf("ParseTestCases(%q) failed: %v", testBin, err)
+ }
+
+ // Get subset of tests corresponding to shard.
+ indices, err := testutil.TestIndicesForShard(len(testCases))
+ if err != nil {
+ fatalf("TestsForShard() failed: %v", err)
+ }
+
+ // Resolve the absolute path for the binary.
+ testBin, err = filepath.Abs(testBin)
+ if err != nil {
+ fatalf("Abs() failed: %v", err)
+ }
+
+ // Run the tests.
+ var tests []testing.InternalTest
+ for _, tci := range indices {
+ // Capture tc.
+ tc := testCases[tci]
+ tests = append(tests, testing.InternalTest{
+ Name: fmt.Sprintf("%s_%s", tc.Suite, tc.Name),
+ F: func(t *testing.T) {
+ if *platform == "native" {
+ // Run the test case on host.
+ runTestCaseNative(testBin, tc, t)
+ } else {
+ // Run the test case in runsc.
+ runTestCaseRunsc(testBin, tc, t)
+ }
+ },
+ })
+ }
+
+ testing.Main(matchString, tests, nil, nil)
+}
diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD
index 0435f61a2..85412f54b 100644
--- a/test/syscalls/BUILD
+++ b/test/syscalls/BUILD
@@ -313,6 +313,10 @@ syscall_test(
)
syscall_test(
+ test = "//test/syscalls/linux:verity_mmap_test",
+)
+
+syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:mount_test",
)
diff --git a/test/syscalls/linux/32bit.cc b/test/syscalls/linux/32bit.cc
index 6080a59b7..cbf1b4f05 100644
--- a/test/syscalls/linux/32bit.cc
+++ b/test/syscalls/linux/32bit.cc
@@ -240,9 +240,9 @@ TEST(Call32Bit, Disallowed) {
}
}
-} // namespace
-
#endif
+} // namespace
+
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
index 1ff001426..c7d0f9036 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -1024,6 +1024,7 @@ cc_binary(
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
+ "//test/util:verity_util",
],
)
@@ -1294,6 +1295,23 @@ cc_binary(
)
cc_binary(
+ name = "verity_mmap_test",
+ testonly = 1,
+ srcs = ["verity_mmap.cc"],
+ linkstatic = 1,
+ deps = [
+ "//test/util:capability_util",
+ gtest,
+ "//test/util:fs_util",
+ "//test/util:memory_util",
+ "//test/util:temp_path",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ "//test/util:verity_util",
+ ],
+)
+
+cc_binary(
name = "mount_test",
testonly = 1,
srcs = ["mount.cc"],
@@ -4248,10 +4266,12 @@ cc_binary(
"//test/util:mount_util",
"@com_google_absl//absl/strings",
gtest,
+ "//test/util:cleanup",
"//test/util:posix_error",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
+ "//test/util:thread_util",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
],
diff --git a/test/syscalls/linux/cgroup.cc b/test/syscalls/linux/cgroup.cc
index 70ad5868f..a009ade7e 100644
--- a/test/syscalls/linux/cgroup.cc
+++ b/test/syscalls/linux/cgroup.cc
@@ -25,9 +25,11 @@
#include "absl/strings/str_split.h"
#include "test/util/capability_util.h"
#include "test/util/cgroup_util.h"
+#include "test/util/cleanup.h"
#include "test/util/mount_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
+#include "test/util/thread_util.h"
namespace gvisor {
namespace testing {
@@ -192,6 +194,56 @@ TEST(Cgroup, MoptAllMustBeExclusive) {
SyscallFailsWithErrno(EINVAL));
}
+TEST(Cgroup, MountRace) {
+ SKIP_IF(!CgroupsAvailable());
+
+ TempPath mountpoint = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+
+ const DisableSave ds; // Too many syscalls.
+
+ auto mount_thread = [&mountpoint]() {
+ for (int i = 0; i < 100; ++i) {
+ mount("none", mountpoint.path().c_str(), "cgroup", 0, 0);
+ }
+ };
+ std::list<ScopedThread> threads;
+ for (int i = 0; i < 10; ++i) {
+ threads.emplace_back(mount_thread);
+ }
+ for (auto& t : threads) {
+ t.Join();
+ }
+
+ auto cleanup = Cleanup([&mountpoint] {
+ // We need 1 umount call per successful mount. If some of the mount calls
+ // were unsuccessful, their corresponding umount will silently fail.
+ for (int i = 0; i < (10 * 100) + 1; ++i) {
+ umount(mountpoint.path().c_str());
+ }
+ });
+
+ Cgroup c = Cgroup(mountpoint.path());
+ // c should be a valid cgroup.
+ EXPECT_NO_ERRNO(c.ContainsCallingProcess());
+}
+
+TEST(Cgroup, UnmountRepeated) {
+ SKIP_IF(!CgroupsAvailable());
+
+ const DisableSave ds; // Too many syscalls.
+
+ Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
+ Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs(""));
+
+ // First unmount should succeed.
+ EXPECT_THAT(umount(c.Path().c_str()), SyscallSucceeds());
+
+ // We just manually unmounted, so release managed resources.
+ m.release(c);
+
+ EXPECT_THAT(umount(c.Path().c_str()), SyscallFailsWithErrno(EINVAL));
+}
+
TEST(MemoryCgroup, MemoryUsageInBytes) {
SKIP_IF(!CgroupsAvailable());
diff --git a/test/syscalls/linux/chdir.cc b/test/syscalls/linux/chdir.cc
index 3182c228b..3c64b9eab 100644
--- a/test/syscalls/linux/chdir.cc
+++ b/test/syscalls/linux/chdir.cc
@@ -41,8 +41,8 @@ TEST(ChdirTest, Success) {
TEST(ChdirTest, PermissionDenied) {
// Drop capabilities that allow us to override directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(
TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0666 /* mode */));
diff --git a/test/syscalls/linux/chmod.cc b/test/syscalls/linux/chmod.cc
index 4a5ea84d4..dd82c5fb1 100644
--- a/test/syscalls/linux/chmod.cc
+++ b/test/syscalls/linux/chmod.cc
@@ -33,7 +33,7 @@ namespace {
TEST(ChmodTest, ChmodFileSucceeds) {
// Drop capabilities that allow us to override file permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
+ AutoCapability cap(CAP_DAC_OVERRIDE, false);
auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
@@ -43,8 +43,8 @@ TEST(ChmodTest, ChmodFileSucceeds) {
TEST(ChmodTest, ChmodDirSucceeds) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
const std::string fileInDir = NewTempAbsPathInDir(dir.path());
@@ -55,7 +55,7 @@ TEST(ChmodTest, ChmodDirSucceeds) {
TEST(ChmodTest, FchmodFileSucceeds) {
// Drop capabilities that allow us to file directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
+ AutoCapability cap(CAP_DAC_OVERRIDE, false);
auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0666));
int fd;
@@ -72,8 +72,8 @@ TEST(ChmodTest, FchmodFileSucceeds) {
TEST(ChmodTest, FchmodDirSucceeds) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
int fd;
@@ -118,7 +118,7 @@ TEST(ChmodTest, FchmodDirWithOpath) {
TEST(ChmodTest, FchmodatWithOpath) {
SKIP_IF(IsRunningWithVFS1());
// Drop capabilities that allow us to override file permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
+ AutoCapability cap(CAP_DAC_OVERRIDE, false);
auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
@@ -140,7 +140,7 @@ TEST(ChmodTest, FchmodatNotDir) {
TEST(ChmodTest, FchmodatFileAbsolutePath) {
// Drop capabilities that allow us to override file permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
+ AutoCapability cap(CAP_DAC_OVERRIDE, false);
auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
@@ -150,8 +150,8 @@ TEST(ChmodTest, FchmodatFileAbsolutePath) {
TEST(ChmodTest, FchmodatDirAbsolutePath) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
@@ -167,7 +167,7 @@ TEST(ChmodTest, FchmodatDirAbsolutePath) {
TEST(ChmodTest, FchmodatFile) {
// Drop capabilities that allow us to override file permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
+ AutoCapability cap(CAP_DAC_OVERRIDE, false);
auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
@@ -188,8 +188,8 @@ TEST(ChmodTest, FchmodatFile) {
TEST(ChmodTest, FchmodatDir) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
@@ -227,8 +227,8 @@ TEST(ChmodTest, ChmodDowngradeWritability) {
TEST(ChmodTest, ChmodFileToNoPermissionsSucceeds) {
// Drop capabilities that allow us to override file permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0666));
@@ -254,8 +254,8 @@ TEST(ChmodTest, FchmodDowngradeWritability) {
TEST(ChmodTest, FchmodFileToNoPermissionsSucceeds) {
// Drop capabilities that allow us to override file permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0666));
diff --git a/test/syscalls/linux/chown.cc b/test/syscalls/linux/chown.cc
index ff0d39343..b0c1b6f4a 100644
--- a/test/syscalls/linux/chown.cc
+++ b/test/syscalls/linux/chown.cc
@@ -91,9 +91,7 @@ using Chown =
class ChownParamTest : public ::testing::TestWithParam<Chown> {};
TEST_P(ChownParamTest, ChownFileSucceeds) {
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_CHOWN))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_CHOWN, false));
- }
+ AutoCapability cap(CAP_CHOWN, false);
const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
@@ -135,9 +133,7 @@ TEST_P(ChownParamTest, ChownFilePermissionDenied) {
// thread won't be able to open some log files after the test ends.
ScopedThread([&] {
// Drop privileges.
- if (HaveCapability(CAP_CHOWN).ValueOrDie()) {
- EXPECT_NO_ERRNO(SetCapability(CAP_CHOWN, false));
- }
+ AutoCapability cap(CAP_CHOWN, false);
// Change EUID and EGID.
//
diff --git a/test/syscalls/linux/epoll.cc b/test/syscalls/linux/epoll.cc
index b180f633c..af3d27894 100644
--- a/test/syscalls/linux/epoll.cc
+++ b/test/syscalls/linux/epoll.cc
@@ -39,6 +39,15 @@ namespace {
constexpr int kFDsPerEpoll = 3;
constexpr uint64_t kMagicConstant = 0x0102030405060708;
+#ifndef SYS_epoll_pwait2
+#define SYS_epoll_pwait2 441
+#endif
+
+int epoll_pwait2(int fd, struct epoll_event* events, int maxevents,
+ const struct timespec* timeout, const sigset_t* sigset) {
+ return syscall(SYS_epoll_pwait2, fd, events, maxevents, timeout, sigset);
+}
+
TEST(EpollTest, AllWritable) {
auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
std::vector<FileDescriptor> eventfds;
@@ -144,6 +153,50 @@ TEST(EpollTest, Timeout) {
EXPECT_GT(ms_elapsed(begin, end), kTimeoutMs - 1);
}
+TEST(EpollTest, EpollPwait2Timeout) {
+ auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
+ // 200 milliseconds.
+ constexpr int kTimeoutNs = 200000000;
+ struct timespec timeout;
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 0;
+ struct timespec begin;
+ struct timespec end;
+ struct epoll_event result[kFDsPerEpoll];
+
+ std::vector<FileDescriptor> eventfds;
+ for (int i = 0; i < kFDsPerEpoll; i++) {
+ eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()));
+ ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN,
+ kMagicConstant + i));
+ }
+
+ // Pass valid arguments so that the syscall won't be blocked indefinitely
+ // nor return errno EINVAL.
+ //
+ // The syscall returns immediately when timeout is zero,
+ // even if no events are available.
+ SKIP_IF(!IsRunningOnGvisor() &&
+ epoll_pwait2(epollfd.get(), result, kFDsPerEpoll, &timeout, nullptr) <
+ 0 &&
+ errno == ENOSYS);
+
+ {
+ const DisableSave ds; // Timing-related.
+ EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &begin), SyscallSucceeds());
+
+ timeout.tv_nsec = kTimeoutNs;
+ ASSERT_THAT(RetryEINTR(epoll_pwait2)(epollfd.get(), result, kFDsPerEpoll,
+ &timeout, nullptr),
+ SyscallSucceedsWithValue(0));
+ EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &end), SyscallSucceeds());
+ }
+
+ // Check the lower bound on the timeout. Checking for an upper bound is
+ // fragile because Linux can overrun the timeout due to scheduling delays.
+ EXPECT_GT(ns_elapsed(begin, end), kTimeoutNs - 1);
+}
+
void* writer(void* arg) {
int fd = *reinterpret_cast<int*>(arg);
uint64_t tmp = 1;
diff --git a/test/syscalls/linux/fchdir.cc b/test/syscalls/linux/fchdir.cc
index c6675802d..0383f3f85 100644
--- a/test/syscalls/linux/fchdir.cc
+++ b/test/syscalls/linux/fchdir.cc
@@ -46,8 +46,8 @@ TEST(FchdirTest, InvalidFD) {
TEST(FchdirTest, PermissionDenied) {
// Drop capabilities that allow us to override directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(
TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0666 /* mode */));
diff --git a/test/syscalls/linux/fcntl.cc b/test/syscalls/linux/fcntl.cc
index 4fa6751ff..91526572b 100644
--- a/test/syscalls/linux/fcntl.cc
+++ b/test/syscalls/linux/fcntl.cc
@@ -390,9 +390,7 @@ TEST_F(FcntlLockTest, SetLockDir) {
}
TEST_F(FcntlLockTest, SetLockSymlink) {
- // TODO(gvisor.dev/issue/2782): Replace with IsRunningWithVFS1() when O_PATH
- // is supported.
- SKIP_IF(IsRunningOnGvisor());
+ SKIP_IF(IsRunningWithVFS1());
auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
auto symlink = ASSERT_NO_ERRNO_AND_VALUE(
diff --git a/test/syscalls/linux/flock.cc b/test/syscalls/linux/flock.cc
index fd387aa45..10dad042f 100644
--- a/test/syscalls/linux/flock.cc
+++ b/test/syscalls/linux/flock.cc
@@ -662,9 +662,7 @@ TEST(FlockTestNoFixture, FlockDir) {
}
TEST(FlockTestNoFixture, FlockSymlink) {
- // TODO(gvisor.dev/issue/2782): Replace with IsRunningWithVFS1() when O_PATH
- // is supported.
- SKIP_IF(IsRunningOnGvisor());
+ SKIP_IF(IsRunningWithVFS1());
auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
auto symlink = ASSERT_NO_ERRNO_AND_VALUE(
diff --git a/test/syscalls/linux/memory_accounting.cc b/test/syscalls/linux/memory_accounting.cc
index 94aea4077..867a4513b 100644
--- a/test/syscalls/linux/memory_accounting.cc
+++ b/test/syscalls/linux/memory_accounting.cc
@@ -83,7 +83,7 @@ TEST(MemoryAccounting, AnonAccountingPreservedOnSaveRestore) {
uint64_t anon_after_alloc = ASSERT_NO_ERRNO_AND_VALUE(AnonUsageFromMeminfo());
EXPECT_THAT(anon_after_alloc,
- EquivalentWithin(anon_initial + map_bytes, 0.03));
+ EquivalentWithin(anon_initial + map_bytes, 0.04));
// We have many implicit S/R cycles from scraping /proc/meminfo throughout the
// test, but throw an explicit S/R in here as well.
@@ -91,7 +91,7 @@ TEST(MemoryAccounting, AnonAccountingPreservedOnSaveRestore) {
// Usage should remain the same across S/R.
uint64_t anon_after_sr = ASSERT_NO_ERRNO_AND_VALUE(AnonUsageFromMeminfo());
- EXPECT_THAT(anon_after_sr, EquivalentWithin(anon_after_alloc, 0.03));
+ EXPECT_THAT(anon_after_sr, EquivalentWithin(anon_after_alloc, 0.04));
}
} // namespace
diff --git a/test/syscalls/linux/mkdir.cc b/test/syscalls/linux/mkdir.cc
index 11fbfa5c5..36504fe6d 100644
--- a/test/syscalls/linux/mkdir.cc
+++ b/test/syscalls/linux/mkdir.cc
@@ -72,8 +72,8 @@ TEST_F(MkdirTest, HonorsUmask2) {
TEST_F(MkdirTest, FailsOnDirWithoutWritePerms) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
ASSERT_THAT(mkdir(dirname_.c_str(), 0555), SyscallSucceeds());
auto dir = JoinPath(dirname_.c_str(), "foo");
@@ -84,8 +84,8 @@ TEST_F(MkdirTest, FailsOnDirWithoutWritePerms) {
TEST_F(MkdirTest, DirAlreadyExists) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
ASSERT_THAT(mkdir(dirname_.c_str(), 0777), SyscallSucceeds());
auto dir = JoinPath(dirname_.c_str(), "foo");
diff --git a/test/syscalls/linux/mlock.cc b/test/syscalls/linux/mlock.cc
index 78ac96bed..dfa5b7133 100644
--- a/test/syscalls/linux/mlock.cc
+++ b/test/syscalls/linux/mlock.cc
@@ -114,9 +114,7 @@ TEST(MlockTest, Fork) {
}
TEST(MlockTest, RlimitMemlockZero) {
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
- }
+ AutoCapability cap(CAP_IPC_LOCK, false);
Cleanup reset_rlimit =
ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, 0));
auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
@@ -127,9 +125,7 @@ TEST(MlockTest, RlimitMemlockZero) {
}
TEST(MlockTest, RlimitMemlockInsufficient) {
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
- }
+ AutoCapability cap(CAP_IPC_LOCK, false);
Cleanup reset_rlimit =
ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, kPageSize));
auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
@@ -255,9 +251,7 @@ TEST(MapLockedTest, Basic) {
}
TEST(MapLockedTest, RlimitMemlockZero) {
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
- }
+ AutoCapability cap(CAP_IPC_LOCK, false);
Cleanup reset_rlimit =
ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, 0));
EXPECT_THAT(
@@ -266,9 +260,7 @@ TEST(MapLockedTest, RlimitMemlockZero) {
}
TEST(MapLockedTest, RlimitMemlockInsufficient) {
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
- }
+ AutoCapability cap(CAP_IPC_LOCK, false);
Cleanup reset_rlimit =
ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, kPageSize));
EXPECT_THAT(
@@ -298,9 +290,7 @@ TEST(MremapLockedTest, RlimitMemlockZero) {
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED));
EXPECT_TRUE(IsPageMlocked(mapping.addr()));
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
- }
+ AutoCapability cap(CAP_IPC_LOCK, false);
Cleanup reset_rlimit =
ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, 0));
void* addr = mremap(mapping.ptr(), mapping.len(), 2 * mapping.len(),
@@ -315,9 +305,7 @@ TEST(MremapLockedTest, RlimitMemlockInsufficient) {
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED));
EXPECT_TRUE(IsPageMlocked(mapping.addr()));
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
- }
+ AutoCapability cap(CAP_IPC_LOCK, false);
Cleanup reset_rlimit = ASSERT_NO_ERRNO_AND_VALUE(
ScopedSetSoftRlimit(RLIMIT_MEMLOCK, mapping.len()));
void* addr = mremap(mapping.ptr(), mapping.len(), 2 * mapping.len(),
diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc
index 92d0fb05c..3c7311782 100644
--- a/test/syscalls/linux/mount.cc
+++ b/test/syscalls/linux/mount.cc
@@ -45,6 +45,7 @@ namespace testing {
namespace {
+using ::testing::AnyOf;
using ::testing::Contains;
using ::testing::Pair;
@@ -360,7 +361,8 @@ TEST(MountTest, MountInfo) {
if (e.mount_point == dir.path()) {
EXPECT_EQ(e.fstype, "tmpfs");
auto mopts = ParseMountOptions(e.mount_opts);
- EXPECT_THAT(mopts, Contains(Pair("mode", "0123")));
+ EXPECT_THAT(mopts, AnyOf(Contains(Pair("mode", "0123")),
+ Contains(Pair("mode", "123"))));
}
}
@@ -371,7 +373,8 @@ TEST(MountTest, MountInfo) {
if (e.mount_point == dir.path()) {
EXPECT_EQ(e.fstype, "tmpfs");
auto mopts = ParseMountOptions(e.super_opts);
- EXPECT_THAT(mopts, Contains(Pair("mode", "0123")));
+ EXPECT_THAT(mopts, AnyOf(Contains(Pair("mode", "0123")),
+ Contains(Pair("mode", "123"))));
}
}
}
diff --git a/test/syscalls/linux/open.cc b/test/syscalls/linux/open.cc
index 4697c404c..ab9d19fef 100644
--- a/test/syscalls/linux/open.cc
+++ b/test/syscalls/linux/open.cc
@@ -433,7 +433,7 @@ TEST_F(OpenTest, CanTruncateReadOnly) {
// O_TRUNC should fail.
TEST_F(OpenTest, CanTruncateReadOnlyNoWritePermission) {
// Drop capabilities that allow us to override file permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
+ AutoCapability cap(CAP_DAC_OVERRIDE, false);
const DisableSave ds; // Permissions are dropped.
ASSERT_THAT(chmod(test_file_name_.c_str(), S_IRUSR | S_IRGRP),
@@ -473,8 +473,8 @@ TEST_F(OpenTest, CanTruncateWriteOnlyNoReadPermission) {
}
TEST_F(OpenTest, CanTruncateWithStrangePermissions) {
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
const DisableSave ds; // Permissions are dropped.
std::string path = NewTempAbsPath();
// Create a file without user permissions.
@@ -510,8 +510,8 @@ TEST_F(OpenTest, OpenWithStrangeFlags) {
TEST_F(OpenTest, OpenWithOpath) {
SKIP_IF(IsRunningWithVFS1());
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
const DisableSave ds; // Permissions are dropped.
std::string path = NewTempAbsPath();
diff --git a/test/syscalls/linux/open_create.cc b/test/syscalls/linux/open_create.cc
index 43d446926..177bda54d 100644
--- a/test/syscalls/linux/open_create.cc
+++ b/test/syscalls/linux/open_create.cc
@@ -93,7 +93,8 @@ TEST(CreateTest, CreatFileWithOTruncAndReadOnly) {
TEST(CreateTest, CreateFailsOnDirWithoutWritePerms) {
// Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to
// always override directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
+ AutoCapability cap(CAP_DAC_OVERRIDE, false);
+
auto parent = ASSERT_NO_ERRNO_AND_VALUE(
TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0555));
auto file = JoinPath(parent.path(), "foo");
@@ -123,8 +124,8 @@ TEST(CreateTest, ChmodReadToWriteBetweenOpens) {
// Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to
// override file read/write permissions. CAP_DAC_READ_SEARCH needs to be
// cleared for the same reason.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
const TempPath file =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0400));
@@ -152,7 +153,7 @@ TEST(CreateTest, ChmodReadToWriteBetweenOpens) {
TEST(CreateTest, ChmodWriteToReadBetweenOpens) {
// Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to
// override file read/write permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
+ AutoCapability cap(CAP_DAC_OVERRIDE, false);
const TempPath file =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0200));
@@ -186,8 +187,8 @@ TEST(CreateTest, CreateWithReadFlagNotAllowedByMode) {
// Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to
// override file read/write permissions. CAP_DAC_READ_SEARCH needs to be
// cleared for the same reason.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
// Create and open a file with read flag but without read permissions.
const std::string path = NewTempAbsPath();
@@ -212,7 +213,7 @@ TEST(CreateTest, CreateWithWriteFlagNotAllowedByMode) {
// Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to
// override file read/write permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
+ AutoCapability cap(CAP_DAC_OVERRIDE, false);
// Create and open a file with write flag but without write permissions.
const std::string path = NewTempAbsPath();
diff --git a/test/syscalls/linux/prctl.cc b/test/syscalls/linux/prctl.cc
index f675dc430..19a57d353 100644
--- a/test/syscalls/linux/prctl.cc
+++ b/test/syscalls/linux/prctl.cc
@@ -184,10 +184,8 @@ TEST(PrctlTest, PDeathSig) {
// This test is to validate that calling prctl with PR_SET_MM without the
// CAP_SYS_RESOURCE returns EPERM.
TEST(PrctlTest, InvalidPrSetMM) {
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_RESOURCE))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_RESOURCE,
- false)); // Drop capability to test below.
- }
+ // Drop capability to test below.
+ AutoCapability cap(CAP_SYS_RESOURCE, false);
ASSERT_THAT(prctl(PR_SET_MM, 0, 0, 0, 0), SyscallFailsWithErrno(EPERM));
}
diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc
index 9e48fbca5..24928d876 100644
--- a/test/syscalls/linux/proc.cc
+++ b/test/syscalls/linux/proc.cc
@@ -1201,6 +1201,15 @@ TEST(ProcSelfCwd, Absolute) {
EXPECT_EQ(exe[0], '/');
}
+// Sanity check that /proc/cmdline is present.
+TEST(ProcCmdline, IsPresent) {
+ SKIP_IF(IsRunningWithVFS1());
+
+ std::string proc_cmdline =
+ ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/cmdline"));
+ ASSERT_FALSE(proc_cmdline.empty());
+}
+
// Sanity check for /proc/cpuinfo fields that must be present.
TEST(ProcCpuinfo, RequiredFieldsArePresent) {
std::string proc_cpuinfo =
@@ -1849,8 +1858,8 @@ TEST(ProcPidSymlink, SubprocessRunning) {
}
TEST(ProcPidSymlink, SubprocessZombied) {
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
char buf[1];
@@ -2252,7 +2261,7 @@ TEST(ProcTask, VerifyTaskDir) {
TEST(ProcTask, TaskDirCannotBeDeleted) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
+ AutoCapability cap(CAP_DAC_OVERRIDE, false);
EXPECT_THAT(rmdir("/proc/self/task"), SyscallFails());
EXPECT_THAT(rmdir(absl::StrCat("/proc/self/task/", getpid()).c_str()),
@@ -2698,6 +2707,14 @@ TEST(Proc, Statfs) {
EXPECT_EQ(st.f_namelen, NAME_MAX);
}
+// Tests that /proc/[pid]/fd/[num] can resolve to a path inside /proc.
+TEST(Proc, ResolveSymlinkToProc) {
+ const auto proc = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/cmdline", 0));
+ const auto path = JoinPath("/proc/self/fd/", absl::StrCat(proc.get()));
+ const auto target = ASSERT_NO_ERRNO_AND_VALUE(ReadLink(path));
+ EXPECT_EQ(target, JoinPath("/proc/", absl::StrCat(getpid()), "/cmdline"));
+}
+
} // namespace
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/ptrace.cc b/test/syscalls/linux/ptrace.cc
index 2d9fec371..d519b65e6 100644
--- a/test/syscalls/linux/ptrace.cc
+++ b/test/syscalls/linux/ptrace.cc
@@ -175,7 +175,7 @@ TEST(PtraceTest, AttachSameThreadGroup) {
TEST(PtraceTest, TraceParentNotAllowed) {
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) < 1);
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false));
+ AutoCapability cap(CAP_SYS_PTRACE, false);
pid_t const child_pid = fork();
if (child_pid == 0) {
@@ -193,7 +193,7 @@ TEST(PtraceTest, TraceParentNotAllowed) {
TEST(PtraceTest, TraceNonDescendantNotAllowed) {
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) < 1);
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false));
+ AutoCapability cap(CAP_SYS_PTRACE, false);
pid_t const tracee_pid = fork();
if (tracee_pid == 0) {
@@ -259,7 +259,7 @@ TEST(PtraceTest, TraceNonDescendantWithCapabilityAllowed) {
TEST(PtraceTest, TraceDescendantsAllowed) {
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) > 1);
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false));
+ AutoCapability cap(CAP_SYS_PTRACE, false);
// Use socket pair to communicate tids to this process from its grandchild.
int sockets[2];
@@ -346,7 +346,7 @@ TEST(PtraceTest, PrctlSetPtracerInvalidPID) {
TEST(PtraceTest, PrctlSetPtracerPID) {
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1);
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false));
+ AutoCapability cap(CAP_SYS_PTRACE, false);
// Use sockets to synchronize between tracer and tracee.
int sockets[2];
@@ -410,7 +410,7 @@ TEST(PtraceTest, PrctlSetPtracerPID) {
TEST(PtraceTest, PrctlSetPtracerAny) {
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1);
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false));
+ AutoCapability cap(CAP_SYS_PTRACE, false);
// Use sockets to synchronize between tracer and tracee.
int sockets[2];
@@ -475,7 +475,7 @@ TEST(PtraceTest, PrctlSetPtracerAny) {
TEST(PtraceTest, PrctlClearPtracer) {
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1);
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false));
+ AutoCapability cap(CAP_SYS_PTRACE, false);
// Use sockets to synchronize between tracer and tracee.
int sockets[2];
@@ -543,7 +543,7 @@ TEST(PtraceTest, PrctlClearPtracer) {
TEST(PtraceTest, PrctlReplacePtracer) {
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1);
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false));
+ AutoCapability cap(CAP_SYS_PTRACE, false);
pid_t const unused_pid = fork();
if (unused_pid == 0) {
@@ -633,7 +633,7 @@ TEST(PtraceTest, PrctlReplacePtracer) {
// thread group leader is still around.
TEST(PtraceTest, PrctlSetPtracerPersistsPastTraceeThreadExit) {
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1);
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false));
+ AutoCapability cap(CAP_SYS_PTRACE, false);
// Use sockets to synchronize between tracer and tracee.
int sockets[2];
@@ -703,7 +703,7 @@ TEST(PtraceTest, PrctlSetPtracerPersistsPastTraceeThreadExit) {
// even if the tracee thread is terminated.
TEST(PtraceTest, PrctlSetPtracerPersistsPastLeaderExec) {
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1);
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false));
+ AutoCapability cap(CAP_SYS_PTRACE, false);
// Use sockets to synchronize between tracer and tracee.
int sockets[2];
@@ -770,7 +770,7 @@ TEST(PtraceTest, PrctlSetPtracerPersistsPastLeaderExec) {
// exec.
TEST(PtraceTest, PrctlSetPtracerDoesNotPersistPastNonLeaderExec) {
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1);
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false));
+ AutoCapability cap(CAP_SYS_PTRACE, false);
// Use sockets to synchronize between tracer and tracee.
int sockets[2];
@@ -904,7 +904,7 @@ TEST(PtraceTest, PrctlSetPtracerDoesNotPersistPastTracerThreadExit) {
[[noreturn]] void RunPrctlSetPtracerDoesNotPersistPastTracerThreadExit(
int tracee_tid, int fd) {
- TEST_PCHECK(SetCapability(CAP_SYS_PTRACE, false).ok());
+ AutoCapability cap(CAP_SYS_PTRACE, false);
ScopedThread t([fd] {
pid_t const tracer_tid = gettid();
@@ -1033,7 +1033,7 @@ TEST(PtraceTest, PrctlSetPtracerRespectsTracerThreadID) {
// attached.
TEST(PtraceTest, PrctlClearPtracerDoesNotAffectCurrentTracer) {
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1);
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false));
+ AutoCapability cap(CAP_SYS_PTRACE, false);
// Use sockets to synchronize between tracer and tracee.
int sockets[2];
@@ -1118,7 +1118,7 @@ TEST(PtraceTest, PrctlClearPtracerDoesNotAffectCurrentTracer) {
TEST(PtraceTest, PrctlNotInherited) {
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1);
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false));
+ AutoCapability cap(CAP_SYS_PTRACE, false);
// Allow any ptracer. This should not affect the child processes.
ASSERT_THAT(prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY), SyscallSucceeds());
@@ -2302,7 +2302,7 @@ TEST(PtraceTest, SetYAMAPtraceScope) {
EXPECT_STREQ(buf.data(), "0\n");
// Test that a child can attach to its parent when ptrace_scope is 0.
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false));
+ AutoCapability cap(CAP_SYS_PTRACE, false);
pid_t const child_pid = fork();
if (child_pid == 0) {
TEST_PCHECK(CheckPtraceAttach(getppid()) == 0);
diff --git a/test/syscalls/linux/pty.cc b/test/syscalls/linux/pty.cc
index 541b2744c..1c83e2656 100644
--- a/test/syscalls/linux/pty.cc
+++ b/test/syscalls/linux/pty.cc
@@ -396,12 +396,13 @@ TEST(PtyTrunc, Truncate) {
setsid();
// Make sure we're ignoring SIGHUP, which will be sent to this process once we
- // disconnect they TTY.
+ // disconnect the TTY.
struct sigaction sa = {};
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
- auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGHUP, sa));
+ const Cleanup cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGHUP, sa));
// Opening PTYs with O_TRUNC shouldn't cause an error, but calls to
// (f)truncate should.
@@ -484,7 +485,7 @@ TEST(BasicPtyTest, OpenSetsControllingTTY) {
setsid();
// Make sure we're ignoring SIGHUP, which will be sent to this process once we
- // disconnect they TTY.
+ // disconnect the TTY.
struct sigaction sa = {};
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
@@ -1422,7 +1423,7 @@ TEST_F(JobControlTest, ReleaseTTY) {
ASSERT_THAT(ioctl(replica_.get(), TIOCSCTTY, 0), SyscallSucceeds());
// Make sure we're ignoring SIGHUP, which will be sent to this process once we
- // disconnect they TTY.
+ // disconnect the TTY.
struct sigaction sa = {};
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
@@ -1543,7 +1544,7 @@ TEST_F(JobControlTest, ReleaseTTYSignals) {
EXPECT_THAT(setpgid(diff_pgrp_child, diff_pgrp_child), SyscallSucceeds());
// Make sure we're ignoring SIGHUP, which will be sent to this process once we
- // disconnect they TTY.
+ // disconnect the TTY.
struct sigaction sighup_sa = {};
sighup_sa.sa_handler = SIG_IGN;
sighup_sa.sa_flags = 0;
diff --git a/test/syscalls/linux/raw_socket_hdrincl.cc b/test/syscalls/linux/raw_socket_hdrincl.cc
index 2f25aceb2..8b3d02d97 100644
--- a/test/syscalls/linux/raw_socket_hdrincl.cc
+++ b/test/syscalls/linux/raw_socket_hdrincl.cc
@@ -177,10 +177,8 @@ TEST_F(RawHDRINCL, ConnectToLoopback) {
SyscallSucceeds());
}
-TEST_F(RawHDRINCL, SendWithoutConnectSucceeds) {
- // FIXME(gvisor.dev/issue/3159): Test currently flaky.
- SKIP_IF(true);
-
+// FIXME(gvisor.dev/issue/3159): Test currently flaky.
+TEST_F(RawHDRINCL, DISABLED_SendWithoutConnectSucceeds) {
struct iphdr hdr = LoopbackHeader();
ASSERT_THAT(send(socket_, &hdr, sizeof(hdr), 0),
SyscallSucceedsWithValue(sizeof(hdr)));
diff --git a/test/syscalls/linux/rename.cc b/test/syscalls/linux/rename.cc
index b1a813de0..76a8da65f 100644
--- a/test/syscalls/linux/rename.cc
+++ b/test/syscalls/linux/rename.cc
@@ -259,8 +259,8 @@ TEST(RenameTest, DirectoryDoesNotOverwriteNonemptyDirectory) {
TEST(RenameTest, FailsWhenOldParentNotWritable) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
auto f1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
@@ -275,8 +275,8 @@ TEST(RenameTest, FailsWhenOldParentNotWritable) {
TEST(RenameTest, FailsWhenNewParentNotWritable) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
auto f1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
@@ -293,8 +293,8 @@ TEST(RenameTest, FailsWhenNewParentNotWritable) {
// to overwrite.
TEST(RenameTest, OverwriteFailsWhenNewParentNotWritable) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
auto f1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
@@ -312,8 +312,8 @@ TEST(RenameTest, OverwriteFailsWhenNewParentNotWritable) {
// because the user cannot determine if source exists.
TEST(RenameTest, FileDoesNotExistWhenNewParentNotExecutable) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
// No execute permission.
auto dir = ASSERT_NO_ERRNO_AND_VALUE(
diff --git a/test/syscalls/linux/rlimits.cc b/test/syscalls/linux/rlimits.cc
index 860f0f688..d31a2a880 100644
--- a/test/syscalls/linux/rlimits.cc
+++ b/test/syscalls/linux/rlimits.cc
@@ -41,9 +41,7 @@ TEST(RlimitTest, SetRlimitHigher) {
TEST(RlimitTest, UnprivilegedSetRlimit) {
// Drop privileges if necessary.
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_RESOURCE))) {
- EXPECT_NO_ERRNO(SetCapability(CAP_SYS_RESOURCE, false));
- }
+ AutoCapability cap(CAP_SYS_RESOURCE, false);
struct rlimit rl = {};
rl.rlim_cur = 1000;
diff --git a/test/syscalls/linux/semaphore.cc b/test/syscalls/linux/semaphore.cc
index 207377efb..2ce8f836c 100644
--- a/test/syscalls/linux/semaphore.cc
+++ b/test/syscalls/linux/semaphore.cc
@@ -535,7 +535,7 @@ TEST(SemaphoreTest, SemCtlGetPidFork) {
TEST(SemaphoreTest, SemIpcSet) {
// Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false));
+ AutoCapability cap(CAP_IPC_OWNER, false);
AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
ASSERT_THAT(sem.get(), SyscallSucceeds());
@@ -560,7 +560,7 @@ TEST(SemaphoreTest, SemIpcSet) {
TEST(SemaphoreTest, SemCtlIpcStat) {
// Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false));
+ AutoCapability cap(CAP_IPC_OWNER, false);
const uid_t kUid = getuid();
const gid_t kGid = getgid();
time_t start_time = time(nullptr);
@@ -635,7 +635,7 @@ PosixErrorOr<int> WaitSemctl(int semid, int target, int cmd) {
TEST(SemaphoreTest, SemopGetzcnt) {
// Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false));
+ AutoCapability cap(CAP_IPC_OWNER, false);
// Create a write only semaphore set.
AutoSem sem(semget(IPC_PRIVATE, 1, 0200 | IPC_CREAT));
ASSERT_THAT(sem.get(), SyscallSucceeds());
@@ -743,7 +743,7 @@ TEST(SemaphoreTest, SemopGetzcntOnSignal) {
TEST(SemaphoreTest, SemopGetncnt) {
// Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false));
+ AutoCapability cap(CAP_IPC_OWNER, false);
// Create a write only semaphore set.
AutoSem sem(semget(IPC_PRIVATE, 1, 0200 | IPC_CREAT));
ASSERT_THAT(sem.get(), SyscallSucceeds());
@@ -853,7 +853,7 @@ TEST(SemaphoreTest, IpcInfo) {
std::set<int> sem_ids;
struct seminfo info;
// Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false));
+ AutoCapability cap(CAP_IPC_OWNER, false);
for (int i = 0; i < kLoops; i++) {
AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
ASSERT_THAT(sem.get(), SyscallSucceeds());
@@ -923,7 +923,7 @@ TEST(SemaphoreTest, SemInfo) {
std::set<int> sem_ids;
struct seminfo info;
// Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false));
+ AutoCapability cap(CAP_IPC_OWNER, false);
for (int i = 0; i < kLoops; i++) {
AutoSem sem(semget(IPC_PRIVATE, kSemSetSize, 0600 | IPC_CREAT));
ASSERT_THAT(sem.get(), SyscallSucceeds());
diff --git a/test/syscalls/linux/socket_capability.cc b/test/syscalls/linux/socket_capability.cc
index 84b5b2b21..f75482aba 100644
--- a/test/syscalls/linux/socket_capability.cc
+++ b/test/syscalls/linux/socket_capability.cc
@@ -40,7 +40,7 @@ TEST(SocketTest, UnixConnectNeedsWritePerm) {
// Drop capabilites that allow us to override permision checks. Otherwise if
// the test is run as root, the connect below will bypass permission checks
// and succeed unexpectedly.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
+ AutoCapability cap(CAP_DAC_OVERRIDE, false);
// Connect should fail without write perms.
ASSERT_THAT(chmod(addr.sun_path, 0500), SyscallSucceeds());
diff --git a/test/syscalls/linux/socket_ip_tcp_generic.cc b/test/syscalls/linux/socket_ip_tcp_generic.cc
index 59b56dc1a..2f5743cda 100644
--- a/test/syscalls/linux/socket_ip_tcp_generic.cc
+++ b/test/syscalls/linux/socket_ip_tcp_generic.cc
@@ -1155,7 +1155,7 @@ TEST_P(TCPSocketPairTest, IpMulticastLoopDefault) {
TEST_P(TCPSocketPairTest, TCPResetDuringClose) {
DisableSave ds; // Too many syscalls.
- constexpr int kThreadCount = 1000;
+ constexpr int kThreadCount = 100;
std::unique_ptr<ScopedThread> instances[kThreadCount];
for (int i = 0; i < kThreadCount; i++) {
instances[i] = absl::make_unique<ScopedThread>([&]() {
diff --git a/test/syscalls/linux/sticky.cc b/test/syscalls/linux/sticky.cc
index 4afed6d08..5a2841899 100644
--- a/test/syscalls/linux/sticky.cc
+++ b/test/syscalls/linux/sticky.cc
@@ -56,9 +56,7 @@ TEST(StickyTest, StickyBitPermDenied) {
// thread won't be able to open some log files after the test ends.
ScopedThread([&] {
// Drop privileges.
- if (HaveCapability(CAP_FOWNER).ValueOrDie()) {
- EXPECT_NO_ERRNO(SetCapability(CAP_FOWNER, false));
- }
+ AutoCapability cap(CAP_FOWNER, false);
// Change EUID and EGID.
EXPECT_THAT(
@@ -98,9 +96,7 @@ TEST(StickyTest, StickyBitSameUID) {
// thread won't be able to open some log files after the test ends.
ScopedThread([&] {
// Drop privileges.
- if (HaveCapability(CAP_FOWNER).ValueOrDie()) {
- EXPECT_NO_ERRNO(SetCapability(CAP_FOWNER, false));
- }
+ AutoCapability cap(CAP_FOWNER, false);
// Change EGID.
EXPECT_THAT(
diff --git a/test/syscalls/linux/symlink.cc b/test/syscalls/linux/symlink.cc
index 9f6c59446..fa6849f11 100644
--- a/test/syscalls/linux/symlink.cc
+++ b/test/syscalls/linux/symlink.cc
@@ -100,8 +100,8 @@ TEST(SymlinkTest, CanCreateSymlinkDir) {
TEST(SymlinkTest, CannotCreateSymlinkInReadOnlyDir) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
const std::string olddir = NewTempAbsPath();
ASSERT_THAT(mkdir(olddir.c_str(), 0444), SyscallSucceeds());
@@ -250,8 +250,8 @@ TEST(SymlinkTest, PwriteToSymlink) {
TEST(SymlinkTest, SymlinkAtDegradedPermissions) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
@@ -301,8 +301,8 @@ TEST(SymlinkTest, ReadlinkAtDirWithOpath) {
TEST(SymlinkTest, ReadlinkAtDegradedPermissions) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
const std::string oldpath = NewTempAbsPathInDir(dir.path());
diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc
index 011b60f0e..5bfdecc79 100644
--- a/test/syscalls/linux/tcp_socket.cc
+++ b/test/syscalls/linux/tcp_socket.cc
@@ -1164,7 +1164,13 @@ TEST_P(SimpleTcpSocketTest, SelfConnectSend) {
ASSERT_THAT(RetryEINTR(connect)(s.get(), AsSockAddr(&addr), addrlen),
SyscallSucceeds());
- std::vector<char> writebuf(512 << 10); // 512 KiB.
+ // Ensure the write buffer is large enough not to block on a single write.
+ size_t write_size = 512 << 10; // 512 KiB.
+ EXPECT_THAT(setsockopt(s.get(), SOL_SOCKET, SO_SNDBUF, &write_size,
+ sizeof(write_size)),
+ SyscallSucceedsWithValue(0));
+
+ std::vector<char> writebuf(write_size);
// Try to send the whole thing.
int n;
diff --git a/test/syscalls/linux/truncate.cc b/test/syscalls/linux/truncate.cc
index 5db0b8276..0f08d9996 100644
--- a/test/syscalls/linux/truncate.cc
+++ b/test/syscalls/linux/truncate.cc
@@ -181,7 +181,7 @@ TEST(TruncateTest, FtruncateDir) {
TEST(TruncateTest, TruncateNonWriteable) {
// Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to
// always override write permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
+ AutoCapability cap(CAP_DAC_OVERRIDE, false);
auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
GetAbsoluteTestTmpdir(), absl::string_view(), 0555 /* mode */));
EXPECT_THAT(truncate(temp_file.path().c_str(), 0),
@@ -210,7 +210,7 @@ TEST(TruncateTest, FtruncateWithOpath) {
// regardless of whether the file permissions allow writing.
TEST(TruncateTest, FtruncateWithoutWritePermission) {
// Drop capabilities that allow us to override file permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
+ AutoCapability cap(CAP_DAC_OVERRIDE, false);
// The only time we can open a file with flags forbidden by its permissions
// is when we are creating the file. We cannot re-open with the same flags,
diff --git a/test/syscalls/linux/tuntap.cc b/test/syscalls/linux/tuntap.cc
index 6e3a00d2c..279fe342c 100644
--- a/test/syscalls/linux/tuntap.cc
+++ b/test/syscalls/linux/tuntap.cc
@@ -170,10 +170,10 @@ TEST(TuntapStaticTest, NetTunExists) {
class TuntapTest : public ::testing::Test {
protected:
void SetUp() override {
- have_net_admin_cap_ =
+ const bool have_net_admin_cap =
ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN));
- if (have_net_admin_cap_ && !IsRunningOnGvisor()) {
+ if (have_net_admin_cap && !IsRunningOnGvisor()) {
// gVisor always creates enabled/up'd interfaces, while Linux does not (as
// observed in b/110961832). Some of the tests require the Linux stack to
// notify the socket of any link-address-resolution failures. Those
@@ -183,21 +183,12 @@ class TuntapTest : public ::testing::Test {
ASSERT_NO_ERRNO(LinkChangeFlags(link.index, IFF_UP, IFF_UP));
}
}
-
- void TearDown() override {
- if (have_net_admin_cap_) {
- // Bring back capability if we had dropped it in test case.
- ASSERT_NO_ERRNO(SetCapability(CAP_NET_ADMIN, true));
- }
- }
-
- bool have_net_admin_cap_;
};
TEST_F(TuntapTest, CreateInterfaceNoCap) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
- ASSERT_NO_ERRNO(SetCapability(CAP_NET_ADMIN, false));
+ AutoCapability cap(CAP_NET_ADMIN, false);
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(kDevNetTun, O_RDWR));
diff --git a/test/syscalls/linux/uname.cc b/test/syscalls/linux/uname.cc
index d8824b171..759ea4f53 100644
--- a/test/syscalls/linux/uname.cc
+++ b/test/syscalls/linux/uname.cc
@@ -76,9 +76,7 @@ TEST(UnameTest, SetNames) {
}
TEST(UnameTest, UnprivilegedSetNames) {
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))) {
- EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false));
- }
+ AutoCapability cap(CAP_SYS_ADMIN, false);
EXPECT_THAT(sethostname("", 0), SyscallFailsWithErrno(EPERM));
EXPECT_THAT(setdomainname("", 0), SyscallFailsWithErrno(EPERM));
diff --git a/test/syscalls/linux/unlink.cc b/test/syscalls/linux/unlink.cc
index 7c301c305..75dcf4465 100644
--- a/test/syscalls/linux/unlink.cc
+++ b/test/syscalls/linux/unlink.cc
@@ -66,8 +66,8 @@ TEST(UnlinkTest, AtDir) {
TEST(UnlinkTest, AtDirDegradedPermissions) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
@@ -86,8 +86,8 @@ TEST(UnlinkTest, AtDirDegradedPermissions) {
// Files cannot be unlinked if the parent is not writable and executable.
TEST(UnlinkTest, ParentDegradedPermissions) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
diff --git a/test/syscalls/linux/utimes.cc b/test/syscalls/linux/utimes.cc
index e647d2896..e711d6657 100644
--- a/test/syscalls/linux/utimes.cc
+++ b/test/syscalls/linux/utimes.cc
@@ -225,7 +225,8 @@ void TestUtimensat(int dirFd, std::string const& path) {
EXPECT_GE(mtime3, before);
EXPECT_LE(mtime3, after);
- EXPECT_EQ(atime3, mtime3);
+ // TODO(b/187074006): atime/mtime may differ with local_gofer_uncached.
+ // EXPECT_EQ(atime3, mtime3);
}
TEST(UtimensatTest, OnAbsPath) {
diff --git a/test/syscalls/linux/verity_ioctl.cc b/test/syscalls/linux/verity_ioctl.cc
index 822e16f3c..be91b23d0 100644
--- a/test/syscalls/linux/verity_ioctl.cc
+++ b/test/syscalls/linux/verity_ioctl.cc
@@ -28,40 +28,13 @@
#include "test/util/mount_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
+#include "test/util/verity_util.h"
namespace gvisor {
namespace testing {
namespace {
-#ifndef FS_IOC_ENABLE_VERITY
-#define FS_IOC_ENABLE_VERITY 1082156677
-#endif
-
-#ifndef FS_IOC_MEASURE_VERITY
-#define FS_IOC_MEASURE_VERITY 3221513862
-#endif
-
-#ifndef FS_VERITY_FL
-#define FS_VERITY_FL 1048576
-#endif
-
-#ifndef FS_IOC_GETFLAGS
-#define FS_IOC_GETFLAGS 2148034049
-#endif
-
-struct fsverity_digest {
- __u16 digest_algorithm;
- __u16 digest_size; /* input/output */
- __u8 digest[];
-};
-
-constexpr int kMaxDigestSize = 64;
-constexpr int kDefaultDigestSize = 32;
-constexpr char kContents[] = "foobarbaz";
-constexpr char kMerklePrefix[] = ".merkle.verity.";
-constexpr char kMerkleRootPrefix[] = ".merkleroot.verity.";
-
class IoctlTest : public ::testing::Test {
protected:
void SetUp() override {
@@ -85,80 +58,6 @@ class IoctlTest : public ::testing::Test {
std::string filename_;
};
-// Provide a function to convert bytes to hex string, since
-// absl::BytesToHexString does not seem to be compatible with golang
-// hex.DecodeString used in verity due to zero-padding.
-std::string BytesToHexString(uint8_t bytes[], int size) {
- std::stringstream ss;
- ss << std::hex;
- for (int i = 0; i < size; ++i) {
- ss << std::setw(2) << std::setfill('0') << static_cast<int>(bytes[i]);
- }
- return ss.str();
-}
-
-std::string MerklePath(absl::string_view path) {
- return JoinPath(Dirname(path),
- std::string(kMerklePrefix) + std::string(Basename(path)));
-}
-
-std::string MerkleRootPath(absl::string_view path) {
- return JoinPath(Dirname(path),
- std::string(kMerkleRootPrefix) + std::string(Basename(path)));
-}
-
-// Flip a random bit in the file represented by fd.
-PosixError FlipRandomBit(int fd, int size) {
- // Generate a random offset in the file.
- srand(time(nullptr));
- unsigned int seed = 0;
- int random_offset = rand_r(&seed) % size;
-
- // Read a random byte and flip a bit in it.
- char buf[1];
- RETURN_ERROR_IF_SYSCALL_FAIL(PreadFd(fd, buf, 1, random_offset));
- buf[0] ^= 1;
- RETURN_ERROR_IF_SYSCALL_FAIL(PwriteFd(fd, buf, 1, random_offset));
- return NoError();
-}
-
-// Mount a verity on the tmpfs and enable both the file and the direcotry. Then
-// mount a new verity with measured root hash.
-PosixErrorOr<std::string> MountVerity(std::string tmpfs_dir,
- std::string filename) {
- // Mount a verity fs on the existing tmpfs mount.
- std::string mount_opts = "lower_path=" + tmpfs_dir;
- ASSIGN_OR_RETURN_ERRNO(TempPath verity_dir, TempPath::CreateDir());
- RETURN_ERROR_IF_SYSCALL_FAIL(
- mount("", verity_dir.path().c_str(), "verity", 0, mount_opts.c_str()));
-
- // Enable both the file and the directory.
- ASSIGN_OR_RETURN_ERRNO(
- auto fd, Open(JoinPath(verity_dir.path(), filename), O_RDONLY, 0777));
- RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(fd.get(), FS_IOC_ENABLE_VERITY));
- ASSIGN_OR_RETURN_ERRNO(auto dir_fd, Open(verity_dir.path(), O_RDONLY, 0777));
- RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(dir_fd.get(), FS_IOC_ENABLE_VERITY));
-
- // Measure the root hash.
- uint8_t digest_array[sizeof(struct fsverity_digest) + kMaxDigestSize] = {0};
- struct fsverity_digest* digest =
- reinterpret_cast<struct fsverity_digest*>(digest_array);
- digest->digest_size = kMaxDigestSize;
- RETURN_ERROR_IF_SYSCALL_FAIL(
- ioctl(dir_fd.get(), FS_IOC_MEASURE_VERITY, digest));
-
- // Mount a verity fs with specified root hash.
- mount_opts +=
- ",root_hash=" + BytesToHexString(digest->digest, digest->digest_size);
- ASSIGN_OR_RETURN_ERRNO(TempPath verity_with_hash_dir, TempPath::CreateDir());
- RETURN_ERROR_IF_SYSCALL_FAIL(mount("", verity_with_hash_dir.path().c_str(),
- "verity", 0, mount_opts.c_str()));
- // Verity directories should not be deleted. Release the TempPath objects to
- // prevent those directories from being deleted by the destructor.
- verity_dir.release();
- return verity_with_hash_dir.release();
-}
-
TEST_F(IoctlTest, Enable) {
// Mount a verity fs on the existing tmpfs mount.
std::string mount_opts = "lower_path=" + tmpfs_dir_.path();
diff --git a/test/syscalls/linux/verity_mmap.cc b/test/syscalls/linux/verity_mmap.cc
new file mode 100644
index 000000000..dde74cc91
--- /dev/null
+++ b/test/syscalls/linux/verity_mmap.cc
@@ -0,0 +1,158 @@
+// Copyright 2021 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "test/util/capability_util.h"
+#include "test/util/fs_util.h"
+#include "test/util/memory_util.h"
+#include "test/util/temp_path.h"
+#include "test/util/test_util.h"
+#include "test/util/verity_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+class MmapTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Verity is implemented in VFS2.
+ SKIP_IF(IsRunningWithVFS1());
+
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
+ // Mount a tmpfs file system, to be wrapped by a verity fs.
+ tmpfs_dir_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ ASSERT_THAT(mount("", tmpfs_dir_.path().c_str(), "tmpfs", 0, ""),
+ SyscallSucceeds());
+
+ // Create a new file in the tmpfs mount.
+ file_ = ASSERT_NO_ERRNO_AND_VALUE(
+ TempPath::CreateFileWith(tmpfs_dir_.path(), kContents, 0777));
+ filename_ = Basename(file_.path());
+ }
+
+ TempPath tmpfs_dir_;
+ TempPath file_;
+ std::string filename_;
+};
+
+TEST_F(MmapTest, MmapRead) {
+ std::string verity_dir =
+ ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_));
+
+ // Make sure the file can be open and mmapped in the mounted verity fs.
+ auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777));
+
+ Mapping const m =
+ ASSERT_NO_ERRNO_AND_VALUE(Mmap(nullptr, sizeof(kContents) - 1, PROT_READ,
+ MAP_SHARED, verity_fd.get(), 0));
+ EXPECT_THAT(std::string(m.view()), ::testing::StrEq(kContents));
+}
+
+TEST_F(MmapTest, ModifiedBeforeMmap) {
+ std::string verity_dir =
+ ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_));
+
+ // Modify the file and check verification failure upon mmapping.
+ auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Open(JoinPath(tmpfs_dir_.path(), filename_), O_RDWR, 0777));
+ ASSERT_NO_ERRNO(FlipRandomBit(fd.get(), sizeof(kContents) - 1));
+
+ auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777));
+ Mapping const m =
+ ASSERT_NO_ERRNO_AND_VALUE(Mmap(nullptr, sizeof(kContents) - 1, PROT_READ,
+ MAP_SHARED, verity_fd.get(), 0));
+
+ // Memory fault is expected when Translate fails.
+ EXPECT_EXIT(std::string(m.view()), ::testing::KilledBySignal(SIGSEGV), "");
+}
+
+TEST_F(MmapTest, ModifiedAfterMmap) {
+ std::string verity_dir =
+ ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_));
+
+ auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777));
+ Mapping const m =
+ ASSERT_NO_ERRNO_AND_VALUE(Mmap(nullptr, sizeof(kContents) - 1, PROT_READ,
+ MAP_SHARED, verity_fd.get(), 0));
+
+ // Modify the file after mapping and check verification failure upon mmapping.
+ auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Open(JoinPath(tmpfs_dir_.path(), filename_), O_RDWR, 0777));
+ ASSERT_NO_ERRNO(FlipRandomBit(fd.get(), sizeof(kContents) - 1));
+
+ // Memory fault is expected when Translate fails.
+ EXPECT_EXIT(std::string(m.view()), ::testing::KilledBySignal(SIGSEGV), "");
+}
+
+class MmapParamTest
+ : public MmapTest,
+ public ::testing::WithParamInterface<std::tuple<int, int>> {
+ protected:
+ int prot() const { return std::get<0>(GetParam()); }
+ int flags() const { return std::get<1>(GetParam()); }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+ WriteExecNoneSharedPrivate, MmapParamTest,
+ ::testing::Combine(::testing::ValuesIn({
+ PROT_WRITE,
+ PROT_EXEC,
+ PROT_NONE,
+ }),
+ ::testing::ValuesIn({MAP_SHARED, MAP_PRIVATE})));
+
+TEST_P(MmapParamTest, Mmap) {
+ std::string verity_dir =
+ ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_));
+
+ // Make sure the file can be open and mmapped in the mounted verity fs.
+ auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777));
+
+ if (prot() == PROT_WRITE && flags() == MAP_SHARED) {
+ // Verity file system is read-only.
+ EXPECT_THAT(
+ reinterpret_cast<intptr_t>(mmap(nullptr, sizeof(kContents) - 1, prot(),
+ flags(), verity_fd.get(), 0)),
+ SyscallFailsWithErrno(EACCES));
+ } else {
+ Mapping const m = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
+ nullptr, sizeof(kContents) - 1, prot(), flags(), verity_fd.get(), 0));
+ if (prot() == PROT_NONE) {
+ // Memory mapped by MAP_NONE cannot be accessed.
+ EXPECT_EXIT(std::string(m.view()), ::testing::KilledBySignal(SIGSEGV),
+ "");
+ } else {
+ EXPECT_THAT(std::string(m.view()), ::testing::StrEq(kContents));
+ }
+ }
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/xattr.cc b/test/syscalls/linux/xattr.cc
index dd8067807..c8a97df6b 100644
--- a/test/syscalls/linux/xattr.cc
+++ b/test/syscalls/linux/xattr.cc
@@ -109,8 +109,8 @@ TEST_F(XattrTest, XattrInvalidPrefix) {
// the restore will fail to open it with r/w permissions.
TEST_F(XattrTest, XattrReadOnly) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
const char* path = test_file_name_.c_str();
const char name[] = "user.test";
@@ -140,8 +140,8 @@ TEST_F(XattrTest, XattrReadOnly) {
// the restore will fail to open it with r/w permissions.
TEST_F(XattrTest, XattrWriteOnly) {
// Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ AutoCapability cap1(CAP_DAC_OVERRIDE, false);
+ AutoCapability cap2(CAP_DAC_READ_SEARCH, false);
DisableSave ds;
ASSERT_NO_ERRNO(testing::Chmod(test_file_name_, S_IWUSR));
@@ -632,7 +632,7 @@ TEST_F(XattrTest, TrustedNamespaceWithCapSysAdmin) {
// Trusted namespace not supported in VFS1.
SKIP_IF(IsRunningWithVFS1());
- // TODO(b/66162845): Only gVisor tmpfs currently supports trusted namespace.
+ // TODO(b/166162845): Only gVisor tmpfs currently supports trusted namespace.
SKIP_IF(IsRunningOnGvisor() &&
!ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(test_file_name_)));
@@ -680,9 +680,7 @@ TEST_F(XattrTest, TrustedNamespaceWithoutCapSysAdmin) {
!ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(test_file_name_)));
// Drop CAP_SYS_ADMIN if we have it.
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))) {
- EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false));
- }
+ AutoCapability cap(CAP_SYS_ADMIN, false);
const char* path = test_file_name_.c_str();
const char name[] = "trusted.test";
diff --git a/test/util/BUILD b/test/util/BUILD
index 6feda0e26..cc83221ea 100644
--- a/test/util/BUILD
+++ b/test/util/BUILD
@@ -149,6 +149,18 @@ cc_library(
],
)
+cc_test(
+ name = "mount_util_test",
+ size = "small",
+ srcs = ["mount_util_test.cc"],
+ deps = [
+ ":mount_util",
+ ":test_main",
+ ":test_util",
+ gtest,
+ ],
+)
+
cc_library(
name = "save_util",
testonly = 1,
@@ -389,3 +401,16 @@ cc_library(
"@com_google_absl//absl/strings",
],
)
+
+cc_library(
+ name = "verity_util",
+ testonly = 1,
+ srcs = ["verity_util.cc"],
+ hdrs = ["verity_util.h"],
+ deps = [
+ ":fs_util",
+ ":mount_util",
+ ":posix_error",
+ ":temp_path",
+ ],
+)
diff --git a/test/util/cgroup_util.cc b/test/util/cgroup_util.cc
index 04d4f8de0..977993f41 100644
--- a/test/util/cgroup_util.cc
+++ b/test/util/cgroup_util.cc
@@ -142,6 +142,20 @@ PosixError Mounter::Unmount(const Cgroup& c) {
return NoError();
}
+void Mounter::release(const Cgroup& c) {
+ auto mp = mountpoints_.find(c.id());
+ if (mp != mountpoints_.end()) {
+ mp->second.release();
+ mountpoints_.erase(mp);
+ }
+
+ auto m = mounts_.find(c.id());
+ if (m != mounts_.end()) {
+ m->second.Release();
+ mounts_.erase(m);
+ }
+}
+
constexpr char kProcCgroupsHeader[] =
"#subsys_name\thierarchy\tnum_cgroups\tenabled";
diff --git a/test/util/cgroup_util.h b/test/util/cgroup_util.h
index b797a8b24..e3f696a89 100644
--- a/test/util/cgroup_util.h
+++ b/test/util/cgroup_util.h
@@ -83,6 +83,8 @@ class Mounter {
PosixError Unmount(const Cgroup& c);
+ void release(const Cgroup& c);
+
private:
// The destruction order of these members avoids errors during cleanup. We
// first unmount (by executing the mounts_ cleanups), then delete the
diff --git a/test/util/mount_util.cc b/test/util/mount_util.cc
index a79ce6420..48640d6a1 100644
--- a/test/util/mount_util.cc
+++ b/test/util/mount_util.cc
@@ -26,9 +26,14 @@ namespace testing {
PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntries() {
std::string content;
RETURN_IF_ERRNO(GetContents("/proc/self/mounts", &content));
+ return ProcSelfMountsEntriesFrom(content);
+}
+PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntriesFrom(
+ const std::string& content) {
std::vector<ProcMountsEntry> entries;
- std::vector<std::string> lines = absl::StrSplit(content, '\n');
+ std::vector<std::string> lines =
+ absl::StrSplit(content, absl::ByChar('\n'), absl::AllowEmpty());
std::cerr << "<contents of /proc/self/mounts>" << std::endl;
for (const std::string& line : lines) {
std::cerr << line << std::endl;
@@ -47,11 +52,11 @@ PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntries() {
ProcMountsEntry entry;
std::vector<std::string> fields =
- absl::StrSplit(line, absl::ByChar(' '), absl::SkipEmpty());
+ absl::StrSplit(line, absl::ByChar(' '), absl::AllowEmpty());
if (fields.size() != 6) {
- return PosixError(EINVAL,
- absl::StrFormat("Not enough tokens, got %d, line: %s",
- fields.size(), line));
+ return PosixError(
+ EINVAL, absl::StrFormat("Not enough tokens, got %d, content: <<%s>>",
+ fields.size(), content));
}
entry.spec = fields[0];
@@ -71,9 +76,14 @@ PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntries() {
PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntries() {
std::string content;
RETURN_IF_ERRNO(GetContents("/proc/self/mountinfo", &content));
+ return ProcSelfMountInfoEntriesFrom(content);
+}
+PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntriesFrom(
+ const std::string& content) {
std::vector<ProcMountInfoEntry> entries;
- std::vector<std::string> lines = absl::StrSplit(content, '\n');
+ std::vector<std::string> lines =
+ absl::StrSplit(content, absl::ByChar('\n'), absl::AllowEmpty());
std::cerr << "<contents of /proc/self/mountinfo>" << std::endl;
for (const std::string& line : lines) {
std::cerr << line << std::endl;
@@ -92,12 +102,12 @@ PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntries() {
ProcMountInfoEntry entry;
std::vector<std::string> fields =
- absl::StrSplit(line, absl::ByChar(' '), absl::SkipEmpty());
+ absl::StrSplit(line, absl::ByChar(' '), absl::AllowEmpty());
if (fields.size() < 10 || fields.size() > 11) {
return PosixError(
- EINVAL,
- absl::StrFormat("Unexpected number of tokens, got %d, line: %s",
- fields.size(), line));
+ EINVAL, absl::StrFormat(
+ "Unexpected number of tokens, got %d, content: <<%s>>",
+ fields.size(), content));
}
ASSIGN_OR_RETURN_ERRNO(entry.id, Atoi<uint64_t>(fields[0]));
@@ -142,7 +152,7 @@ absl::flat_hash_map<std::string, std::string> ParseMountOptions(
std::string mopts) {
absl::flat_hash_map<std::string, std::string> entries;
const std::vector<std::string> tokens =
- absl::StrSplit(mopts, absl::ByChar(','), absl::SkipEmpty());
+ absl::StrSplit(mopts, absl::ByChar(','), absl::AllowEmpty());
for (const auto& token : tokens) {
std::vector<std::string> kv =
absl::StrSplit(token, absl::MaxSplits('=', 1));
diff --git a/test/util/mount_util.h b/test/util/mount_util.h
index b75a490fb..3f8a1c0f1 100644
--- a/test/util/mount_util.h
+++ b/test/util/mount_util.h
@@ -58,6 +58,11 @@ struct ProcMountsEntry {
// ProcSelfMountsEntries returns a parsed representation of /proc/self/mounts.
PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntries();
+// ProcSelfMountsEntries returns a parsed representation of mounts from the
+// provided content.
+PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntriesFrom(
+ const std::string& content);
+
struct ProcMountInfoEntry {
uint64_t id;
uint64_t parent_id;
@@ -76,6 +81,11 @@ struct ProcMountInfoEntry {
// /proc/self/mountinfo.
PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntries();
+// ProcSelfMountInfoEntriesFrom returns a parsed representation of
+// mountinfo from the provided content.
+PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntriesFrom(
+ const std::string&);
+
// Interprets the input string mopts as a comma separated list of mount
// options. A mount option can either be just a value, or a key=value pair. For
// example, the string "rw,relatime,fd=7" will be parsed into a map like { "rw":
diff --git a/test/util/mount_util_test.cc b/test/util/mount_util_test.cc
new file mode 100644
index 000000000..2bcb6cc43
--- /dev/null
+++ b/test/util/mount_util_test.cc
@@ -0,0 +1,47 @@
+// Copyright 2018 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "test/util/mount_util.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+TEST(ParseMounts, Mounts) {
+ auto entries = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntriesFrom(
+ R"proc(sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
+proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
+ /mnt tmpfs rw,noexec 0 0
+)proc"));
+ EXPECT_EQ(entries.size(), 3);
+}
+
+TEST(ParseMounts, MountInfo) {
+ auto entries = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntriesFrom(
+ R"proc(22 28 0:20 / /sys rw,relatime shared:7 - sysfs sysfs rw
+23 28 0:21 / /proc rw,relatime shared:14 - proc proc rw
+2007 8844 0:278 / /mnt rw,noexec - tmpfs rw,mode=123,uid=268601820,gid=5000
+)proc"));
+ EXPECT_EQ(entries.size(), 3);
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/test_util.h b/test/util/test_util.h
index 876ff58db..bcbb388ed 100644
--- a/test/util/test_util.h
+++ b/test/util/test_util.h
@@ -272,10 +272,15 @@ PosixErrorOr<std::vector<OpenFd>> GetOpenFDs();
// Returns the number of hard links to a path.
PosixErrorOr<uint64_t> Links(const std::string& path);
+inline uint64_t ns_elapsed(const struct timespec& begin,
+ const struct timespec& end) {
+ return (end.tv_sec - begin.tv_sec) * 1000000000 +
+ (end.tv_nsec - begin.tv_nsec);
+}
+
inline uint64_t ms_elapsed(const struct timespec& begin,
const struct timespec& end) {
- return (end.tv_sec - begin.tv_sec) * 1000 +
- (end.tv_nsec - begin.tv_nsec) / 1000000;
+ return ns_elapsed(begin, end) / 1000000;
}
namespace internal {
diff --git a/test/util/verity_util.cc b/test/util/verity_util.cc
new file mode 100644
index 000000000..f1b4c251b
--- /dev/null
+++ b/test/util/verity_util.cc
@@ -0,0 +1,93 @@
+// Copyright 2021 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "test/util/verity_util.h"
+
+#include "test/util/fs_util.h"
+#include "test/util/mount_util.h"
+#include "test/util/temp_path.h"
+
+namespace gvisor {
+namespace testing {
+
+std::string BytesToHexString(uint8_t bytes[], int size) {
+ std::stringstream ss;
+ ss << std::hex;
+ for (int i = 0; i < size; ++i) {
+ ss << std::setw(2) << std::setfill('0') << static_cast<int>(bytes[i]);
+ }
+ return ss.str();
+}
+
+std::string MerklePath(absl::string_view path) {
+ return JoinPath(Dirname(path),
+ std::string(kMerklePrefix) + std::string(Basename(path)));
+}
+
+std::string MerkleRootPath(absl::string_view path) {
+ return JoinPath(Dirname(path),
+ std::string(kMerkleRootPrefix) + std::string(Basename(path)));
+}
+
+PosixError FlipRandomBit(int fd, int size) {
+ // Generate a random offset in the file.
+ srand(time(nullptr));
+ unsigned int seed = 0;
+ int random_offset = rand_r(&seed) % size;
+
+ // Read a random byte and flip a bit in it.
+ char buf[1];
+ RETURN_ERROR_IF_SYSCALL_FAIL(PreadFd(fd, buf, 1, random_offset));
+ buf[0] ^= 1;
+ RETURN_ERROR_IF_SYSCALL_FAIL(PwriteFd(fd, buf, 1, random_offset));
+ return NoError();
+}
+
+PosixErrorOr<std::string> MountVerity(std::string tmpfs_dir,
+ std::string filename) {
+ // Mount a verity fs on the existing tmpfs mount.
+ std::string mount_opts = "lower_path=" + tmpfs_dir;
+ ASSIGN_OR_RETURN_ERRNO(TempPath verity_dir, TempPath::CreateDir());
+ RETURN_ERROR_IF_SYSCALL_FAIL(
+ mount("", verity_dir.path().c_str(), "verity", 0, mount_opts.c_str()));
+
+ // Enable both the file and the directory.
+ ASSIGN_OR_RETURN_ERRNO(
+ auto fd, Open(JoinPath(verity_dir.path(), filename), O_RDONLY, 0777));
+ RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(fd.get(), FS_IOC_ENABLE_VERITY));
+ ASSIGN_OR_RETURN_ERRNO(auto dir_fd, Open(verity_dir.path(), O_RDONLY, 0777));
+ RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(dir_fd.get(), FS_IOC_ENABLE_VERITY));
+
+ // Measure the root hash.
+ uint8_t digest_array[sizeof(struct fsverity_digest) + kMaxDigestSize] = {0};
+ struct fsverity_digest* digest =
+ reinterpret_cast<struct fsverity_digest*>(digest_array);
+ digest->digest_size = kMaxDigestSize;
+ RETURN_ERROR_IF_SYSCALL_FAIL(
+ ioctl(dir_fd.get(), FS_IOC_MEASURE_VERITY, digest));
+
+ // Mount a verity fs with specified root hash.
+ mount_opts +=
+ ",root_hash=" + BytesToHexString(digest->digest, digest->digest_size);
+ ASSIGN_OR_RETURN_ERRNO(TempPath verity_with_hash_dir, TempPath::CreateDir());
+ RETURN_ERROR_IF_SYSCALL_FAIL(mount("", verity_with_hash_dir.path().c_str(),
+ "verity", 0, mount_opts.c_str()));
+ // Verity directories should not be deleted. Release the TempPath objects to
+ // prevent those directories from being deleted by the destructor.
+ verity_dir.release();
+ return verity_with_hash_dir.release();
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/util/verity_util.h b/test/util/verity_util.h
new file mode 100644
index 000000000..18743ecd6
--- /dev/null
+++ b/test/util/verity_util.h
@@ -0,0 +1,75 @@
+// 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.
+
+#ifndef GVISOR_TEST_UTIL_VERITY_UTIL_H_
+#define GVISOR_TEST_UTIL_VERITY_UTIL_H_
+
+#include <stdint.h>
+
+#include "test/util/posix_error.h"
+
+namespace gvisor {
+namespace testing {
+
+#ifndef FS_IOC_ENABLE_VERITY
+#define FS_IOC_ENABLE_VERITY 1082156677
+#endif
+
+#ifndef FS_IOC_MEASURE_VERITY
+#define FS_IOC_MEASURE_VERITY 3221513862
+#endif
+
+#ifndef FS_VERITY_FL
+#define FS_VERITY_FL 1048576
+#endif
+
+#ifndef FS_IOC_GETFLAGS
+#define FS_IOC_GETFLAGS 2148034049
+#endif
+
+struct fsverity_digest {
+ unsigned short digest_algorithm;
+ unsigned short digest_size; /* input/output */
+ unsigned char digest[];
+};
+
+constexpr int kMaxDigestSize = 64;
+constexpr int kDefaultDigestSize = 32;
+constexpr char kContents[] = "foobarbaz";
+constexpr char kMerklePrefix[] = ".merkle.verity.";
+constexpr char kMerkleRootPrefix[] = ".merkleroot.verity.";
+
+// Get the Merkle tree file path for |path|.
+std::string MerklePath(absl::string_view path);
+
+// Get the root Merkle tree file path for |path|.
+std::string MerkleRootPath(absl::string_view path);
+
+// Provide a function to convert bytes to hex string, since
+// absl::BytesToHexString does not seem to be compatible with golang
+// hex.DecodeString used in verity due to zero-padding.
+std::string BytesToHexString(uint8_t bytes[], int size);
+
+// Flip a random bit in the file represented by fd.
+PosixError FlipRandomBit(int fd, int size);
+
+// Mount a verity on the tmpfs and enable both the file and the direcotry. Then
+// mount a new verity with measured root hash.
+PosixErrorOr<std::string> MountVerity(std::string tmpfs_dir,
+ std::string filename);
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_UTIL_VERITY_UTIL_H_