• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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