diff --git a/pkg/sentry/socket/netfilter/tcp_matcher.go b/pkg/sentry/socket/netfilter/tcp_matcher.go index e09853fd1..7ae0f51f7 100644 --- a/pkg/sentry/socket/netfilter/tcp_matcher.go +++ b/pkg/sentry/socket/netfilter/tcp_matcher.go @@ -51,6 +51,7 @@ func (tcpMarshaler) marshal(mr matcher) []byte { DestinationPortEnd: matcher.destinationPortEnd, FlagMask: matcher.flagMask, FlagCompare: matcher.flagCompare, + InverseFlags: matcher.inverseFlags, } return marshalEntryMatch(matcherNameTCP, marshal.Marshal(&xttcp)) } @@ -67,7 +68,8 @@ func (tcpMarshaler) unmarshal(_ IDMapper, buf []byte, filter stack.IPHeaderFilte matchData.UnmarshalUnsafe(buf) nflog("parseMatchers: parsed XTTCP: %+v", matchData) - if matchData.Option != 0 || matchData.InverseFlags != 0 { + // Only support inverse dport/sport + if matchData.Option != 0 || matchData.InverseFlags > 2 { return nil, fmt.Errorf("unsupported TCP matcher flags set") } @@ -82,6 +84,7 @@ func (tcpMarshaler) unmarshal(_ IDMapper, buf []byte, filter stack.IPHeaderFilte destinationPortEnd: matchData.DestinationPortEnd, flagMask: matchData.FlagMask, flagCompare: matchData.FlagCompare, + inverseFlags: matchData.InverseFlags, }, nil } @@ -93,6 +96,7 @@ type TCPMatcher struct { destinationPortEnd uint16 flagMask uint8 flagCompare uint8 + inverseFlags uint8 } // name implements matcher.name. @@ -142,10 +146,18 @@ func (tm *TCPMatcher) Match(hook stack.Hook, pkt stack.PacketBufferPtr, _, _ str // Check whether the source and destination ports are within the // matching range. - if sourcePort := tcpHeader.SourcePort(); sourcePort < tm.sourcePortStart || tm.sourcePortEnd < sourcePort { + // Take into account inverseFlags for DSTPT & SRCPT only + sPort := tcpHeader.SourcePort() + sPortMatch := sPort < tm.sourcePortStart || tm.sourcePortEnd < sPort + sPortMatch = sPortMatch != (tm.inverseFlags&linux.XT_TCP_INV_SRCPT == linux.XT_TCP_INV_SRCPT) + if sPortMatch { return false, false } - if destinationPort := tcpHeader.DestinationPort(); destinationPort < tm.destinationPortStart || tm.destinationPortEnd < destinationPort { + + dPort := tcpHeader.DestinationPort() + dPortMatch := dPort < tm.destinationPortStart || tm.destinationPortEnd < dPort + dPortMatch = dPortMatch != (tm.inverseFlags&linux.XT_TCP_INV_DSTPT == linux.XT_TCP_INV_DSTPT) + if dPortMatch { return false, false } diff --git a/test/iptables/filter_input.go b/test/iptables/filter_input.go index 4739bc06f..a0545e2cd 100644 --- a/test/iptables/filter_input.go +++ b/test/iptables/filter_input.go @@ -57,6 +57,8 @@ func init() { RegisterTestCase(&FilterInputInterfaceBeginsWith{}) RegisterTestCase(&FilterInputInterfaceInvertDrop{}) RegisterTestCase(&FilterInputInterfaceInvertAccept{}) + RegisterTestCase(&FilterInputInvertDportAccept{}) + RegisterTestCase(&FilterInputInvertDportDrop{}) } // FilterInputDropUDP tests that we can drop UDP traffic. @@ -988,3 +990,69 @@ func (*FilterInputInterfaceInvertAccept) ContainerAction(ctx context.Context, ip func (*FilterInputInterfaceInvertAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { return connectTCP(ctx, ip, acceptPort, ipv6) } + +// FilterInputInvertDportAccept tests that we can send packets on a negated +// --dport match +type FilterInputInvertDportAccept struct{ baseCase } + +var _ TestCase = (*FilterInputInvertDportAccept)(nil) + +// Name implements TestCase.Name. +func (*FilterInputInvertDportAccept) Name() string { + return "FilterInputInvertDportAccept" +} + +// ContainerAction implements TestCase.ContainerAction. +func (*FilterInputInvertDportAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { + if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "!", "--dport", fmt.Sprintf("%d", dropPort), "-j", "ACCEPT"); err != nil { + return err + } + + // Listen for TCP packets on accept port. + return listenTCP(ctx, acceptPort, ipv6) +} + +// LocalAction implements TestCase.LocalAction. +func (*FilterInputInvertDportAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { + return connectTCP(ctx, ip, acceptPort, ipv6) +} + +// FilterInputInvertDportDrop tests that we can send packets on a negated +// --dport match +type FilterInputInvertDportDrop struct{ baseCase } + +var _ TestCase = (*FilterInputInvertDportDrop)(nil) + +// Name implements TestCase.Name. +func (*FilterInputInvertDportDrop) Name() string { + return "FilterInputInvertDportDrop" +} + +// ContainerAction implements TestCase.ContainerAction. +func (*FilterInputInvertDportDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { + if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "!", "--dport", fmt.Sprintf("%d", acceptPort), "-j", "DROP"); err != nil { + return err + } + + // Listen for TCP packets on accept port. + timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) + defer cancel() + if err := listenTCP(timedCtx, dropPort, ipv6); err == nil { + return fmt.Errorf("connection was established when it shouldnt have been") + } else if !errors.Is(err, context.DeadlineExceeded) { + return fmt.Errorf("error reading: %v", err) + } + + return nil +} + +// LocalAction implements TestCase.LocalAction. +func (*FilterInputInvertDportDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { + timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) + defer cancel() + if err := connectTCP(timedCtx, ip, dropPort, ipv6); err == nil { + return fmt.Errorf("connection on %d port was accepted when it should have been dropped", dropPort) + } + + return nil +} diff --git a/test/iptables/filter_output.go b/test/iptables/filter_output.go index bcb2a3b70..b05c8a79b 100644 --- a/test/iptables/filter_output.go +++ b/test/iptables/filter_output.go @@ -42,6 +42,8 @@ func init() { RegisterTestCase(&FilterOutputInterfaceBeginsWith{}) RegisterTestCase(&FilterOutputInterfaceInvertDrop{}) RegisterTestCase(&FilterOutputInterfaceInvertAccept{}) + RegisterTestCase(&FilterOutputInvertSportAccept{}) + RegisterTestCase(&FilterOutputInvertSportDrop{}) } // FilterOutputDropTCPDestPort tests that connections are not accepted on @@ -712,3 +714,69 @@ func (*FilterOutputInterfaceInvertAccept) ContainerAction(ctx context.Context, i func (*FilterOutputInterfaceInvertAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { return connectTCP(ctx, ip, acceptPort, ipv6) } + +// FilterOutputInvertSportAccept tests that we can send packets on a negated +// --sport match +type FilterOutputInvertSportAccept struct{ baseCase } + +var _ TestCase = (*FilterOutputInvertSportAccept)(nil) + +// Name implements TestCase.Name. +func (*FilterOutputInvertSportAccept) Name() string { + return "FilterOutputInvertSportAccept" +} + +// ContainerAction implements TestCase.ContainerAction. +func (*FilterOutputInvertSportAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { + if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "!", "--sport", fmt.Sprintf("%d", dropPort), "-j", "ACCEPT"); err != nil { + return err + } + + // Listen for TCP packets on accept port. + return listenTCP(ctx, acceptPort, ipv6) +} + +// LocalAction implements TestCase.LocalAction. +func (*FilterOutputInvertSportAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { + return connectTCP(ctx, ip, acceptPort, ipv6) +} + +// FilterOutputInvertSportDrop tests that we can send packets on a negated +// --dport match +type FilterOutputInvertSportDrop struct{ baseCase } + +var _ TestCase = (*FilterOutputInvertSportDrop)(nil) + +// Name implements TestCase.Name. +func (*FilterOutputInvertSportDrop) Name() string { + return "FilterOutputInvertSportDrop" +} + +// ContainerAction implements TestCase.ContainerAction. +func (*FilterOutputInvertSportDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { + if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "!", "--sport", fmt.Sprintf("%d", acceptPort), "-j", "DROP"); err != nil { + return err + } + + // Listen for TCP packets on accept port. + timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) + defer cancel() + if err := listenTCP(timedCtx, dropPort, ipv6); err == nil { + return fmt.Errorf("connection was established when it shouldnt have been") + } else if !errors.Is(err, context.DeadlineExceeded) { + return fmt.Errorf("error reading: %v", err) + } + + return nil +} + +// LocalAction implements TestCase.LocalAction. +func (*FilterOutputInvertSportDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { + timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) + defer cancel() + if err := connectTCP(timedCtx, ip, dropPort, ipv6); err == nil { + return fmt.Errorf("connection on %d port was accepted when it should have been dropped", dropPort) + } + + return nil +} diff --git a/test/iptables/iptables_test.go b/test/iptables/iptables_test.go index 7b656b708..e5a2162a4 100644 --- a/test/iptables/iptables_test.go +++ b/test/iptables/iptables_test.go @@ -287,6 +287,14 @@ func TestFilterOutputInterfaceInvertAccept(t *testing.T) { singleTest(t, &FilterOutputInterfaceInvertAccept{}) } +func TestFilterOutputInvertSportAccept(t *testing.T) { + singleTest(t, &FilterOutputInvertSportAccept{}) +} + +func TestFilterOutputInvertSportDrop(t *testing.T) { + singleTest(t, &FilterOutputInvertSportDrop{}) +} + func TestJumpSerialize(t *testing.T) { singleTest(t, &FilterInputSerializeJump{}) } @@ -434,6 +442,14 @@ func TestInputInterfaceInvertAccept(t *testing.T) { singleTest(t, &FilterInputInterfaceInvertAccept{}) } +func TestFilterInputInvertDportAccept(t *testing.T) { + singleTest(t, &FilterInputInvertDportAccept{}) +} + +func TestFilterInputInvertDportDrop(t *testing.T) { + singleTest(t, &FilterInputInvertDportDrop{}) +} + func TestFilterAddrs(t *testing.T) { tcs := []struct { ipv6 bool