cannam@147: Problem cannam@147: ======= cannam@147: cannam@147: Some bounds checks are elided by Apple's compiler and possibly others, leading cannam@147: to a possible attack especially in 32-bit builds. cannam@147: cannam@147: Although triggered by a compiler optimization, this is a bug in Cap'n Proto, cannam@147: not the compiler. cannam@147: cannam@147: Discovered by cannam@147: ============= cannam@147: cannam@147: Kenton Varda <kenton@cloudflare.com> <kenton@sandstorm.io> cannam@147: cannam@147: Announced cannam@147: ========= cannam@147: cannam@147: 2017-04-17 cannam@147: cannam@147: CVE cannam@147: === cannam@147: cannam@147: CVE-2017-7892 cannam@147: cannam@147: Impact cannam@147: ====== cannam@147: cannam@147: - Remotely segfault a 32-bit application by sending it a malicious message. cannam@147: - Exfiltration of memory from such applications **might** be possible, but our cannam@147: current analysis indicates that other checks would cause any such attempt to cannam@147: fail (see below). cannam@147: - At present I've only reproduced the problem on Mac OS using Apple's cannam@147: compiler. Other compilers and platforms do not seem to apply the problematic cannam@147: optimization. cannam@147: cannam@147: Fixed in cannam@147: ======== cannam@147: cannam@147: - git commit [52bc956459a5e83d7c31be95763ff6399e064ae4][0] cannam@147: - release 0.5.3.1: cannam@147: - Unix: https://capnproto.org/capnproto-c++-0.5.3.1.tar.gz cannam@147: - Windows: https://capnproto.org/capnproto-c++-win32-0.5.3.1.zip cannam@147: - release 0.6 (future) cannam@147: cannam@147: [0]: https://github.com/sandstorm-io/capnproto/commit/52bc956459a5e83d7c31be95763ff6399e064ae4 cannam@147: cannam@147: Details cannam@147: ======= cannam@147: cannam@147: *The following text contains speculation about the exploitability of this cannam@147: bug. This is provided for informational purposes, but as such speculation is cannam@147: often shown to be wrong, you should not rely on the accuracy of this cannam@147: section for the safety of your service. Please update your library.* cannam@147: cannam@147: During regular testing in preparation for a release, it was discovered that cannam@147: when Cap'n Proto is built using the current version of Apple's Clang compiler cannam@147: in 32-bit mode with optimizations enabled, it is vulnerable to attack via cannam@147: specially-crafted malicious input, causing the application to read from an cannam@147: invalid memory location and crash. cannam@147: cannam@147: Specifically, a malicious message could contain a [far pointer][1] pointing cannam@147: outside of the message. Cap'n Proto messages are broken into segments of cannam@147: continuous memory. A far pointer points from one segment into another, cannam@147: indicating both the segment number and an offset within that segment. If a cannam@147: malicious far pointer contained an offset large enough to overflow the pointer cannam@147: arithmetic (wrapping around to the beginning of memory), then it would escape cannam@147: bounds checking, in part because the compiler would elide half of the bounds cannam@147: check as an optimization. cannam@147: cannam@147: That is, the code looked like (simplification): cannam@147: cannam@147: word* target = segmentStart + farPointer.offset; cannam@147: if (target < segmentStart || target >= segmentEnd) { cannam@147: throwBoundsError(); cannam@147: } cannam@147: doSomething(*target); cannam@147: cannam@147: To most observers, this code would appear to be correct. However, as it turns cannam@147: out, pointer arithmetic that overflows is undefined behavior under the C cannam@147: standard. As a result, the compiler is allowed to assume that the addition on cannam@147: the first line never overflows. Since `farPointer.offset` is an unsigned cannam@147: number, the compiler is able to conclude that `target < segmentStart` always cannam@147: evaluates false. Thus, the compiler removes this part of the check. cannam@147: Unfortunately, in the case of overflow, this is exactly the part of the check cannam@147: that we need. cannam@147: cannam@147: Based on both fuzz testing and logical analysis, I believe that the far pointer cannam@147: bounds check is the only check affected by this optimization. If true, then it cannam@147: is not possible to exfiltrate memory through this vulnerability: the target of cannam@147: a far pointer is always expected to be another pointer, which in turn points to cannam@147: the final object. That second pointer will go through its own bounds checking, cannam@147: which will fail (unless it somehow happens to point back into the message cannam@147: bounds, in which case no harm is done). cannam@147: cannam@147: I believe this bug does not affect any common 64-bit platform because the cannam@147: maximum offset expressible by a far pointer is 2^32 bytes. In order to trigger cannam@147: the bug in a 64-bit address space, the message location would have to begin cannam@147: with 0xFFFFFFFF (allocated in the uppermost 4GB of address space) and the cannam@147: target would have to begin with 0x00000000 (allocated in the lowermost 4GB of cannam@147: address space). Typically, on 64-bit architectures, the upper half of the cannam@147: address space is reserved for the OS kernel, thus a message could not possibly cannam@147: be located there. cannam@147: cannam@147: I was not able to reproduce this bug on other platforms, perhaps because the cannam@147: compiler optimization is not applied by other compilers. On Linux, I tested GCC cannam@147: 4.9, 5.4, and 6.3, as well as Clang 3.6, 3.8, and 4.0. None were affected. cannam@147: Nevertheless, the compiler behavior is technically allowed, thus it should be cannam@147: assumed that it can happen on other platforms as well. cannam@147: cannam@147: The specific compiler version which was observed to be affected is: cannam@147: cannam@147: $ clang++ --version cannam@147: Apple LLVM version 8.1.0 (clang-802.0.41) cannam@147: Target: x86_64-apple-darwin16.5.0 cannam@147: Thread model: posix cannam@147: InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin cannam@147: cannam@147: (Note: Despite being Clang-based, Apple's compiler version numbers have no cannam@147: apparent relationship to Clang version numbers.) cannam@147: cannam@147: [1]: https://capnproto.org/encoding.html#inter-segment-pointers cannam@147: cannam@147: Preventative measures cannam@147: ===================== cannam@147: cannam@147: The problem was caught by running Cap'n Proto's standard fuzz tests in this cannam@147: configuration. These tests are part of the Cap'n Proto test suite which runs cannam@147: when you invoke `make check`, which Cap'n Proto's installation instructions cannam@147: suggest to all users. cannam@147: cannam@147: However, these fuzz tests were introduced after the 0.5.x release branch, cannam@147: hence are not currently present in release versions of Cap'n Proto, only in cannam@147: git. A 0.6 release will come soon, fixing this. cannam@147: cannam@147: The bugfix commit forces the compiler to perform all checks by casting the cannam@147: relevant pointers to `uintptr_t`. According to the standard, unsigned integers cannam@147: implement modulo arithmetic, rather than leaving overflow undefined, thus the cannam@147: compiler cannot make the assumptions that lead to eliding the check. This cannam@147: change has been shown to fix the problem in practice. However, this quick fix cannam@147: does not technically avoid undefined behavior, as the code still computes cannam@147: pointers that point to invalid locations before they are checked. A cannam@147: technically-correct solution has been implemented in the next commit, cannam@147: [2ca8e41140ebc618b8fb314b393b0a507568cf21][2]. However, as this required more cannam@147: extensive refactoring, it is not appropriate for cherry-picking, and will cannam@147: only land in versions 0.6 and up. cannam@147: cannam@147: [2]: https://github.com/sandstorm-io/capnproto/commit/2ca8e41140ebc618b8fb314b393b0a507568cf21