diff options
Diffstat (limited to 'test')
28 files changed, 950 insertions, 192 deletions
diff --git a/test/benchmarks/base/BUILD b/test/benchmarks/base/BUILD index 32c139204..7dfd4b693 100644 --- a/test/benchmarks/base/BUILD +++ b/test/benchmarks/base/BUILD @@ -13,7 +13,7 @@ go_library( go_test( name = "base_test", - size = "large", + size = "enormous", srcs = [ "size_test.go", "startup_test.go", diff --git a/test/fuse/linux/stat_test.cc b/test/fuse/linux/stat_test.cc index 6f032cac1..73321592b 100644 --- a/test/fuse/linux/stat_test.cc +++ b/test/fuse/linux/stat_test.cc @@ -76,8 +76,13 @@ TEST_F(StatTest, StatNormal) { // Check filesystem operation result. struct stat expected_stat = { .st_ino = attr.ino, +#ifdef __aarch64__ + .st_mode = expected_mode, + .st_nlink = attr.nlink, +#else .st_nlink = attr.nlink, .st_mode = expected_mode, +#endif .st_uid = attr.uid, .st_gid = attr.gid, .st_rdev = attr.rdev, @@ -152,8 +157,13 @@ TEST_F(StatTest, FstatNormal) { // Check filesystem operation result. struct stat expected_stat = { .st_ino = attr.ino, +#ifdef __aarch64__ + .st_mode = expected_mode, + .st_nlink = attr.nlink, +#else .st_nlink = attr.nlink, .st_mode = expected_mode, +#endif .st_uid = attr.uid, .st_gid = attr.gid, .st_rdev = attr.rdev, diff --git a/test/iptables/README.md b/test/iptables/README.md index 28ab195ca..1196f8eb5 100644 --- a/test/iptables/README.md +++ b/test/iptables/README.md @@ -2,8 +2,38 @@ iptables tests are run via `make iptables-tests`. -iptables requires raw socket support, so you must add the `--net-raw=true` flag -to `/etc/docker/daemon.json` in order to use it. +iptables require some extra Docker configuration to work. Enable IPv6 in +`/etc/docker/daemon.json` (make sure to restart Docker if you change this file): + +```json +{ + "experimental": true, + "fixed-cidr-v6": "2001:db8:1::/64", + "ipv6": true, + // Runtimes and other Docker config... +} +``` + +And if you're running manually (i.e. not using the `make` target), you'll need +to: + +* Enable iptables via `modprobe iptables_filter && modprobe ip6table_filter`. +* Enable `--net-raw` in your chosen runtime in `/etc/docker/daemon.json` (make + sure to restart Docker if you change this file). + +The resulting runtime should look something like this: + +```json +"runsc": { + "path": "/tmp/iptables/runsc", + "runtimeArgs": [ + "--debug-log", + "/tmp/iptables/logs/runsc.log.%TEST%.%TIMESTAMP%.%COMMAND%", + "--net-raw" + ] +}, +// ... +``` ## Test Structure diff --git a/test/packetimpact/netdevs/netdevs.go b/test/packetimpact/netdevs/netdevs.go index eecfe0730..006988896 100644 --- a/test/packetimpact/netdevs/netdevs.go +++ b/test/packetimpact/netdevs/netdevs.go @@ -40,7 +40,7 @@ var ( deviceLine = regexp.MustCompile(`^\s*(\d+): (\w+)`) linkLine = regexp.MustCompile(`^\s*link/\w+ ([0-9a-fA-F:]+)`) inetLine = regexp.MustCompile(`^\s*inet ([0-9./]+)`) - inet6Line = regexp.MustCompile(`^\s*inet6 ([0-9a-fA-Z:/]+)`) + inet6Line = regexp.MustCompile(`^\s*inet6 ([0-9a-fA-F:/]+)`) ) // ParseDevices parses the output from `ip addr show` into a map from device diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl index 1546d0d51..c03c2c62c 100644 --- a/test/packetimpact/runner/defs.bzl +++ b/test/packetimpact/runner/defs.bzl @@ -252,6 +252,9 @@ ALL_TESTS = [ expect_netstack_failure = True, ), PacketimpactTestInfo( + name = "ipv4_fragment_reassembly", + ), + PacketimpactTestInfo( name = "ipv6_fragment_reassembly", ), PacketimpactTestInfo( diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go index a90046f69..8fa585804 100644 --- a/test/packetimpact/testbench/connections.go +++ b/test/packetimpact/testbench/connections.go @@ -839,6 +839,61 @@ func (conn *TCPIPv4) Drain(t *testing.T) { conn.sniffer.Drain(t) } +// IPv4Conn maintains the state for all the layers in a IPv4 connection. +type IPv4Conn Connection + +// NewIPv4Conn creates a new IPv4Conn connection with reasonable defaults. +func NewIPv4Conn(t *testing.T, outgoingIPv4, incomingIPv4 IPv4) IPv4Conn { + t.Helper() + + etherState, err := newEtherState(Ether{}, Ether{}) + if err != nil { + t.Fatalf("can't make EtherState: %s", err) + } + ipv4State, err := newIPv4State(outgoingIPv4, incomingIPv4) + if err != nil { + t.Fatalf("can't make IPv4State: %s", err) + } + + injector, err := NewInjector(t) + if err != nil { + t.Fatalf("can't make injector: %s", err) + } + sniffer, err := NewSniffer(t) + if err != nil { + t.Fatalf("can't make sniffer: %s", err) + } + + return IPv4Conn{ + layerStates: []layerState{etherState, ipv4State}, + injector: injector, + sniffer: sniffer, + } +} + +// Send sends a frame with ipv4 overriding the IPv4 layer defaults and +// additionalLayers added after it. +func (c *IPv4Conn) Send(t *testing.T, ipv4 IPv4, additionalLayers ...Layer) { + t.Helper() + + (*Connection)(c).send(t, Layers{&ipv4}, additionalLayers...) +} + +// Close cleans up any resources held. +func (c *IPv4Conn) Close(t *testing.T) { + t.Helper() + + (*Connection)(c).Close(t) +} + +// ExpectFrame expects a frame that matches the provided Layers within the +// timeout specified. If it doesn't arrive in time, an error is returned. +func (c *IPv4Conn) ExpectFrame(t *testing.T, frame Layers, timeout time.Duration) (Layers, error) { + t.Helper() + + return (*Connection)(c).ExpectFrame(t, frame, timeout) +} + // IPv6Conn maintains the state for all the layers in a IPv6 connection. type IPv6Conn Connection diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD index 8c2de5a9f..c30c77a17 100644 --- a/test/packetimpact/tests/BUILD +++ b/test/packetimpact/tests/BUILD @@ -298,6 +298,18 @@ packetimpact_testbench( ) packetimpact_testbench( + name = "ipv4_fragment_reassembly", + srcs = ["ipv4_fragment_reassembly_test.go"], + deps = [ + "//pkg/tcpip/buffer", + "//pkg/tcpip/header", + "//test/packetimpact/testbench", + "@com_github_google_go_cmp//cmp:go_default_library", + "@org_golang_x_sys//unix:go_default_library", + ], +) + +packetimpact_testbench( name = "ipv6_fragment_reassembly", srcs = ["ipv6_fragment_reassembly_test.go"], deps = [ @@ -305,6 +317,7 @@ packetimpact_testbench( "//pkg/tcpip/buffer", "//pkg/tcpip/header", "//test/packetimpact/testbench", + "@com_github_google_go_cmp//cmp:go_default_library", "@org_golang_x_sys//unix:go_default_library", ], ) diff --git a/test/packetimpact/tests/ipv4_fragment_reassembly_test.go b/test/packetimpact/tests/ipv4_fragment_reassembly_test.go new file mode 100644 index 000000000..65c0df140 --- /dev/null +++ b/test/packetimpact/tests/ipv4_fragment_reassembly_test.go @@ -0,0 +1,142 @@ +// Copyright 2020 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 ipv4_fragment_reassembly_test + +import ( + "flag" + "math/rand" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "gvisor.dev/gvisor/pkg/tcpip/buffer" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/test/packetimpact/testbench" +) + +func init() { + testbench.RegisterFlags(flag.CommandLine) +} + +type fragmentInfo struct { + offset uint16 + size uint16 + more uint8 +} + +func TestIPv4FragmentReassembly(t *testing.T) { + const fragmentID = 42 + icmpv4ProtoNum := uint8(header.ICMPv4ProtocolNumber) + + tests := []struct { + description string + ipPayloadLen int + fragments []fragmentInfo + expectReply bool + }{ + { + description: "basic reassembly", + ipPayloadLen: 2000, + fragments: []fragmentInfo{ + {offset: 0, size: 1000, more: header.IPv4FlagMoreFragments}, + {offset: 1000, size: 1000, more: 0}, + }, + expectReply: true, + }, + { + description: "out of order fragments", + ipPayloadLen: 2000, + fragments: []fragmentInfo{ + {offset: 1000, size: 1000, more: 0}, + {offset: 0, size: 1000, more: header.IPv4FlagMoreFragments}, + }, + expectReply: true, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + dut := testbench.NewDUT(t) + defer dut.TearDown() + conn := testbench.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{}) + defer conn.Close(t) + + data := make([]byte, test.ipPayloadLen) + icmp := header.ICMPv4(data[:header.ICMPv4MinimumSize]) + icmp.SetType(header.ICMPv4Echo) + icmp.SetCode(header.ICMPv4UnusedCode) + icmp.SetChecksum(0) + icmp.SetSequence(0) + icmp.SetIdent(0) + originalPayload := data[header.ICMPv4MinimumSize:] + if _, err := rand.Read(originalPayload); err != nil { + t.Fatalf("rand.Read: %s", err) + } + cksum := header.ICMPv4Checksum( + icmp, + buffer.NewVectorisedView(len(originalPayload), []buffer.View{originalPayload}), + ) + icmp.SetChecksum(cksum) + + for _, fragment := range test.fragments { + conn.Send(t, + testbench.IPv4{ + Protocol: &icmpv4ProtoNum, + FragmentOffset: testbench.Uint16(fragment.offset), + Flags: testbench.Uint8(fragment.more), + ID: testbench.Uint16(fragmentID), + }, + &testbench.Payload{ + Bytes: data[fragment.offset:][:fragment.size], + }) + } + + var bytesReceived int + reassembledPayload := make([]byte, test.ipPayloadLen) + for { + incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{ + &testbench.Ether{}, + &testbench.IPv4{}, + &testbench.ICMPv4{}, + }, time.Second) + if err != nil { + // Either an unexpected frame was received, or none at all. + if bytesReceived < test.ipPayloadLen { + t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err) + } + break + } + if !test.expectReply { + t.Fatalf("unexpected reply received:\n%s", incomingFrame) + } + ipPayload, err := incomingFrame[2 /* ICMPv4 */].ToBytes() + if err != nil { + t.Fatalf("failed to parse ICMPv4 header: incomingPacket[2].ToBytes() = (_, %s)", err) + } + offset := *incomingFrame[1 /* IPv4 */].(*testbench.IPv4).FragmentOffset + if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) { + t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload)) + } + bytesReceived += len(ipPayload) + } + + if test.expectReply { + if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv4MinimumSize:]); diff != "" { + t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff) + } + } + }) + } +} diff --git a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go index a24c85566..4a29de688 100644 --- a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go +++ b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go @@ -15,154 +15,137 @@ package ipv6_fragment_reassembly_test import ( - "bytes" - "encoding/binary" - "encoding/hex" "flag" + "math/rand" "net" "testing" "time" + "github.com/google/go-cmp/cmp" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/buffer" "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/test/packetimpact/testbench" ) -const ( - // The payload length for the first fragment we send. This number - // is a multiple of 8 near 750 (half of 1500). - firstPayloadLength = 752 - // The ID field for our outgoing fragments. - fragmentID = 1 - // A node must be able to accept a fragmented packet that, - // after reassembly, is as large as 1500 octets. - reassemblyCap = 1500 -) - func init() { testbench.RegisterFlags(flag.CommandLine) } -func TestIPv6FragmentReassembly(t *testing.T) { - dut := testbench.NewDUT(t) - defer dut.TearDown() - conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) - defer conn.Close(t) - - firstPayloadToSend := make([]byte, firstPayloadLength) - for i := range firstPayloadToSend { - firstPayloadToSend[i] = 'A' - } - - secondPayloadLength := reassemblyCap - firstPayloadLength - header.ICMPv6EchoMinimumSize - secondPayloadToSend := firstPayloadToSend[:secondPayloadLength] - - icmpv6EchoPayload := make([]byte, 4) - binary.BigEndian.PutUint16(icmpv6EchoPayload[0:], 0) - binary.BigEndian.PutUint16(icmpv6EchoPayload[2:], 0) - icmpv6EchoPayload = append(icmpv6EchoPayload, firstPayloadToSend...) - - lIP := tcpip.Address(net.ParseIP(testbench.LocalIPv6).To16()) - rIP := tcpip.Address(net.ParseIP(testbench.RemoteIPv6).To16()) - icmpv6 := testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest), - Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode), - Payload: icmpv6EchoPayload, - } - icmpv6Bytes, err := icmpv6.ToBytes() - if err != nil { - t.Fatalf("failed to serialize ICMPv6: %s", err) - } - cksum := header.ICMPv6Checksum( - header.ICMPv6(icmpv6Bytes), - lIP, - rIP, - buffer.NewVectorisedView(len(secondPayloadToSend), []buffer.View{secondPayloadToSend}), - ) - - conn.Send(t, testbench.IPv6{}, - &testbench.IPv6FragmentExtHdr{ - FragmentOffset: testbench.Uint16(0), - MoreFragments: testbench.Bool(true), - Identification: testbench.Uint32(fragmentID), - }, - &testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest), - Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode), - Payload: icmpv6EchoPayload, - Checksum: &cksum, - }) +type fragmentInfo struct { + offset uint16 + size uint16 + more bool +} +func TestIPv6FragmentReassembly(t *testing.T) { + const fragmentID = 42 icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber) - conn.Send(t, testbench.IPv6{}, - &testbench.IPv6FragmentExtHdr{ - NextHeader: &icmpv6ProtoNum, - FragmentOffset: testbench.Uint16((firstPayloadLength + header.ICMPv6EchoMinimumSize) / 8), - MoreFragments: testbench.Bool(false), - Identification: testbench.Uint32(fragmentID), + tests := []struct { + description string + ipPayloadLen int + fragments []fragmentInfo + expectReply bool + }{ + { + description: "basic reassembly", + ipPayloadLen: 1500, + fragments: []fragmentInfo{ + {offset: 0, size: 760, more: true}, + {offset: 760, size: 740, more: false}, + }, + expectReply: true, }, - &testbench.Payload{ - Bytes: secondPayloadToSend, - }) - - gotEchoReplyFirstPart, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &testbench.IPv6FragmentExtHdr{ - FragmentOffset: testbench.Uint16(0), - MoreFragments: testbench.Bool(true), + { + description: "out of order fragments", + ipPayloadLen: 3000, + fragments: []fragmentInfo{ + {offset: 0, size: 1024, more: true}, + {offset: 2048, size: 952, more: false}, + {offset: 1024, size: 1024, more: true}, + }, + expectReply: true, }, - &testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6EchoReply), - Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode), - }, - }, time.Second) - if err != nil { - t.Fatalf("expected a fragmented ICMPv6 Echo Reply, but got none: %s", err) } - id := *gotEchoReplyFirstPart[2].(*testbench.IPv6FragmentExtHdr).Identification - gotFirstPayload, err := gotEchoReplyFirstPart[len(gotEchoReplyFirstPart)-1].ToBytes() - if err != nil { - t.Fatalf("failed to serialize ICMPv6: %s", err) - } - icmpPayload := gotFirstPayload[header.ICMPv6EchoMinimumSize:] - receivedLen := len(icmpPayload) - wantSecondPayloadLen := reassemblyCap - header.ICMPv6EchoMinimumSize - receivedLen - wantFirstPayload := make([]byte, receivedLen) - for i := range wantFirstPayload { - wantFirstPayload[i] = 'A' - } - wantSecondPayload := wantFirstPayload[:wantSecondPayloadLen] - if !bytes.Equal(icmpPayload, wantFirstPayload) { - t.Fatalf("received unexpected payload, got: %s, want: %s", - hex.Dump(icmpPayload), - hex.Dump(wantFirstPayload)) - } - - gotEchoReplySecondPart, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &testbench.IPv6FragmentExtHdr{ - NextHeader: &icmpv6ProtoNum, - FragmentOffset: testbench.Uint16(uint16((receivedLen + header.ICMPv6EchoMinimumSize) / 8)), - MoreFragments: testbench.Bool(false), - Identification: &id, - }, - &testbench.ICMPv6{}, - }, time.Second) - if err != nil { - t.Fatalf("expected the rest of ICMPv6 Echo Reply, but got none: %s", err) - } - secondPayload, err := gotEchoReplySecondPart[len(gotEchoReplySecondPart)-1].ToBytes() - if err != nil { - t.Fatalf("failed to serialize ICMPv6 Echo Reply: %s", err) - } - if !bytes.Equal(secondPayload, wantSecondPayload) { - t.Fatalf("received unexpected payload, got: %s, want: %s", - hex.Dump(secondPayload), - hex.Dump(wantSecondPayload)) + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + dut := testbench.NewDUT(t) + defer dut.TearDown() + conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) + defer conn.Close(t) + + lIP := tcpip.Address(net.ParseIP(testbench.LocalIPv6).To16()) + rIP := tcpip.Address(net.ParseIP(testbench.RemoteIPv6).To16()) + + data := make([]byte, test.ipPayloadLen) + icmp := header.ICMPv6(data[:header.ICMPv6HeaderSize]) + icmp.SetType(header.ICMPv6EchoRequest) + icmp.SetCode(header.ICMPv6UnusedCode) + icmp.SetChecksum(0) + originalPayload := data[header.ICMPv6HeaderSize:] + if _, err := rand.Read(originalPayload); err != nil { + t.Fatalf("rand.Read: %s", err) + } + + cksum := header.ICMPv6Checksum( + icmp, + lIP, + rIP, + buffer.NewVectorisedView(len(originalPayload), []buffer.View{originalPayload}), + ) + icmp.SetChecksum(cksum) + + for _, fragment := range test.fragments { + conn.Send(t, testbench.IPv6{}, + &testbench.IPv6FragmentExtHdr{ + NextHeader: &icmpv6ProtoNum, + FragmentOffset: testbench.Uint16(fragment.offset / header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit), + MoreFragments: testbench.Bool(fragment.more), + Identification: testbench.Uint32(fragmentID), + }, + &testbench.Payload{ + Bytes: data[fragment.offset:][:fragment.size], + }) + } + + var bytesReceived int + reassembledPayload := make([]byte, test.ipPayloadLen) + for { + incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{ + &testbench.Ether{}, + &testbench.IPv6{}, + &testbench.IPv6FragmentExtHdr{}, + &testbench.ICMPv6{}, + }, time.Second) + if err != nil { + // Either an unexpected frame was received, or none at all. + if bytesReceived < test.ipPayloadLen { + t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err) + } + break + } + if !test.expectReply { + t.Fatalf("unexpected reply received:\n%s", incomingFrame) + } + ipPayload, err := incomingFrame[3 /* ICMPv6 */].ToBytes() + if err != nil { + t.Fatalf("failed to parse ICMPv6 header: incomingPacket[3].ToBytes() = (_, %s)", err) + } + offset := *incomingFrame[2 /* IPv6FragmentExtHdr */].(*testbench.IPv6FragmentExtHdr).FragmentOffset + offset *= header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit + if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) { + t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload)) + } + bytesReceived += len(ipPayload) + } + + if test.expectReply { + if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv6HeaderSize:]); diff != "" { + t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff) + } + } + }) } } diff --git a/test/root/oom_score_adj_test.go b/test/root/oom_score_adj_test.go index 4243eb59e..0dcc0fdea 100644 --- a/test/root/oom_score_adj_test.go +++ b/test/root/oom_score_adj_test.go @@ -40,11 +40,7 @@ var ( // TestOOMScoreAdjSingle tests that oom_score_adj is set properly in a // single container sandbox. func TestOOMScoreAdjSingle(t *testing.T) { - ppid, err := specutils.GetParentPid(os.Getpid()) - if err != nil { - t.Fatalf("getting parent pid: %v", err) - } - parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(ppid) + parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(os.Getppid()) if err != nil { t.Fatalf("getting parent oom_score_adj: %v", err) } @@ -122,11 +118,7 @@ func TestOOMScoreAdjSingle(t *testing.T) { // TestOOMScoreAdjMulti tests that oom_score_adj is set properly in a // multi-container sandbox. func TestOOMScoreAdjMulti(t *testing.T) { - ppid, err := specutils.GetParentPid(os.Getpid()) - if err != nil { - t.Fatalf("getting parent pid: %v", err) - } - parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(ppid) + parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(os.Getppid()) if err != nil { t.Fatalf("getting parent oom_score_adj: %v", err) } diff --git a/test/runner/defs.bzl b/test/runner/defs.bzl index 9b5994d59..4992147d4 100644 --- a/test/runner/defs.bzl +++ b/test/runner/defs.bzl @@ -97,10 +97,10 @@ def _syscall_test( # we figure out how to request ipv4 sockets on Guitar machines. if network == "host": tags.append("noguitar") - tags.append("block-network") # Disable off-host networking. tags.append("requires-net:loopback") + tags.append("block-network") # gotsan makes sense only if tests are running in gVisor. if platform == "native": diff --git a/test/runtimes/exclude/php7.3.6.csv b/test/runtimes/exclude/php7.3.6.csv index f984a579a..c051fe571 100644 --- a/test/runtimes/exclude/php7.3.6.csv +++ b/test/runtimes/exclude/php7.3.6.csv @@ -8,6 +8,7 @@ ext/mbstring/tests/bug77165.phpt,, ext/mbstring/tests/bug77454.phpt,, ext/mbstring/tests/mb_convert_encoding_leak.phpt,, ext/mbstring/tests/mb_strrpos_encoding_3rd_param.phpt,, +ext/pcre/tests/cache_limit.phpt,,Broken test - Flaky ext/session/tests/session_module_name_variation4.phpt,,Flaky ext/session/tests/session_set_save_handler_class_018.phpt,, ext/session/tests/session_set_save_handler_iface_003.phpt,, @@ -29,7 +30,7 @@ ext/standard/tests/file/php_fd_wrapper_04.phpt,, ext/standard/tests/file/realpath_bug77484.phpt,b/162894969,VFS1 only failure ext/standard/tests/file/rename_variation.phpt,b/68717309, ext/standard/tests/file/symlink_link_linkinfo_is_link_variation4.phpt,b/162895341, -ext/standard/tests/file/symlink_link_linkinfo_is_link_variation8.phpt,b/162896223, +ext/standard/tests/file/symlink_link_linkinfo_is_link_variation8.phpt,b/162896223,VFS1 only failure ext/standard/tests/general_functions/escapeshellarg_bug71270.phpt,, ext/standard/tests/general_functions/escapeshellcmd_bug71270.phpt,, ext/standard/tests/streams/proc_open_bug60120.phpt,,Flaky until php-src 3852a35fdbcb diff --git a/test/runtimes/exclude/python3.7.3.csv b/test/runtimes/exclude/python3.7.3.csv index 911f22855..e9fef03b7 100644 --- a/test/runtimes/exclude/python3.7.3.csv +++ b/test/runtimes/exclude/python3.7.3.csv @@ -4,7 +4,6 @@ test_asyncore,b/162973328, test_epoll,b/162983393, test_fcntl,b/162978767,fcntl invalid argument -- artificial test to make sure something works in 64 bit mode. test_httplib,b/163000009,OSError: [Errno 98] Address already in use -test_imaplib,b/162979661, test_logging,b/162980079, test_multiprocessing_fork,,Flaky. Sometimes times out. test_multiprocessing_forkserver,,Flaky. Sometimes times out. diff --git a/test/runtimes/runner/lib/lib.go b/test/runtimes/runner/lib/lib.go index 78285cb0e..64e6e14db 100644 --- a/test/runtimes/runner/lib/lib.go +++ b/test/runtimes/runner/lib/lib.go @@ -34,8 +34,16 @@ import ( // RunTests is a helper that is called by main. It exists so that we can run // defered functions before exiting. It returns an exit code that should be // passed to os.Exit. -func RunTests(lang, image, excludeFile string, batchSize int, timeout time.Duration) int { - // Get tests to exclude.. +func RunTests(lang, image, excludeFile string, partitionNum, totalPartitions, batchSize int, timeout time.Duration) int { + if partitionNum <= 0 || totalPartitions <= 0 || partitionNum > totalPartitions { + fmt.Fprintf(os.Stderr, "invalid partition %d of %d", partitionNum, totalPartitions) + return 1 + } + + // TODO(gvisor.dev/issue/1624): Remove those tests from all exclude lists + // that only fail with VFS1. + + // Get tests to exclude. excludes, err := getExcludes(excludeFile) if err != nil { fmt.Fprintf(os.Stderr, "Error getting exclude list: %s\n", err.Error()) @@ -55,7 +63,7 @@ func RunTests(lang, image, excludeFile string, batchSize int, timeout time.Durat // Get a slice of tests to run. This will also start a single Docker // container that will be used to run each test. The final test will // stop the Docker container. - tests, err := getTests(ctx, d, lang, image, batchSize, timeout, excludes) + tests, err := getTests(ctx, d, lang, image, partitionNum, totalPartitions, batchSize, timeout, excludes) if err != nil { fmt.Fprintf(os.Stderr, "%s\n", err.Error()) return 1 @@ -66,7 +74,7 @@ func RunTests(lang, image, excludeFile string, batchSize int, timeout time.Durat } // getTests executes all tests as table tests. -func getTests(ctx context.Context, d *dockerutil.Container, lang, image string, batchSize int, timeout time.Duration, excludes map[string]struct{}) ([]testing.InternalTest, error) { +func getTests(ctx context.Context, d *dockerutil.Container, lang, image string, partitionNum, totalPartitions, batchSize int, timeout time.Duration, excludes map[string]struct{}) ([]testing.InternalTest, error) { // Start the container. opts := dockerutil.RunOpts{ Image: fmt.Sprintf("runtimes/%s", image), @@ -86,6 +94,14 @@ func getTests(ctx context.Context, d *dockerutil.Container, lang, image string, // shard. tests := strings.Fields(list) sort.Strings(tests) + + partitionSize := len(tests) / totalPartitions + if partitionNum == totalPartitions { + tests = tests[(partitionNum-1)*partitionSize:] + } else { + tests = tests[(partitionNum-1)*partitionSize : partitionNum*partitionSize] + } + indices, err := testutil.TestIndicesForShard(len(tests)) if err != nil { return nil, fmt.Errorf("TestsForShard() failed: %v", err) @@ -116,8 +132,15 @@ func getTests(ctx context.Context, d *dockerutil.Container, lang, image string, err error ) + state, err := d.Status(ctx) + if err != nil { + t.Fatalf("Could not find container status: %v", err) + } + if !state.Running { + t.Fatalf("container is not running: state = %s", state.Status) + } + go func() { - fmt.Printf("RUNNING the following in a batch\n%s\n", strings.Join(tcs, "\n")) output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/proctor/proctor", "--runtime", lang, "--tests", strings.Join(tcs, ",")) close(done) }() @@ -125,12 +148,12 @@ func getTests(ctx context.Context, d *dockerutil.Container, lang, image string, select { case <-done: if err == nil { - fmt.Printf("PASS: (%v)\n\n", time.Since(now)) + fmt.Printf("PASS: (%v) %d tests passed\n", time.Since(now), len(tcs)) return } - t.Errorf("FAIL: (%v):\n%s\n", time.Since(now), output) + t.Errorf("FAIL: (%v):\nBatch:\n%s\nOutput:\n%s\n", time.Since(now), strings.Join(tcs, "\n"), output) case <-time.After(timeout): - t.Errorf("TIMEOUT: (%v):\n%s\n", time.Since(now), output) + t.Errorf("TIMEOUT: (%v):\nBatch:\n%s\nOutput:\n%s\n", time.Since(now), strings.Join(tcs, "\n"), output) } }, }) diff --git a/test/runtimes/runner/main.go b/test/runtimes/runner/main.go index ec79a22c2..5b3443e36 100644 --- a/test/runtimes/runner/main.go +++ b/test/runtimes/runner/main.go @@ -25,11 +25,13 @@ import ( ) var ( - lang = flag.String("lang", "", "language runtime to test") - image = flag.String("image", "", "docker image with runtime tests") - excludeFile = flag.String("exclude_file", "", "file containing list of tests to exclude, in CSV format with fields: test name, bug id, comment") - batchSize = flag.Int("batch", 50, "number of test cases run in one command") - timeout = flag.Duration("timeout", 90*time.Minute, "batch timeout") + lang = flag.String("lang", "", "language runtime to test") + image = flag.String("image", "", "docker image with runtime tests") + excludeFile = flag.String("exclude_file", "", "file containing list of tests to exclude, in CSV format with fields: test name, bug id, comment") + partition = flag.Int("partition", 1, "partition number, this is 1-indexed") + totalPartitions = flag.Int("total_partitions", 1, "total number of partitions") + batchSize = flag.Int("batch", 50, "number of test cases run in one command") + timeout = flag.Duration("timeout", 90*time.Minute, "batch timeout") ) func main() { @@ -38,5 +40,5 @@ func main() { fmt.Fprintf(os.Stderr, "lang and image flags must not be empty\n") os.Exit(1) } - os.Exit(lib.RunTests(*lang, *image, *excludeFile, *batchSize, *timeout)) + os.Exit(lib.RunTests(*lang, *image, *excludeFile, *partition, *totalPartitions, *batchSize, *timeout)) } diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index f66a9ceb4..b5a4ef4df 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -695,6 +695,10 @@ syscall_test( ) syscall_test( + test = "//test/syscalls/linux:socket_ip_unbound_netlink_test", +) + +syscall_test( test = "//test/syscalls/linux:socket_netdevice_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index c94c1d5bd..2350f7e69 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -1802,10 +1802,14 @@ cc_binary( "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/time", gtest, + "//test/util:file_descriptor", + "//test/util:fs_util", "//test/util:logging", + "//test/util:memory_util", "//test/util:multiprocess_util", "//test/util:platform_util", "//test/util:signal_util", + "//test/util:temp_path", "//test/util:test_util", "//test/util:thread_util", "//test/util:time_util", @@ -2102,10 +2106,12 @@ cc_binary( "@com_google_absl//absl/strings", "@com_google_absl//absl/time", gtest, + "//test/util:signal_util", "//test/util:temp_path", "//test/util:test_main", "//test/util:test_util", "//test/util:thread_util", + "//test/util:timer_util", ], ) @@ -2125,6 +2131,7 @@ cc_binary( "//test/util:test_main", "//test/util:test_util", "//test/util:thread_util", + "//test/util:timer_util", ], ) @@ -2138,10 +2145,12 @@ cc_binary( "@com_google_absl//absl/strings", "@com_google_absl//absl/time", gtest, + "//test/util:signal_util", "//test/util:temp_path", "//test/util:test_main", "//test/util:test_util", "//test/util:thread_util", + "//test/util:timer_util", ], ) @@ -2880,6 +2889,24 @@ cc_binary( ) cc_binary( + name = "socket_ip_unbound_netlink_test", + testonly = 1, + srcs = [ + "socket_ip_unbound_netlink.cc", + ], + linkstatic = 1, + deps = [ + ":ip_socket_test_util", + ":socket_netlink_route_util", + ":socket_test_util", + "//test/util:capability_util", + gtest, + "//test/util:test_main", + "//test/util:test_util", + ], +) + +cc_binary( name = "socket_domain_test", testonly = 1, srcs = [ diff --git a/test/syscalls/linux/ptrace.cc b/test/syscalls/linux/ptrace.cc index 926690eb8..13c19d4a8 100644 --- a/test/syscalls/linux/ptrace.cc +++ b/test/syscalls/linux/ptrace.cc @@ -30,10 +30,13 @@ #include "absl/flags/flag.h" #include "absl/time/clock.h" #include "absl/time/time.h" +#include "test/util/fs_util.h" #include "test/util/logging.h" +#include "test/util/memory_util.h" #include "test/util/multiprocess_util.h" #include "test/util/platform_util.h" #include "test/util/signal_util.h" +#include "test/util/temp_path.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" #include "test/util/time_util.h" @@ -113,10 +116,21 @@ TEST(PtraceTest, AttachParent_PeekData_PokeData_SignalSuppression) { // except disabled. SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) > 0); - constexpr long kBeforePokeDataValue = 10; - constexpr long kAfterPokeDataValue = 20; + // Test PTRACE_POKE/PEEKDATA on both anonymous and file mappings. + const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + ASSERT_NO_ERRNO(Truncate(file.path(), kPageSize)); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); + const auto file_mapping = ASSERT_NO_ERRNO_AND_VALUE(Mmap( + nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0)); - volatile long word = kBeforePokeDataValue; + constexpr long kBeforePokeDataAnonValue = 10; + constexpr long kAfterPokeDataAnonValue = 20; + constexpr long kBeforePokeDataFileValue = 0; // implicit, due to truncate() + constexpr long kAfterPokeDataFileValue = 30; + + volatile long anon_word = kBeforePokeDataAnonValue; + auto* file_word_ptr = static_cast<volatile long*>(file_mapping.ptr()); pid_t const child_pid = fork(); if (child_pid == 0) { @@ -134,12 +148,22 @@ TEST(PtraceTest, AttachParent_PeekData_PokeData_SignalSuppression) { MaybeSave(); TEST_CHECK(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP); - // Replace the value of word in the parent process with kAfterPokeDataValue. - long const parent_word = ptrace(PTRACE_PEEKDATA, parent_pid, &word, 0); + // Replace the value of anon_word in the parent process with + // kAfterPokeDataAnonValue. + long parent_word = ptrace(PTRACE_PEEKDATA, parent_pid, &anon_word, 0); + MaybeSave(); + TEST_CHECK(parent_word == kBeforePokeDataAnonValue); + TEST_PCHECK(ptrace(PTRACE_POKEDATA, parent_pid, &anon_word, + kAfterPokeDataAnonValue) == 0); + MaybeSave(); + + // Replace the value pointed to by file_word_ptr in the mapped file with + // kAfterPokeDataFileValue, via the parent process' mapping. + parent_word = ptrace(PTRACE_PEEKDATA, parent_pid, file_word_ptr, 0); MaybeSave(); - TEST_CHECK(parent_word == kBeforePokeDataValue); - TEST_PCHECK( - ptrace(PTRACE_POKEDATA, parent_pid, &word, kAfterPokeDataValue) == 0); + TEST_CHECK(parent_word == kBeforePokeDataFileValue); + TEST_PCHECK(ptrace(PTRACE_POKEDATA, parent_pid, file_word_ptr, + kAfterPokeDataFileValue) == 0); MaybeSave(); // Detach from the parent and suppress the SIGSTOP. If the SIGSTOP is not @@ -160,7 +184,8 @@ TEST(PtraceTest, AttachParent_PeekData_PokeData_SignalSuppression) { << " status " << status; // Check that the child's PTRACE_POKEDATA was effective. - EXPECT_EQ(kAfterPokeDataValue, word); + EXPECT_EQ(kAfterPokeDataAnonValue, anon_word); + EXPECT_EQ(kAfterPokeDataFileValue, *file_word_ptr); } TEST(PtraceTest, GetSigMask) { diff --git a/test/syscalls/linux/semaphore.cc b/test/syscalls/linux/semaphore.cc index ed6a1c2aa..890f4a246 100644 --- a/test/syscalls/linux/semaphore.cc +++ b/test/syscalls/linux/semaphore.cc @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <signal.h> #include <sys/ipc.h> #include <sys/sem.h> #include <sys/types.h> @@ -542,6 +543,236 @@ TEST(SemaphoreTest, SemCtlIpcStat) { SyscallFailsWithErrno(EACCES)); } +// Calls semctl(semid, 0, cmd) until the returned value is >= target, an +// internal timeout expires, or semctl returns an error. +PosixErrorOr<int> WaitSemctl(int semid, int target, int cmd) { + constexpr absl::Duration timeout = absl::Seconds(10); + const auto deadline = absl::Now() + timeout; + int semcnt = 0; + while (absl::Now() < deadline) { + semcnt = semctl(semid, 0, cmd); + if (semcnt < 0) { + return PosixError(errno, "semctl(GETZCNT) failed"); + } + if (semcnt >= target) { + break; + } + absl::SleepFor(absl::Milliseconds(10)); + } + return semcnt; +} + +TEST(SemaphoreTest, SemopGetzcnt) { + // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions. + ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false)); + // Create a write only semaphore set. + AutoSem sem(semget(IPC_PRIVATE, 1, 0200 | IPC_CREAT)); + ASSERT_THAT(sem.get(), SyscallSucceeds()); + + // No read permission to retrieve semzcnt. + EXPECT_THAT(semctl(sem.get(), 0, GETZCNT), SyscallFailsWithErrno(EACCES)); + + // Remove the calling thread's read permission. + struct semid_ds ds = {}; + ds.sem_perm.uid = getuid(); + ds.sem_perm.gid = getgid(); + ds.sem_perm.mode = 0600; + ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &ds), SyscallSucceeds()); + + std::vector<pid_t> children; + ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 1), SyscallSucceeds()); + + struct sembuf buf = {}; + buf.sem_num = 0; + buf.sem_op = 0; + constexpr size_t kLoops = 10; + for (auto i = 0; i < kLoops; i++) { + auto child_pid = fork(); + if (child_pid == 0) { + TEST_PCHECK(RetryEINTR(semop)(sem.get(), &buf, 1) == 0); + _exit(0); + } + children.push_back(child_pid); + } + + EXPECT_THAT(WaitSemctl(sem.get(), kLoops, GETZCNT), + IsPosixErrorOkAndHolds(kLoops)); + // Set semval to 0, which wakes up children that sleep on the semop. + ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 0), SyscallSucceeds()); + for (const auto& child_pid : children) { + int status; + ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), + SyscallSucceedsWithValue(child_pid)); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } + EXPECT_EQ(semctl(sem.get(), 0, GETZCNT), 0); +} + +TEST(SemaphoreTest, SemopGetzcntOnSetRemoval) { + auto semid = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT); + ASSERT_THAT(semid, SyscallSucceeds()); + ASSERT_THAT(semctl(semid, 0, SETVAL, 1), SyscallSucceeds()); + ASSERT_EQ(semctl(semid, 0, GETZCNT), 0); + + auto child_pid = fork(); + if (child_pid == 0) { + struct sembuf buf = {}; + buf.sem_num = 0; + buf.sem_op = 0; + + // Ensure that wait will only unblock when the semaphore is removed. On + // EINTR retry it may race with deletion and return EINVAL. + TEST_PCHECK(RetryEINTR(semop)(semid, &buf, 1) < 0 && + (errno == EIDRM || errno == EINVAL)); + _exit(0); + } + + EXPECT_THAT(WaitSemctl(semid, 1, GETZCNT), IsPosixErrorOkAndHolds(1)); + // Remove the semaphore set, which fails the sleep semop. + ASSERT_THAT(semctl(semid, 0, IPC_RMID), SyscallSucceeds()); + int status; + ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), + SyscallSucceedsWithValue(child_pid)); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + EXPECT_THAT(semctl(semid, 0, GETZCNT), SyscallFailsWithErrno(EINVAL)); +} + +TEST(SemaphoreTest, SemopGetzcntOnSignal_NoRandomSave) { + AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); + ASSERT_THAT(sem.get(), SyscallSucceeds()); + ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 1), SyscallSucceeds()); + ASSERT_EQ(semctl(sem.get(), 0, GETZCNT), 0); + + // Saving will cause semop() to be spuriously interrupted. + DisableSave ds; + + auto child_pid = fork(); + if (child_pid == 0) { + TEST_PCHECK(signal(SIGHUP, [](int sig) -> void {}) != SIG_ERR); + struct sembuf buf = {}; + buf.sem_num = 0; + buf.sem_op = 0; + + TEST_PCHECK(semop(sem.get(), &buf, 1) < 0 && errno == EINTR); + _exit(0); + } + + EXPECT_THAT(WaitSemctl(sem.get(), 1, GETZCNT), IsPosixErrorOkAndHolds(1)); + // Send a signal to the child, which fails the sleep semop. + ASSERT_EQ(kill(child_pid, SIGHUP), 0); + + ds.reset(); + + int status; + ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), + SyscallSucceedsWithValue(child_pid)); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + EXPECT_EQ(semctl(sem.get(), 0, GETZCNT), 0); +} + +TEST(SemaphoreTest, SemopGetncnt) { + // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions. + ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false)); + // Create a write only semaphore set. + AutoSem sem(semget(IPC_PRIVATE, 1, 0200 | IPC_CREAT)); + ASSERT_THAT(sem.get(), SyscallSucceeds()); + + // No read permission to retrieve semzcnt. + EXPECT_THAT(semctl(sem.get(), 0, GETNCNT), SyscallFailsWithErrno(EACCES)); + + // Remove the calling thread's read permission. + struct semid_ds ds = {}; + ds.sem_perm.uid = getuid(); + ds.sem_perm.gid = getgid(); + ds.sem_perm.mode = 0600; + ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &ds), SyscallSucceeds()); + + std::vector<pid_t> children; + + struct sembuf buf = {}; + buf.sem_num = 0; + buf.sem_op = -1; + constexpr size_t kLoops = 10; + for (auto i = 0; i < kLoops; i++) { + auto child_pid = fork(); + if (child_pid == 0) { + TEST_PCHECK(RetryEINTR(semop)(sem.get(), &buf, 1) == 0); + _exit(0); + } + children.push_back(child_pid); + } + EXPECT_THAT(WaitSemctl(sem.get(), kLoops, GETNCNT), + IsPosixErrorOkAndHolds(kLoops)); + // Set semval to 1, which wakes up children that sleep on the semop. + ASSERT_THAT(semctl(sem.get(), 0, SETVAL, kLoops), SyscallSucceeds()); + for (const auto& child_pid : children) { + int status; + ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), + SyscallSucceedsWithValue(child_pid)); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } + EXPECT_EQ(semctl(sem.get(), 0, GETNCNT), 0); +} + +TEST(SemaphoreTest, SemopGetncntOnSetRemoval) { + auto semid = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT); + ASSERT_THAT(semid, SyscallSucceeds()); + ASSERT_EQ(semctl(semid, 0, GETNCNT), 0); + + auto child_pid = fork(); + if (child_pid == 0) { + struct sembuf buf = {}; + buf.sem_num = 0; + buf.sem_op = -1; + + // Ensure that wait will only unblock when the semaphore is removed. On + // EINTR retry it may race with deletion and return EINVAL + TEST_PCHECK(RetryEINTR(semop)(semid, &buf, 1) < 0 && + (errno == EIDRM || errno == EINVAL)); + _exit(0); + } + + EXPECT_THAT(WaitSemctl(semid, 1, GETNCNT), IsPosixErrorOkAndHolds(1)); + // Remove the semaphore set, which fails the sleep semop. + ASSERT_THAT(semctl(semid, 0, IPC_RMID), SyscallSucceeds()); + int status; + ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), + SyscallSucceedsWithValue(child_pid)); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + EXPECT_THAT(semctl(semid, 0, GETNCNT), SyscallFailsWithErrno(EINVAL)); +} + +TEST(SemaphoreTest, SemopGetncntOnSignal_NoRandomSave) { + AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); + ASSERT_THAT(sem.get(), SyscallSucceeds()); + ASSERT_EQ(semctl(sem.get(), 0, GETNCNT), 0); + + // Saving will cause semop() to be spuriously interrupted. + DisableSave ds; + + auto child_pid = fork(); + if (child_pid == 0) { + TEST_PCHECK(signal(SIGHUP, [](int sig) -> void {}) != SIG_ERR); + struct sembuf buf = {}; + buf.sem_num = 0; + buf.sem_op = -1; + + TEST_PCHECK(semop(sem.get(), &buf, 1) < 0 && errno == EINTR); + _exit(0); + } + EXPECT_THAT(WaitSemctl(sem.get(), 1, GETNCNT), IsPosixErrorOkAndHolds(1)); + // Send a signal to the child, which fails the sleep semop. + ASSERT_EQ(kill(child_pid, SIGHUP), 0); + + ds.reset(); + + int status; + ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), + SyscallSucceedsWithValue(child_pid)); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + EXPECT_EQ(semctl(sem.get(), 0, GETNCNT), 0); +} + } // namespace } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/sendfile.cc b/test/syscalls/linux/sendfile.cc index a8bfb01f1..cf0977118 100644 --- a/test/syscalls/linux/sendfile.cc +++ b/test/syscalls/linux/sendfile.cc @@ -25,9 +25,11 @@ #include "absl/time/time.h" #include "test/util/eventfd_util.h" #include "test/util/file_descriptor.h" +#include "test/util/signal_util.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" +#include "test/util/timer_util.h" namespace gvisor { namespace testing { @@ -629,6 +631,57 @@ TEST(SendFileTest, SendFileToPipe) { SyscallSucceedsWithValue(kDataSize)); } +static volatile int signaled = 0; +void SigUsr1Handler(int sig, siginfo_t* info, void* context) { signaled = 1; } + +TEST(SendFileTest, ToEventFDDoesNotSpin_NoRandomSave) { + FileDescriptor efd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0)); + + // Write the maximum value of an eventfd to a file. + const uint64_t kMaxEventfdValue = 0xfffffffffffffffe; + const auto tempfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const auto tempfd = ASSERT_NO_ERRNO_AND_VALUE(Open(tempfile.path(), O_RDWR)); + ASSERT_THAT( + pwrite(tempfd.get(), &kMaxEventfdValue, sizeof(kMaxEventfdValue), 0), + SyscallSucceedsWithValue(sizeof(kMaxEventfdValue))); + + // Set the eventfd's value to 1. + const uint64_t kOne = 1; + ASSERT_THAT(write(efd.get(), &kOne, sizeof(kOne)), + SyscallSucceedsWithValue(sizeof(kOne))); + + // Set up signal handler. + struct sigaction sa = {}; + sa.sa_sigaction = SigUsr1Handler; + sa.sa_flags = SA_SIGINFO; + const auto cleanup_sigact = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGUSR1, sa)); + + // Send SIGUSR1 to this thread in 1 second. + struct sigevent sev = {}; + sev.sigev_notify = SIGEV_THREAD_ID; + sev.sigev_signo = SIGUSR1; + sev.sigev_notify_thread_id = gettid(); + auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); + struct itimerspec its = {}; + its.it_value = absl::ToTimespec(absl::Seconds(1)); + DisableSave ds; // Asserting an EINTR. + ASSERT_NO_ERRNO(timer.Set(0, its)); + + // Sendfile from tempfd to the eventfd. Since the eventfd is not already at + // its maximum value, the eventfd is "ready for writing"; however, since the + // eventfd's existing value plus the new value would exceed the maximum, the + // write should internally fail with EWOULDBLOCK. In this case, sendfile() + // should block instead of spinning, and eventually be interrupted by our + // timer. See b/172075629. + EXPECT_THAT( + sendfile(efd.get(), tempfd.get(), nullptr, sizeof(kMaxEventfdValue)), + SyscallFailsWithErrno(EINTR)); + + // Signal should have been handled. + EXPECT_EQ(signaled, 1); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc index 39a68c5a5..e19a83413 100644 --- a/test/syscalls/linux/socket_inet_loopback.cc +++ b/test/syscalls/linux/socket_inet_loopback.cc @@ -940,7 +940,7 @@ void setupTimeWaitClose(const TestAddress* listener, } // shutdown to trigger TIME_WAIT. - ASSERT_THAT(shutdown(active_closefd.get(), SHUT_RDWR), SyscallSucceeds()); + ASSERT_THAT(shutdown(active_closefd.get(), SHUT_WR), SyscallSucceeds()); { const int kTimeout = 10000; struct pollfd pfd = { @@ -950,7 +950,8 @@ void setupTimeWaitClose(const TestAddress* listener, ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); ASSERT_EQ(pfd.revents, POLLIN); } - ScopedThread t([&]() { + ASSERT_THAT(shutdown(passive_closefd.get(), SHUT_WR), SyscallSucceeds()); + { constexpr int kTimeout = 10000; constexpr int16_t want_events = POLLHUP; struct pollfd pfd = { @@ -958,11 +959,8 @@ void setupTimeWaitClose(const TestAddress* listener, .events = want_events, }; ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); - }); + } - passive_closefd.reset(); - t.Join(); - active_closefd.reset(); // This sleep is needed to reduce flake to ensure that the passive-close // ensures the state transitions to CLOSE from LAST_ACK. absl::SleepFor(absl::Seconds(1)); diff --git a/test/syscalls/linux/socket_ip_unbound.cc b/test/syscalls/linux/socket_ip_unbound.cc index 8f7ccc868..029f1e872 100644 --- a/test/syscalls/linux/socket_ip_unbound.cc +++ b/test/syscalls/linux/socket_ip_unbound.cc @@ -454,23 +454,15 @@ TEST_P(IPUnboundSocketTest, SetReuseAddr) { INSTANTIATE_TEST_SUITE_P( IPUnboundSockets, IPUnboundSocketTest, - ::testing::ValuesIn(VecCat<SocketKind>(VecCat<SocketKind>( + ::testing::ValuesIn(VecCat<SocketKind>( ApplyVec<SocketKind>(IPv4UDPUnboundSocket, - AllBitwiseCombinations(List<int>{SOCK_DGRAM}, - List<int>{0, - SOCK_NONBLOCK})), + std::vector<int>{0, SOCK_NONBLOCK}), ApplyVec<SocketKind>(IPv6UDPUnboundSocket, - AllBitwiseCombinations(List<int>{SOCK_DGRAM}, - List<int>{0, - SOCK_NONBLOCK})), + std::vector<int>{0, SOCK_NONBLOCK}), ApplyVec<SocketKind>(IPv4TCPUnboundSocket, - AllBitwiseCombinations(List<int>{SOCK_STREAM}, - List<int>{0, - SOCK_NONBLOCK})), + std::vector{0, SOCK_NONBLOCK}), ApplyVec<SocketKind>(IPv6TCPUnboundSocket, - AllBitwiseCombinations(List<int>{SOCK_STREAM}, - List<int>{ - 0, SOCK_NONBLOCK})))))); + std::vector{0, SOCK_NONBLOCK})))); } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_unbound_netlink.cc b/test/syscalls/linux/socket_ip_unbound_netlink.cc new file mode 100644 index 000000000..6036bfcaf --- /dev/null +++ b/test/syscalls/linux/socket_ip_unbound_netlink.cc @@ -0,0 +1,104 @@ +// Copyright 2019 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 <arpa/inet.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/un.h> + +#include <cstdio> +#include <cstring> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "test/syscalls/linux/ip_socket_test_util.h" +#include "test/syscalls/linux/socket_netlink_route_util.h" +#include "test/syscalls/linux/socket_test_util.h" +#include "test/util/capability_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +// Test fixture for tests that apply to pairs of IP sockets. +using IPv6UnboundSocketTest = SimpleSocketTest; + +TEST_P(IPv6UnboundSocketTest, ConnectToBadLocalAddress_NoRandomSave) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); + + // TODO(gvisor.dev/issue/4595): Addresses on net devices are not saved + // across save/restore. + DisableSave ds; + + // Delete the loopback address from the loopback interface. + Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); + EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET6, + /*prefixlen=*/128, &in6addr_loopback, + sizeof(in6addr_loopback))); + Cleanup defer_addr_removal = + Cleanup([loopback_link = std::move(loopback_link)] { + EXPECT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET6, + /*prefixlen=*/128, &in6addr_loopback, + sizeof(in6addr_loopback))); + }); + + TestAddress addr = V6Loopback(); + reinterpret_cast<sockaddr_in6*>(&addr.addr)->sin6_port = 65535; + auto sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + EXPECT_THAT(connect(sock->get(), reinterpret_cast<sockaddr*>(&addr.addr), + addr.addr_len), + SyscallFailsWithErrno(EADDRNOTAVAIL)); +} + +INSTANTIATE_TEST_SUITE_P(IPUnboundSockets, IPv6UnboundSocketTest, + ::testing::ValuesIn(std::vector<SocketKind>{ + IPv6UDPUnboundSocket(0), + IPv6TCPUnboundSocket(0)})); + +using IPv4UnboundSocketTest = SimpleSocketTest; + +TEST_P(IPv4UnboundSocketTest, ConnectToBadLocalAddress_NoRandomSave) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); + + // TODO(gvisor.dev/issue/4595): Addresses on net devices are not saved + // across save/restore. + DisableSave ds; + + // Delete the loopback address from the loopback interface. + Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); + struct in_addr laddr; + laddr.s_addr = htonl(INADDR_LOOPBACK); + EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET, + /*prefixlen=*/8, &laddr, sizeof(laddr))); + Cleanup defer_addr_removal = Cleanup( + [loopback_link = std::move(loopback_link), addr = std::move(laddr)] { + EXPECT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET, + /*prefixlen=*/8, &addr, sizeof(addr))); + }); + TestAddress addr = V4Loopback(); + reinterpret_cast<sockaddr_in*>(&addr.addr)->sin_port = 65535; + auto sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + EXPECT_THAT(connect(sock->get(), reinterpret_cast<sockaddr*>(&addr.addr), + addr.addr_len), + SyscallFailsWithErrno(EADDRNOTAVAIL)); +} + +INSTANTIATE_TEST_SUITE_P(IPUnboundSockets, IPv4UnboundSocketTest, + ::testing::ValuesIn(std::vector<SocketKind>{ + IPv4UDPUnboundSocket(0), + IPv4TCPUnboundSocket(0)})); + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc index e83f0d81f..ee3c08770 100644 --- a/test/syscalls/linux/socket_netlink_route.cc +++ b/test/syscalls/linux/socket_netlink_route.cc @@ -536,7 +536,7 @@ TEST(NetlinkRouteTest, AddAndRemoveAddr) { // Second delete should fail, as address no longer exists. EXPECT_THAT(LinkDelLocalAddr(loopback_link.index, AF_INET, /*prefixlen=*/24, &addr, sizeof(addr)), - PosixErrorIs(EINVAL, ::testing::_)); + PosixErrorIs(EADDRNOTAVAIL, ::testing::_)); }); // Replace an existing address should succeed. diff --git a/test/syscalls/linux/splice.cc b/test/syscalls/linux/splice.cc index a1d2b9b11..c2369db54 100644 --- a/test/syscalls/linux/splice.cc +++ b/test/syscalls/linux/splice.cc @@ -26,9 +26,11 @@ #include "absl/time/clock.h" #include "absl/time/time.h" #include "test/util/file_descriptor.h" +#include "test/util/signal_util.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" +#include "test/util/timer_util.h" namespace gvisor { namespace testing { @@ -772,6 +774,59 @@ TEST(SpliceTest, FromPipeToDevZero) { SyscallSucceedsWithValue(0)); } +static volatile int signaled = 0; +void SigUsr1Handler(int sig, siginfo_t* info, void* context) { signaled = 1; } + +TEST(SpliceTest, ToPipeWithSmallCapacityDoesNotSpin_NoRandomSave) { + // Writes to a pipe that are less than PIPE_BUF must be atomic. This test + // creates a pipe with only 128 bytes of capacity (< PIPE_BUF) and checks that + // splicing to the pipe does not spin. See b/170743336. + + // Create a file with one page of data. + std::vector<char> buf(kPageSize); + RandomizeBuffer(buf.data(), buf.size()); + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( + GetAbsoluteTestTmpdir(), absl::string_view(buf.data(), buf.size()), + TempPath::kDefaultFileMode)); + auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); + + // Create a pipe with size 4096, and fill all but 128 bytes of it. + int p[2]; + ASSERT_THAT(pipe(p), SyscallSucceeds()); + ASSERT_THAT(fcntl(p[1], F_SETPIPE_SZ, kPageSize), SyscallSucceeds()); + const int kWriteSize = kPageSize - 128; + std::vector<char> writeBuf(kWriteSize); + RandomizeBuffer(writeBuf.data(), writeBuf.size()); + ASSERT_THAT(write(p[1], writeBuf.data(), writeBuf.size()), + SyscallSucceedsWithValue(kWriteSize)); + + // Set up signal handler. + struct sigaction sa = {}; + sa.sa_sigaction = SigUsr1Handler; + sa.sa_flags = SA_SIGINFO; + const auto cleanup_sigact = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGUSR1, sa)); + + // Send SIGUSR1 to this thread in 1 second. + struct sigevent sev = {}; + sev.sigev_notify = SIGEV_THREAD_ID; + sev.sigev_signo = SIGUSR1; + sev.sigev_notify_thread_id = gettid(); + auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); + struct itimerspec its = {}; + its.it_value = absl::ToTimespec(absl::Seconds(1)); + DisableSave ds; // Asserting an EINTR. + ASSERT_NO_ERRNO(timer.Set(0, its)); + + // Now splice the file to the pipe. This should block, but not spin, and + // should return EINTR because it is interrupted by the signal. + EXPECT_THAT(splice(fd.get(), nullptr, p[1], nullptr, kPageSize, 0), + SyscallFailsWithErrno(EINTR)); + + // Alarm should have been handled. + EXPECT_EQ(signaled, 1); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/timers.cc b/test/syscalls/linux/timers.cc index cac94d9e1..93a98adb1 100644 --- a/test/syscalls/linux/timers.cc +++ b/test/syscalls/linux/timers.cc @@ -322,11 +322,6 @@ TEST(IntervalTimerTest, PeriodicGroupDirectedSignal) { EXPECT_GE(counted_signals.load(), kCycles); } -// From Linux's include/uapi/asm-generic/siginfo.h. -#ifndef sigev_notify_thread_id -#define sigev_notify_thread_id _sigev_un._tid -#endif - TEST(IntervalTimerTest, PeriodicThreadDirectedSignal) { constexpr int kSigno = SIGUSR1; constexpr int kSigvalue = 42; diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc index bc5bd9218..d65275fd3 100644 --- a/test/syscalls/linux/udp_socket.cc +++ b/test/syscalls/linux/udp_socket.cc @@ -1887,6 +1887,22 @@ TEST_P(UdpSocketTest, GetSocketDetachFilter) { SyscallFailsWithErrno(ENOPROTOOPT)); } +TEST_P(UdpSocketTest, SendToZeroPort) { + char buf[8]; + struct sockaddr_storage addr = InetLoopbackAddr(); + + // Sending to an invalid port should fail. + SetPort(&addr, 0); + EXPECT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, + reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), + SyscallFailsWithErrno(EINVAL)); + + SetPort(&addr, 1234); + EXPECT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, + reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), + SyscallSucceedsWithValue(sizeof(buf))); +} + INSTANTIATE_TEST_SUITE_P(AllInetTests, UdpSocketTest, ::testing::Values(AddressFamily::kIpv4, AddressFamily::kIpv6, diff --git a/test/util/timer_util.h b/test/util/timer_util.h index 926e6632f..e389108ef 100644 --- a/test/util/timer_util.h +++ b/test/util/timer_util.h @@ -33,6 +33,11 @@ namespace gvisor { namespace testing { +// From Linux's include/uapi/asm-generic/siginfo.h. +#ifndef sigev_notify_thread_id +#define sigev_notify_thread_id _sigev_un._tid +#endif + // Returns the current time. absl::Time Now(clockid_t id); |