--- /dev/null
+#!/bin/bash
+
+# test case to attempt to fool ruleset validation.
+# Initial ruleset added here is fine, then we try to make the
+# ruleset exceed the jump chain depth via jumps, gotos, verdict
+# map entries etc, either by having the map loop back to itself,
+# jumping back to an earlier chain and so on.
+#
+# Also check that can't hook up a user-defined chain with a
+# restricted expression (here: tproxy, only valid from prerouting
+# hook) to the input hook, even if reachable indirectly via vmap.
+
+bad_ruleset()
+{
+ ret=$1
+ shift
+
+ if [ $ret -eq 0 ];then
+ echo "Accepted bad ruleset with $@"
+ $NFT list ruleset
+ exit 1
+ fi
+}
+
+good_ruleset()
+{
+ ret=$1
+ shift
+
+ if [ $ret -ne 0 ];then
+ echo "Rejected good ruleset with $@"
+ exit 1
+ fi
+}
+
+# add a loop with a vmap statement, either goto or jump,
+# both with single rule and delta-transaction that also
+# contains valid information.
+check_loop()
+{
+ what=$1
+
+ $NFT "add element t m { 1.2.3.9 : $what c1 }"
+ bad_ruleset $? "bound map with $what to backjump should exceed jump stack"
+
+ $NFT "add element t m { 1.2.3.9 : $what c7 }"
+ bad_ruleset $? "bound map with $what to backjump should exceed jump stack"
+
+ $NFT "add element t m { 1.2.3.9 : $what c8 }"
+ bad_ruleset $? "bound map with $what to self should exceed jump stack"
+
+ # rule bound to c8, this should not work -- jump stack should be exceeded.
+ $NFT "add element t m { 1.2.3.9 : jump c9 }"
+ bad_ruleset $? "bound map with $what should exceed jump stack"
+
+ # rule bound to c8, this should be within jump stack limit
+ $NFT "add element t m { 1.2.3.9 : jump c10 }"
+ good_ruleset $? "bound map with $what should not have exceeded jump stack"
+
+$NFT -f - <<EOF
+flush chain t c16
+flush chain t c15
+table t {
+ chain c9 {
+ ip protocol 6 goto c14
+ }
+
+ # calls @m again, but @m now runs c10, which is linked to c14 already.
+ chain c14 {
+ ip protocol 6 return
+ ip daddr vmap @m
+ }
+}
+EOF
+ bad_ruleset $? "delta with bound map with $what loop and rule deletions"
+
+ # delete mapping again
+ $NFT "delete element t m { 1.2.3.9 }"
+ good_ruleset $? "cannot delete mapping"
+}
+
+check_bad_expr()
+{
+$NFT -f -<<EOF
+table t {
+ chain c1 {
+ jump c9
+ }
+}
+EOF
+bad_ruleset $? "tproxy expr exposed to input hook"
+
+$NFT -f -<<EOF
+flush map t m
+
+table t {
+ chain c1 {
+ ip saddr vmap @m
+ }
+}
+EOF
+good_ruleset $? "bound vmap to c1"
+
+$NFT -f -<<EOF
+table t {
+ map m {
+ type ipv4_addr : verdict
+ elements = { 1.2.3.4 : jump c9 }
+ }
+}
+EOF
+bad_ruleset $? "tproxy expr exposed to input hook by vmap"
+
+$NFT -f -<<EOF
+flush chain t c10
+flush chain t c11
+add rule t c8 jump c9
+
+table t {
+ map m {
+ type ipv4_addr : verdict
+ elements = { 1.2.3.4 : goto c2 }
+ }
+}
+EOF
+bad_ruleset $? "tproxy expr exposed to input hook by vmap"
+
+$NFT -f -<<EOF
+flush chain t c2
+flush chain t c3
+flush chain t c4
+flush chain t c5
+flush chain t c6
+flush chain t c7
+flush chain t c10
+flush chain t c11
+flush chain t c12
+flush chain t c13
+flush chain t c14
+flush chain t c15
+flush chain t c16
+delete chain t c16
+delete chain t c15
+delete chain t c14
+delete chain t c13
+delete chain t c12
+delete chain t c11
+delete chain t c7
+delete chain t c6
+delete chain t c5
+delete chain t c4
+delete chain t c3
+add rule t c8 jump c9
+EOF
+good_ruleset $? "connect chain c8 to chain c9"
+
+$NFT -f -<<EOF
+table t {
+ map m {
+ type ipv4_addr : verdict
+ elements = { 1.2.3.4 : goto c8 }
+ }
+}
+EOF
+bad_ruleset $? "tproxy expr exposed to input hook by vmap c1 -> vmap -> c8 -> c9"
+}
+
+# 16 jump levels are permitted.
+# First ruleset is fine, there is no jump
+# from c8 to c9.
+$NFT -f - <<EOF
+table t {
+ map m {
+ type ipv4_addr : verdict
+ }
+
+ chain c16 { }
+ chain c15 { jump c16; }
+ chain c14 { jump c15; }
+ chain c13 { jump c14; }
+ chain c12 { jump c13; }
+ chain c11 { jump c12; }
+ chain c10 { jump c11; }
+ chain c9 { jump c10; }
+ chain c8 { }
+ chain c7 { jump c8; }
+ chain c6 { jump c7; }
+ chain c5 { jump c6; }
+ chain c4 { jump c5; }
+ chain c3 { jump c4; }
+ chain c2 { jump c3; }
+ chain c1 { jump c2; }
+ chain c0 { type filter hook input priority 0;
+ jump c1
+ }
+}
+EOF
+
+ret=$?
+if [ $ret -ne 0 ];then
+ exit 1
+fi
+
+# ensure kernel catches the exceeded jumpstack use, despite no new chains
+# are added here and cycle is acyclic.
+$NFT -f - <<EOF
+# unrelated rule.
+add rule t c14 accept
+add rule t c15 accept
+
+# close jump gap; after this jumpstack limit is exceeded.
+add rule t c8 goto c9
+
+# unrelated rules.
+add rule t c14 accept
+add rule t c15 accept
+EOF
+
+bad_ruleset $? "chain jump stack exhausted without cycle"
+
+$NFT -f - <<EOF
+# unrelated rule.
+add rule t c12 accept
+add rule t c13 accept
+
+add element t m { 1.2.3.1 : accept }
+add element t m { 1.2.3.16 : goto c16 }
+add element t m { 1.2.3.15 : goto c15 }
+
+# after this jumpstack limit is exceeded,
+# IFF @m was bound to c8, but it is not.
+add element t m { 1.2.3.9 : jump c9 }
+
+# unrelated rules.
+add rule t c12 accept
+add rule t c13 accept
+
+add element t m { 1.2.3.16 : goto c16 }
+EOF
+good_ruleset $? "unbounded map"
+
+# bind vmap to c8. This MUST fail, map jumps to c9.
+$NFT "add rule t c8 ip saddr vmap @m"
+bad_ruleset $? "jump c8->c9 via vmap expression"
+
+# delete the mapping again.
+$NFT "delete element t m { 1.2.3.9 }"
+$NFT "add rule t c8 ip saddr vmap @m"
+good_ruleset $? "bind empty map to c8"
+
+check_loop "jump"
+check_loop "goto"
+
+$NFT "flush chain t c8"
+good_ruleset $? "flush chain t c8"
+
+# should work, c9 not connected to c0 aka filter input.
+$NFT "add rule t c9 tcp dport 80 tproxy to :20000 meta mark set 1 accept"
+good_ruleset $? "add tproxy expression to c9"
+check_bad_expr
+
+exit $?