annotate src/capnproto-0.6.0/security-advisories/2017-04-17-0-apple-clang-elides-bounds-check.md @ 62:0994c39f1e94

Cap'n Proto v0.6 + build for OSX
author Chris Cannam <cannam@all-day-breakfast.com>
date Mon, 22 May 2017 10:01:37 +0100
parents
children
rev   line source
cannam@62 1 Problem
cannam@62 2 =======
cannam@62 3
cannam@62 4 Some bounds checks are elided by Apple's compiler and possibly others, leading
cannam@62 5 to a possible attack especially in 32-bit builds.
cannam@62 6
cannam@62 7 Although triggered by a compiler optimization, this is a bug in Cap'n Proto,
cannam@62 8 not the compiler.
cannam@62 9
cannam@62 10 Discovered by
cannam@62 11 =============
cannam@62 12
cannam@62 13 Kenton Varda &lt;kenton@cloudflare.com> &lt;kenton@sandstorm.io>
cannam@62 14
cannam@62 15 Announced
cannam@62 16 =========
cannam@62 17
cannam@62 18 2017-04-17
cannam@62 19
cannam@62 20 CVE
cannam@62 21 ===
cannam@62 22
cannam@62 23 CVE-2017-7892
cannam@62 24
cannam@62 25 Impact
cannam@62 26 ======
cannam@62 27
cannam@62 28 - Remotely segfault a 32-bit application by sending it a malicious message.
cannam@62 29 - Exfiltration of memory from such applications **might** be possible, but our
cannam@62 30 current analysis indicates that other checks would cause any such attempt to
cannam@62 31 fail (see below).
cannam@62 32 - At present I've only reproduced the problem on Mac OS using Apple's
cannam@62 33 compiler. Other compilers and platforms do not seem to apply the problematic
cannam@62 34 optimization.
cannam@62 35
cannam@62 36 Fixed in
cannam@62 37 ========
cannam@62 38
cannam@62 39 - git commit [52bc956459a5e83d7c31be95763ff6399e064ae4][0]
cannam@62 40 - release 0.5.3.1:
cannam@62 41 - Unix: https://capnproto.org/capnproto-c++-0.5.3.1.tar.gz
cannam@62 42 - Windows: https://capnproto.org/capnproto-c++-win32-0.5.3.1.zip
cannam@62 43 - release 0.6 (future)
cannam@62 44
cannam@62 45 [0]: https://github.com/sandstorm-io/capnproto/commit/52bc956459a5e83d7c31be95763ff6399e064ae4
cannam@62 46
cannam@62 47 Details
cannam@62 48 =======
cannam@62 49
cannam@62 50 *The following text contains speculation about the exploitability of this
cannam@62 51 bug. This is provided for informational purposes, but as such speculation is
cannam@62 52 often shown to be wrong, you should not rely on the accuracy of this
cannam@62 53 section for the safety of your service. Please update your library.*
cannam@62 54
cannam@62 55 During regular testing in preparation for a release, it was discovered that
cannam@62 56 when Cap'n Proto is built using the current version of Apple's Clang compiler
cannam@62 57 in 32-bit mode with optimizations enabled, it is vulnerable to attack via
cannam@62 58 specially-crafted malicious input, causing the application to read from an
cannam@62 59 invalid memory location and crash.
cannam@62 60
cannam@62 61 Specifically, a malicious message could contain a [far pointer][1] pointing
cannam@62 62 outside of the message. Cap'n Proto messages are broken into segments of
cannam@62 63 continuous memory. A far pointer points from one segment into another,
cannam@62 64 indicating both the segment number and an offset within that segment. If a
cannam@62 65 malicious far pointer contained an offset large enough to overflow the pointer
cannam@62 66 arithmetic (wrapping around to the beginning of memory), then it would escape
cannam@62 67 bounds checking, in part because the compiler would elide half of the bounds
cannam@62 68 check as an optimization.
cannam@62 69
cannam@62 70 That is, the code looked like (simplification):
cannam@62 71
cannam@62 72 word* target = segmentStart + farPointer.offset;
cannam@62 73 if (target < segmentStart || target >= segmentEnd) {
cannam@62 74 throwBoundsError();
cannam@62 75 }
cannam@62 76 doSomething(*target);
cannam@62 77
cannam@62 78 To most observers, this code would appear to be correct. However, as it turns
cannam@62 79 out, pointer arithmetic that overflows is undefined behavior under the C
cannam@62 80 standard. As a result, the compiler is allowed to assume that the addition on
cannam@62 81 the first line never overflows. Since `farPointer.offset` is an unsigned
cannam@62 82 number, the compiler is able to conclude that `target < segmentStart` always
cannam@62 83 evaluates false. Thus, the compiler removes this part of the check.
cannam@62 84 Unfortunately, in the case of overflow, this is exactly the part of the check
cannam@62 85 that we need.
cannam@62 86
cannam@62 87 Based on both fuzz testing and logical analysis, I believe that the far pointer
cannam@62 88 bounds check is the only check affected by this optimization. If true, then it
cannam@62 89 is not possible to exfiltrate memory through this vulnerability: the target of
cannam@62 90 a far pointer is always expected to be another pointer, which in turn points to
cannam@62 91 the final object. That second pointer will go through its own bounds checking,
cannam@62 92 which will fail (unless it somehow happens to point back into the message
cannam@62 93 bounds, in which case no harm is done).
cannam@62 94
cannam@62 95 I believe this bug does not affect any common 64-bit platform because the
cannam@62 96 maximum offset expressible by a far pointer is 2^32 bytes. In order to trigger
cannam@62 97 the bug in a 64-bit address space, the message location would have to begin
cannam@62 98 with 0xFFFFFFFF (allocated in the uppermost 4GB of address space) and the
cannam@62 99 target would have to begin with 0x00000000 (allocated in the lowermost 4GB of
cannam@62 100 address space). Typically, on 64-bit architectures, the upper half of the
cannam@62 101 address space is reserved for the OS kernel, thus a message could not possibly
cannam@62 102 be located there.
cannam@62 103
cannam@62 104 I was not able to reproduce this bug on other platforms, perhaps because the
cannam@62 105 compiler optimization is not applied by other compilers. On Linux, I tested GCC
cannam@62 106 4.9, 5.4, and 6.3, as well as Clang 3.6, 3.8, and 4.0. None were affected.
cannam@62 107 Nevertheless, the compiler behavior is technically allowed, thus it should be
cannam@62 108 assumed that it can happen on other platforms as well.
cannam@62 109
cannam@62 110 The specific compiler version which was observed to be affected is:
cannam@62 111
cannam@62 112 $ clang++ --version
cannam@62 113 Apple LLVM version 8.1.0 (clang-802.0.41)
cannam@62 114 Target: x86_64-apple-darwin16.5.0
cannam@62 115 Thread model: posix
cannam@62 116 InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
cannam@62 117
cannam@62 118 (Note: Despite being Clang-based, Apple's compiler version numbers have no
cannam@62 119 apparent relationship to Clang version numbers.)
cannam@62 120
cannam@62 121 [1]: https://capnproto.org/encoding.html#inter-segment-pointers
cannam@62 122
cannam@62 123 Preventative measures
cannam@62 124 =====================
cannam@62 125
cannam@62 126 The problem was caught by running Cap'n Proto's standard fuzz tests in this
cannam@62 127 configuration. These tests are part of the Cap'n Proto test suite which runs
cannam@62 128 when you invoke `make check`, which Cap'n Proto's installation instructions
cannam@62 129 suggest to all users.
cannam@62 130
cannam@62 131 However, these fuzz tests were introduced after the 0.5.x release branch,
cannam@62 132 hence are not currently present in release versions of Cap'n Proto, only in
cannam@62 133 git. A 0.6 release will come soon, fixing this.
cannam@62 134
cannam@62 135 The bugfix commit forces the compiler to perform all checks by casting the
cannam@62 136 relevant pointers to `uintptr_t`. According to the standard, unsigned integers
cannam@62 137 implement modulo arithmetic, rather than leaving overflow undefined, thus the
cannam@62 138 compiler cannot make the assumptions that lead to eliding the check. This
cannam@62 139 change has been shown to fix the problem in practice. However, this quick fix
cannam@62 140 does not technically avoid undefined behavior, as the code still computes
cannam@62 141 pointers that point to invalid locations before they are checked. A
cannam@62 142 technically-correct solution has been implemented in the next commit,
cannam@62 143 [2ca8e41140ebc618b8fb314b393b0a507568cf21][2]. However, as this required more
cannam@62 144 extensive refactoring, it is not appropriate for cherry-picking, and will
cannam@62 145 only land in versions 0.6 and up.
cannam@62 146
cannam@62 147 [2]: https://github.com/sandstorm-io/capnproto/commit/2ca8e41140ebc618b8fb314b393b0a507568cf21