Skip to content

Commit

Permalink
iptables: check revision numbers and support owner matcher v1
Browse files Browse the repository at this point in the history
This change also replaces use of kernel.Task with a narrower type (IDMapper) in
preparation for a follow-up CL.

PiperOrigin-RevId: 578387814
  • Loading branch information
kevinGC authored and gvisor-bot committed Nov 1, 2023
1 parent 5f75371 commit 7f08016
Show file tree
Hide file tree
Showing 13 changed files with 328 additions and 41 deletions.
28 changes: 23 additions & 5 deletions pkg/abi/linux/netfilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -633,8 +633,8 @@ const (
XT_UDP_INV_MASK = 0x03
)

// IPTOwnerInfo holds data for matching packets with owner. It corresponds
// to struct ipt_owner_info in libxt_owner.c of iptables binary.
// IPTOwnerInfo holds data for matching packets with the owner v0 matcher. It
// corresponds to struct ipt_owner_info in libxt_owner.c of iptables binary.
//
// +marshal
type IPTOwnerInfo struct {
Expand All @@ -661,11 +661,29 @@ type IPTOwnerInfo struct {
Invert uint8 `marshal:"unaligned"`
}

// SizeOfIPTOwnerInfo is the size of an XTOwnerMatchInfo.
// SizeOfIPTOwnerInfo is the size of an IPTOwnerInfo.
const SizeOfIPTOwnerInfo = 34

// Flags in IPTOwnerInfo.Match. Corresponding constants are in
// include/uapi/linux/netfilter/xt_owner.h.
// XTOwnerMatchInfo holds data for matching packets with the owner v1 matcher.
// It corresponds to struct xt_owner_match_info in
// include/uapi/linux/netfilter/xt_owner.h
//
// +marshal
type XTOwnerMatchInfo struct {
UIDMin uint32
UIDMax uint32
GIDMin uint32
GIDMax uint32
Match uint8
Invert uint8
_ [2]byte
}

// SizeOfXTOwnerMatchInfo is the size of an XTOwnerMatchInfo.
const SizeOfXTOwnerMatchInfo = 20

// Flags in IPTOwnerInfo.Match and XTOwnerMatchInfo.Match. Corresponding
// constants are in include/uapi/linux/netfilter/xt_owner.h.
const (
// Match the UID of the packet.
XT_OWNER_UID = 1 << 0
Expand Down
14 changes: 13 additions & 1 deletion pkg/sentry/socket/netfilter/BUILD
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("//tools:defs.bzl", "go_library")
load("//tools:defs.bzl", "go_library", "go_test")

package(
default_applicable_licenses = ["//:license"],
Expand All @@ -13,6 +13,7 @@ go_library(
"ipv6.go",
"netfilter.go",
"owner_matcher.go",
"owner_matcher_v1.go",
"targets.go",
"tcp_matcher.go",
"udp_matcher.go",
Expand All @@ -35,3 +36,14 @@ go_library(
"//pkg/tcpip/stack",
],
)

go_test(
name = "netfilter_test",
srcs = ["netfilter_test.go"],
embedsrcs = ["istio_blob"],
library = ":netfilter",
deps = [
"//pkg/sentry/kernel/auth",
"//pkg/tcpip/stack",
],
)
48 changes: 36 additions & 12 deletions pkg/sentry/socket/netfilter/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,45 +19,65 @@ import (

"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/bits"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)

// matchMaker knows how to (un)marshal the matcher named name().
// matchMaker knows how to (un)marshal the matcher named name(). UPDATE THIS
type matchMaker interface {
// name is the matcher name as stored in the xt_entry_match struct.
name() string

// revision is the match revision as stored in the xt_entry_match
// struct.
revision() uint8

// marshal converts from a stack.Matcher to an ABI struct.
marshal(matcher matcher) []byte

// unmarshal converts from the ABI matcher struct to an
// stack.Matcher.
unmarshal(task *kernel.Task, buf []byte, filter stack.IPHeaderFilter) (stack.Matcher, error)
unmarshal(mapper IDMapper, buf []byte, filter stack.IPHeaderFilter) (stack.Matcher, error)
}

type matchKey struct {
name string
revision uint8
}

func key(mm matchMaker) matchKey {
return matchKey{
name: mm.name(),
revision: mm.revision(),
}
}

type matcher interface {
name() string
revision() uint8
}

// matchMakers maps the name of supported matchers to the matchMaker that
// marshals and unmarshals it. It is immutable after package initialization.
var matchMakers = map[string]matchMaker{}
var matchMakers = map[matchKey]matchMaker{}

// registermatchMaker should be called by match extensions to register them
// with the netfilter package.
func registerMatchMaker(mm matchMaker) {
if _, ok := matchMakers[mm.name()]; ok {
panic(fmt.Sprintf("Multiple matches registered with name %q.", mm.name()))
if _, ok := matchMakers[key(mm)]; ok {
panic(fmt.Sprintf("Multiple matches registered with key %+v.", key(mm)))
}
matchMakers[mm.name()] = mm
matchMakers[key(mm)] = mm
}

func marshalMatcher(mr stack.Matcher) []byte {
matcher := mr.(matcher)
matchMaker, ok := matchMakers[matcher.name()]
key := matchKey{
name: matcher.name(),
revision: matcher.revision(),
}
matchMaker, ok := matchMakers[key]
if !ok {
panic(fmt.Sprintf("Unknown matcher of type %T.", matcher))
}
Expand Down Expand Up @@ -85,12 +105,16 @@ func marshalEntryMatch(name string, data []byte) []byte {
return buf
}

func unmarshalMatcher(task *kernel.Task, match linux.XTEntryMatch, filter stack.IPHeaderFilter, buf []byte) (stack.Matcher, error) {
matchMaker, ok := matchMakers[match.Name.String()]
func unmarshalMatcher(mapper IDMapper, match linux.XTEntryMatch, filter stack.IPHeaderFilter, buf []byte) (stack.Matcher, error) {
key := matchKey{
name: match.Name.String(),
revision: match.Revision,
}
matchMaker, ok := matchMakers[key]
if !ok {
return nil, fmt.Errorf("unsupported matcher with name %q", match.Name.String())
return nil, fmt.Errorf("unsupported matcher with name %q and revision %d", match.Name.String(), match.Revision)
}
return matchMaker.unmarshal(task, buf, filter)
return matchMaker.unmarshal(mapper, buf, filter)
}

// targetMaker knows how to (un)marshal a target. Once registered,
Expand Down
5 changes: 2 additions & 3 deletions pkg/sentry/socket/netfilter/ipv4.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"fmt"

"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
Expand Down Expand Up @@ -126,7 +125,7 @@ func getEntries4(table stack.Table, tablename linux.TableName) (linux.KernelIPTG
return entries, info
}

func modifyEntries4(task *kernel.Task, stk *stack.Stack, optVal []byte, replace *linux.IPTReplace, table *stack.Table) (map[uint32]int, *syserr.Error) {
func modifyEntries4(mapper IDMapper, stk *stack.Stack, optVal []byte, replace *linux.IPTReplace, table *stack.Table) (map[uint32]int, *syserr.Error) {
nflog("set entries: setting entries in table %q", replace.Name.String())

// Convert input into a list of rules and their offsets.
Expand Down Expand Up @@ -162,7 +161,7 @@ func modifyEntries4(task *kernel.Task, stk *stack.Stack, optVal []byte, replace
nflog("entry doesn't have enough room for its matchers (only %d bytes remain)", len(optVal))
return nil, syserr.ErrInvalidArgument
}
matchers, err := parseMatchers(task, filter, optVal[:matchersSize])
matchers, err := parseMatchers(mapper, filter, optVal[:matchersSize])
if err != nil {
nflog("failed to parse matchers: %v", err)
return nil, syserr.ErrInvalidArgument
Expand Down
5 changes: 2 additions & 3 deletions pkg/sentry/socket/netfilter/ipv6.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"fmt"

"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
Expand Down Expand Up @@ -129,7 +128,7 @@ func getEntries6(table stack.Table, tablename linux.TableName) (linux.KernelIP6T
return entries, info
}

func modifyEntries6(task *kernel.Task, stk *stack.Stack, optVal []byte, replace *linux.IPTReplace, table *stack.Table) (map[uint32]int, *syserr.Error) {
func modifyEntries6(mapper IDMapper, stk *stack.Stack, optVal []byte, replace *linux.IPTReplace, table *stack.Table) (map[uint32]int, *syserr.Error) {
nflog("set entries: setting entries in table %q", replace.Name.String())

// Convert input into a list of rules and their offsets.
Expand Down Expand Up @@ -165,7 +164,7 @@ func modifyEntries6(task *kernel.Task, stk *stack.Stack, optVal []byte, replace
nflog("entry doesn't have enough room for its matchers (only %d bytes remain)", len(optVal))
return nil, syserr.ErrInvalidArgument
}
matchers, err := parseMatchers(task, filter, optVal[:matchersSize])
matchers, err := parseMatchers(mapper, filter, optVal[:matchersSize])
if err != nil {
nflog("failed to parse matchers: %v", err)
return nil, syserr.ErrInvalidArgument
Expand Down
Binary file added pkg/sentry/socket/netfilter/istio_blob
Binary file not shown.
19 changes: 13 additions & 6 deletions pkg/sentry/socket/netfilter/netfilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
Expand All @@ -34,7 +35,7 @@ import (
// enableLogging controls whether to log the (de)serialization of netfilter
// structs between userspace and netstack. These logs are useful when
// developing iptables, but can pollute sentry logs otherwise.
const enableLogging = false
const enableLogging = true

// nflog logs messages related to the writing and reading of iptables.
func nflog(format string, args ...any) {
Expand Down Expand Up @@ -173,9 +174,15 @@ func setHooksAndUnderflow(info *linux.IPTGetinfo, table stack.Table, offset uint
}
}

// An IDMapper maps UIDs and GIDs to KUIDs and KGIDs.
type IDMapper interface {
MapToKUID(uid auth.UID) auth.KUID
MapToKGID(uid auth.GID) auth.KGID
}

// SetEntries sets iptables rules for a single table. See
// net/ipv4/netfilter/ip_tables.c:translate_table for reference.
func SetEntries(task *kernel.Task, stk *stack.Stack, optVal []byte, ipv6 bool) *syserr.Error {
func SetEntries(mapper IDMapper, stk *stack.Stack, optVal []byte, ipv6 bool) *syserr.Error {
var replace linux.IPTReplace
optVal = replace.UnmarshalBytes(optVal)

Expand All @@ -193,9 +200,9 @@ func SetEntries(task *kernel.Task, stk *stack.Stack, optVal []byte, ipv6 bool) *
var err *syserr.Error
var offsets map[uint32]int
if ipv6 {
offsets, err = modifyEntries6(task, stk, optVal, &replace, &table)
offsets, err = modifyEntries6(mapper, stk, optVal, &replace, &table)
} else {
offsets, err = modifyEntries4(task, stk, optVal, &replace, &table)
offsets, err = modifyEntries4(mapper, stk, optVal, &replace, &table)
}
if err != nil {
return err
Expand Down Expand Up @@ -295,7 +302,7 @@ func SetEntries(task *kernel.Task, stk *stack.Stack, optVal []byte, ipv6 bool) *

// parseMatchers parses 0 or more matchers from optVal. optVal should contain
// only the matchers.
func parseMatchers(task *kernel.Task, filter stack.IPHeaderFilter, optVal []byte) ([]stack.Matcher, error) {
func parseMatchers(mapper IDMapper, filter stack.IPHeaderFilter, optVal []byte) ([]stack.Matcher, error) {
nflog("set entries: parsing matchers of size %d", len(optVal))
var matchers []stack.Matcher
for len(optVal) > 0 {
Expand All @@ -318,7 +325,7 @@ func parseMatchers(task *kernel.Task, filter stack.IPHeaderFilter, optVal []byte
}

// Parse the specific matcher.
matcher, err := unmarshalMatcher(task, match, filter, optVal[linux.SizeOfXTEntryMatch:match.MatchSize])
matcher, err := unmarshalMatcher(mapper, match, filter, optVal[linux.SizeOfXTEntryMatch:match.MatchSize])
if err != nil {
return nil, fmt.Errorf("failed to create matcher: %v", err)
}
Expand Down
56 changes: 56 additions & 0 deletions pkg/sentry/socket/netfilter/netfilter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2023 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 netfilter

import (
_ "embed"
"testing"

"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)

// istioBlob is a golden iptables ruleset generated by Istio. It is already in
// the format ready to by passed to IPT_SO_SET_REPLACE.
//
// Updating this requires running Istio, calling IPT_SO_GET_INFO and
// IPT_SO_GET_ENTRIES, then stitching the structs up. So be careful when
// updating!
//
//go:embed istio_blob
var istioBlob []byte

// FakeIDMapper implements IDMapper.
type FakeIDMapper struct{}

// MapToKUID implements IDMapper.
func (*FakeIDMapper) MapToKUID(auth.UID) auth.KUID {
return 0
}

// MapToKGID implements IDMapper.
func (*FakeIDMapper) MapToKGID(auth.GID) auth.KGID {
return 0
}

// TestIstioBlob tests that we support the iptables ruleset generated by Istio,
// i.e. that we can parse the rules and set them in netstack.
func TestIstioBlob(t *testing.T) {
mapper := FakeIDMapper{}
stack := stack.New(stack.Options{})
if err := SetEntries(&mapper, stack, istioBlob, false); err != nil {
t.Fatalf("failed to set Istio rules, try setting enableLogging and running again: %v", err)
}
}
16 changes: 11 additions & 5 deletions pkg/sentry/socket/netfilter/owner_matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (

"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/marshal"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
Expand All @@ -38,6 +37,10 @@ func (ownerMarshaler) name() string {
return matcherNameOwner
}

func (ownerMarshaler) revision() uint8 {
return 0
}

// marshal implements matchMaker.marshal.
func (ownerMarshaler) marshal(mr matcher) []byte {
matcher := mr.(*OwnerMatcher)
Expand Down Expand Up @@ -65,7 +68,7 @@ func (ownerMarshaler) marshal(mr matcher) []byte {
}

// unmarshal implements matchMaker.unmarshal.
func (ownerMarshaler) unmarshal(task *kernel.Task, buf []byte, filter stack.IPHeaderFilter) (stack.Matcher, error) {
func (ownerMarshaler) unmarshal(mapper IDMapper, buf []byte, filter stack.IPHeaderFilter) (stack.Matcher, error) {
if len(buf) < linux.SizeOfIPTOwnerInfo {
return nil, fmt.Errorf("buf has insufficient size for owner match: %d", len(buf))
}
Expand All @@ -77,9 +80,8 @@ func (ownerMarshaler) unmarshal(task *kernel.Task, buf []byte, filter stack.IPHe
nflog("parsed IPTOwnerInfo: %+v", matchData)

var owner OwnerMatcher
creds := task.Credentials()
owner.uid = creds.UserNamespace.MapToKUID(auth.UID(matchData.UID))
owner.gid = creds.UserNamespace.MapToKGID(auth.GID(matchData.GID))
owner.uid = mapper.MapToKUID(auth.UID(matchData.UID))
owner.gid = mapper.MapToKGID(auth.GID(matchData.GID))

// Check flags.
if matchData.Match&linux.XT_OWNER_UID != 0 {
Expand Down Expand Up @@ -113,6 +115,10 @@ func (*OwnerMatcher) name() string {
return matcherNameOwner
}

func (*OwnerMatcher) revision() uint8 {
return 0
}

// Match implements Matcher.Match.
func (om *OwnerMatcher) Match(hook stack.Hook, pkt stack.PacketBufferPtr, _, _ string) (bool, bool) {
// Support only for OUTPUT chain.
Expand Down

0 comments on commit 7f08016

Please sign in to comment.