Analysis of iOS & OS X Vulnerability: CVE-2016-1722

By:Joshua Drake

Follow Joshua Drake (@jduck)Nikias Bassen

Follow Nikias Bassen (@pimskeks)

Apple released iOS 9.2.1 update, the first iOS security update in 2016. As part of this update, Apple fixed code execution vulnerability in syslogd that was reported by Zimperium zLabs researchers Nikias Bassen and Joshua J. Drake. In this blog we’ll share how we identified this vulnerability and the original advisory containing the technical details behind this vulnerability that can allow an attacker to execute code with root privileges on iOS devices running any version between 6.0 to 9.2.

During our fuzzing attempts in effort to improve the state of security on iOS devices, we investigated one of the crashes that our fuzzer triggered. Our fuzzer was not targeting syslog code, but once we investigated the crash it led to a comprehensive review of the open-source portion of syslogd. We identified the error in the code and were able to successfully trigger the crash. We notified and worked with Apple’s Security Team to fix the issue. Apple was quick to respond. Zimperium enterprise mobile security customers are protected from this and other critical attacks that were patched as part of iOS 9.2.1 security update. The full security advisory is below:

iOS / OS X syslogd heap buffer overflow

Affected component:

/usr/sbin/syslogd

Affected platforms and versions:

iOS 6.0 through 9.2

OS X 10.9 through 10.11.2

Vendor:

Apple, Inc.

CVE:

CVE-2016-1722

Latest public source code version:

syslog-267 (OS X 10.10.5)

http://opensource.apple.com/source/syslog/syslog-267/

Disclosure Timeline:

Bug discovered and confirmed: Oct. 26, 2015 Vendor Notified: Nov. 25, 2015 Vulnerability patched: January 14th, 2016 with iOS 9.2.1

Summary

An incorrect size calculation during memory re-allocation leads to a heap buffer overflow in syslogd when multiple client connections are established.

Impact

Local elevation of privileges, remote code execution (trusted device on WiFi scenario) or Denial of Service

Description

The syslogd process runs with root privileges on iOS. We found a heap overflow vulnerability which results in memory corruption and under certain scenarios may lead to arbitrary code execution. Data beyond the bounds of the heap buffer gets overwritten with file descriptor values. Certain manipulations allow some control over the value written. However, exploitation of this vulnerability does not appear to be trivial.

Background

The software running on a development machine (typically Xcode running on a Mac) connects to a service called the syslog relay service (com.apple.syslog_relay). This connection enables access to the device’s system log (syslog) and is usually accomplished via USB. When on the same WiFi network as the device, it is also possible to connect to this service remotely. Regardless of which medium is used, devices must be configured to trust the host computer before it can connect to the service. That is to say, it must be “paired”.

Exploiting this vulnerability involves host-to-device infection. Other typical attack vectors do not appear to be viable. Since a passcode-locked device can only trust a computer after the device has been unlocked, exploiting this issue on a device that is not trusted requires the passcode. Third-party apps running on the device have no way to directly communicate with syslogd.

Details

The root cause of this vulnerability lies within the add_lockdown_session function in the source file syslogd.tproj/dbserver.c. During the execution of this function, the programmer re-allocates memory to grow the size of an array. Figure 1 shows the vulnerable part of the code.

160 void

161 add_lockdown_session(int fd)

162 {

…

167 dispatch_async(watch_queue, ^{

168 if (global.lockdown_session_count == 0)

global.lockdown_session_fds = NULL;

169

170 global.lockdown_session_fds = reallocf(global.lockdown_session_fds,

global.lockdown_session_count + 1 * sizeof(int));

…

Figure 1: Code excerpt from syslogd’s syslogd.tproj/dbserver.c source file. The highlighted lines show the root cause of the vulnerability.

Line 170 reveals the source of the problem. The new size passed to the reallocf function call is miscalculated. The code is supposed to grow the array of session file descriptors for each new connection by sizeof(int), but operator precedence rules mean that the calculation goes wrong. The line of code should instead read:

global.lockdown_session_fds = reallocf(global.lockdown_session_fds,

(global.lockdown_session_count + 1) * sizeof(int));

The original code only adds sizeof(int) to the number of sessions, whereas the proposed line above adds 1 to the number of sessions and then multiplies the result with sizeof(int). In this particular situation, the use of parentheses makes all the difference.

The source code for newer versions of iOS and OS X are often not available. This fact makes consulting a disassembly of the binaries a necessity for proving the existence of these issues in the latest version. Additionally, looking at the disassembly makes the matter a bit more obvious since operator precedence rules have already been applied and the code has been optimized to show the operation in a simplified form. Figure 2 depicts commented disassembly from the syslogd binary shipped with iOS 9.0.2:

Figure 2: Disassembly of iOS 9.0.2’s syslogd binary showing the heap overflow issue.

This issue also exists on iOS 9.1, which can be confirmed even without inspecting the binary by connecting to the com.apple.syslog_relay service multiple times. Assuming important data within the heap gets corrupted, syslogd will crash. The same issue can also be spotted in OS­ X’s syslogd binary. Figure 3 shows the equivalent commented disassembly from OS X 10.11.1’s syslogd binary:

Figure 3: Disassembly of OS X 10.11.1 syslogd binary showing the heap overflow issue.

Notice how the compilers in both cases optimized the “1 * sizeof(int)” expression to just “sizeof(int)”, which makes the bug clearly visible.

The first time the function is called is when a new client connects. On the first execution the function will allocate the initial buffer to hold the first file descriptor for that connection. Remember that sizeof(int) is always 4. The global.lockdown_session_count value is 0 at that time so the calculated size will be correct: 4 bytes. However, when another client connects, the global.lockdown_session_count value is 1 and the new buffer size will be incorrectly calculated as 5 bytes. For a third connection the size will be 6, and so on.

The actual heap overflow happens when the developer stores the file descriptor of the new connection in the global.lockdown_session_fds array. The following code excerpt shows the code where this occurs:

…

172 if (global.lockdown_session_fds == NULL)

…

177 else

178 {

179 global.lockdown_session_fds[global.lockdown_session_count++] = fd;

180 }

…

183 });

184 }

Figure 4: Code excerpt from syslogd’s syslogd.tproj/dbserver.c source file. The highlighted lines show where the heap corruption occurs.

Line 179 in Figure 4, as well as the last lines of disassembly in Figure 2 and 3, show what happens: the code writes 4 bytes into a buffer that does not have enough memory allocated – an out of bounds write. Since memory allocations are typically rounded to 8 bytes, creating two connections won’t lead to memory corruption. However, as soon as a third connection is made, the file descriptor writes out of bounds and would likely cause heap memory corruption as depicted in Figure 5.

Figure 5: Heap overflow (out of bounds write) because of invalid size calculation during reallocation. Writing of the file descriptor might corrupt heap memory.



While memory corruption might already happen with three connections, it most likely takes some more connections to make syslogd crash. However, this depends on if and how the data after the undersized heap block is utilized after being corrupted. In our testing, we witnessed syslogd abort its execution due to heap corruption within the remove_lockdown_session function.