annotate src/capnproto-0.6.0/security-advisories/2017-04-17-0-apple-clang-elides-bounds-check.md @ 147:45360b968bf4

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