Adding operation pruning test (DoS)
Creating n number of operations from same owner. Expecting creation
of all operations should be successful without any error. Whenever all
operation slots are full older operations are expected to be pruned.
Bug: 194359114
Test: atest keystore2_client_test
Change-Id: I9d231658e77cb10f67cfd248072bde0755c1660d
diff --git a/keystore2/tests/keystore2_client_tests.rs b/keystore2/tests/keystore2_client_tests.rs
index cfb8a41..03dcfec 100644
--- a/keystore2/tests/keystore2_client_tests.rs
+++ b/keystore2/tests/keystore2_client_tests.rs
@@ -321,3 +321,116 @@
.count();
assert!(busy_count > 0);
}
+
+/// This test will verify the use case with the same owner(UID) requesting `n` number of operations.
+/// This test confirms that when all operation slots are full and a new operation is requested,
+/// an operation which is least recently used and lived longest will be pruned to make a room
+/// for a new operation. Pruning strategy should prevent the operations of the other owners(UID)
+/// from being pruned.
+///
+/// 1. Create an operation in a child process with `untrusted_app` context and wait for parent
+/// notification to complete the operation.
+/// 2. Let parent process create `n` number of operations such that there are enough operations
+/// outstanding to trigger cannibalizing their own sibling operations.
+/// 3. Sequentially try to use above created `n` number of operations and also add a new operation,
+/// so that it should trigger cannibalizing one of their own sibling operations.
+/// 3.1 While trying to use these pruned operations an `INVALID_OPERATION_HANDLE` error is
+/// expected as they are already pruned.
+/// 4. Notify the child process to resume and complete the operation. It is expected to complete the
+/// operation successfully.
+/// 5. Try to use the latest operation of parent. It is expected to complete the operation
+/// successfully.
+#[test]
+fn keystore2_ops_prune_test() {
+ const MAX_OPS: usize = 40; // This should be at least 32 with sec_level TEE.
+
+ static TARGET_CTX: &str = "u:r:untrusted_app:s0";
+ const USER_ID: u32 = 99;
+ const APPLICATION_ID: u32 = 10601;
+
+ let uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+ let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+
+ // Create an operation in an untrusted_app context. Wait until the parent notifies to continue.
+ // Once the parent notifies, this operation is expected to be completed successfully.
+ let alias = format!("ks_reg_op_key_{}", getuid());
+ let mut child_handle = execute_op_run_as_child(
+ TARGET_CTX,
+ Domain::APP,
+ -1,
+ Some(alias),
+ Uid::from_raw(uid),
+ Gid::from_raw(gid),
+ ForcedOp(false),
+ );
+
+ // Wait until child process notifies us to continue, so that an operation from child process is
+ // outstanding to complete the operation.
+ child_handle.recv();
+
+ // Generate a key to use in below operations.
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = format!("ks_prune_op_test_key_{}", getuid());
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sec_level,
+ Domain::SELINUX,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ Some(alias),
+ None,
+ None,
+ )
+ .unwrap();
+
+ // Create multiple operations in this process to trigger cannibalizing sibling operations.
+ let mut ops: Vec<binder::Result<CreateOperationResponse>> = (0..MAX_OPS)
+ .into_iter()
+ .map(|_| {
+ sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(Digest::SHA_2_256),
+ false,
+ )
+ })
+ .collect();
+
+ // Sequentially try to use operation handles created above and also add a new operation.
+ for vec_index in 0..MAX_OPS {
+ match &ops[vec_index] {
+ Ok(CreateOperationResponse { iOperation: Some(op), .. }) => {
+ // Older operation handle is pruned, if we try to use that an error is expected.
+ assert_eq!(
+ Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)),
+ key_generations::map_ks_error(op.update(b"my message"))
+ );
+ }
+ _ => panic!("Operation should have created successfully."),
+ }
+
+ // Create a new operation, it should trigger to cannibalize one of their own sibling
+ // operations.
+ ops.push(
+ sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(Digest::SHA_2_256),
+ false,
+ ),
+ );
+ }
+
+ // Notify child process to continue the operation.
+ child_handle.send(&BarrierReached {});
+ assert!((child_handle.get_result() == TestOutcome::Ok), "Failed to perform an operation");
+
+ // Try to use the latest operation created by parent, should be able to use it successfully.
+ match ops.last() {
+ Some(Ok(CreateOperationResponse { iOperation: Some(op), .. })) => {
+ assert_eq!(Ok(()), key_generations::map_ks_error(perform_sample_sign_operation(op)));
+ }
+ _ => panic!("Operation should have created successfully."),
+ }
+}