1 // Copyright 2022, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 use nix::unistd::{getuid, Gid, Uid};
16 use rustutils::users::AID_USER_OFFSET;
17 use std::thread;
18 use std::thread::JoinHandle;
19
20 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
21 Digest::Digest, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
22 };
23 use android_system_keystore2::aidl::android::system::keystore2::{
24 CreateOperationResponse::CreateOperationResponse, Domain::Domain,
25 IKeystoreOperation::IKeystoreOperation, ResponseCode::ResponseCode,
26 };
27
28 use keystore2_test_utils::{
29 authorizations, get_keystore_service, key_generations, key_generations::Error, run_as,
30 };
31
32 use crate::keystore2_client_test_utils::{
33 create_signing_operation, execute_op_run_as_child, perform_sample_sign_operation,
34 BarrierReached, ForcedOp, TestOutcome,
35 };
36
37 /// Create `max_ops` number child processes with the given context and perform an operation under each
38 /// child process.
39 ///
40 /// # Safety
41 ///
42 /// Must be called from a process with no other threads.
create_operations( target_ctx: &'static str, forced_op: ForcedOp, max_ops: i32, ) -> Vec<run_as::ChildHandle<TestOutcome, BarrierReached>>43 pub unsafe fn create_operations(
44 target_ctx: &'static str,
45 forced_op: ForcedOp,
46 max_ops: i32,
47 ) -> Vec<run_as::ChildHandle<TestOutcome, BarrierReached>> {
48 let alias = format!("ks_op_test_key_{}", getuid());
49 let base_gid = 99 * AID_USER_OFFSET + 10001;
50 let base_uid = 99 * AID_USER_OFFSET + 10001;
51 (0..max_ops)
52 // SAFETY: The caller guarantees that there are no other threads.
53 .map(|i| unsafe {
54 execute_op_run_as_child(
55 target_ctx,
56 Domain::APP,
57 key_generations::SELINUX_SHELL_NAMESPACE,
58 Some(alias.to_string()),
59 Uid::from_raw(base_uid + (i as u32)),
60 Gid::from_raw(base_gid + (i as u32)),
61 forced_op,
62 )
63 })
64 .collect()
65 }
66
67 /// Executes an operation in a thread. Expect an `OPERATION_BUSY` error in case of operation
68 /// failure. Returns True if `OPERATION_BUSY` error is encountered otherwise returns false.
perform_op_busy_in_thread(op: binder::Strong<dyn IKeystoreOperation>) -> JoinHandle<bool>69 fn perform_op_busy_in_thread(op: binder::Strong<dyn IKeystoreOperation>) -> JoinHandle<bool> {
70 thread::spawn(move || {
71 for _n in 1..1000 {
72 match key_generations::map_ks_error(op.update(b"my message")) {
73 Ok(_) => continue,
74 Err(e) => {
75 assert_eq!(Error::Rc(ResponseCode::OPERATION_BUSY), e);
76 return true;
77 }
78 }
79 }
80 let sig = op.finish(None, None).unwrap();
81 assert!(sig.is_some());
82 false
83 })
84 }
85
86 /// This test verifies that backend service throws BACKEND_BUSY error when all
87 /// operations slots are full. This test creates operations in child processes and
88 /// collects the status of operations performed in each child proc and determines
89 /// whether any child proc exited with error status.
90 #[test]
keystore2_backend_busy_test()91 fn keystore2_backend_busy_test() {
92 const MAX_OPS: i32 = 100;
93 static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
94
95 // SAFETY: The test is run in a separate process with no other threads.
96 let mut child_handles = unsafe { create_operations(TARGET_CTX, ForcedOp(false), MAX_OPS) };
97
98 // Wait until all child procs notifies us to continue,
99 // so that there are definitely enough operations outstanding to trigger a BACKEND_BUSY.
100 for ch in child_handles.iter_mut() {
101 ch.recv();
102 }
103 // Notify each child to resume and finish.
104 for ch in child_handles.iter_mut() {
105 ch.send(&BarrierReached {});
106 }
107
108 // Collect the result and validate whether backend busy has occurred.
109 let mut busy_count = 0;
110 for ch in child_handles.into_iter() {
111 if ch.get_result() == TestOutcome::BackendBusy {
112 busy_count += 1;
113 }
114 }
115 assert!(busy_count > 0)
116 }
117
118 /// This test confirms that forced operation is having high pruning power.
119 /// 1. Initially create regular operations such that there are enough operations outstanding
120 /// to trigger BACKEND_BUSY.
121 /// 2. Then, create a forced operation. System should be able to prune one of the regular
122 /// operations and create a slot for forced operation successfully.
123 #[test]
keystore2_forced_op_after_backendbusy_test()124 fn keystore2_forced_op_after_backendbusy_test() {
125 const MAX_OPS: i32 = 100;
126 static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
127
128 // Create regular operations.
129 // SAFETY: The test is run in a separate process with no other threads.
130 let mut child_handles = unsafe { create_operations(TARGET_CTX, ForcedOp(false), MAX_OPS) };
131
132 // Wait until all child procs notifies us to continue, so that there are enough
133 // operations outstanding to trigger a BACKEND_BUSY.
134 for ch in child_handles.iter_mut() {
135 ch.recv();
136 }
137
138 // Create a forced operation.
139 let auid = 99 * AID_USER_OFFSET + 10604;
140 let agid = 99 * AID_USER_OFFSET + 10604;
141 // SAFETY: The test is run in a separate process with no other threads.
142 unsafe {
143 run_as::run_as(
144 key_generations::TARGET_VOLD_CTX,
145 Uid::from_raw(auid),
146 Gid::from_raw(agid),
147 move || {
148 let alias = format!("ks_prune_forced_op_key_{}", getuid());
149
150 // To make room for this forced op, system should be able to prune one of the
151 // above created regular operations and create a slot for this forced operation
152 // successfully.
153 create_signing_operation(
154 ForcedOp(true),
155 KeyPurpose::SIGN,
156 Digest::SHA_2_256,
157 Domain::SELINUX,
158 100,
159 Some(alias),
160 )
161 .expect("Client failed to create forced operation after BACKEND_BUSY state.");
162 },
163 );
164 };
165
166 // Notify each child to resume and finish.
167 for ch in child_handles.iter_mut() {
168 ch.send(&BarrierReached {});
169 }
170
171 // Collect the results of above created regular operations.
172 let mut pruned_count = 0;
173 let mut busy_count = 0;
174 let mut _other_err = 0;
175 for ch in child_handles.into_iter() {
176 match ch.get_result() {
177 TestOutcome::BackendBusy => {
178 busy_count += 1;
179 }
180 TestOutcome::InvalidHandle => {
181 pruned_count += 1;
182 }
183 _ => {
184 _other_err += 1;
185 }
186 }
187 }
188 // Verify that there should be at least one backend busy has occurred while creating
189 // above regular operations.
190 assert!(busy_count > 0);
191
192 // Verify that there should be at least one pruned operation which should have failed while
193 // performing operation.
194 assert!(pruned_count > 0);
195 }
196
197 /// This test confirms that forced operations can't be pruned.
198 /// 1. Creates an initial forced operation and tries to complete the operation after BACKEND_BUSY
199 /// error is triggered.
200 /// 2. Create MAX_OPS number of forced operations so that definitely enough number of operations
201 /// outstanding to trigger a BACKEND_BUSY.
202 /// 3. Try to use initially created forced operation (in step #1) and able to perform the
203 /// operation successfully. This confirms that none of the later forced operations evicted the
204 /// initial forced operation.
205 #[test]
keystore2_max_forced_ops_test()206 fn keystore2_max_forced_ops_test() {
207 const MAX_OPS: i32 = 100;
208 let auid = 99 * AID_USER_OFFSET + 10205;
209 let agid = 99 * AID_USER_OFFSET + 10205;
210
211 // Create initial forced operation in a child process
212 // and wait for the parent to notify to perform operation.
213 let alias = format!("ks_forced_op_key_{}", getuid());
214 // SAFETY: The test is run in a separate process with no other threads.
215 let mut first_op_handle = unsafe {
216 execute_op_run_as_child(
217 key_generations::TARGET_SU_CTX,
218 Domain::SELINUX,
219 key_generations::SELINUX_SHELL_NAMESPACE,
220 Some(alias),
221 Uid::from_raw(auid),
222 Gid::from_raw(agid),
223 ForcedOp(true),
224 )
225 };
226
227 // Wait until above child proc notifies us to continue, so that there is definitely a forced
228 // operation outstanding to perform a operation.
229 first_op_handle.recv();
230
231 // Create MAX_OPS number of forced operations.
232 let mut child_handles =
233 // SAFETY: The test is run in a separate process with no other threads.
234 unsafe { create_operations(key_generations::TARGET_SU_CTX, ForcedOp(true), MAX_OPS) };
235
236 // Wait until all child procs notifies us to continue, so that there are enough operations
237 // outstanding to trigger a BACKEND_BUSY.
238 for ch in child_handles.iter_mut() {
239 ch.recv();
240 }
241
242 // Notify initial created forced operation to continue performing the operations.
243 first_op_handle.send(&BarrierReached {});
244
245 // Collect initially created forced operation result and is expected to complete operation
246 // successfully.
247 let first_op_result = first_op_handle.get_result();
248 assert_eq!(first_op_result, TestOutcome::Ok);
249
250 // Notify each child to resume and finish.
251 for ch in child_handles.iter_mut() {
252 ch.send(&BarrierReached {});
253 }
254
255 // Collect the result and validate whether backend busy has occurred with MAX_OPS number
256 // of forced operations.
257 let busy_count = child_handles
258 .into_iter()
259 .map(|ch| ch.get_result())
260 .filter(|r| *r == TestOutcome::BackendBusy)
261 .count();
262 assert!(busy_count > 0);
263 }
264
265 /// This test will verify the use case with the same owner(UID) requesting `n` number of operations.
266 /// This test confirms that when all operation slots are full and a new operation is requested,
267 /// an operation which is least recently used and lived longest will be pruned to make a room
268 /// for a new operation. Pruning strategy should prevent the operations of the other owners(UID)
269 /// from being pruned.
270 ///
271 /// 1. Create an operation in a child process with `untrusted_app` context and wait for parent
272 /// notification to complete the operation.
273 /// 2. Let parent process create `n` number of operations such that there are enough operations
274 /// outstanding to trigger cannibalizing their own sibling operations.
275 /// 3. Sequentially try to use above created `n` number of operations and also add a new operation,
276 /// so that it should trigger cannibalizing one of their own sibling operations.
277 /// 3.1 While trying to use these pruned operations an `INVALID_OPERATION_HANDLE` error is
278 /// expected as they are already pruned.
279 /// 4. Notify the child process to resume and complete the operation. It is expected to complete the
280 /// operation successfully.
281 /// 5. Try to use the latest operation of parent. It is expected to complete the operation
282 /// successfully.
283 #[test]
keystore2_ops_prune_test()284 fn keystore2_ops_prune_test() {
285 const MAX_OPS: usize = 40; // This should be at least 32 with sec_level TEE.
286
287 static TARGET_CTX: &str = "u:r:untrusted_app:s0";
288 const USER_ID: u32 = 99;
289 const APPLICATION_ID: u32 = 10601;
290
291 let uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
292 let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
293
294 // Create an operation in an untrusted_app context. Wait until the parent notifies to continue.
295 // Once the parent notifies, this operation is expected to be completed successfully.
296 let alias = format!("ks_reg_op_key_{}", getuid());
297 // SAFETY: The test is run in a separate process with no other threads.
298 let mut child_handle = unsafe {
299 execute_op_run_as_child(
300 TARGET_CTX,
301 Domain::APP,
302 -1,
303 Some(alias),
304 Uid::from_raw(uid),
305 Gid::from_raw(gid),
306 ForcedOp(false),
307 )
308 };
309
310 // Wait until child process notifies us to continue, so that an operation from child process is
311 // outstanding to complete the operation.
312 child_handle.recv();
313
314 // Generate a key to use in below operations.
315 let keystore2 = get_keystore_service();
316 let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
317 let alias = format!("ks_prune_op_test_key_{}", getuid());
318 let key_metadata = key_generations::generate_ec_p256_signing_key(
319 &sec_level,
320 Domain::SELINUX,
321 key_generations::SELINUX_SHELL_NAMESPACE,
322 Some(alias),
323 None,
324 )
325 .unwrap();
326
327 // Create multiple operations in this process to trigger cannibalizing sibling operations.
328 let mut ops: Vec<binder::Result<CreateOperationResponse>> = (0..MAX_OPS)
329 .map(|_| {
330 sec_level.createOperation(
331 &key_metadata.key,
332 &authorizations::AuthSetBuilder::new()
333 .purpose(KeyPurpose::SIGN)
334 .digest(Digest::SHA_2_256),
335 false,
336 )
337 })
338 .collect();
339
340 // Sequentially try to use operation handles created above and also add a new operation.
341 for vec_index in 0..MAX_OPS {
342 match &ops[vec_index] {
343 Ok(CreateOperationResponse { iOperation: Some(op), .. }) => {
344 // Older operation handle is pruned, if we try to use that an error is expected.
345 assert_eq!(
346 Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)),
347 key_generations::map_ks_error(op.update(b"my message"))
348 );
349 }
350 _ => panic!("Operation should have created successfully."),
351 }
352
353 // Create a new operation, it should trigger to cannibalize one of their own sibling
354 // operations.
355 ops.push(
356 sec_level.createOperation(
357 &key_metadata.key,
358 &authorizations::AuthSetBuilder::new()
359 .purpose(KeyPurpose::SIGN)
360 .digest(Digest::SHA_2_256),
361 false,
362 ),
363 );
364 }
365
366 // Notify child process to continue the operation.
367 child_handle.send(&BarrierReached {});
368 assert!((child_handle.get_result() == TestOutcome::Ok), "Failed to perform an operation");
369
370 // Try to use the latest operation created by parent, should be able to use it successfully.
371 match ops.last() {
372 Some(Ok(CreateOperationResponse { iOperation: Some(op), .. })) => {
373 assert_eq!(Ok(()), key_generations::map_ks_error(perform_sample_sign_operation(op)));
374 }
375 _ => panic!("Operation should have created successfully."),
376 }
377 }
378
379 /// Try to create forced operations with various contexts -
380 /// - untrusted_app
381 /// - system_server
382 /// - priv_app
383 /// `PERMISSION_DENIED` error response is expected.
384 #[test]
keystore2_forced_op_perm_denied_test()385 fn keystore2_forced_op_perm_denied_test() {
386 static TARGET_CTXS: &[&str] =
387 &["u:r:untrusted_app:s0", "u:r:system_server:s0", "u:r:priv_app:s0"];
388 const USER_ID: u32 = 99;
389 const APPLICATION_ID: u32 = 10601;
390
391 let uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
392 let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
393
394 for context in TARGET_CTXS.iter() {
395 // SAFETY: The test is run in a separate process with no other threads.
396 unsafe {
397 run_as::run_as(context, Uid::from_raw(uid), Gid::from_raw(gid), move || {
398 let alias = format!("ks_app_forced_op_test_key_{}", getuid());
399 let result = key_generations::map_ks_error(create_signing_operation(
400 ForcedOp(true),
401 KeyPurpose::SIGN,
402 Digest::SHA_2_256,
403 Domain::APP,
404 -1,
405 Some(alias),
406 ));
407 assert!(result.is_err());
408 assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
409 });
410 }
411 }
412 }
413
414 /// Try to create a forced operation with `vold` context.
415 /// Should be able to create forced operation with `vold` context successfully.
416 #[test]
keystore2_forced_op_success_test()417 fn keystore2_forced_op_success_test() {
418 static TARGET_CTX: &str = "u:r:vold:s0";
419 const USER_ID: u32 = 99;
420 const APPLICATION_ID: u32 = 10601;
421
422 let uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
423 let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
424
425 // SAFETY: The test is run in a separate process with no other threads.
426 unsafe {
427 run_as::run_as(TARGET_CTX, Uid::from_raw(uid), Gid::from_raw(gid), move || {
428 let alias = format!("ks_vold_forced_op_key_{}", getuid());
429 create_signing_operation(
430 ForcedOp(true),
431 KeyPurpose::SIGN,
432 Digest::SHA_2_256,
433 Domain::SELINUX,
434 key_generations::SELINUX_VOLD_NAMESPACE,
435 Some(alias),
436 )
437 .expect("Client with vold context failed to create forced operation.");
438 });
439 }
440 }
441
442 /// Create an operation and try to use this operation handle in multiple threads to perform
443 /// operations. Test should fail to perform an operation with an error response `OPERATION_BUSY`
444 /// when multiple threads try to access the operation handle at same time.
445 #[test]
keystore2_op_fails_operation_busy()446 fn keystore2_op_fails_operation_busy() {
447 let op_response = create_signing_operation(
448 ForcedOp(false),
449 KeyPurpose::SIGN,
450 Digest::SHA_2_256,
451 Domain::APP,
452 -1,
453 Some("op_busy_alias_test_key".to_string()),
454 )
455 .unwrap();
456
457 let op: binder::Strong<dyn IKeystoreOperation> = op_response.iOperation.unwrap();
458
459 let th_handle_1 = perform_op_busy_in_thread(op.clone());
460 let th_handle_2 = perform_op_busy_in_thread(op);
461
462 let result1 = th_handle_1.join().unwrap();
463 let result2 = th_handle_2.join().unwrap();
464
465 assert!(result1 || result2);
466 }
467