Add tests covering android.system.keystore2.IKeystoreOperation::abort
API.
1. When attempting to abort a finalized operation, the test should
expect the backend implementation to return an
`ErrorCode::INVALID_OPERATION_HANDLE`
2. If an operation is aborted before completion, the test should expect
the operation to finalize successfully. Additionally, attempting to
reuse the aborted operation handle should result in an
`INVALID_OPERATION_HANDLE` error code.
3. To test concurrency and abort behavior, create multiple threads that
use the same operation handle to perform a large number of update
operations. Subsequently, attempt to abort the operation. The abort
operation should fail with an `OPERATION_BUSY` error response,
indicating that the operation is currently in use by multiple
threads.
Bug: 361167455
Test: atest keystore2_client_tests
Change-Id: Icc2ccddb86bc08f91468746e0bdcb47c43fea635
diff --git a/keystore2/tests/keystore2_client_operation_tests.rs b/keystore2/tests/keystore2_client_operation_tests.rs
index 02cf260..5f640ef 100644
--- a/keystore2/tests/keystore2_client_operation_tests.rs
+++ b/keystore2/tests/keystore2_client_operation_tests.rs
@@ -28,6 +28,10 @@
};
use nix::unistd::{getuid, Gid, Uid};
use rustutils::users::AID_USER_OFFSET;
+use std::sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
+};
use std::thread;
use std::thread::JoinHandle;
@@ -461,3 +465,120 @@
assert!(result1 || result2);
}
+
+/// Create an operation and use it for performing sign operation. After completing the operation
+/// try to abort the operation. Test should fail to abort already finalized operation with error
+/// code `INVALID_OPERATION_HANDLE`.
+#[test]
+fn keystore2_abort_finalized_op_fail_test() {
+ let op_response = create_signing_operation(
+ ForcedOp(false),
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ Domain::APP,
+ -1,
+ Some("ks_op_abort_fail_test_key".to_string()),
+ )
+ .unwrap();
+
+ let op: binder::Strong<dyn IKeystoreOperation> = op_response.iOperation.unwrap();
+ perform_sample_sign_operation(&op).unwrap();
+ let result = key_generations::map_ks_error(op.abort());
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE), result.unwrap_err());
+}
+
+/// Create an operation and use it for performing sign operation. Before finishing the operation
+/// try to abort the operation. Test should successfully abort the operation. After aborting try to
+/// use the operation handle, test should fail to use already aborted operation handle with error
+/// code `INVALID_OPERATION_HANDLE`.
+#[test]
+fn keystore2_op_abort_success_test() {
+ let op_response = create_signing_operation(
+ ForcedOp(false),
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ Domain::APP,
+ -1,
+ Some("ks_op_abort_success_key".to_string()),
+ )
+ .unwrap();
+
+ let op: binder::Strong<dyn IKeystoreOperation> = op_response.iOperation.unwrap();
+ op.update(b"my message").unwrap();
+ let result = key_generations::map_ks_error(op.abort());
+ assert!(result.is_ok());
+
+ // Try to use the op handle after abort.
+ let result = key_generations::map_ks_error(op.finish(None, None));
+ assert!(result.is_err());
+ assert_eq!(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE), result.unwrap_err());
+}
+
+/// Executes an operation in a thread. Performs an `update` operation repeatedly till the user
+/// interrupts it or encounters any error other than `OPERATION_BUSY`.
+/// Return `false` in case of any error other than `OPERATION_BUSY`, otherwise it returns true.
+fn perform_abort_op_busy_in_thread(
+ op: binder::Strong<dyn IKeystoreOperation>,
+ should_exit_clone: Arc<AtomicBool>,
+) -> JoinHandle<bool> {
+ thread::spawn(move || {
+ loop {
+ if should_exit_clone.load(Ordering::Relaxed) {
+ // Caller requested to exit the thread.
+ return true;
+ }
+
+ match key_generations::map_ks_error(op.update(b"my message")) {
+ Ok(_) => continue,
+ Err(Error::Rc(ResponseCode::OPERATION_BUSY)) => continue,
+ Err(_) => return false,
+ }
+ }
+ })
+}
+
+/// Create an operation and try to use same operation handle in multiple threads to perform
+/// operations. Test tries to abort the operation and expects `abort` call to fail with the error
+/// response `OPERATION_BUSY` as multiple threads try to access the same operation handle
+/// simultaneously. Test tries to simulate `OPERATION_BUSY` error response from `abort` api.
+#[test]
+fn keystore2_op_abort_fails_with_operation_busy_error_test() {
+ loop {
+ let op_response = create_signing_operation(
+ ForcedOp(false),
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ Domain::APP,
+ -1,
+ Some("op_abort_busy_alias_test_key".to_string()),
+ )
+ .unwrap();
+ let op: binder::Strong<dyn IKeystoreOperation> = op_response.iOperation.unwrap();
+
+ let should_exit = Arc::new(AtomicBool::new(false));
+
+ let update_t_handle1 = perform_abort_op_busy_in_thread(op.clone(), should_exit.clone());
+ let update_t_handle2 = perform_abort_op_busy_in_thread(op.clone(), should_exit.clone());
+
+ // Attempt to abort the operation and anticipate an 'OPERATION_BUSY' error, as multiple
+ // threads are concurrently accessing the same operation handle.
+ let result = match op.abort() {
+ Ok(_) => 0, // Operation successfully aborted.
+ Err(e) => e.service_specific_error(),
+ };
+
+ // Notify threads to stop performing `update` operation.
+ should_exit.store(true, Ordering::Relaxed);
+
+ let _update_op_result = update_t_handle1.join().unwrap();
+ let _update_op_result2 = update_t_handle2.join().unwrap();
+
+ if result == ResponseCode::OPERATION_BUSY.0 {
+ // The abort call failed with an OPERATION_BUSY error, as anticipated, due to multiple
+ // threads competing for access to the same operation handle.
+ return;
+ }
+ assert_eq!(result, 0);
+ }
+}