All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH nft,v2 1/3] mergesort: use lhs expression when sorting concatenation
@ 2026-08-05 16:39 Pablo Neira Ayuso
  2026-08-05 16:39 ` [PATCH nft 2/3] segtree: basic support for binary operations in concatenated set ranges Pablo Neira Ayuso
  2026-08-05 16:39 ` [PATCH nft 3/3] tests: shell: add flush set after expiration Pablo Neira Ayuso
  0 siblings, 2 replies; 3+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-05 16:39 UTC (permalink / raw)
  To: netfilter-devel

Otherwise, concatenations using binary operations hit an assertion.

 # cat ruleset.nft
 table inet t {
        chain c {
                tcp flags . tcp dport vmap { syn | ack . 80 : drop, ack . 90 : accept }
        }
 }
 # nft -f ruleset.nft
 # nft list ruleset
 nft: src/mergesort.c:23: concat_expr_msort_value: Assertion `ilen > 0' failed.
 Aborted

Inspect the left-hand size of the expression for the merge sorting.

This patch includes a new tests/shell unit file.

Fixes: 741a06ac15d2 ("mergesort: find base value expression type via recursion")
Closes: https://bugzilla.netfilter.org/show_bug.cgi?id=1841
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
v2: add tests/shell unit file.

 src/mergesort.c                               |  38 +++-
 .../maps/dumps/vmap_concat_binop.json-nft     | 196 ++++++++++++++++++
 .../maps/dumps/vmap_concat_binop.nft          |  13 ++
 tests/shell/testcases/maps/vmap_concat_binop  |  19 ++
 4 files changed, 262 insertions(+), 4 deletions(-)
 create mode 100644 tests/shell/testcases/maps/dumps/vmap_concat_binop.json-nft
 create mode 100644 tests/shell/testcases/maps/dumps/vmap_concat_binop.nft
 create mode 100755 tests/shell/testcases/maps/vmap_concat_binop

diff --git a/src/mergesort.c b/src/mergesort.c
index 2e8ddd22f813..f4b4d56b579c 100644
--- a/src/mergesort.c
+++ b/src/mergesort.c
@@ -12,16 +12,46 @@
 #include <gmputil.h>
 #include <list.h>
 
+static mpz_srcptr concat_expr_msort_value_one(const struct expr *expr,
+					      unsigned int *i_len)
+{
+	mpz_srcptr i_value;
+
+	switch (expr->etype) {
+	case EXPR_BINOP:
+	case EXPR_MAPPING:
+	case EXPR_RANGE:
+		i_value = expr->left->value;
+		*i_len = expr->left->len;
+		break;
+	case EXPR_VALUE:
+		i_value = expr->value;
+		*i_len = expr->len;
+		break;
+	case EXPR_RANGE_VALUE:
+		i_value = expr->range.low;
+		*i_len = expr->len;
+		break;
+	default:
+		BUG("Unknown expression %s", expr_name(expr));
+	}
+
+	*i_len = div_round_up(*i_len, BITS_PER_BYTE);
+
+	return i_value;
+}
+
 static void concat_expr_msort_value(const struct expr *expr, mpz_t value)
 {
-	unsigned int len = 0, ilen;
+	unsigned int len = 0, i_len;
 	const struct expr *i;
+	mpz_srcptr i_value;
 	char data[512];
 
 	list_for_each_entry(i, &expr_concat(expr)->expressions, list) {
-		ilen = div_round_up(i->len, BITS_PER_BYTE);
-		mpz_export_data(data + len, i->value, BYTEORDER_BIG_ENDIAN, ilen);
-		len += ilen;
+		i_value = concat_expr_msort_value_one(i, &i_len);
+		mpz_export_data(data + len, i_value, BYTEORDER_BIG_ENDIAN, i_len);
+		len += i_len;
 	}
 
 	mpz_import_data(value, data, BYTEORDER_BIG_ENDIAN, len);
diff --git a/tests/shell/testcases/maps/dumps/vmap_concat_binop.json-nft b/tests/shell/testcases/maps/dumps/vmap_concat_binop.json-nft
new file mode 100644
index 000000000000..502648f53dec
--- /dev/null
+++ b/tests/shell/testcases/maps/dumps/vmap_concat_binop.json-nft
@@ -0,0 +1,196 @@
+{
+  "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",
+        "elem": [
+          [
+            {
+              "concat": [
+                {
+                  "|": [
+                    "syn",
+                    "ack"
+                  ]
+                },
+                80
+              ]
+            },
+            {
+              "accept": null
+            }
+          ],
+          [
+            {
+              "concat": [
+                "rst",
+                100
+              ]
+            },
+            {
+              "drop": null
+            }
+          ],
+          [
+            {
+              "concat": [
+                "ack",
+                90
+              ]
+            },
+            {
+              "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": [
+                        "rst",
+                        100
+                      ]
+                    },
+                    {
+                      "drop": null
+                    }
+                  ],
+                  [
+                    {
+                      "concat": [
+                        "ack",
+                        90
+                      ]
+                    },
+                    {
+                      "drop": null
+                    }
+                  ]
+                ]
+              }
+            }
+          }
+        ]
+      }
+    }
+  ]
+}
diff --git a/tests/shell/testcases/maps/dumps/vmap_concat_binop.nft b/tests/shell/testcases/maps/dumps/vmap_concat_binop.nft
new file mode 100644
index 000000000000..92e50a4cbcc1
--- /dev/null
+++ b/tests/shell/testcases/maps/dumps/vmap_concat_binop.nft
@@ -0,0 +1,13 @@
+table ip x {
+	map y {
+		typeof tcp flags . tcp dport : verdict
+		elements = { syn | ack . 80 : accept,
+			     rst . 100 : drop,
+			     ack . 90 : drop }
+	}
+
+	chain z {
+		tcp flags . tcp dport vmap @y
+		tcp flags . tcp dport vmap { syn | ack . 80 : accept, rst . 100 : drop, ack . 90 : drop }
+	}
+}
diff --git a/tests/shell/testcases/maps/vmap_concat_binop b/tests/shell/testcases/maps/vmap_concat_binop
new file mode 100755
index 000000000000..fc6e9584740b
--- /dev/null
+++ b/tests/shell/testcases/maps/vmap_concat_binop
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+set -e
+
+RULESET="table ip x {
+        map y {
+                typeof tcp flags . tcp dport : verdict
+                elements = { syn | ack . 80 : accept,
+                             ack . 90 : drop,
+			     rst . 100 : drop }
+        }
+
+        chain z {
+                tcp flags . tcp dport vmap @y
+                tcp flags . tcp dport vmap { syn | ack . 80 : accept, ack . 90 : drop, rst . 100 : drop }
+        }
+}"
+
+$NFT -f - <<< $RULESET
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH nft 2/3] segtree: basic support for binary operations in concatenated set ranges
  2026-08-05 16:39 [PATCH nft,v2 1/3] mergesort: use lhs expression when sorting concatenation Pablo Neira Ayuso
@ 2026-08-05 16:39 ` Pablo Neira Ayuso
  2026-08-05 16:39 ` [PATCH nft 3/3] tests: shell: add flush set after expiration Pablo Neira Ayuso
  1 sibling, 0 replies; 3+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-05 16:39 UTC (permalink / raw)
  To: netfilter-devel

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 <pablo@netfilter.org>
---
 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


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH nft 3/3] tests: shell: add flush set after expiration
  2026-08-05 16:39 [PATCH nft,v2 1/3] mergesort: use lhs expression when sorting concatenation Pablo Neira Ayuso
  2026-08-05 16:39 ` [PATCH nft 2/3] segtree: basic support for binary operations in concatenated set ranges Pablo Neira Ayuso
@ 2026-08-05 16:39 ` Pablo Neira Ayuso
  1 sibling, 0 replies; 3+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-05 16:39 UTC (permalink / raw)
  To: netfilter-devel

Add a new test to cover for flush commands after elements have expired,
this allowed me to catch a bug in:

("netfilter: nf_tables: call set ops .commit when building new ruleset")

This tests works fine with <= 7.2-rc6, this is not specifically covering
any existing regression.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 .../sets/dumps/flush_after_expiration.nodump  |  0
 .../testcases/sets/flush_after_expiration     | 45 +++++++++++++++++++
 2 files changed, 45 insertions(+)
 create mode 100644 tests/shell/testcases/sets/dumps/flush_after_expiration.nodump
 create mode 100755 tests/shell/testcases/sets/flush_after_expiration

diff --git a/tests/shell/testcases/sets/dumps/flush_after_expiration.nodump b/tests/shell/testcases/sets/dumps/flush_after_expiration.nodump
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/tests/shell/testcases/sets/flush_after_expiration b/tests/shell/testcases/sets/flush_after_expiration
new file mode 100755
index 000000000000..eaf03a546ca0
--- /dev/null
+++ b/tests/shell/testcases/sets/flush_after_expiration
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+$NFT -f - <<EOF
+table ip x {
+        set s0 {
+                type inet_service
+                elements = { 20, 21, 22 }
+                flags interval
+                timeout 3s
+        }
+}
+EOF
+
+sleep 6
+
+$NFT flush set ip x s0
+
+$NFT -f - <<EOF
+table ip x {
+        set s1 {
+                type ipv4_addr . inet_service
+                elements = { 1.2.3.4 . 20, 1.2.3.5 . 21, 1.2.3.6 . 22 }
+                flags interval
+                timeout 3s
+        }
+}
+EOF
+
+sleep 6
+
+$NFT flush set ip x s1
+
+$NFT -f - <<EOF
+table ip x {
+        set s2 {
+                type ipv4_addr
+                elements = { 2.2.2.1, 2.2.2.2, 2.2.2.3 }
+                timeout 3s
+        }
+}
+EOF
+
+sleep 6
+
+$NFT flush set ip x s2
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-08-05 16:39 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-05 16:39 [PATCH nft,v2 1/3] mergesort: use lhs expression when sorting concatenation Pablo Neira Ayuso
2026-08-05 16:39 ` [PATCH nft 2/3] segtree: basic support for binary operations in concatenated set ranges Pablo Neira Ayuso
2026-08-05 16:39 ` [PATCH nft 3/3] tests: shell: add flush set after expiration Pablo Neira Ayuso

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.