Skip to content

Commit bc42af4

Browse files
parth-opensrcgvisor-bot
authored andcommitted
Nftables: Remove the overheads from register ops.
- Shift register boundary checks from `evaluate` (hot path) to rule `init` (once at setup). - Remove `registerData` interfaces with concrete types to eliminate transient heap allocations and interface overhead in the evaluation hot path. - Fix register store validation to match Linux kernel behavior (net/netfilter/nf_tables_api.c:nft_validate_register_store). Instead of hard-limiting to 4 or 16 bytes, we validate against the 64-byte total array limit. - nft_payload: safeguard signed-unsigned overflow. PiperOrigin-RevId: 888913686
1 parent d2403f2 commit bc42af4

15 files changed

Lines changed: 605 additions & 707 deletions

pkg/tcpip/nftables/nft_bitwise.go

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ func (bop bitwiseOp) String() string {
4949
// a given register, storing the result in a destination register.
5050
// Note: bitwise operations are not supported for the verdict register.
5151
type bitwise struct {
52-
sreg uint8 // Number of the source register.
53-
dreg uint8 // Number of the destination register.
54-
bop bitwiseOp // Bitwise operator to use.
55-
blen uint8 // Number of bytes to apply bitwise operation to.
56-
mask bytesData // Mask to apply bitwise & for boolean operations (before ^).
57-
xor bytesData // Xor to apply bitwise ^ for boolean operations (after &).
58-
shift uint32 // Shift to apply bitwise <</>> for non-boolean operations.
52+
sregIdx uint8 // Index of the source register in registerSet.data.
53+
dregIdx uint8 // Index of the destination register in registerSet.data.
54+
bop bitwiseOp // Bitwise operator to use.
55+
blen uint8 // Number of bytes to apply bitwise operation to.
56+
mask []byte // Mask to apply bitwise & for boolean operations (before ^).
57+
xor []byte // Xor to apply bitwise ^ for boolean operations (after &).
58+
shift uint32 // Shift to apply bitwise <</>> for non-boolean operations.
5959

6060
// Note: Technically, the linux kernel has defined bool, lshift, and rshift
6161
// as the 3 types of bitwise operations. However, we have not been able to
@@ -78,7 +78,15 @@ func newBitwiseBool(sreg, dreg uint8, mask, xor []byte) (*bitwise, *syserr.Annot
7878
if blen > linux.NFT_REG_SIZE || (blen > linux.NFT_REG32_SIZE && (is4ByteRegister(sreg) || is4ByteRegister(dreg))) {
7979
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("bitwise boolean operation cannot use more than %d bytes", linux.NFT_REG_SIZE))
8080
}
81-
return &bitwise{sreg: sreg, dreg: dreg, bop: linux.NFT_BITWISE_BOOL, blen: uint8(blen), mask: newBytesData(mask), xor: newBytesData(xor)}, nil
81+
var err *syserr.AnnotatedError
82+
var sregIdx, dregIdx uint8
83+
if sregIdx, err = regNumToIdx(sreg, blen); err != nil {
84+
return nil, err
85+
}
86+
if dregIdx, err = regNumToIdx(dreg, blen); err != nil {
87+
return nil, err
88+
}
89+
return &bitwise{sregIdx: sregIdx, dregIdx: dregIdx, bop: linux.NFT_BITWISE_BOOL, blen: uint8(blen), mask: mask, xor: xor}, nil
8290
}
8391

8492
// newBitwiseShift creates a new bitwise shift operation.
@@ -96,7 +104,15 @@ func newBitwiseShift(sreg, dreg, blen uint8, shift uint32, right bool) (*bitwise
96104
if right {
97105
bop = linux.NFT_BITWISE_RSHIFT
98106
}
99-
return &bitwise{sreg: sreg, dreg: dreg, blen: blen, bop: bop, shift: shift}, nil
107+
var err *syserr.AnnotatedError
108+
var sregIdx, dregIdx uint8
109+
if sregIdx, err = regNumToIdx(sreg, int(blen)); err != nil {
110+
return nil, err
111+
}
112+
if dregIdx, err = regNumToIdx(dreg, int(blen)); err != nil {
113+
return nil, err
114+
}
115+
return &bitwise{sregIdx: sregIdx, dregIdx: dregIdx, blen: blen, bop: bop, shift: shift}, nil
100116
}
101117

102118
// evaluateBitwiseBool performs the bitwise boolean operation on the source register
@@ -176,11 +192,11 @@ func evaluateBitwiseRshift(sregBuf, dregBuf []byte, shift uint32) {
176192
// data and stores the result in the destination register.
177193
func (op bitwise) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Rule) {
178194
// Gets the specified buffers of the source and destination registers.
179-
sregBuf := getRegisterBuffer(regs, op.sreg)[:op.blen]
180-
dregBuf := getRegisterBuffer(regs, op.dreg)[:op.blen]
195+
sregBuf := regs.data[op.sregIdx : op.sregIdx+op.blen]
196+
dregBuf := regs.data[op.dregIdx : op.dregIdx+op.blen]
181197

182198
if op.bop == linux.NFT_BITWISE_BOOL {
183-
evaluateBitwiseBool(sregBuf, dregBuf, op.mask.data, op.xor.data)
199+
evaluateBitwiseBool(sregBuf, dregBuf, op.mask, op.xor)
184200
return
185201
}
186202

@@ -189,7 +205,6 @@ func (op bitwise) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Rul
189205
} else {
190206
evaluateBitwiseRshift(sregBuf, dregBuf, op.shift)
191207
}
192-
193208
}
194209

195210
func (op bitwise) GetExprName() string {

pkg/tcpip/nftables/nft_byteorder.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ import (
2727
// byteorder is an operation that performs byte order operations on a register.
2828
// Note: byteorder operations are not supported for the verdict register.
2929
type byteorder struct {
30-
sreg uint8 // Number of the source register.
31-
dreg uint8 // Number of the destination register.
32-
bop byteorderOp // Byte order operation to perform.
33-
blen uint8 // Number of total bytes to operate on.
34-
size uint8 // Granular size in bytes to operate on.
30+
sregIdx uint8 // Index of the source register in registerSet.data.
31+
dregIdx uint8 // Index of the destination register in registerSet.data.
32+
bop byteorderOp // Byte order operation to perform.
33+
blen uint8 // Number of total bytes to operate on.
34+
size uint8 // Granular size in bytes to operate on.
3535
}
3636

3737
// byteorderOp is the byte order operator for a byteorder operation.
@@ -86,15 +86,23 @@ func newByteorder(sreg, dreg uint8, bop byteorderOp, blen, size uint8) (*byteord
8686
if size != 2 && size != 4 && size != 8 {
8787
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("byteorder operation size %d is not supported", size))
8888
}
89-
return &byteorder{sreg: sreg, dreg: dreg, bop: bop, blen: blen, size: size}, nil
89+
var err *syserr.AnnotatedError
90+
var sregIdx, dregIdx uint8
91+
if sregIdx, err = regNumToIdx(sreg, int(blen)); err != nil {
92+
return nil, err
93+
}
94+
if dregIdx, err = regNumToIdx(dreg, int(blen)); err != nil {
95+
return nil, err
96+
}
97+
return &byteorder{sregIdx: sregIdx, dregIdx: dregIdx, bop: bop, blen: blen, size: size}, nil
9098
}
9199

92100
// evaluate for byteorder performs the byte order operation on the source
93101
// register and stores the result in the destination register.
94102
func (op byteorder) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Rule) {
95103
// Gets the source and destination registers.
96-
src := getRegisterBuffer(regs, op.sreg)
97-
dst := getRegisterBuffer(regs, op.dreg)
104+
src := regs.data[op.sregIdx:]
105+
dst := regs.data[op.dregIdx:]
98106

99107
// Performs the byte order operations on the source register and stores the
100108
// result in as many bytes as are available in the destination register.

pkg/tcpip/nftables/nft_comparison.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ import (
3030
// if the comparison is false.
3131
// Note: comparison operations are not supported for the verdict register.
3232
type comparison struct {
33-
data bytesData // Data to compare the source register to.
34-
sreg uint8 // Number of the source register.
35-
cop cmpOp // Comparison operator.
33+
data []byte // Data to compare the source register to.
34+
sregIdx uint8 // Index of the source register in registerSet.data.
35+
cop cmpOp // Comparison operator.
3636
}
3737

3838
// cmpOp is the comparison operator for a Comparison operation.
@@ -73,25 +73,25 @@ func newComparison(sreg uint8, op int, data []byte) (*comparison, *syserr.Annota
7373
if isVerdictRegister(sreg) {
7474
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "comparison operation does not support verdict register as source register")
7575
}
76-
bytesData := newBytesData(data)
77-
if err := bytesData.validateRegister(sreg); err != nil {
76+
sregIdx, err := regNumToIdx(sreg, len(data))
77+
if err != nil {
7878
return nil, err
7979
}
8080
cop := cmpOp(op)
8181
if err := validateComparisonOp(cop); err != nil {
8282
return nil, err
8383
}
84-
return &comparison{sreg: sreg, cop: cop, data: bytesData}, nil
84+
return &comparison{sregIdx: sregIdx, cop: cop, data: data}, nil
8585
}
8686

8787
// evaluate for Comparison compares the data in the source register to the given
8888
// data and breaks from the rule if the comparison is false.
8989
func (op comparison) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Rule) {
9090
// Gets the data to compare to.
91-
data := op.data.data
91+
data := op.data
9292

9393
// Gets the data from the source register.
94-
regBuf := getRegisterBuffer(regs, op.sreg)[:len(data)]
94+
regBuf := regs.data[op.sregIdx : int(op.sregIdx)+len(data)]
9595

9696
// Compares bytes from left to right for all bytes in the comparison data.
9797
dif := bytes.Compare(regBuf, data)
@@ -123,9 +123,9 @@ func (op comparison) GetExprName() string {
123123

124124
func (op comparison) Dump() ([]byte, *syserr.AnnotatedError) {
125125
m := &nlmsg.Message{}
126-
m.PutAttr(linux.NFTA_CMP_SREG, nlmsg.PutU32(uint32(op.sreg)))
126+
m.PutAttr(linux.NFTA_CMP_SREG, nlmsg.PutU32(uint32(formatRegIdxForDump(op.sregIdx))))
127127
m.PutAttr(linux.NFTA_CMP_OP, nlmsg.PutU32(uint32(op.cop)))
128-
regDump, err := op.data.Dump()
128+
regDump, err := dumpDataAttr(op.data)
129129
if err != nil {
130130
return nil, err
131131
}

pkg/tcpip/nftables/nft_immediate.go

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,20 @@ import (
2424

2525
// immediate is an operation that sets the data in a register.
2626
type immediate struct {
27-
data registerData // Data to set the destination register to.
28-
dreg uint8 // Number of the destination register.
27+
dregIdx uint8 // Index of the destination register in registerSet.data.
28+
dataType uint32 // Type of data in the register (NFT_DATA_VALUE or NFT_DATA_VERDICT).
29+
data []byte // optional
30+
verdict stack.NFVerdict // optional
2931
}
3032

3133
// evaluate for immediate sets the data in the destination register.
3234
func (op immediate) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Rule) {
33-
op.data.storeData(regs, op.dreg)
35+
switch op.dataType {
36+
case linux.NFT_DATA_VALUE:
37+
copy(regs.data[op.dregIdx:], op.data)
38+
case linux.NFT_DATA_VERDICT:
39+
regs.verdict = op.verdict
40+
}
3441
}
3542

3643
func (op immediate) GetExprName() string {
@@ -39,21 +46,37 @@ func (op immediate) GetExprName() string {
3946

4047
func (op immediate) Dump() ([]byte, *syserr.AnnotatedError) {
4148
m := &nlmsg.Message{}
42-
m.PutAttr(linux.NFTA_IMMEDIATE_DREG, nlmsg.PutU32(uint32(op.dreg)))
43-
regDump, err := op.data.Dump()
49+
var regDump []byte
50+
var err *syserr.AnnotatedError
51+
reg := uint32(0)
52+
switch op.dataType {
53+
case linux.NFT_DATA_VERDICT:
54+
regDump, err = dumpVerdictDataAttr(op.verdict)
55+
case linux.NFT_DATA_VALUE:
56+
reg = uint32(formatRegIdxForDump(op.dregIdx))
57+
regDump, err = dumpDataAttr(op.data)
58+
}
4459
if err != nil {
4560
return nil, err
4661
}
62+
m.PutAttr(linux.NFTA_IMMEDIATE_DREG, nlmsg.PutU32(reg))
4763
m.PutAttr(linux.NFTA_IMMEDIATE_DATA, primitive.AsByteSlice(regDump))
4864
return m.Buffer(), nil
4965
}
5066

5167
// newImmediate creates a new immediate operation.
52-
func newImmediate(dreg uint8, data registerData) (*immediate, *syserr.AnnotatedError) {
53-
if err := data.validateRegister(dreg); err != nil {
54-
return nil, err
68+
func newImmediate(dreg uint8, dataType uint32, data []byte, verdict stack.NFVerdict) (*immediate, *syserr.AnnotatedError) {
69+
switch dataType {
70+
case linux.NFT_DATA_VALUE:
71+
dregIdx, err := regNumToIdx(dreg, len(data))
72+
if err != nil {
73+
return nil, err
74+
}
75+
return &immediate{dregIdx: dregIdx, dataType: dataType, data: data}, nil
76+
case linux.NFT_DATA_VERDICT:
77+
return &immediate{dataType: dataType, verdict: verdict}, nil
5578
}
56-
return &immediate{dreg: dreg, data: data}, nil
79+
return nil, syserr.NewAnnotatedError(syserr.ErrRange, "Nftables: NFTA_IMMEDIATE_DATA is not a valid data type")
5780
}
5881

5982
// InitImmediate initializes the immediate operation from the expression info.
@@ -64,40 +87,33 @@ func initImmediate(tab *Table, exprInfo ExprInfo) (*immediate, *syserr.Annotated
6487
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Failed to parse immediate expression data")
6588
}
6689

67-
regBytes, ok := immDataAttrs[linux.NFTA_IMMEDIATE_DREG]
90+
reg, ok := AttrNetToHost[uint32](linux.NFTA_IMMEDIATE_DREG, immDataAttrs)
6891
if !ok {
6992
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: NFTA_IMMEDIATE_DREG attribute is not found")
7093
}
71-
72-
reg, ok := regBytes.Uint32()
73-
if !ok {
74-
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: NFTA_IMMEDIATE_DREG attribute is malformed")
75-
}
76-
77-
reg = nlmsg.NetToHostU32(reg)
7894
dataBytes, ok := immDataAttrs[linux.NFTA_IMMEDIATE_DATA]
7995
if !ok {
8096
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: NFTA_IMMEDIATE_DATA attribute is not found")
8197
}
82-
83-
dregType := immRegToType(reg)
84-
regData, err := nftDataInit(tab, dregType, nlmsg.AttrsView(dataBytes))
85-
if err != nil {
86-
return nil, err
98+
dataAttrs, ok := NfParse(nlmsg.AttrsView(dataBytes))
99+
if !ok {
100+
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Failed to parse data bytes for nested expression data")
87101
}
88102

89-
// Now find the register to store it in.
90-
dreg, err := nftParseReg(reg, dregType, regData)
103+
regType := immRegToType(reg)
104+
if regType == linux.NFT_DATA_VERDICT {
105+
verdict, err := parseVerdictAttrs(tab, dataAttrs)
106+
if err != nil {
107+
return nil, err
108+
}
109+
return newImmediate(linux.NFT_REG_VERDICT /* dreg */, regType, nil /* data */, verdict)
110+
}
111+
// Data register.
112+
data, err := parseDataAttrs(dataAttrs)
91113
if err != nil {
92114
return nil, err
93115
}
94-
95-
switch int32(dreg) {
96-
case linux.NFT_GOTO:
97-
// TODO - b/434244017: Add support for goto verdicts.
98-
return nil, syserr.NewAnnotatedError(syserr.ErrNotSupported, "Nftables: Verdicts with goto codes are not yet supported")
99-
}
100-
return newImmediate(dreg, regData)
116+
return newImmediate(uint8(reg), regType, data, stack.NFVerdict{})
101117
}
102118

103119
// immRegToType returns the corresponding data type for a given register number.
@@ -106,6 +122,5 @@ func immRegToType(reg uint32) uint32 {
106122
if reg == linux.NFT_REG_VERDICT {
107123
return linux.NFT_DATA_VERDICT
108124
}
109-
110125
return linux.NFT_DATA_VALUE
111126
}

pkg/tcpip/nftables/nft_metaload.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ import (
2929
// Note: meta operations are not supported for the verdict register.
3030
// TODO(b/345684870): Support retrieving more meta fields for Meta Load.
3131
type metaLoad struct {
32-
key metaKey // Meta key specifying what data to retrieve.
33-
dreg uint8 // Number of the destination register.
32+
key metaKey // Meta key specifying what data to retrieve.
33+
dregIdx uint8 // Index of the destination register in registerSet.data.
3434

3535
// Note: Similar to route, meta fields are stored AS IS. If the meta data is
3636
// a field stored by the kernel (i.e. length), it is stored in host endian. On
@@ -50,11 +50,15 @@ func newMetaLoad(key metaKey, dreg uint8) (*metaLoad, *syserr.AnnotatedError) {
5050
if err := validateMetaKey(key); err != nil {
5151
return nil, err
5252
}
53-
if metaDataLengths[key] > 4 && !is16ByteRegister(dreg) {
53+
blen := metaDataLengths[key]
54+
if blen > 4 && !is16ByteRegister(dreg) {
5455
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("meta load operation cannot use 4-byte register as destination for key %v", key))
5556
}
56-
57-
return &metaLoad{key: key, dreg: dreg}, nil
57+
dregIdx, err := regNumToIdx(dreg, blen)
58+
if err != nil {
59+
return nil, err
60+
}
61+
return &metaLoad{key: key, dregIdx: dregIdx}, nil
5862
}
5963

6064
// evaluate for MetaLoad loads specific meta data into the destination register.
@@ -148,7 +152,7 @@ func (op metaLoad) evaluate(regs *registerSet, pkt *stack.PacketBuffer, rule *Ru
148152
}
149153

150154
// Gets the destination register.
151-
dst := getRegisterBuffer(regs, op.dreg)
155+
dst := regs.data[op.dregIdx:]
152156
// Zeroes out excess bytes of the destination register.
153157
// This is done since comparison can be done in multiples of 4 bytes.
154158
blen := metaDataLengths[op.key]
@@ -166,7 +170,7 @@ func (op metaLoad) GetExprName() string {
166170
func (op metaLoad) Dump() ([]byte, *syserr.AnnotatedError) {
167171
m := &nlmsg.Message{}
168172
m.PutAttr(linux.NFTA_META_KEY, nlmsg.PutU32(uint32(op.key)))
169-
m.PutAttr(linux.NFTA_META_DREG, nlmsg.PutU32(uint32(op.dreg)))
173+
m.PutAttr(linux.NFTA_META_DREG, nlmsg.PutU32(formatRegIdxForDump(op.dregIdx)))
170174
return m.Buffer(), nil
171175
}
172176

@@ -179,9 +183,5 @@ func initMetaLoad(attrs map[uint16]nlmsg.BytesView) (*metaLoad, *syserr.Annotate
179183
if !ok {
180184
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Failed to parse NFTA_META_DREG attribute")
181185
}
182-
dreg, err := nftMatchReg(reg)
183-
if err != nil {
184-
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("Nftables: Invalid source register: %d", reg))
185-
}
186-
return newMetaLoad(metaKey(key), uint8(dreg))
186+
return newMetaLoad(metaKey(key), uint8(reg))
187187
}

0 commit comments

Comments
 (0)