1 /*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define TLOG_TAG "cfi-crasher"
18
19 #include <assert.h>
20 #include <lib/tipc/tipc.h>
21 #include <lib/tipc/tipc_srv.h>
22 #include <lk/err_ptr.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <trusty/time.h>
26 #include <trusty_log.h>
27 #include <uapi/err.h>
28
29 #include <cfi-crasher.h>
30 #include <cfi_crasher_consts.h>
31
32 static int crasher_on_message(const struct tipc_port* port,
33 handle_t chan,
34 void* ctx);
35
36 static struct tipc_port_acl crasher_port_acl = {
37 .flags = IPC_PORT_ALLOW_TA_CONNECT,
38 .uuid_num = 0,
39 .uuids = NULL,
40 .extra_data = NULL,
41 };
42
43 static struct tipc_port crasher_port = {
44 .name = CFI_CRASHER_PORT,
45 .msg_max_size = sizeof(struct crasher_msg),
46 .msg_queue_len = 1,
47 .acl = &crasher_port_acl,
48 .priv = NULL,
49 };
50
51 typedef int (*int_func)(int);
52
53 /*
54 * function against which all subsequent functions are compared.
55 * cfi-icall ensures that control is only transferred to a function with
56 * the same static type.
57 */
crasher_int_func(int arg)58 __NO_INLINE static int crasher_int_func(int arg) {
59 TLOG("function with int return type and 1 int arg ran\n");
60 return arg;
61 }
62
crasher_float_func(float arg)63 __NO_INLINE static float crasher_float_func(float arg) {
64 TLOG("function with float return type and 1 float arg ran\n");
65 return arg;
66 }
67
68 /*
69 * We need a global variable for the channel handle since crasher_void
70 * doesn't take arguments. Additionally, we need a global variable for
71 * the program counter in order to calculate the address for jumping
72 * partway into crasher_not_entry_func.
73 */
74 handle_t global_chan;
75 void* volatile global_pc;
crasher_void(void)76 __NO_INLINE static void crasher_void(void) {
77 TLOG("crasher_not_entry_func ran, logging from crasher_void\n");
78 /*
79 * We suppress sending a tipc message in the case where global_pc is
80 * set since in this case crasher_not_entry_func was properly called
81 * so we expect to send CRASHER_ENTRY back to the client and want to
82 * suppress a second CRASHER_VOID message. Also, crasher_not_entry_func
83 * is called properly by CRASHER_NOT_ENTRY in order to first calculate
84 * the offset for jumping partway into the function, so we do not want
85 * to send a tipc message in this case. This if statement is placed
86 * in crasher_void instead of crasher_not_entry_func to minimize
87 * conditionals in a function entered partway, as this could lead
88 * to crashing even without CFI enabled.
89 */
90 if (global_pc) {
91 return;
92 }
93
94 struct crasher_msg msg;
95 msg.cmd = CRASHER_VOID;
96 /*
97 * Because entering crasher_not_entry_func partway skips the
98 * preamble, regardless of whether CFI is enabled the app will
99 * crash. We need to know whether the app crashes immediately
100 * after jumping to crasher_not_entry_func due to CFI, or if
101 * it crashed because the stack is corrupted. So we send a
102 * message back to communicate that the app is not crashing
103 * due to CFI if crasher_void runs.
104 */
105 int ret = tipc_send1(global_chan, &msg, sizeof(msg));
106 if (ret < 0 || ret != sizeof(msg)) {
107 TLOGE("Failed to send message (%d)\n", ret);
108 }
109 }
110
111 /*
112 * Returns the program counter for the caller's context. We call
113 * get_pc from the beginning of crasher_not_entry_func in order to
114 * obtain a natural entry point partway through crasher_not_entry_func.
115 */
get_pc(void)116 __NO_INLINE static void* get_pc(void) {
117 return __builtin_extract_return_addr(__builtin_return_address(0));
118 }
119
crasher_not_entry_func(int arg)120 __NO_INLINE static int crasher_not_entry_func(int arg) {
121 global_pc = get_pc();
122 /*
123 * We place logging in a separate function call since otherwise
124 * arguments of TLOG are corrupted by jumping into the function partway.
125 */
126 crasher_void();
127
128 /*
129 * A segfault occurs if the indirect call skips the function preamble.
130 * To ensure the test app does not interpret this segfault as CFI
131 * crashing the app, we send a message from crasher_void. This way, the
132 * test app sees the message before seeing the channel has closed,
133 * preventing the test from incorrectly passing. With CFI enabled,
134 * crasher_void should never run if a test attempts to enter
135 * crasher_not_entry_func partway. We sleep for 100 ms so that test app sees
136 * the response message and channel closed event as separate events.
137 */
138 trusty_nanosleep(0, 0, 100 * 1000 * 1000);
139
140 /* Log point to prevent tail-call optimisation to trusty_nanosleep(),
141 * which can cause a crash before the sleep with certain CFI techniques
142 * such as PAC.
143 */
144 TLOGI("crasher_not_entry_func done\n");
145
146 return arg;
147 }
148
149 /*
150 * We call this function instead of directly calling crasher_float_func
151 * in crasher_on_message since no_sanitize("cfi-icall") must be specified
152 * for the function which calls crasher_float_func in order to exempt
153 * it from CFI.
154 */
155 __NO_INLINE static void __attribute__((no_sanitize("cfi-icall")))
crasher_call_exclude_float_func(void)156 crasher_call_exclude_float_func(void) {
157 TLOG("crasher_call_exclude_float_func ran, calling crasher_float_func\n");
158 ((int_func)crasher_float_func)(0);
159 }
160
161 /*
162 * Similar to crasher_call_exclude_float_func,
163 * crasher_call_exclude_not_entry_func is a wrapper function which ensures
164 * a CFI-exempt context when attempting to enter crasher_not_entry_func
165 * partway.
166 */
167 __NO_INLINE static void __attribute__((no_sanitize("cfi-icall")))
crasher_call_exclude_not_entry_func(void)168 crasher_call_exclude_not_entry_func(void) {
169 TLOG("crasher_call_exclude_not_entry_func ran, entering crasher_not_entry_func partway\n");
170 /*
171 * Because global_pc is global, we reset it before each test to make sure
172 * different tests do not interfere with each other.
173 */
174 global_pc = NULL;
175 crasher_not_entry_func(0); /* set global_pc by calling correctly */
176 /*
177 * We need to reset global_pc so the crasher_void tipc message is not
178 * suppressed. We save global_pc into crasher_not_entry_func_partway before
179 * resetting it.
180 */
181 void* crasher_not_entry_func_partway = global_pc;
182 global_pc = NULL;
183 ((int_func)(crasher_not_entry_func_partway))(0);
184 }
185
crasher_on_message(const struct tipc_port * port,handle_t chan,void * ctx)186 static int crasher_on_message(const struct tipc_port* port,
187 handle_t chan,
188 void* ctx) {
189 struct crasher_msg msg;
190
191 int ret = tipc_recv1(chan, sizeof(msg), &msg, sizeof(msg));
192 if (ret < 0 || ret != sizeof(msg)) {
193 TLOGE("Failed to receive message (%d)\n", ret);
194 return ret;
195 }
196
197 switch (msg.cmd) {
198 /*
199 * CRASHER_NOP test checks that the internal testing machinery
200 * is working properly even when crasher_on_message does not transfer
201 * control flow to another internal function. Since the last 2 tests
202 * are expected to crash the app when CFI is enabled, we need to make
203 * sure the app isn't just always crashing.
204 */
205 case CRASHER_NOP:
206 TLOGI("nop\n");
207 break;
208 /*
209 * Similar to CRASHER_NOP, CRASHER_CORRECT is expected to not crash
210 * the app regardless of whether CFI is enabled since we are calling
211 * a function with the expected signature. We want to ensure the app
212 * is not just always crashing.
213 */
214 case CRASHER_CORRECT:
215 TLOGI("call crasher_int_func\n");
216 /*
217 * The argument is arbitrary, we just follow a convention of using
218 * 0, for simplicity.
219 */
220 crasher_int_func(0);
221 break;
222 /*
223 * Similar to how CRASHER_CORRECT ensures the app doesn't crash when
224 * calling crasher_int_func correctly, CRASHER_ENTRY ensures the app
225 * doesn't crash when calling crasher_not_entry_func correctly.
226 */
227 case CRASHER_ENTRY:
228 TLOGI("call crasher_not_entry_func\n");
229 global_chan = chan;
230 global_pc = NULL;
231 crasher_not_entry_func(0);
232 break;
233 /*
234 * Part of testing that CFI is working properly includes testing that
235 * exempting certain functions from CFI works as intended.
236 * CRASHER_EXCLUDE_WRONG_TYPE ensures that calling crasher_float_func
237 * from a CFI-exempt context does not crash the app. This test essentially
238 * checks that the no_sanitize("cfi-icall") function decorator on
239 * crasher_call_exclude_float_func works as intended.
240 */
241 case CRASHER_EXCLUDE_WRONG_TYPE:
242 TLOGI("call crasher_call_exclude_float_func\n");
243 crasher_call_exclude_float_func();
244 break;
245 /*
246 * Similar to CRASHER_EXCLUDE_WRONG_TYPE, CRASHER_EXCLUDE_NOT_ENTRY
247 * tests that entering crasher_not_entry_func partway from a CFI-exempt
248 * context does not cause a CFI-induced crash.
249 */
250 case CRASHER_EXCLUDE_NOT_ENTRY:
251 TLOGI("call crasher_call_exclude_not_entry_func\n");
252 global_chan = chan;
253 crasher_call_exclude_not_entry_func();
254 break;
255 /*
256 * CRASHER_WRONG_TYPE is the first test expected to crash when CFI
257 * is enabled. CFI ensures that control is only transferred to functions
258 * with the expected signature. Since we cast crasher_float_func to an
259 * int_func function pointer when it is actually a float_func, we expect
260 * CFI to crash the app when transferring control to crasher_float_func.
261 * If the app does not crash, then cfi-icall is not enabled.
262 */
263 case CRASHER_WRONG_TYPE:
264 TLOGI("call crasher_float_func\n");
265 ((int_func)crasher_float_func)(0);
266 break;
267 /*
268 * In addition to checking the function signature, cfi-icall ensures that
269 * control is only transferred to valid function entry points.
270 * When CFI is enabled, control should not be transferred to the middle of
271 * crasher_not_entry_func.
272 */
273 case CRASHER_NOT_ENTRY:
274 TLOGI("enter crasher_not_entry_func partway\n");
275 global_chan = chan;
276 global_pc = NULL;
277 crasher_not_entry_func(0);
278 void* crasher_not_entry_func_partway = global_pc;
279 global_pc = NULL;
280 ((int_func)(crasher_not_entry_func_partway))(0);
281 break;
282 default:
283 TLOGE("Bad command: %d\n", msg.cmd);
284 msg.cmd = CRASHER_BAD_CMD;
285 }
286 /*
287 * We echo the incoming command in the case where the crasher app
288 * runs the test without crashing. This is effectively saying "did
289 * not crash when executing command X."
290 */
291 ret = tipc_send1(chan, &msg, sizeof(msg));
292 if (ret < 0 || ret != sizeof(msg)) {
293 TLOGE("Failed to send message (%d)\n", ret);
294 return ret < 0 ? ret : ERR_IO;
295 }
296
297 return 0;
298 }
299
300 static struct tipc_srv_ops crasher_ops = {
301 .on_message = crasher_on_message,
302 };
303
main(void)304 int main(void) {
305 struct tipc_hset* hset = tipc_hset_create();
306 if (IS_ERR(hset)) {
307 TLOGE("Failed to create handle set (%d)\n", PTR_ERR(hset));
308 return PTR_ERR(hset);
309 }
310
311 int rc = tipc_add_service(hset, &crasher_port, 1, 1, &crasher_ops);
312 if (rc < 0) {
313 TLOGE("Failed to add service (%d)\n", rc);
314 return rc;
315 }
316
317 /* if app exits, kernel will log that */
318 return tipc_run_event_loop(hset);
319 }
320