CVE-2022-26009
A stack-based buffer overflow vulnerability exists in the confsrv ucloud_set_node_location functionality of TCL LinkHub Mesh Wi-Fi MS1G_00_01.00_14. A specially-crafted network packet can lead to stack-based buffer overflow. An attacker can send a malicious packet to trigger this vulnerability.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
TCL LinkHub Mesh Wifi MS1G_00_01.00_14
LinkHub Mesh Wifi - https://www.tcl.com/us/en/products/connected-home/linkhub/linkhub-mesh-wifi-system-3-pack
8.8 - CVSS:3.0/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-121 - Stack-based Buffer Overflow
The LinkHub Mesh Wi-Fi system is a node-based mesh system designed for Wi-Fi deployments across large homes. These nodes include most features standard in current Wi-Fi solutions and allow for easy expansion of the system by adding nodes. The mesh is managed solely by a phone application, and the routers have no web-based management console.
The LinkHub Mesh system uses protobuffers to communicate both internally on the device as well as externally with the controlling phone application. These protobuffers can be sent to port 9003 while on the Wi-Fi provided by the LinkHub Mesh in order to issue commands, much like the phone application would. Once the protobuffer is received, it is routed internally starting from the ucloud
binary and is dispatched to the appropriate handler.
In this case, the handler is confsrv
which handles many message types. In this case we are interested in NodeLocation
message NodeLocation {
required string serialNum = 1; [1]
required string location = 2;
optional uint64 timestamp = 3;
}
Using [1] we have control over serialNum
in the packet. The parsing of the data in the protobuf is done in ucloud_set_node_location
.
00429390 int32_t ucloud_set_node_location(int32_t arg1, int32_t arg2, int32_t arg3)
004293b0 arg_0 = arg1
004293bc int32_t $a3
004293bc arg_c = $a3
004293c0 int32_t var_12c = 0
004293c4 int32_t var_128 = 0
004293c8 int32_t var_124 = 0
004293cc int32_t var_120 = 0
004293d0 int32_t var_11c = 0
004293d4 int32_t var_118 = 0
004293d8 int32_t var_114 = 0
004293dc int32_t var_110 = 0
004293e0 int32_t var_10c = 0
004293e4 int32_t var_130 = 0
00429404 void var_108
00429404 memset(&var_108, 0, 0x100)
00429428 GetValue(name: "serial.number", output_buffer: &var_128)
00429450 struct NodeLocationDescriptor* pkt = node_location__unpack(0, arg3, arg2)
00429464 int32_t $v0_2
00429464 if (pkt == 0) {
0042948c _td_snprintf(3, "api/map_manage.c", 0x83a, " unpack failed ! \n", 0x4ae4b0)
00429498 $v0_2 = 0xffffffff
00429498 } else {
004294b8 void* $v0_5 = client_sn_lkup(sn: pkt->serial_number)
004294cc if ($v0_5 != 0) {
004294f4 strcpy($v0_5 + 0x30, pkt->location)
0042950c if (sx.d(*($v0_5 + 0x30)) == 0) {
00429544 *($v0_5 + 4) = *($v0_5 + 4) & 0xfffffffd
0042953c } else {
00429524 *($v0_5 + 4) = *($v0_5 + 4) | 2
0042951c }
0042951c }
00429574 sprintf(&var_108, "%s%s", "node.location@", pkt->serial_number, 0x4ae4b0) [2]
...
At [2] we can clearly see that a sprintf
is performed without any validation of buffer or input length, which can lead to a stack-based buffer overflow. Below we can verify the issue in ASM:
00429548 b480828f lw $v0, -0x7f4c($gp) {data_4a6564}
0042954c 38084324 addiu $v1, $v0, 0x838 {data_480838, "%s%s"}
00429550 1c00c28f lw $v0, 0x1c($fp) {var_12c_1}
00429554 0c00428c lw $v0, 0xc($v0) {NodeLocationDescriptor::serial_number}
00429558 4000c427 addiu $a0, $fp, 0x40 {var_108} [3]
0042955c 21286000 move $a1, $v1 {data_480838, "%s%s"}
00429560 b480838f lw $v1, -0x7f4c($gp) {data_4a6564}
00429564 40086624 addiu $a2, $v1, 0x840 {data_480840, "node.location@"}
00429568 21384000 move $a3, $v0 [4]
0042956c 0088828f lw $v0, -0x7800($gp) {sprintf}
00429570 21c84000 move $t9, $v0
00429574 09f82003 jalr $t9 [5]
00429578 00000000 nop
At [3] we see that a stack buffer is being loaded as the destination argument of sprintf
. At [4] the serialNum
from the protobuf is being loaded as the second portion of the formation string. At [5] we see that sprintf
is called with no further validation or verification that the buffer is large enough to hold the format string, or that the input is small enough to fit in the buffer. This leads to a simple stack-based buffer overflow using sprintf
.
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$zero: 0x0
$at : 0x806f0000
$v0 : 0x0
$v1 : 0x77c798e0
$a0 : 0x11
$a1 : 0x2
$a2 : 0x1
$a3 : 0x0
$t0 : 0x0
$t1 : 0x0
$t2 : 0x4
$t3 : 0x0
$t4 : 0x8785e654
$t5 : 0x8000
$t6 : 0x0
$t7 : 0x0
$s0 : 0x7f839ad8 → 0x82031107
$s1 : 0x7f839ad8 → 0x82031107
$s2 : 0x77a8aa60 → "uc_api_lib.c"
$s3 : 0x0
$s4 : 0x77a8bbe4 → "_session_read_and_dispatch"
$s5 : 0x77a71090 → 0x3c1c0003
$s6 : 0x115
$s7 : 0x10
$t8 : 0x0
$t9 : 0x7767b52c → 0x3c1c0002
$k0 : 0x0
$k1 : 0x0
$s8 : 0x41414141 ("AAAA"?)
$pc : 0x41414141 ("AAAA"?)
$sp : 0x7f8399b0 → "AAAAAAAAAAAAAA"
$hi : 0x3a4f47
$lo : 0x21743d08 ("\b=t!"?)
$fir : 0x0
$ra : 0x41414141 ("AAAA"?)
$gp : 0x004ae4b0 → 0x00000000
───────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x7f8399b0│+0x0000: "AAAAAAAAAAAAAA" ← $sp
0x7f8399b4│+0x0004: "AAAAAAAAAA"
0x7f8399b8│+0x0008: "AAAAAA"
0x7f8399bc│+0x000c: 0x7f004141 ("AA"?)
0x7f8399c0│+0x0010: 0x7f839a2c → 0x00000000
0x7f8399c4│+0x0014: 0x00000000
0x7f8399c8│+0x0018: 0x004ae4b0 → 0x00000000
0x7f8399cc│+0x001c: 0x7f839ad8 → 0x82031107
────────────────────────────────────────────────────────────────────────────────────────────────── code:mips:MIPS32 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x41414140
─────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, stopped 0x41414141 in ?? (), reason: SIGSEGV
───────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
2022-03-16 - Vendor Disclosure
2022-08-01 - Public Release
Discovered by Carl Hurd of Cisco Talos.