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 <kenton@cloudflare.com> <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
|