[PATCH] iptables: fix null dereference parsing bitwise operations

Remy D. Farley
2026-02-02T10:14:59Z

Iptables binary only understands NFT_BITWISE_MASK_XOR bitwise operation and
assumes its attributes are always present without actually checking, which
leads to a segfault in some cases.

This commit introduces this missing check.

| /**
| * enum nft_bitwise_ops - nf_tables bitwise operations
| *
| * @NFT_BITWISE_MASK_XOR: mask-and-xor operation used to implement NOT, AND, OR
| * and XOR boolean operations
| * @NFT_BITWISE_LSHIFT: left-shift operation \
| * @NFT_BITWISE_RSHIFT: right-shift operation |
| * @NFT_BITWISE_AND: and operation | These all are affected
| * @NFT_BITWISE_OR: or operation |
| * @NFT_BITWISE_XOR: xor operation /
| */

From iptables/nft-ruleparse.c:

| static void nft_parse_bitwise(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
| {
| [...]
|
| data = nftnl_expr_get(e, NFTNL_EXPR_BITWISE_XOR, &len); // <-- this attribute may not be present
|
| if (len > sizeof(dreg->bitwise.xor)) {
| ctx->errmsg = "bitwise xor too large";
| return;
| }
|
| memcpy(dreg->bitwise.xor, data, len); // <-- zero dereference happens here
|
| data = nftnl_expr_get(e, NFTNL_EXPR_BITWISE_MASK, &len);
|
| if (len > sizeof(dreg->bitwise.mask)) {
| ctx->errmsg = "bitwise mask too large";
| return;
| }
|
| memcpy(dreg->bitwise.mask, data, len);
|
| dreg->bitwise.set = true;
|
| }

The bug can be reproduced by creating a rule like this:

| # newrule.json
| {"chain": "example-chain",
| "expressions": {"elem": [{"data": {"base": 1,
| "dreg": 1,
| "len": 4,
| "offset": 12},
| "name": "payload"},
| {"data": {"data": {"value": [255, 255, 255, 0]},
| "dreg": 1,
| "len": 4,
| "op": 3,
| "sreg": 1},
| "name": "bitwise"},
| {"data": {"data": {"value": [1, 2, 3, 0]},
| "op": 0,
| "sreg": 1},
| "name": "cmp"},
| {"data": {"data": {"verdict": {"code": 1}},
| "dreg": 0},
| "name": "immediate"}]},
| "nfgen-family": 2,
| "table": "filter"}

| # newrule.sh
| set -euo pipefail
|
| iptables -N example-chain || true
|
| genid="$(
| ./tools/net/ynl/pyynl/cli.py --spec Documentation/netlink/specs/nftables.yaml \
| --do getgen --json "{}" --output-json |
| jq -r ".id"
| )"
|
| ./tools/net/ynl/pyynl/cli.py --spec Documentation/netlink/specs/nftables.yaml \
| --multi batch-begin "{\"genid\": $genid, \"res-id\": 10}" \
| --creat --append --multi newrule "$(cat ./newrule.json)" \
| --creat --multi batch-end '{}' \
| --output-json

Signed-off-by: Remy D. Farley <one-d-wide@protonmail.com>
---
iptables/nft-ruleparse.c | 5 +++++
iptables/nft.c | 5 ++++-
2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/iptables/nft-ruleparse.c b/iptables/nft-ruleparse.c
index cdf1af4f..1a9084e3 100644
--- a/iptables/nft-ruleparse.c
+++ b/iptables/nft-ruleparse.c
@@ -232,6 +232,11 @@ static void nft_parse_bitwise(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
const void *data;
uint32_t len;

+ if (nftnl_expr_get_u32(e, NFTNL_EXPR_BITWISE_OP) != 0 /* empty or MASK_XOR */) {
+ ctx->errmsg = "unsupported bitwise operation";
+ return;
+ }
+
if (!sreg)
return;

diff --git a/iptables/nft.c b/iptables/nft.c
index 85080a6d..661fac29 100644
--- a/iptables/nft.c
+++ b/iptables/nft.c
@@ -4029,7 +4029,6 @@ static const char *supported_exprs[] = {
"payload",
"meta",
"cmp",
- "bitwise",
"counter",
"immediate",
"lookup",
@@ -4056,6 +4055,10 @@ static int nft_is_expr_compatible(struct nftnl_expr *expr, void *data)
nftnl_expr_is_set(expr, NFTNL_EXPR_LOG_GROUP))
return 0;

+ if (!strcmp(name, "bitwise") &&
+ nftnl_expr_get_u32(expr, NFTNL_EXPR_BITWISE_OP) == 0 /* empty or MASK_XOR */)
+ return 0;
+
return -1;
}

--
2.51.2