Files
Parth SarthiandgVisor bot 3155f353f1 Nftables: Support flushing ruleset.
- Added support for NFT_MSG_DELRULE/NFT_MSG_DESTROYRULE
- Added `destroy` interface for operations to cleanup.
- Added `nftables.GC` and `table.GC` to delete left out objects.

PiperOrigin-RevId: 956605231
2026-07-30 10:32:28 -07:00

388 lines
15 KiB
Go

// Copyright 2024 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 nftables
import (
"encoding/binary"
"fmt"
"math"
"slices"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/marshal/primitive"
"gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg"
"gvisor.dev/gvisor/pkg/syserr"
)
// bitwiseOp is the bitwise operator for a bitwise operation.
// Note: corresponds to enum nft_bitwise_ops from
// include/uapi/linux/netfilter/nf_tables.h and uses the same constants.
type bitwiseOp int
// bitwiseOpStrings is a map of bitwiseOp to its string representation.
var bitwiseOpStrings = map[bitwiseOp]string{
linux.NFT_BITWISE_BOOL: "bitwise boolean",
linux.NFT_BITWISE_LSHIFT: "bitwise <<",
linux.NFT_BITWISE_RSHIFT: "bitwise >>",
}
// String for bitwiseOp returns the string representation of the bitwise
// operator.
func (bop bitwiseOp) String() string {
if str, ok := bitwiseOpStrings[bop]; ok {
return str
}
panic(fmt.Sprintf("invalid bitwise operator: %d", int(bop)))
}
// bitwise is an operation that performs bitwise math operations over data in
// a given register, storing the result in a destination register.
// Note: bitwise operations are not supported for the verdict register.
type bitwise struct {
sregIdx int // Index of the source register in registerSet.data.
dregIdx int // Index of the destination register in registerSet.data.
bop bitwiseOp // Bitwise operator to use.
blen int // Number of bytes to apply bitwise operation to.
mask []byte // Mask to apply bitwise & for boolean operations (before ^).
xor []byte // Xor to apply bitwise ^ for boolean operations (after &).
shift uint32 // Shift to apply bitwise <</>> for non-boolean operations.
// Note: Technically, the linux kernel has defined bool, lshift, and rshift
// as the 3 types of bitwise operations. However, we have not been able to
// observe the lshift or rshift operations used by the nft binary. Thus, we
// have no way to test the interpretation of these operations. Maintaining
// consistency with the linux kernel, we have fully implemented lshift and
// rshift, and We will leave the code here in case we are able to observe
// their use in the future (perhaps outside the nft binary debug output).
}
// newBitwiseBool creates a new bitwise boolean operation.
func newBitwiseBool(sreg, dreg uint8, mask, xor []byte, blen int) (*bitwise, *syserr.AnnotatedError) {
if isVerdictRegister(sreg) || isVerdictRegister(dreg) {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "bitwise operation does not support verdict register as source or destination register")
}
l := len(mask)
if l != len(xor) {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "bitwise boolean operation mask and xor data lengths must be the same")
}
if l != blen {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "bitwise boolean operation invalid length")
}
if blen > linux.NFT_REG_SIZE || (blen > linux.NFT_REG32_SIZE && (is4ByteRegister(sreg) || is4ByteRegister(dreg))) {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "bitwise boolean operation invalid length")
}
var err *syserr.AnnotatedError
var sregIdx, dregIdx int
if sregIdx, err = regNumToIdx(sreg, blen); err != nil {
return nil, err
}
if dregIdx, err = regNumToIdx(dreg, blen); err != nil {
return nil, err
}
return &bitwise{sregIdx: sregIdx, dregIdx: dregIdx, bop: linux.NFT_BITWISE_BOOL, blen: blen, mask: mask[:blen], xor: xor[:blen]}, nil
}
// newBitwiseShift creates a new bitwise shift operation.
func newBitwiseShift(sreg, dreg uint8, blen int, shift uint32, right bool) (*bitwise, *syserr.AnnotatedError) {
if isVerdictRegister(sreg) || isVerdictRegister(dreg) {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "bitwise shift operation does not support verdict register as source or destination register")
}
if blen > linux.NFT_REG_SIZE || (blen > linux.NFT_REG32_SIZE && (is4ByteRegister(sreg) || is4ByteRegister(dreg))) {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("bitwise operation length %d is too long for source register %d, destination register %d", blen, sreg, dreg))
}
if shift >= bitshiftLimit {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("bitwise operation shift %d must be less than %d", shift, bitshiftLimit))
}
bop := bitwiseOp(linux.NFT_BITWISE_LSHIFT)
if right {
bop = linux.NFT_BITWISE_RSHIFT
}
var err *syserr.AnnotatedError
var sregIdx, dregIdx int
if sregIdx, err = regNumToIdx(sreg, int(blen)); err != nil {
return nil, err
}
if dregIdx, err = regNumToIdx(dreg, int(blen)); err != nil {
return nil, err
}
return &bitwise{sregIdx: sregIdx, dregIdx: dregIdx, blen: blen, bop: bop, shift: shift}, nil
}
func (op *bitwise) deepCopy() operation {
opCopy := *op
opCopy.mask = slices.Clone(op.mask)
opCopy.xor = slices.Clone(op.xor)
return &opCopy
}
// updateReferences implements operation.updateReferences.
func (op *bitwise) updateReferences(table *Table, sourceTable *Table, sourceOp operation) {}
// destroy implements operation.destroy.
func (op *bitwise) destroy() {}
// evaluateBitwiseBool performs the bitwise boolean operation on the source register
// data and stores the result in the destination register.
func evaluateBitwiseBool(sregBuf, dregBuf, mask, xor []byte) {
for i := 0; i < len(mask); i++ {
dregBuf[i] = (sregBuf[i] & mask[i]) ^ xor[i]
}
}
// evaluateBitwiseLshift performs the bitwise left shift operation on source
// register in 4 byte chunks and stores the result in the destination register.
func evaluateBitwiseLshift(sregBuf, dregBuf []byte, shift uint32) {
carry := uint32(0)
// Rounds down to nearest 4-byte multiple.
for start := (len(sregBuf) - 1) & ^3; start >= 0; start -= 4 {
// Extracts the 4-byte chunk from the source register, padding if necessary.
var chunk uint32
if start+4 <= len(sregBuf) {
chunk = binary.NativeEndian.Uint32(sregBuf[start:])
} else {
var padded [4]byte
copy(padded[:], sregBuf[start:])
chunk = binary.NativeEndian.Uint32(padded[:])
}
// Does left shift, adds the carry, and calculates the new carry.
res := (chunk << shift) | carry
carry = chunk >> (bitshiftLimit - shift)
// Stores the result in the destination register, using temporary buffer
// if necessary.
if start+4 <= len(dregBuf) {
binary.NativeEndian.PutUint32(dregBuf[start:], res)
} else {
var padded [4]byte
binary.NativeEndian.PutUint32(padded[:], res)
copy(dregBuf[start:], padded[:])
}
}
}
// evaluateBitwiseRshift performs the bitwise right shift operation on source
// register in 4 byte chunks and stores the result in the destination register.
func evaluateBitwiseRshift(sregBuf, dregBuf []byte, shift uint32) {
carry := uint32(0)
for start := 0; start < len(sregBuf); start += 4 {
// Extracts the 4-byte chunk from the source register, padding if necessary.
var chunk uint32
if start+4 <= len(sregBuf) {
chunk = binary.NativeEndian.Uint32(sregBuf[start:])
} else {
var padded [4]byte
copy(padded[:], sregBuf[start:])
chunk = binary.NativeEndian.Uint32(padded[:])
}
// Does right shift, adds the carry, and calculates the new carry.
res := carry | (chunk >> shift)
carry = chunk << (bitshiftLimit - shift)
// Stores the result in the destination register, using temporary buffer
// if necessary.
if start+4 <= len(dregBuf) {
binary.NativeEndian.PutUint32(dregBuf[start:], res)
} else {
var padded [4]byte
binary.NativeEndian.PutUint32(padded[:], res)
copy(dregBuf[start:], padded[:])
}
}
}
// evaluate for bitwise performs the bitwise operation on the source register
// data and stores the result in the destination register.
func (op bitwise) evaluate(regs *registerSet, evalCtx opEvalCtx) {
// Gets the specified buffers of the source and destination registers.
sregBuf := regs.data[op.sregIdx : op.sregIdx+op.blen]
dregBuf := regs.data[op.dregIdx : op.dregIdx+op.blen]
if op.bop == linux.NFT_BITWISE_BOOL {
evaluateBitwiseBool(sregBuf, dregBuf, op.mask, op.xor)
return
}
if op.bop == linux.NFT_BITWISE_LSHIFT {
evaluateBitwiseLshift(sregBuf, dregBuf, op.shift)
} else {
evaluateBitwiseRshift(sregBuf, dregBuf, op.shift)
}
}
func (op bitwise) GetExprName() string {
return OpTypeBitwise.String()
}
func (op bitwise) Dump() ([]byte, *syserr.AnnotatedError) {
m := &nlmsg.Message{}
m.PutAttr(linux.NFTA_BITWISE_SREG, formatRegIdxForDump(op.sregIdx))
m.PutAttr(linux.NFTA_BITWISE_DREG, formatRegIdxForDump(op.dregIdx))
m.PutAttr(linux.NFTA_BITWISE_LEN, nlmsg.PutU32(uint32(op.blen)))
m.PutAttr(linux.NFTA_BITWISE_OP, nlmsg.PutU32(uint32(op.bop)))
switch op.bop {
case linux.NFT_BITWISE_BOOL:
maskDump, err := dumpDataAttr(op.mask)
if err != nil {
return nil, err
}
m.PutAttr(linux.NFTA_BITWISE_MASK, primitive.AsByteSlice(maskDump))
xorDump, err := dumpDataAttr(op.xor)
if err != nil {
return nil, err
}
m.PutAttr(linux.NFTA_BITWISE_XOR, primitive.AsByteSlice(xorDump))
case linux.NFT_BITWISE_LSHIFT, linux.NFT_BITWISE_RSHIFT:
shiftData := make([]byte, 4)
binary.NativeEndian.PutUint32(shiftData, op.shift)
dataDump, err := dumpDataAttr(shiftData)
if err != nil {
return nil, err
}
m.PutAttr(linux.NFTA_BITWISE_DATA, primitive.AsByteSlice(dataDump))
}
return m.Buffer(), nil
}
// checkCompatibility implements operation.checkCompatibility.
func (op bitwise) checkCompatibility(cCtx *opCompatCtx) *syserr.AnnotatedError {
return nil
}
// initBitwiseBool initializes a bitwise boolean operation.
// Ref: net/netfilter/nft_bitwise.c:nft_bitwise_init_bool()
func initBitwiseBool(maskAttrBytes, xorAttrBytes nlmsg.BytesView, l uint32, sreg, dreg uint8) (*bitwise, *syserr.AnnotatedError) {
maskAttrs, ok := NfParse(nlmsg.AttrsView(maskAttrBytes))
if !ok {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "failed to parse bitwise boolean operation mask attribute")
}
mask, err := parseDataAttrs(maskAttrs)
if err != nil {
return nil, err
}
xorAttrs, ok := NfParse(nlmsg.AttrsView(xorAttrBytes))
if !ok {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "failed to parse bitwise boolean operation xor attribute")
}
xor, err := parseDataAttrs(xorAttrs)
if err != nil {
return nil, err
}
return newBitwiseBool(uint8(sreg), uint8(dreg), mask, xor, int(l))
}
// initBitwiseShift initializes a bitwise shift operation.
// Ref: net/netfilter/nft_bitwise.c:nft_bitwise_init_shift()
func initBitwiseShift(dataAttrBytes nlmsg.BytesView, l uint32, sreg, dreg uint8, bitwiseOp uint32) (*bitwise, *syserr.AnnotatedError) {
dataAttrs, ok := NfParse(nlmsg.AttrsView(dataAttrBytes))
if !ok {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "failed to parse bitwise shift operation data attribute")
}
shiftData, err := parseDataAttrs(dataAttrs)
if err != nil {
return nil, err
}
if len(shiftData) != 4 {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "bitwise shift operation data length must be 4 bytes")
}
shift := binary.NativeEndian.Uint32(shiftData[:4])
return newBitwiseShift(uint8(sreg), uint8(dreg), int(l), shift, bitwiseOp == linux.NFT_BITWISE_RSHIFT)
}
// bitwiseAttrPolicy is the policy for parsing the attributes of a bitwise operation.
// Ref: net/netfilter/nft_bitwise.c:nft_bitwise_policy
var bitwiseAttrPolicy = []NlaPolicy{
linux.NFTA_BITWISE_SREG: {nlaType: linux.NLA_U32},
linux.NFTA_BITWISE_DREG: {nlaType: linux.NLA_U32},
linux.NFTA_BITWISE_LEN: {nlaType: linux.NLA_U32},
linux.NFTA_BITWISE_MASK: {nlaType: linux.NLA_NESTED},
linux.NFTA_BITWISE_XOR: {nlaType: linux.NLA_NESTED},
linux.NFTA_BITWISE_OP: NlaPolicy{nlaType: linux.NLA_BE32, validator: AttrMaxValidator[uint32](math.MaxUint8)},
linux.NFTA_BITWISE_DATA: {nlaType: linux.NLA_NESTED},
}
// initBitwise initializes a bitwise operation.
// Ref: net/netfilter/nft_bitwise.c:nft_bitwise_init()
func initBitwise(tab *Table, exprInfo ExprInfo) (*bitwise, *syserr.AnnotatedError) {
attrs, err := NfParseWithOpts(exprInfo.ExprData, &NfParseOpts{
Policy: bitwiseAttrPolicy,
})
if err != nil {
return nil, err
}
blen, ok := AttrNetToHost[uint32](linux.NFTA_BITWISE_LEN, attrs)
if !ok {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "failed to parse bitwise expression data length")
}
sreg, ok := AttrNetToHost[uint32](linux.NFTA_BITWISE_SREG, attrs)
if !ok {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "failed to parse bitwise expression source register")
}
dreg, ok := AttrNetToHost[uint32](linux.NFTA_BITWISE_DREG, attrs)
if !ok {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "failed to parse bitwise expression destination register")
}
bitwiseOp, ok := AttrNetToHost[uint32](linux.NFTA_BITWISE_OP, attrs)
if !ok {
bitwiseOp = uint32(linux.NFT_BITWISE_BOOL)
} else {
switch int(bitwiseOp) {
case linux.NFT_BITWISE_BOOL, linux.NFT_BITWISE_LSHIFT, linux.NFT_BITWISE_RSHIFT:
default:
return nil, syserr.NewAnnotatedError(syserr.ErrNotSupported, "unsupported bitwise operation")
}
}
data, dataOk := attrs[linux.NFTA_BITWISE_DATA]
mask, maskOk := attrs[linux.NFTA_BITWISE_MASK]
xor, xorOk := attrs[linux.NFTA_BITWISE_XOR]
if bitwiseOp == linux.NFT_BITWISE_BOOL {
if dataOk {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "bitwise boolean operation cannot use data attribute")
}
if !maskOk {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "bitwise boolean operation mask attribute is missing")
}
if !xorOk {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "bitwise boolean operation xor attribute is missing")
}
return initBitwiseBool(mask, xor, blen, uint8(sreg), uint8(dreg))
}
if bitwiseOp == linux.NFT_BITWISE_LSHIFT || bitwiseOp == linux.NFT_BITWISE_RSHIFT {
if maskOk || xorOk {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "bitwise shift operation cannot use mask or xor attribute")
}
if !dataOk {
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "bitwise shift operation data attribute is missing")
}
return initBitwiseShift(data, blen, uint8(sreg), uint8(dreg), bitwiseOp)
}
return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "failed to parse bitwise expression operation")
}