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