Files
Lucas ManningandgVisor bot afa323bd30 Replace most instances of IncRef with Clone.
Incrementing the reference count of a packet as a means of granting ownership
is unsafe when the packet is shared across gorountines. The underlying buffer's
reference count is unchanged since it "technically" has the same owning
PacketBuffer, which means different goroutines operating on the underlying
buffer (and packet itself) race.

Clones are roughly as fast as IncRefs because the PacketBuffers allocate from
a pool and the underlying buffers are cloned with copy-on-write
semantics.

I've left IncRef in places where the original packet in obviously going out of
scope at the end of the function or in some tests.

Reported-by: syzbot+e026046f4bf8ad09ae1f@syzkaller.appspotmail.com
Reported-by: syzbot+559365d6050db4b30e0f@syzkaller.appspotmail.com
Reported-by: syzbot+63c78a2c88a5744c636b@syzkaller.appspotmail.com
PiperOrigin-RevId: 705676806
2024-12-12 17:09:40 -08:00

400 lines
14 KiB
Go

// 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 testutil defines types and functions used to test Network Layer
// functionality such as IP fragmentation.
package testutil
import (
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/rand"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/checker"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// MockLinkEndpoint is an endpoint used for testing, it stores packets written
// to it and can mock errors.
type MockLinkEndpoint struct {
// WrittenPackets is where packets written to the endpoint are stored.
WrittenPackets []*stack.PacketBuffer
mtu uint32
err tcpip.Error
allowPackets int
}
// NewMockLinkEndpoint creates a new MockLinkEndpoint.
//
// err is the error that will be returned once allowPackets packets are written
// to the endpoint.
func NewMockLinkEndpoint(mtu uint32, err tcpip.Error, allowPackets int) *MockLinkEndpoint {
return &MockLinkEndpoint{
mtu: mtu,
err: err,
allowPackets: allowPackets,
}
}
// MTU implements LinkEndpoint.MTU.
func (ep *MockLinkEndpoint) MTU() uint32 { return ep.mtu }
// SetMTU implements LinkEndpoint.SetMTU.
func (ep *MockLinkEndpoint) SetMTU(mtu uint32) { ep.mtu = mtu }
// Capabilities implements LinkEndpoint.Capabilities.
func (*MockLinkEndpoint) Capabilities() stack.LinkEndpointCapabilities { return 0 }
// MaxHeaderLength implements LinkEndpoint.MaxHeaderLength.
func (*MockLinkEndpoint) MaxHeaderLength() uint16 { return 0 }
// LinkAddress implements LinkEndpoint.LinkAddress.
func (*MockLinkEndpoint) LinkAddress() tcpip.LinkAddress { return "" }
// SetLinkAddress implements LinkEndpoint.LinkAddress.
func (*MockLinkEndpoint) SetLinkAddress(tcpip.LinkAddress) {}
// WritePackets implements LinkEndpoint.WritePackets.
func (ep *MockLinkEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
var n int
for _, pkt := range pkts.AsSlice() {
if ep.allowPackets == 0 {
return n, ep.err
}
ep.allowPackets--
ep.WrittenPackets = append(ep.WrittenPackets, pkt.Clone())
n++
}
return n, nil
}
// Attach implements LinkEndpoint.Attach.
func (*MockLinkEndpoint) Attach(stack.NetworkDispatcher) {}
// IsAttached implements LinkEndpoint.IsAttached.
func (*MockLinkEndpoint) IsAttached() bool { return false }
// Wait implements LinkEndpoint.Wait.
func (*MockLinkEndpoint) Wait() {}
// ARPHardwareType implements LinkEndpoint.ARPHardwareType.
func (*MockLinkEndpoint) ARPHardwareType() header.ARPHardwareType { return header.ARPHardwareNone }
// AddHeader implements LinkEndpoint.AddHeader.
func (*MockLinkEndpoint) AddHeader(*stack.PacketBuffer) {}
// ParseHeader implements LinkEndpoint.ParseHeader.
func (*MockLinkEndpoint) ParseHeader(*stack.PacketBuffer) bool { return true }
// Close releases all resources.
func (ep *MockLinkEndpoint) Close() {
for _, pkt := range ep.WrittenPackets {
pkt.DecRef()
}
ep.WrittenPackets = nil
}
// SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction.
func (*MockLinkEndpoint) SetOnCloseAction(func()) {}
// MakeRandPkt generates a randomized packet. transportHeaderLength indicates
// how many random bytes will be copied in the Transport Header.
// extraHeaderReserveLength indicates how much extra space will be reserved for
// the other headers. The payload is made from Views of the sizes listed in
// viewSizes.
func MakeRandPkt(transportHeaderLength int, extraHeaderReserveLength int, viewSizes []int, proto tcpip.NetworkProtocolNumber) *stack.PacketBuffer {
var buf buffer.Buffer
for _, s := range viewSizes {
newView := buffer.NewViewSize(s)
if _, err := rand.Read(newView.AsSlice()); err != nil {
panic(fmt.Sprintf("rand.Read: %s", err))
}
buf.Append(newView)
}
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: transportHeaderLength + extraHeaderReserveLength,
Payload: buf,
})
pkt.NetworkProtocolNumber = proto
if _, err := rand.Read(pkt.TransportHeader().Push(transportHeaderLength)); err != nil {
panic(fmt.Sprintf("rand.Read: %s", err))
}
return pkt
}
func checkIGMPStats(t *testing.T, s *stack.Stack, reports, leaves, reportsV2 uint64) {
t.Helper()
if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != reports {
t.Errorf("got s.Stats().IGMP.PacketsSent.V2MembershipReport.Value() = %d, want = %d", got, reports)
}
if got := s.Stats().IGMP.PacketsSent.V3MembershipReport.Value(); got != reportsV2 {
t.Errorf("got s.Stats().IGMP.PacketsSent.V3MembershipReport.Value() = %d, want = %d", got, reportsV2)
}
if got := s.Stats().IGMP.PacketsSent.LeaveGroup.Value(); got != leaves {
t.Errorf("got s.Stats().IGMP.PacketsSent.LeaveGroup.Value() = %d, want = %d", got, leaves)
}
}
// CheckIGMPv2Stats checks IGMPv2 stats.
func CheckIGMPv2Stats(t *testing.T, s *stack.Stack, reports, leaves, reportsV2 uint64) {
t.Helper()
// We still check V3 stats in V2 compatibility tests because the test may send
// V3 reports before we drop into compatibility mode.
checkIGMPStats(t, s, reports, leaves, reportsV2)
}
// CheckIGMPv3Stats checks IGMPv3 stats.
func CheckIGMPv3Stats(t *testing.T, s *stack.Stack, reports, leaves, reportsV2 uint64) {
t.Helper()
// In IGMPv3 tests, reports/leaves are just IGMPv3 reports.
checkIGMPStats(t, s, 0 /* reports */, 0 /* leaves */, reports+leaves+reportsV2)
}
func checkMLDStats(t *testing.T, s *stack.Stack, reports, leaves, reportsV2 uint64) {
t.Helper()
if got := s.Stats().ICMP.V6.PacketsSent.MulticastListenerReport.Value(); got != reports {
t.Errorf("got s.Stats().ICMP.V6.PacketsSent.MulticastListenerReport.Value() = %d, want = %d", got, reports)
}
if got := s.Stats().ICMP.V6.PacketsSent.MulticastListenerReportV2.Value(); got != reportsV2 {
t.Errorf("got s.Stats().ICMP.V6.PacketsSent.MulticastListenerReportV2.Value() = %d, want = %d", got, reportsV2)
}
if got := s.Stats().ICMP.V6.PacketsSent.MulticastListenerDone.Value(); got != leaves {
t.Errorf("got s.Stats().ICMP.V6.PacketsSent.MulticastListenerDone.Value() = %d, want = %d", got, leaves)
}
}
// CheckMLDv1Stats checks MLDv1 stats.
func CheckMLDv1Stats(t *testing.T, s *stack.Stack, reports, leaves, reportsV2 uint64) {
t.Helper()
// We still check V2 stats in V1 compatibility tests because the test may send
// V2 reports before we drop into compatibility mode.
checkMLDStats(t, s, reports, leaves, reportsV2)
}
// CheckMLDv2Stats checks MLDv2 stats.
func CheckMLDv2Stats(t *testing.T, s *stack.Stack, reports, leaves, reportsV2 uint64) {
t.Helper()
// In MLDv2 tests, reports/leaves are just MLDv2 reports.
checkMLDStats(t, s, 0 /* reports */, 0 /* leaves */, reports+leaves+reportsV2)
}
// ValidateIGMPv3ReportWithRecords validates an IGMPv3 report.
//
// Note that observed records are removed from expectedRecords. No error is
// logged if the report does not have all the records expected.
func ValidateIGMPv3ReportWithRecords(t *testing.T, v *buffer.View, srcAddr tcpip.Address, expectedRecords map[tcpip.Address]header.IGMPv3ReportRecordType) {
t.Helper()
checker.IPv4(t, v,
checker.SrcAddr(srcAddr),
checker.DstAddr(header.IGMPv3RoutersAddress),
checker.TTL(header.IGMPTTL),
checker.IPv4RouterAlert(),
checker.IGMPv3Report(expectedRecords),
)
}
// ValidateIGMPv3Report validates an IGMPv3 report.
func ValidateIGMPv3Report(t *testing.T, v *buffer.View, srcAddr tcpip.Address, addrs []tcpip.Address, recordType header.IGMPv3ReportRecordType) {
t.Helper()
records := make(map[tcpip.Address]header.IGMPv3ReportRecordType)
for _, addr := range addrs {
records[addr] = recordType
}
ValidateIGMPv3ReportWithRecords(t, v, srcAddr, records)
if diff := cmp.Diff(map[tcpip.Address]header.IGMPv3ReportRecordType{}, records); diff != "" {
t.Errorf("post-validation records map mismatch (-want +got):\n%s", diff)
}
}
// ValidateIGMPv3RecordsAcrossReports validates IGMPv3 records across one or
// more reports.
func ValidateIGMPv3RecordsAcrossReports(t *testing.T, e *channel.Endpoint, srcAddr tcpip.Address, addrs []tcpip.Address, recordType header.IGMPv3ReportRecordType) {
t.Helper()
expectedRecords := make(map[tcpip.Address]header.IGMPv3ReportRecordType)
for _, addr := range addrs {
expectedRecords[addr] = recordType
}
for len(expectedRecords) != 0 {
p := e.Read()
if p == nil {
t.Fatalf("expected IGMP message with expectedRecords = %#v", expectedRecords)
}
v := stack.PayloadSince(p.NetworkHeader())
ValidateIGMPv3ReportWithRecords(t, v, srcAddr, expectedRecords)
v.Release()
p.DecRef()
}
if diff := cmp.Diff(map[tcpip.Address]header.IGMPv3ReportRecordType{}, expectedRecords); diff != "" {
t.Errorf("post-validation records map mismatch (-want +got):\n%s", diff)
}
}
// ValidMultipleIGMPv2ReportLeaves validates the reception of multiple IGMPv2
// report/leave messages.
func ValidMultipleIGMPv2ReportLeaves(t *testing.T, e *channel.Endpoint, srcAddr tcpip.Address, addrs []tcpip.Address, leave bool) {
t.Helper()
expectedGroups := make(map[tcpip.Address]struct{})
for _, addr := range addrs {
expectedGroups[addr] = struct{}{}
}
igmpType := header.IGMPv2MembershipReport
if leave {
igmpType = header.IGMPLeaveGroup
}
for len(expectedGroups) != 0 {
p := e.Read()
if p == nil {
t.Fatalf("expected IGMP message with expectedGroups = %#v", expectedGroups)
}
v := stack.PayloadSince(p.NetworkHeader())
checker.IPv4(t, v,
checker.SrcAddr(srcAddr),
checker.TTL(header.IGMPTTL),
checker.IPv4RouterAlert(),
checker.IGMP(
checker.IGMPType(igmpType),
checker.IGMPMaxRespTime(0),
checker.IGMPGroupAddressUnordered(expectedGroups),
),
)
v.Release()
p.DecRef()
}
if diff := cmp.Diff(map[tcpip.Address]struct{}{}, expectedGroups); diff != "" {
t.Errorf("post-validation groups map mismatch (-want +got):\n%s", diff)
}
}
// ValidateMLDv2ReportWithRecords validates an MLDv2 report.
//
// Note that observed records are removed from expectedRecords. No error is
// logged if the report does not have all the records expected.
func ValidateMLDv2ReportWithRecords(t *testing.T, v *buffer.View, srcAddr tcpip.Address, expectedRecords map[tcpip.Address]header.MLDv2ReportRecordType) {
t.Helper()
checker.IPv6WithExtHdr(t, v,
checker.IPv6ExtHdr(
checker.IPv6HopByHopExtensionHeader(checker.IPv6RouterAlert(header.IPv6RouterAlertMLD)),
),
checker.SrcAddr(srcAddr),
checker.DstAddr(header.MLDv2RoutersAddress),
checker.TTL(header.MLDHopLimit),
checker.MLDv2Report(expectedRecords),
)
}
// ValidateMLDv2Report validates an MLDv2 report.
func ValidateMLDv2Report(t *testing.T, v *buffer.View, srcAddr tcpip.Address, addrs []tcpip.Address, recordType header.MLDv2ReportRecordType) {
t.Helper()
records := make(map[tcpip.Address]header.MLDv2ReportRecordType)
for _, addr := range addrs {
records[addr] = recordType
}
ValidateMLDv2ReportWithRecords(t, v, srcAddr, records)
if diff := cmp.Diff(map[tcpip.Address]header.MLDv2ReportRecordType{}, records); diff != "" {
t.Errorf("post-validation records map mismatch (-want +got):\n%s", diff)
}
}
// ValidateMLDv2RecordsAcrossReports validates MLDv2 records across one or more
// reports.
func ValidateMLDv2RecordsAcrossReports(t *testing.T, e *channel.Endpoint, srcAddr tcpip.Address, addrs []tcpip.Address, recordType header.MLDv2ReportRecordType) {
t.Helper()
expectedRecords := make(map[tcpip.Address]header.MLDv2ReportRecordType)
for _, addr := range addrs {
expectedRecords[addr] = recordType
}
for len(expectedRecords) != 0 {
p := e.Read()
if p == nil {
t.Fatalf("expected MLD Message with expectedRecords = %#v", expectedRecords)
}
v := stack.PayloadSince(p.NetworkHeader())
ValidateMLDv2ReportWithRecords(t, v, srcAddr, expectedRecords)
v.Release()
p.DecRef()
}
if diff := cmp.Diff(map[tcpip.Address]header.MLDv2ReportRecordType{}, expectedRecords); diff != "" {
t.Errorf("post-validation records map mismatch (-want +got):\n%s", diff)
}
}
// ValidMultipleMLDv1ReportLeaves validates the reception of multiple MLDv1
// report/leave messages.
func ValidMultipleMLDv1ReportLeaves(t *testing.T, e *channel.Endpoint, srcAddr tcpip.Address, addrs []tcpip.Address, leave bool) {
t.Helper()
expectedGroups := make(map[tcpip.Address]struct{})
for _, addr := range addrs {
expectedGroups[addr] = struct{}{}
}
mldType := header.ICMPv6MulticastListenerReport
if leave {
mldType = header.ICMPv6MulticastListenerDone
}
for len(expectedGroups) != 0 {
p := e.Read()
if p == nil {
t.Fatalf("expected MLD Message with expectedGroups = %#v", expectedGroups)
}
v := stack.PayloadSince(p.NetworkHeader())
checker.IPv6WithExtHdr(t, v,
checker.IPv6ExtHdr(
checker.IPv6HopByHopExtensionHeader(checker.IPv6RouterAlert(header.IPv6RouterAlertMLD)),
),
checker.SrcAddr(srcAddr),
checker.TTL(header.MLDHopLimit),
checker.MLD(mldType, header.MLDMinimumSize,
checker.MLDMaxRespDelay(0),
checker.MLDMulticastAddressUnordered(expectedGroups),
),
)
v.Release()
p.DecRef()
}
if diff := cmp.Diff(map[tcpip.Address]struct{}{}, expectedGroups); diff != "" {
t.Errorf("post-validation groups map mismatch (-want +got):\n%s", diff)
}
}