Skip to content

Commit fc40e23

Browse files
parth-opensrcgvisor-bot
authored andcommitted
nftables: Added support for nla policy.
Similar to `nla_policy` defined in include/net/netlink.h. PiperOrigin-RevId: 836423732
1 parent 95f8769 commit fc40e23

File tree

5 files changed

+289
-25
lines changed

5 files changed

+289
-25
lines changed

pkg/abi/linux/netlink.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,33 @@ const NetlinkAttrHeaderSize = 4
136136
// uapi/linux/netlink.h.
137137
const NLA_ALIGNTO = 4
138138

139+
// Standard attribute types to specify validation policy, from
140+
// include/net/netlink.h.
141+
const (
142+
NLA_UNSPEC = iota
143+
NLA_U8
144+
NLA_U16
145+
NLA_U32
146+
NLA_U64
147+
NLA_STRING
148+
NLA_FLAG
149+
NLA_MSECS
150+
NLA_NESTED
151+
NLA_NESTED_ARRAY
152+
NLA_NUL_STRING
153+
NLA_BINARY
154+
NLA_S8
155+
NLA_S16
156+
NLA_S32
157+
NLA_S64
158+
NLA_BITFIELD32
159+
NLA_REJECT
160+
NLA_BE16
161+
NLA_BE32
162+
__NLA_TYPE_MAX
163+
NLA_TYPE_MAX = __NLA_TYPE_MAX - 1
164+
)
165+
139166
// Socket options, from uapi/linux/netlink.h.
140167
const (
141168
NETLINK_ADD_MEMBERSHIP = 1

pkg/tcpip/nftables/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ go_library(
2929
"nft_ranged.go",
3030
"nft_route.go",
3131
"nftables.go",
32+
"nftables_attrs.go",
3233
"nftables_mutex.go",
3334
"nftables_types.go",
3435
"nftinterp.go",
@@ -63,10 +64,12 @@ go_test(
6364
"//pkg/abi/linux",
6465
"//pkg/buffer",
6566
"//pkg/rand",
67+
"//pkg/sentry/socket/netlink/nlmsg",
6668
"//pkg/sync",
6769
"//pkg/tcpip",
6870
"//pkg/tcpip/faketime",
6971
"//pkg/tcpip/header",
7072
"//pkg/tcpip/stack",
73+
"@com_github_google_go_cmp//cmp:go_default_library",
7174
],
7275
)
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
// Copyright 2024 The gVisor Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package nftables
16+
17+
import (
18+
"gvisor.dev/gvisor/pkg/abi/linux"
19+
"gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg"
20+
)
21+
22+
var nlaTypeToString = [...]string{
23+
linux.NLA_UNSPEC: "NLA_UNSPEC",
24+
linux.NLA_U8: "NLA_U8",
25+
linux.NLA_U16: "NLA_U16",
26+
linux.NLA_U32: "NLA_U32",
27+
linux.NLA_U64: "NLA_U64",
28+
linux.NLA_STRING: "NLA_STRING",
29+
linux.NLA_FLAG: "NLA_FLAG",
30+
linux.NLA_MSECS: "NLA_MSECS",
31+
linux.NLA_NESTED: "NLA_NESTED",
32+
linux.NLA_NESTED_ARRAY: "NLA_NESTED_ARRAY",
33+
linux.NLA_NUL_STRING: "NLA_NUL_STRING",
34+
linux.NLA_BINARY: "NLA_BINARY",
35+
linux.NLA_S8: "NLA_S8",
36+
linux.NLA_S16: "NLA_S16",
37+
linux.NLA_S32: "NLA_S32",
38+
linux.NLA_S64: "NLA_S64",
39+
linux.NLA_BITFIELD32: "NLA_BITFIELD32",
40+
linux.NLA_REJECT: "NLA_REJECT",
41+
linux.NLA_BE16: "NLA_BE16",
42+
linux.NLA_BE32: "NLA_BE32",
43+
}
44+
45+
// NlaPolicy represents the policy for a netlink attribute.
46+
// Similar to struct nla_policy defined in include/net/netlink.h.
47+
type NlaPolicy struct {
48+
nlaType uint8
49+
validator NlaPolicyValidator
50+
}
51+
52+
// NlaPolicyValidator is used to validate the data for a netlink attribute.
53+
type NlaPolicyValidator func(data any) bool
54+
55+
func validateData(policy *NlaPolicy, data nlmsg.BytesView) bool {
56+
validator := policy.validator
57+
if validator == nil {
58+
// No validator is set, still check the data type
59+
// through the switch case below.
60+
validator = func(data any) bool { return true }
61+
}
62+
switch policy.nlaType {
63+
case linux.NLA_U8:
64+
if v, ok := data.Uint8(); ok {
65+
return validator(v)
66+
}
67+
case linux.NLA_S8:
68+
if v, ok := data.Int8(); ok {
69+
return validator(v)
70+
}
71+
case linux.NLA_U16:
72+
if v, ok := data.Uint16(); ok {
73+
return validator(v)
74+
}
75+
case linux.NLA_S16:
76+
if v, ok := data.Int16(); ok {
77+
return validator(v)
78+
}
79+
case linux.NLA_BE16:
80+
if v, ok := data.Uint16(); ok {
81+
return validator(nlmsg.NetToHostU16(v))
82+
}
83+
case linux.NLA_U32:
84+
if v, ok := data.Uint32(); ok {
85+
return validator(v)
86+
}
87+
case linux.NLA_S32:
88+
if v, ok := data.Int32(); ok {
89+
return validator(v)
90+
}
91+
case linux.NLA_BE32:
92+
if v, ok := data.Uint32(); ok {
93+
return validator(nlmsg.NetToHostU32(v))
94+
}
95+
case linux.NLA_U64:
96+
if v, ok := data.Uint64(); ok {
97+
return validator(v)
98+
}
99+
case linux.NLA_S64:
100+
if v, ok := data.Int64(); ok {
101+
return validator(v)
102+
}
103+
// return true for all other valid types.
104+
case linux.NLA_STRING,
105+
linux.NLA_NUL_STRING,
106+
linux.NLA_BINARY,
107+
linux.NLA_FLAG,
108+
linux.NLA_MSECS,
109+
linux.NLA_NESTED,
110+
linux.NLA_NESTED_ARRAY,
111+
linux.NLA_BITFIELD32,
112+
linux.NLA_REJECT:
113+
114+
return true
115+
}
116+
return false
117+
}
118+
119+
type integer interface {
120+
int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64
121+
}
122+
123+
// AttrMaxValidator checks if the data is less than or equal to the maxValue.
124+
func AttrMaxValidator[T integer](maxValue T) NlaPolicyValidator {
125+
return func(data any) bool {
126+
v, ok := data.(T)
127+
return ok && v <= maxValue
128+
}
129+
}
130+
131+
// NfParseWithPolicy parses the data bytes, clearing the nested attribute bit if present.
132+
// For nested attributes, Linux supports these attributes having the bit
133+
// set or unset. It is cleared here for consistency. The policy map is used to validate the
134+
// attributes with the given validation function, if present.
135+
func NfParseWithPolicy(data nlmsg.AttrsView, policy []NlaPolicy) (map[uint16]nlmsg.BytesView, bool) {
136+
attrs, ok := data.Parse()
137+
if !ok {
138+
return nil, ok
139+
}
140+
policyLen := uint16(len(policy))
141+
newAttrs := make(map[uint16]nlmsg.BytesView)
142+
for attr, attrData := range attrs {
143+
unNestedAttr := attr & ^linux.NLA_F_NESTED
144+
policyExists := unNestedAttr < policyLen
145+
if policyExists {
146+
if ok := validateData(&policy[unNestedAttr], attrData); !ok {
147+
return nil, false
148+
}
149+
}
150+
newAttrs[unNestedAttr] = attrData
151+
}
152+
return newAttrs, true
153+
}
154+
155+
// NfParse parses the data bytes with no validation.
156+
func NfParse(data nlmsg.AttrsView) (map[uint16]nlmsg.BytesView, bool) {
157+
return NfParseWithPolicy(data, nil)
158+
}
159+
160+
// HasAttr returns whether the given attribute key is present in the attribute map.
161+
func HasAttr(attrName uint16, attrs map[uint16]nlmsg.BytesView) bool {
162+
_, ok := attrs[attrName]
163+
return ok
164+
}

pkg/tcpip/nftables/nftables_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ import (
2222
"testing"
2323
"time"
2424

25+
"github.com/google/go-cmp/cmp"
2526
"gvisor.dev/gvisor/pkg/abi/linux"
2627
"gvisor.dev/gvisor/pkg/buffer"
2728
"gvisor.dev/gvisor/pkg/rand"
29+
"gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg"
2830
"gvisor.dev/gvisor/pkg/sync"
2931
"gvisor.dev/gvisor/pkg/tcpip"
3032
"gvisor.dev/gvisor/pkg/tcpip/faketime"
@@ -3830,3 +3832,96 @@ func (*fixedReader) Read(buf []byte) (int, error) {
38303832
}
38313833
return len(buf), nil
38323834
}
3835+
3836+
func createEmptyNlMsg() *nlmsg.Message {
3837+
m := nlmsg.NewMessage(linux.NetlinkMessageHeader{})
3838+
m.Put(&linux.NetFilterGenMsg{})
3839+
return m
3840+
}
3841+
3842+
func TestNfAttrParser(t *testing.T) {
3843+
tests := []struct {
3844+
name string
3845+
msg *nlmsg.Message
3846+
policy []NlaPolicy
3847+
want map[uint16]nlmsg.BytesView
3848+
}{
3849+
{
3850+
name: "validAttrs",
3851+
msg: func() *nlmsg.Message {
3852+
m := createEmptyNlMsg()
3853+
m.PutAttr(linux.NFTA_TABLE_HANDLE, nlmsg.PutU32(123))
3854+
m.PutAttr(linux.NFTA_TABLE_USE, nlmsg.PutU64(1))
3855+
m.PutAttrString(linux.NFTA_TABLE_NAME, "test_table")
3856+
return m
3857+
}(),
3858+
policy: []NlaPolicy{
3859+
linux.NFTA_TABLE_HANDLE: {nlaType: linux.NLA_BE32, validator: AttrMaxValidator[uint32](256)},
3860+
linux.NFTA_TABLE_USE: {nlaType: linux.NLA_U64, validator: AttrMaxValidator[uint64](nlmsg.HostToNetU64(1))},
3861+
linux.NFTA_TABLE_NAME: {nlaType: linux.NLA_STRING},
3862+
},
3863+
want: map[uint16]nlmsg.BytesView{
3864+
linux.NFTA_TABLE_HANDLE: []byte{0, 0, 0, 123}, // Network byte order.
3865+
linux.NFTA_TABLE_USE: []byte{0, 0, 0, 0, 0, 0, 0, 1}, // Network byte order.
3866+
// The string is expected to be null terminated.
3867+
linux.NFTA_TABLE_NAME: []byte{'t', 'e', 's', 't', '_', 't', 'a', 'b', 'l', 'e', '\x00'},
3868+
},
3869+
},
3870+
{
3871+
name: "attrsWithoutPolicy",
3872+
msg: func() *nlmsg.Message {
3873+
m := createEmptyNlMsg()
3874+
m.PutAttr(linux.NFTA_TABLE_HANDLE, nlmsg.PutU32(123))
3875+
m.PutAttr(linux.NFTA_TABLE_USE, nlmsg.PutU64(1))
3876+
return m
3877+
}(),
3878+
want: map[uint16]nlmsg.BytesView{
3879+
linux.NFTA_TABLE_HANDLE: []byte{0, 0, 0, 123},
3880+
linux.NFTA_TABLE_USE: []byte{0, 0, 0, 0, 0, 0, 0, 1},
3881+
},
3882+
},
3883+
{
3884+
name: "attrGtThanMax",
3885+
msg: func() *nlmsg.Message {
3886+
m := createEmptyNlMsg()
3887+
m.PutAttr(linux.NFTA_TABLE_HANDLE, nlmsg.PutU32(123))
3888+
return m
3889+
}(),
3890+
policy: []NlaPolicy{
3891+
linux.NFTA_TABLE_HANDLE: {nlaType: linux.NLA_BE32, validator: AttrMaxValidator[uint32](1)},
3892+
},
3893+
},
3894+
{
3895+
name: "attrWithInvalidType",
3896+
msg: func() *nlmsg.Message {
3897+
m := createEmptyNlMsg()
3898+
m.PutAttr(linux.NFTA_TABLE_HANDLE, nlmsg.PutU16(123))
3899+
return m
3900+
}(),
3901+
policy: []NlaPolicy{
3902+
linux.NFTA_TABLE_HANDLE: {nlaType: linux.NLA_U32},
3903+
},
3904+
},
3905+
}
3906+
3907+
for _, test := range tests {
3908+
t.Run(test.name, func(t *testing.T) {
3909+
var nfGenMsg linux.NetFilterGenMsg
3910+
attr, ok := test.msg.GetData(&nfGenMsg)
3911+
if !ok {
3912+
t.Fatalf("GetData() failed for msg %v", test.msg)
3913+
}
3914+
got, gotOk := NfParseWithPolicy(attr, test.policy)
3915+
wantOk := test.want != nil
3916+
if wantOk != gotOk {
3917+
t.Fatalf("NfParseWithPolicy() failed, want ok: %v, got ok: %v", wantOk, gotOk)
3918+
}
3919+
if !wantOk {
3920+
return
3921+
}
3922+
if diff := cmp.Diff(test.want, got); diff != "" {
3923+
t.Fatalf("NfParseWithPolicy() returned diff (-want +got): %v", diff)
3924+
}
3925+
})
3926+
}
3927+
}

pkg/tcpip/nftables/nftables_types.go

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,31 +1112,6 @@ func validateVerdictData(tab *Table, bytes nlmsg.AttrsView) (stack.NFVerdict, *s
11121112
return v, nil
11131113
}
11141114

1115-
// HasAttr returns whether the given attribute key is present in the attribute map.
1116-
func HasAttr(attrName uint16, attrs map[uint16]nlmsg.BytesView) bool {
1117-
_, ok := attrs[attrName]
1118-
return ok
1119-
}
1120-
1121-
// NfParse parses the data bytes, clearing the nested attribute bit if present.
1122-
// For nested attributes, Linux supports these attributes having the bit
1123-
// set or unset. It is cleared here for consistency.
1124-
func NfParse(data nlmsg.AttrsView) (map[uint16]nlmsg.BytesView, bool) {
1125-
attrs, ok := data.Parse()
1126-
if !ok {
1127-
return nil, ok
1128-
}
1129-
1130-
newAttrs := make(map[uint16]nlmsg.BytesView)
1131-
// TODO - b/421437663: If any validation has to be done on nested attributes,
1132-
// it should be done here.
1133-
for attr, attrData := range attrs {
1134-
newAttrs[attr & ^linux.NLA_F_NESTED] = attrData
1135-
}
1136-
1137-
return newAttrs, ok
1138-
}
1139-
11401115
// deepCopyRule returns a deep copy of the Rule struct.
11411116
func deepCopyRule(rule *Rule, chainCopy *Chain) *Rule {
11421117
return &Rule{

0 commit comments

Comments
 (0)