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