Skip to content

Commit f90c106

Browse files
kerumetogvisor-bot
authored andcommitted
Added fields to Nftable struct for better NFT_TABLE_NEW and GET functionality.
These fields include a UID referred to as a handle, an owner field, and a byte slice to hold user-specified meta data. This allows us to validate more scenarios when adding and retrieving a table. PiperOrigin-RevId: 772235513
1 parent d1b450d commit f90c106

File tree

9 files changed

+475
-98
lines changed

9 files changed

+475
-98
lines changed

pkg/abi/linux/nf_tables.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ package linux
1616

1717
// This file contains constants required to support nf_tables.
1818

19+
// Name length constants for nf_table structures. These correspond to values in
20+
// include/uapi/linux/netfilter/nf_tables.h.
21+
const (
22+
NFT_NAME_MAXLEN = 256
23+
NFT_TABLE_MAXNAMELEN = NFT_NAME_MAXLEN
24+
NFT_CHAIN_MAXNAMELEN = NFT_NAME_MAXLEN
25+
NFT_SET_MAXNAMELEN = NFT_NAME_MAXLEN
26+
NFT_OBJ_MAXNAMELEN = NFT_NAME_MAXLEN
27+
NFT_USERDATA_MAXLEN = 256
28+
NFT_OSF_MAXGENRELEN = 16
29+
)
30+
1931
// 16-byte Registers that can be used to maintain state for rules.
2032
// These correspond to values in include/uapi/linux/netfilter/nf_tables.h.
2133
const (
@@ -127,7 +139,10 @@ const (
127139
// NfTableFlags represents table flags that can be set for a table, namely dormant.
128140
// These correspond to values in include/uapi/linux/netfilter/nf_tables.h.
129141
const (
130-
NFT_TABLE_F_DORMANT = 0x1
142+
NFT_TABLE_F_DORMANT uint32 = 0x1
143+
NFT_TABLE_F_OWNER = 0x2
144+
NFT_TABLE_F_PERSIST = 0x4
145+
NFT_TABLE_F_MASK = NFT_TABLE_F_DORMANT | NFT_TABLE_F_OWNER | NFT_TABLE_F_PERSIST
131146
)
132147

133148
// NfTableAttributes represents the netfilter table attributes.

pkg/sentry/socket/netlink/netfilter/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ go_library(
1313
"//pkg/abi/linux",
1414
"//pkg/context",
1515
"//pkg/log",
16+
"//pkg/marshal/primitive",
1617
"//pkg/sentry/inet",
1718
"//pkg/sentry/kernel",
1819
"//pkg/sentry/socket/netlink",

pkg/sentry/socket/netlink/netfilter/protocol.go

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"gvisor.dev/gvisor/pkg/abi/linux"
2222
"gvisor.dev/gvisor/pkg/context"
2323
"gvisor.dev/gvisor/pkg/log"
24+
"gvisor.dev/gvisor/pkg/marshal/primitive"
2425
"gvisor.dev/gvisor/pkg/sentry/inet"
2526
"gvisor.dev/gvisor/pkg/sentry/kernel"
2627
"gvisor.dev/gvisor/pkg/sentry/socket/netlink"
@@ -93,7 +94,7 @@ func (p *Protocol) ProcessMessage(ctx context.Context, s *netlink.Socket, msg *n
9394
// TODO: b/421437663 - Match the message type and call the appropriate Nftables function.
9495
switch msgType {
9596
case linux.NFT_MSG_NEWTABLE:
96-
if err := p.newTable(nft, attrs, family, hdr.Flags); err != nil {
97+
if err := p.newTable(nft, attrs, family, hdr.Flags, ms); err != nil {
9798
log.Debugf("Nftables new table error: %s", err)
9899
return err.GetError()
99100
}
@@ -111,42 +112,93 @@ func (p *Protocol) ProcessMessage(ctx context.Context, s *netlink.Socket, msg *n
111112
}
112113

113114
// newTable creates a new table for the given family.
114-
func (p *Protocol) newTable(nft *nftables.NFTables, attrs map[uint16]nlmsg.BytesView, family stack.AddressFamily, flags uint16) *syserr.AnnotatedError {
115+
func (p *Protocol) newTable(nft *nftables.NFTables, attrs map[uint16]nlmsg.BytesView, family stack.AddressFamily, flags uint16, ms *nlmsg.MessageSet) *syserr.AnnotatedError {
115116
// TODO: b/421437663 - Handle the case where the table name is set to empty string.
116117
// The table name is required.
117118
tabNameBytes, ok := attrs[linux.NFTA_TABLE_NAME]
118119
if !ok {
119120
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("Nftables: Table name attribute is malformed or not found"))
120121
}
121122

122-
var dormant bool
123-
if dbytes, ok := attrs[linux.NFTA_TABLE_FLAGS]; ok {
124-
dflag, _ := dbytes.Uint32()
125-
dormant = (dflag & linux.NFT_TABLE_F_DORMANT) == linux.NFT_TABLE_F_DORMANT
126-
}
127-
128-
tab, err := nft.GetTable(family, tabNameBytes.String())
123+
tab, err := nft.GetTable(family, tabNameBytes.String(), uint32(ms.PortID))
129124
if err != nil && err.GetError() != syserr.ErrNoFileOrDir {
130125
return err
131126
}
132127

133128
// If a table already exists, only update its dormant flags if NLM_F_EXCL and NLM_F_REPLACE
134129
// are not set. From net/netfilter/nf_tables_api.c:nf_tables_newtable:nf_tables_updtable
135130
if tab != nil {
136-
if flags&linux.NLM_F_EXCL == linux.NLM_F_EXCL {
137-
return syserr.NewAnnotatedError(syserr.ErrExists, fmt.Sprintf("Nftables: Table with name: %s already exists", tabNameBytes.String()))
131+
if flags&linux.NLM_F_EXCL != 0 {
132+
return syserr.NewAnnotatedError(syserr.ErrExists, fmt.Sprintf("Nftables: Table with name: %s already exists", tab.GetName()))
133+
}
134+
135+
if flags&linux.NLM_F_REPLACE != 0 {
136+
return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("Nftables: Table with name: %s already exists and NLM_F_REPLACE is not supported", tab.GetName()))
137+
}
138+
139+
return p.updateTable(nft, tab, attrs, family, ms)
140+
}
141+
142+
// TODO: b/421437663 - Support additional user-specified table flags.
143+
var attrFlags uint32 = 0
144+
if uflags, ok := attrs[linux.NFTA_TABLE_FLAGS]; ok {
145+
attrFlags, _ = uflags.Uint32()
146+
// Flags sent through the NFTA_TABLE_FLAGS attribute are of type uint32
147+
// but should only have user flags set. This check needs to be done before table creation.
148+
if attrFlags & ^uint32(linux.NFT_TABLE_F_MASK) != 0 {
149+
return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("Nftables: Table flags set are not supported"))
150+
}
151+
}
152+
153+
tab, err = nft.CreateTable(family, tabNameBytes.String())
154+
if err != nil {
155+
return err
156+
}
157+
158+
if udata, ok := attrs[linux.NFTA_TABLE_USERDATA]; ok {
159+
tab.SetUserData(udata)
160+
}
161+
162+
// Flags should only be assigned after we have successfully created the table.
163+
dormant := (attrFlags & uint32(linux.NFT_TABLE_F_DORMANT)) != 0
164+
tab.SetDormant(dormant)
165+
166+
owner := (attrFlags & uint32(linux.NFT_TABLE_F_OWNER)) != 0
167+
if owner {
168+
if err := tab.SetOwner(uint32(ms.PortID)); err != nil {
169+
return err
138170
}
171+
}
139172

140-
if flags&linux.NLM_F_REPLACE == linux.NLM_F_REPLACE {
141-
return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("Nftables: Table with name: %s already exists and NLM_F_REPLACE is not supported", tabNameBytes.String()))
173+
return nil
174+
}
175+
176+
// updateTable updates an existing table.
177+
func (p *Protocol) updateTable(nft *nftables.NFTables, tab *nftables.Table, attrs map[uint16]nlmsg.BytesView, family stack.AddressFamily, ms *nlmsg.MessageSet) *syserr.AnnotatedError {
178+
var attrFlags uint32
179+
if uflags, ok := attrs[linux.NFTA_TABLE_FLAGS]; ok {
180+
attrFlags, _ = uflags.Uint32()
181+
// This check needs to be done before table update.
182+
if attrFlags & ^uint32(linux.NFT_TABLE_F_MASK) > 0 {
183+
return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("Nftables: Table flags set are not supported"))
142184
}
143-
} else {
144-
tab, err = nft.CreateTable(family, tabNameBytes.String())
145-
if err != nil {
185+
}
186+
187+
// When updating the table, if the table has an owner but the owner flag isn't set,
188+
// the table should not be updated.
189+
// From net/netfilter/nf_tables_api.c:nf_tables_updtable.
190+
if tab.HasOwner() && (attrFlags&uint32(linux.NFT_TABLE_F_OWNER)) == 0 {
191+
return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("Nftables: Table with name: %s already has an owner but NFT_TABLE_F_OWNER was not set when updating the table", tab.GetName()))
192+
}
193+
194+
// The owner is only updated if the table has no previous owner.
195+
if !tab.HasOwner() && attrFlags&uint32(linux.NFT_TABLE_F_OWNER) != 0 {
196+
if err := tab.SetOwner(uint32(ms.PortID)); err != nil {
146197
return err
147198
}
148199
}
149200

201+
dormant := (attrFlags & uint32(linux.NFT_TABLE_F_DORMANT)) != 0
150202
tab.SetDormant(dormant)
151203
return nil
152204
}
@@ -159,12 +211,16 @@ func (p *Protocol) getTable(nft *nftables.NFTables, attrs map[uint16]nlmsg.Bytes
159211
return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("Nftables: Table name attribute is malformed or not found"))
160212
}
161213

162-
tab, err := nft.GetTable(family, tabNameBytes.String())
214+
tab, err := nft.GetTable(family, tabNameBytes.String(), uint32(ms.PortID))
163215
if err != nil {
164216
return err
165217
}
166218

167219
tabName := tab.GetName()
220+
userFlags, err := tab.GetLinuxUserFlagSet()
221+
if err != nil {
222+
return err
223+
}
168224
m := ms.AddMessage(linux.NetlinkMessageHeader{
169225
Type: uint16(linux.NFNL_SUBSYS_NFTABLES)<<8 | uint16(linux.NFT_MSG_GETTABLE),
170226
})
@@ -176,6 +232,18 @@ func (p *Protocol) getTable(nft *nftables.NFTables, attrs map[uint16]nlmsg.Bytes
176232
ResourceID: uint16(0),
177233
})
178234
m.PutAttrString(linux.NFTA_TABLE_NAME, tabName)
235+
m.PutAttr(linux.NFTA_TABLE_USE, primitive.AllocateUint32(uint32(tab.ChainCount())))
236+
m.PutAttr(linux.NFTA_TABLE_HANDLE, primitive.AllocateUint64(tab.GetHandle()))
237+
m.PutAttr(linux.NFTA_TABLE_FLAGS, primitive.AllocateUint8(userFlags))
238+
239+
if tab.HasOwner() {
240+
m.PutAttr(linux.NFTA_TABLE_OWNER, primitive.AllocateUint32(tab.GetOwner()))
241+
}
242+
243+
if tab.HasUserData() {
244+
m.PutAttr(linux.NFTA_TABLE_USERDATA, primitive.AsByteSlice(tab.GetUserData()))
245+
}
246+
179247
return nil
180248
}
181249

pkg/tcpip/nftables/nftables.go

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"slices"
2020

2121
"gvisor.dev/gvisor/pkg/abi/linux"
22+
"gvisor.dev/gvisor/pkg/atomicbitops"
2223
"gvisor.dev/gvisor/pkg/rand"
2324
"gvisor.dev/gvisor/pkg/syserr"
2425
"gvisor.dev/gvisor/pkg/tcpip"
@@ -241,7 +242,7 @@ func NewNFTables(clock tcpip.Clock, rng rand.RNG) *NFTables {
241242
if rng.Reader == nil {
242243
panic("nftables state must be initialized with a non-nil random number generator")
243244
}
244-
return &NFTables{clock: clock, startTime: clock.Now(), rng: rng}
245+
return &NFTables{clock: clock, startTime: clock.Now(), rng: rng, tableHandleCounter: atomicbitops.Uint64{}}
245246
}
246247

247248
// Flush clears entire ruleset and all data for all address families.
@@ -264,7 +265,7 @@ func (nf *NFTables) FlushAddressFamily(family stack.AddressFamily) *syserr.Annot
264265
}
265266

266267
// GetTable validates the inputs and gets a table if it exists, error otherwise.
267-
func (nf *NFTables) GetTable(family stack.AddressFamily, tableName string) (*Table, *syserr.AnnotatedError) {
268+
func (nf *NFTables) GetTable(family stack.AddressFamily, tableName string, portID uint32) (*Table, *syserr.AnnotatedError) {
268269
// Ensures address family is valid.
269270
if err := validateAddressFamily(family); err != nil {
270271
return nil, err
@@ -284,6 +285,13 @@ func (nf *NFTables) GetTable(family stack.AddressFamily, tableName string) (*Tab
284285
return nil, syserr.NewAnnotatedError(syserr.ErrNoFileOrDir, fmt.Sprintf("table %s not found for address family %v", tableName, family))
285286
}
286287

288+
// If the table has an owner, it must match the Netlink portID of the calling process.
289+
// User space processes only have non-zero port ids.
290+
// Only the kernel can have a zero port id.
291+
if t.HasOwner() && portID != 0 && portID != t.GetOwner() {
292+
return nil, syserr.NewAnnotatedError(syserr.ErrNotPermitted, fmt.Sprintf("table %s has owner %d, which does not match the Netlink portID of the calling process %d", tableName, t.GetOwner(), portID))
293+
}
294+
287295
return t, nil
288296
}
289297

@@ -329,12 +337,18 @@ func (nf *NFTables) AddTable(family stack.AddressFamily, name string,
329337
afFilter: nf.filters[family],
330338
chains: make(map[string]*Chain),
331339
flagSet: make(map[TableFlag]struct{}),
340+
handle: nf.getNewTableHandle(),
332341
}
333342
tableMap[name] = t
334343

335344
return t, nil
336345
}
337346

347+
// getNewTableHandle returns a new table handle for the NFTables object.
348+
func (nf *NFTables) getNewTableHandle() uint64 {
349+
return nf.tableHandleCounter.Add(1)
350+
}
351+
338352
// CreateTable makes a new table for the specified address family like AddTable
339353
// but also returns an error if a table by the same name already exists.
340354
// Note: this interface mirrors the difference between the create and add
@@ -353,7 +367,7 @@ func (nf *NFTables) DeleteTable(family stack.AddressFamily, tableName string) (b
353367
}
354368

355369
// Gets and checks the table.
356-
t, err := nf.GetTable(family, tableName)
370+
t, err := nf.GetTable(family, tableName, 0)
357371
if err != nil {
358372
return false, err
359373
}
@@ -371,7 +385,7 @@ func (nf *NFTables) DeleteTable(family stack.AddressFamily, tableName string) (b
371385
// GetChain validates the inputs and gets a chain if it exists, error otherwise.
372386
func (nf *NFTables) GetChain(family stack.AddressFamily, tableName string, chainName string) (*Chain, *syserr.AnnotatedError) {
373387
// Gets and checks the table.
374-
t, err := nf.GetTable(family, tableName)
388+
t, err := nf.GetTable(family, tableName, 0)
375389
if err != nil {
376390
return nil, err
377391
}
@@ -389,7 +403,7 @@ func (nf *NFTables) GetChain(family stack.AddressFamily, tableName string, chain
389403
// Note: if the chain is not a base chain, info should be nil.
390404
func (nf *NFTables) AddChain(family stack.AddressFamily, tableName string, chainName string, info *BaseChainInfo, comment string, errorOnDuplicate bool) (*Chain, *syserr.AnnotatedError) {
391405
// Gets and checks the table.
392-
t, err := nf.GetTable(family, tableName)
406+
t, err := nf.GetTable(family, tableName, 0)
393407
if err != nil {
394408
return nil, err
395409
}
@@ -411,7 +425,7 @@ func (nf *NFTables) CreateChain(family stack.AddressFamily, tableName string, ch
411425
// an error if the address family is invalid or the table doesn't exist.
412426
func (nf *NFTables) DeleteChain(family stack.AddressFamily, tableName string, chainName string) (bool, *syserr.AnnotatedError) {
413427
// Gets and checks the table.
414-
t, err := nf.GetTable(family, tableName)
428+
t, err := nf.GetTable(family, tableName, 0)
415429
if err != nil {
416430
return false, err
417431
}
@@ -438,6 +452,56 @@ func (t *Table) GetAddressFamily() stack.AddressFamily {
438452
return t.afFilter.family
439453
}
440454

455+
// GetHandle returns the handle of the table.
456+
func (t *Table) GetHandle() uint64 {
457+
return t.handle
458+
}
459+
460+
// GetOwner returns the owner of the table.
461+
func (t *Table) GetOwner() uint32 {
462+
return t.owner
463+
}
464+
465+
// SetOwner sets the owner of the table. If the table already has an owner, it
466+
// is not updated.
467+
func (t *Table) SetOwner(nlpid uint32) *syserr.AnnotatedError {
468+
// This should only be called once, when setting the owner of a table for the first time.
469+
if t.HasOwner() {
470+
return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("table %s already has an owner", t.name))
471+
}
472+
473+
t.flagSet[TableFlagOwner] = struct{}{}
474+
t.owner = nlpid
475+
return nil
476+
}
477+
478+
// HasOwner returns whether the table has an owner.
479+
func (t *Table) HasOwner() bool {
480+
_, ok := t.flagSet[TableFlagOwner]
481+
return ok
482+
}
483+
484+
// GetUserData returns the user data of the table.
485+
func (t *Table) GetUserData() []byte {
486+
return t.userData
487+
}
488+
489+
// HasUserData returns whether the table has user data.
490+
func (t *Table) HasUserData() bool {
491+
return t.userData != nil
492+
}
493+
494+
// SetUserData sets the user data of the table.
495+
func (t *Table) SetUserData(data []byte) {
496+
// User data should only be set once.
497+
if t.userData != nil {
498+
return
499+
}
500+
501+
t.userData = make([]byte, len(data))
502+
copy(t.userData, data)
503+
}
504+
441505
// IsDormant returns whether the table is dormant.
442506
func (t *Table) IsDormant() bool {
443507
_, dormant := t.flagSet[TableFlagDormant]
@@ -453,6 +517,34 @@ func (t *Table) SetDormant(dormant bool) {
453517
}
454518
}
455519

520+
// GetLinuxFlagSet returns the flag set of the table.
521+
// Although user flags map to uint8 space, internal flags could eventually be
522+
// supported, which together map to a uint32 space.
523+
func (t *Table) GetLinuxFlagSet() (uint32, *syserr.AnnotatedError) {
524+
var flags uint32 = 0
525+
for flag := range t.flagSet {
526+
switch flag {
527+
case TableFlagDormant:
528+
flags |= linux.NFT_TABLE_F_DORMANT
529+
case TableFlagOwner:
530+
flags |= linux.NFT_TABLE_F_OWNER
531+
default:
532+
return 0, syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("unsupported flag %v", flag))
533+
}
534+
}
535+
536+
return flags, nil
537+
}
538+
539+
// GetLinuxUserFlagSet returns the user flag set of the table.
540+
func (t *Table) GetLinuxUserFlagSet() (uint8, *syserr.AnnotatedError) {
541+
flags, err := t.GetLinuxFlagSet()
542+
if err != nil {
543+
return 0, err
544+
}
545+
return uint8(flags & linux.NFT_TABLE_F_MASK), nil
546+
}
547+
456548
// GetChain returns the chain with the specified name if it exists, error
457549
// otherwise.
458550
func (t *Table) GetChain(chainName string) (*Chain, *syserr.AnnotatedError) {

0 commit comments

Comments
 (0)