CVE-2026-30873: jsonfilter, OpenWrt 24.10.5 to 24.10.6
OpenWrt 24.10.6 adds one patch to jsonfilter, the fix of CVE-2026-30873,
which Trail of Bits found (TOB-OWRT-3)
(OpenWrt commit 77d54cc):
jp_get_tokenreads one token of a JSONPath expression into an operation on its stack.match_tokencopies the text of a string, a label or a regular expression into it withstrdup, andjp_alloc_opcopies it again, so each such token leaked its first copy. The fix frees it (jsonpath commit e086664).
The version of the package stays 2025.04.18~8a86fb78. OpenWrt runs
sstrip on what it ships, so only the segments of the file are left.
Get the binaries
$ for v in 24.10.5 24.10.6; do
mkdir -p $v
curl -s https://downloads.openwrt.org/releases/$v/targets/armsr/armv8/openwrt-$v-armsr-armv8-rootfs.cpio.gz \
| gunzip | (cd $v && cpio -id usr/bin/jsonfilter)
done
Compare
$ hereticus --format markdown 24.10.5/usr/bin/jsonfilter 24.10.6/usr/bin/jsonfilter
| FUNCTION | CHANGE | SCORE | +/-/~ | TOUCHED | MATCHED BY |
|---|---|---|---|---|---|
sub_402c48 | changed | 0.92 | 5/0/0 | 1 branch, 1 call | similar |
86 matched (85 unchanged, 1 changed), 0 gone, 0 new
The one row is the fix. GCC inlined jp_get_token into the function
that parses an expression.
Read the fix
$ hereticus --view il --diff sub_402c48 24.10.5/usr/bin/jsonfilter 24.10.6/usr/bin/jsonfilter
...
w2 = load.32 [sp + 0x68]
x4 = 0
call sub_402274
+ x19 = x0
+ x0 = load.64 [sp + 0x60]
+ if (x0 == 0) goto $-0x88
+ call free
...
sub_402274 is jp_alloc_op, and [sp + 0x60] is the str field of
the operation on the stack. The lines read as the C of the fix:
- return jp_alloc_op(s, op.type, op.num, op.str, NULL);
+ newop = jp_alloc_op(s, op.type, op.num, op.str, NULL);
+ /* match_token might alloced str using strdup() */
+ if (op.str)
+ free(op.str);
+
+ return newop;
The other architectures
| Build | Rows | Row of the fix |
|---|---|---|
armsr/armv8, AArch64 | 1 | 1 |
armsr/armv7, A32 | 4 | 1 |
malta/be, MIPS16e big endian | 1 | 1 |
malta/le, MIPS16e little endian | 1 | 1 |
On malta, jsonfilter is a MIPS16e executable that is not PIC, and
calls each import through a MIPS16e PLT stub that GNU ld puts after
.fini. hereticus names each stub by the slot it jumps through, so the
call of the fix reads call free.
hereticus-cli/tests/truth_cves.rs locks these ranks, and
fixtures/cve_binaries.py fetches and pins the binaries.