capnproto

FORK: Cap'n Proto serialization/RPC system - core tools and C++ library
git clone https://git.neptards.moe/neptards/capnproto.git
Log | Files | Refs | README | LICENSE

2017-04-17-0-apple-clang-elides-bounds-check.md (6437B)


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