[PATCH v2 1/2] lib/ts_fsm: preserve token types when exporting patterns
From: Zhiqi Zhang
Date: Sun Nov 01 2026 - 03:19:12 EST
Dumping and restoring a tc filter with an FSM text ematch can silently
change which packets it matches, or fail to restore it with EINVAL. This
happens when userspace reuses the pattern returned by RTM_GETTFILTER to
configure another filter. Dumping alone does not change the live filter.
fsm_init() replaces each public token type with an internal character-class
mask, and fsm_get_pattern() returns that converted array. For example,
TS_FSM_DIGIT (2) becomes the _D mask (4). Restoring the exported pattern
interprets 4 as TS_FSM_PRINT. With a DIGIT/MULTI pattern, a rule that
matched the UDP payload "7777" but rejected "AAAA" starts matching both
after restoration. Other exported masks exceed TS_FSM_TYPE_MAX and are
rejected with EINVAL.
Keep the public token types in the configuration and look up their masks
in match_token(). This preserves the exported representation without
adding another pattern allocation. Initialization still validates every
type before it can be used to index token_map. The extra lookup during
character-class matching avoids storing a second copy of the pattern.
I identified this issue. OpenAI Codex helped confirm it and drafted the
fix and changelog. I reviewed the fix, built it and verified that the
tests passed.
Fixes: 6408f79cce40 ("[LIB]: Naive finite state machine based textsearch")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Zhiqi Zhang <zhiqizhangg@xxxxxxxxx>
---
Changes in v2: describe the tc impact, split out the KUnit tests and add
stable. The production code change is unchanged from v1.
Validation: updated KUnit tests change from 2/15 to 15/15 passing on ARM64
GCC; ARM32 GCC and ARM64 Clang also pass. An isolated QEMU rtnetlink/UDP
test demonstrates altered packet matching and EINVAL restoration before
the fix, with both corrected afterwards. See the cover letter for details.
An ARM64 userspace microbenchmark of the extracted matcher measured median
paired slowdowns of about 1-5% for tested long character-class scans and
13% for a one-byte rejection case with appreciable variability. This is
not a measurement of in-kernel or packet throughput.
lib/ts_fsm.c | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/lib/ts_fsm.c b/lib/ts_fsm.c
index 053615f4f..8725ea6f0 100644
--- a/lib/ts_fsm.c
+++ b/lib/ts_fsm.c
@@ -126,7 +126,7 @@ _W|_L, _W|_L, _W|_L, _W|_L}; /* 252-255 */
static inline int match_token(struct ts_fsm_token *t, u8 d)
{
if (t->type)
- return (token_lookup_tbl[d] & t->type) != 0;
+ return (token_lookup_tbl[d] & token_map[t->type]) != 0;
else
return t->value == d;
}
@@ -288,11 +288,6 @@ static struct ts_config *fsm_init(const void *pattern, unsigned int len,
fsm->ntokens = ntokens;
memcpy(fsm->tokens, pattern, len);
- for (i = 0; i < fsm->ntokens; i++) {
- struct ts_fsm_token *t = &fsm->tokens[i];
- t->type = token_map[t->type];
- }
-
return conf;
errout:
--
2.54.0 (Apple Git-157)