[PATCH nft 2/3] segtree: basic support for binary operations in concatenated set ranges

Pablo Neira Ayuso <[email protected]> Wed, 5 Aug 2026 18:39:19 +0200
Newsgroups gmane.comp.security.firewalls.netfilter.devel
Message-ID <[email protected]>
Use of tcp flags in concatenated set ranges such as (note the 100-110
range):

 table ip x {
       chain y {
             tcp flags . tcp dport vmap { syn | ack . 80 : accept, ack . 90 : drop, rst . 100-110 : drop
       }
 }

is broken when listing the ruleset:

AddressSanitizer:DEADLYSIGNAL
=================================================================
==218685==ERROR: AddressSanitizer: SEGV on unknown address 0x50b000029798 (pc 0x7f8318b8fac0 bp 0x7ffd080a4310 sp 0x7ffd080a4198 T0)
==218685==The signal is caused by a READ memory access.
    #0 0x7f8318b8fac0 in __gmpz_cmp (/lib/x86_64-linux-gnu/libgmp.so.10+0x19ac0) (BuildId: dff5c2156ec812613c5e4431005c576b212dd323)
    #1 0x7f83192fbc3f in concat_range_aggregate src/segtree.c:404
    #2 0x7f8319240889 in netlink_list_setelems src/netlink.c:1774
    #3 0x7f8319115fdd in cache_init_objects src/cache.c:1189
    #4 0x7f8319116f10 in nft_cache_init src/cache.c:1266
    #5 0x7f831911772b in nft_cache_update src/cache.c:1325
    #6 0x7f83191eac34 in nft_evaluate src/libnftables.c:580
    #7 0x7f83191eb8b6 in nft_run_cmd_from_buffer src/libnftables.c:638
    #8 0x558baf3bc403 in main src/main.c:538

add basic support so it works with tcp flags, add a new assertion to
crash safely to ensure expression type is EXPR_VALUE before access
when trying to compose the range (this code only supports EXPR_VALUE
when building a range expression at this stage) and a new test.

This patch includes a new tests/shell unit file.

Fixes: 8ac2f3b2fca3 ("src: Add support for concatenated set ranges")
Signed-off-by: Pablo Neira Ayuso <[email protected]>
---
 src/segtree.c                                 |  27 ++-
 .../dumps/vmap_concat_range_binary.json-nft   | 209 ++++++++++++++++++
 .../maps/dumps/vmap_concat_range_binary.nft   |  14 ++
 .../testcases/maps/vmap_concat_range_binary   |  22 ++
 4 files changed, 271 insertions(+), 1 deletion(-)
 create mode 100644 tests/shell/testcases/maps/dumps/vmap_concat_range_binary.json-nft
 create mode 100755 tests/shell/testcases/maps/dumps/vmap_concat_range_binary.nft
 create mode 100755 tests/shell/testcases/maps/vmap_concat_range_binary

diff --git a/src/segtree.c b/src/segtree.c
index a721078cfa59..c8a7a3541ac3 100644
--- a/src/segtree.c
+++ b/src/segtree.c
@@ -362,6 +362,29 @@ static int range_mask_len(const mpz_t start, const mpz_t end, unsigned int len)
 	return ret;
 }
 
+static int concat_expr_cmp(const struct expr *r1, const struct expr *r2)
+{
+	int ret;
+
+	assert(r1->etype == r2->etype);
+
+	switch (r1->etype) {
+	case EXPR_BINOP:
+		assert(r1->op == r2->op);
+		ret = 0;
+		ret = mpz_cmp(r1->left->value, r2->left->value);
+		ret |= mpz_cmp(r1->right->value, r2->right->value);
+		break;
+	case EXPR_VALUE:
+		ret = mpz_cmp(r1->value, r2->value);
+		break;
+	default:
+		BUG("unexpected expression %s", expr_name(r1->key));
+	}
+
+	return ret;
+}
+
 /* Given a set with two elements (start and end), transform them into a
  * concatenation of ranges. That is, from a list of start expressions and a list
  * of end expressions, form a list of start - end expressions.
@@ -401,11 +424,13 @@ void concat_range_aggregate(struct expr *set)
 			r2_next = r2->list.next;
 			free_r1 = 0;
 
-			if (!mpz_cmp(r1->value, r2->value)) {
+			if (!concat_expr_cmp(r1, r2)) {
 				free_r1 = 1;
 				goto next;
 			}
 
+			assert(r1->etype == EXPR_VALUE && r1->etype == EXPR_VALUE);
+
 			if (expr_basetype(r1)->type == TYPE_STRING &&
 			    expr_basetype(r2)->type == TYPE_STRING) {
 				string_type = true;
diff --git a/tests/shell/testcases/maps/dumps/vmap_concat_range_binary.json-nft b/tests/shell/testcases/maps/dumps/vmap_concat_range_binary.json-nft
new file mode 100644
index 000000000000..8868f2eb180e
--- /dev/null
+++ b/tests/shell/testcases/maps/dumps/vmap_concat_range_binary.json-nft
@@ -0,0 +1,209 @@
+{
+  "nftables": [
+    {
+      "metainfo": {
+        "version": "VERSION",
+        "release_name": "RELEASE_NAME",
+        "json_schema_version": 1
+      }
+    },
+    {
+      "table": {
+        "family": "ip",
+        "name": "x",
+        "handle": 0
+      }
+    },
+    {
+      "chain": {
+        "family": "ip",
+        "table": "x",
+        "name": "z",
+        "handle": 0
+      }
+    },
+    {
+      "map": {
+        "family": "ip",
+        "name": "y",
+        "table": "x",
+        "type": {
+          "typeof": {
+            "concat": [
+              {
+                "payload": {
+                  "protocol": "tcp",
+                  "field": "flags"
+                }
+              },
+              {
+                "payload": {
+                  "protocol": "tcp",
+                  "field": "dport"
+                }
+              }
+            ]
+          }
+        },
+        "handle": 0,
+        "map": "verdict",
+        "flags": [
+          "interval"
+        ],
+        "elem": [
+          [
+            {
+              "concat": [
+                {
+                  "|": [
+                    "syn",
+                    "ack"
+                  ]
+                },
+                80
+              ]
+            },
+            {
+              "accept": null
+            }
+          ],
+          [
+            {
+              "concat": [
+                "ack",
+                90
+              ]
+            },
+            {
+              "drop": null
+            }
+          ],
+          [
+            {
+              "concat": [
+                "rst",
+                {
+                  "range": [
+                    100,
+                    110
+                  ]
+                }
+              ]
+            },
+            {
+              "drop": null
+            }
+          ]
+        ]
+      }
+    },
+    {
+      "rule": {
+        "family": "ip",
+        "table": "x",
+        "chain": "z",
+        "handle": 0,
+        "expr": [
+          {
+            "vmap": {
+              "key": {
+                "concat": [
+                  {
+                    "payload": {
+                      "protocol": "tcp",
+                      "field": "flags"
+                    }
+                  },
+                  {
+                    "payload": {
+                      "protocol": "tcp",
+                      "field": "dport"
+                    }
+                  }
+                ]
+              },
+              "data": "@y"
+            }
+          }
+        ]
+      }
+    },
+    {
+      "rule": {
+        "family": "ip",
+        "table": "x",
+        "chain": "z",
+        "handle": 0,
+        "expr": [
+          {
+            "vmap": {
+              "key": {
+                "concat": [
+                  {
+                    "payload": {
+                      "protocol": "tcp",
+                      "field": "flags"
+                    }
+                  },
+                  {
+                    "payload": {
+                      "protocol": "tcp",
+                      "field": "dport"
+                    }
+                  }
+                ]
+              },
+              "data": {
+                "set": [
+                  [
+                    {
+                      "concat": [
+                        {
+                          "|": [
+                            "syn",
+                            "ack"
+                          ]
+                        },
+                        80
+                      ]
+                    },
+                    {
+                      "accept": null
+                    }
+                  ],
+                  [
+                    {
+                      "concat": [
+                        "ack",
+                        90
+                      ]
+                    },
+                    {
+                      "drop": null
+                    }
+                  ],
+                  [
+                    {
+                      "concat": [
+                        "rst",
+                        {
+                          "range": [
+                            100,
+                            110
+                          ]
+                        }
+                      ]
+                    },
+                    {
+                      "drop": null
+                    }
+                  ]
+                ]
+              }
+            }
+          }
+        ]
+      }
+    }
+  ]
+}
diff --git a/tests/shell/testcases/maps/dumps/vmap_concat_range_binary.nft b/tests/shell/testcases/maps/dumps/vmap_concat_range_binary.nft
new file mode 100755
index 000000000000..bc74535b852f
--- /dev/null
+++ b/tests/shell/testcases/maps/dumps/vmap_concat_range_binary.nft
@@ -0,0 +1,14 @@
+table ip x {
+	map y {
+		typeof tcp flags . tcp dport : verdict
+		flags interval
+		elements = { syn | ack . 80 : accept,
+			     ack . 90 : drop,
+			     rst . 100-110 : drop }
+	}
+
+	chain z {
+		tcp flags . tcp dport vmap @y
+		tcp flags . tcp dport vmap { syn | ack . 80 : accept, ack . 90 : drop, rst . 100-110 : drop }
+	}
+}
diff --git a/tests/shell/testcases/maps/vmap_concat_range_binary b/tests/shell/testcases/maps/vmap_concat_range_binary
new file mode 100755
index 000000000000..ad5ef4a9177e
--- /dev/null
+++ b/tests/shell/testcases/maps/vmap_concat_range_binary
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+# NFT_TEST_REQUIRES(NFT_TEST_HAVE_pipapo)
+
+set -e
+
+RULESET="table ip x {
+        map y {
+                typeof tcp flags . tcp dport : verdict
+                flags interval
+                elements = { syn | ack . 80 : accept,
+                             ack . 90 : drop,
+			     rst . 100-110 : drop }
+        }
+
+        chain z {
+                tcp flags . tcp dport vmap @y
+                tcp flags . tcp dport vmap { syn | ack . 80 : accept, ack . 90 : drop, rst . 100-110 : drop }
+        }
+}"
+
+$NFT -f - <<< $RULESET
-- 
2.47.3