CVE-2025-62525: the ltq-ptm kernel module, OpenWrt 24.10.3 to 24.10.4
This repeats the fix of
OpenWrt security advisory 2025-10-22-2.
The ioctl handler of the PTM driver of the Lantiq xRX200 (VR9) wrote to
and read from the address a user gave, with no check, so any local user
could read and write kernel memory. The fix
(OpenWrt commit e001b31)
asks for CAP_NET_ADMIN first, and copies through copy_from_user and
copy_to_user.
The module is MIPS32 big endian. OpenWrt’s strip-kmod.sh keeps only the
symbols that relocations need, and renames the local ones to numbers, as
_7: 8 symbols name 1.5 KiB of the 12 KiB of code, and none of them is
the handler. So hereticus reads the module as stripped code: it places
each code section after the one before, scans it, takes the starts that
tables of callbacks in the data point at, and names each call by the
relocation at it.
Get the binaries
$ for v in 24.10.3 24.10.4; do
base=https://downloads.openwrt.org/releases/$v/targets/lantiq/xrx200/kmods
kernel=$(curl -s $base/ | grep -o 'href="6[^"]*"' | cut -d'"' -f2)
ipk=$(curl -s $base/$kernel | grep -o 'href="kmod-ltq-ptm-vr9_[^"]*"' | cut -d'"' -f2)
mkdir -p $v && curl -s $base/$kernel$ipk | tar xzO ./data.tar.gz | tar xz -C $v
done
Compare
$ hereticus --format markdown 24.10.3/lib/modules/*/ltq_ptm_vr9.ko 24.10.4/lib/modules/*/ltq_ptm_vr9.ko
| FUNCTION | CHANGE | SCORE | +/-/~ | TOUCHED | MATCHED BY |
|---|---|---|---|---|---|
sub_267c | changed | 0.70 | 69/37/9 | 11 branch, 7 call, 3 jump, 34 mem, 1 const | similar |
sub_2b0c: gone
20 matched (10 unchanged, 10 changed), 1 gone, 0 new
The fix is the only changed row. The kernel moved from 6.6.104 to 6.6.110 between the two releases, and the 9 functions that changed only a constant are hidden.
Read the fix
$ hereticus --view il --diff sub_267c 24.10.3/lib/modules/*/ltq_ptm_vr9.ko 24.10.4/lib/modules/*/ltq_ptm_vr9.ko
...
if (v0 != a0) goto $+0x57c
a2 = 0
+ call capable
+ a0 = 0xc
+ if (v0 == 0) goto $+0x3c
+ v0 = -0x1
...
+ a0 = s0
+ a1 = sp + 0x24
+ a2 = 0x14
+ at = 0
+ at = at
+ call __raw_copy_to_user
+ nop
+ if (a2 == 0) goto $-0x118
v0 = 0
+ v0 = -0xe
...
capable(12) asks for CAP_NET_ADMIN, and its failure returns -1,
-EPERM. Each answer is now built on the stack and copied out with
__raw_copy_to_user, which fails with -14, -EFAULT.
hereticus-cli/tests/truth_cves.rs locks the rank, and
fixtures/cve_binaries.py fetches the package and pins the module.