aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWeston Carvalho <westoncarvalho@google.com>2023-09-28 18:20:03 -0500
committerWeston Carvalho <westoncarvalho@google.com>2024-04-09 13:58:24 -0500
commit170ebeddc8b51c2b54df475ef1b0fd84d92a3095 (patch)
tree682b1b3c1dac45e38f656ce6abd5685e767fc958
parentdf16cc4ff58d60e171a64c320f601236541850e4 (diff)
downloadstorage-170ebeddc8b51c2b54df475ef1b0fd84d92a3095.tar.gz
storage: Add rust test for secure storage aidlHEADmastermain
Translate some of the unittests from `storage-unittest` into Rust tests calling the AIDL service. Tested: ./build-root/build-qemu-generic-arm64-test-debug/run --headless --boot-test "com.android.trusty.rust.storage_unittest_aidl.test" Bug: 300673823 Change-Id: Ib75e3afb3c179418da936c8e529e57b7bcb9efd8
-rw-r--r--PREUPLOAD.cfg2
-rw-r--r--build-config-usertests2
l---------rustfmt.toml1
-rw-r--r--test/storage-unittest-aidl/lib.rs69
-rw-r--r--test/storage-unittest-aidl/manifest.json9
-rw-r--r--test/storage-unittest-aidl/rules.mk39
-rw-r--r--test/storage-unittest-aidl/unittests/helpers.rs154
-rw-r--r--test/storage-unittest-aidl/unittests/mod.rs203
-rw-r--r--usertests-inc.mk6
9 files changed, 484 insertions, 1 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 587b0de..20c50ba 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -2,6 +2,8 @@
clang_format = true
commit_msg_bug_field = true
commit_msg_changeid_field = true
+rustfmt = true
[Builtin Hooks Options]
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+rustfmt = --config-path=rustfmt.toml \ No newline at end of file
diff --git a/build-config-usertests b/build-config-usertests
index 45d99f5..568ad2f 100644
--- a/build-config-usertests
+++ b/build-config-usertests
@@ -13,8 +13,8 @@
# limitations under the License.
# This file lists userspace tests
-
[
+ porttest("com.android.trusty.rust.storage_unittest_aidl.test", timeout=(60 * 30)),
# userspace tests using storage available at early boot
needs(
[
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 120000
index 0000000..3956e6b
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1 @@
+../../base/rustfmt.toml \ No newline at end of file
diff --git a/test/storage-unittest-aidl/lib.rs b/test/storage-unittest-aidl/lib.rs
new file mode 100644
index 0000000..6eb0e47
--- /dev/null
+++ b/test/storage-unittest-aidl/lib.rs
@@ -0,0 +1,69 @@
+#![feature(c_str_literals)]
+
+#[cfg(test)]
+mod unittests;
+
+#[cfg(test)]
+mod tests {
+ use crate::unittests;
+ use android_hardware_security_see_storage::aidl::android::hardware::security::see::storage::{
+ FileAvailability::FileAvailability, FileIntegrity::FileIntegrity,
+ FileProperties::FileProperties, ISecureStorage::ISecureStorage,
+ IStorageSession::IStorageSession,
+ };
+ use binder::{Status, StatusCode, Strong};
+ use core::ffi::CStr;
+ use rpcbinder::RpcSession;
+ use test::assert_ok;
+
+ //This line is needed in order to run the unit tests in Trusty
+ test::init!();
+
+ fn connect() -> Result<Strong<dyn ISecureStorage>, StatusCode> {
+ const STORAGE_AIDL_PORT_NAME: &CStr = c"com.android.hardware.security.see.storage";
+
+ RpcSession::new().setup_trusty_client(STORAGE_AIDL_PORT_NAME)
+ }
+
+ fn start_session(properties: &FileProperties) -> Result<Strong<dyn IStorageSession>, Status> {
+ connect()?.startSession(properties)
+ }
+
+ #[test]
+ fn ping() {
+ use binder::IBinder as _;
+
+ let secure_storage = assert_ok!(connect());
+ assert_ok!(secure_storage.as_binder().ping_binder());
+ }
+
+ mod tp {
+ use super::*;
+ const TP: &'static FileProperties = &FileProperties {
+ integrity: FileIntegrity::TAMPER_PROOF_AT_REST,
+ availability: FileAvailability::AFTER_USERDATA,
+ persistent: false,
+ };
+
+ #[test]
+ fn create_delete() {
+ let ss = assert_ok!(start_session(TP));
+ unittests::create_delete(&*ss);
+ }
+ #[test]
+ fn create_move_delete() {
+ let ss = assert_ok!(start_session(TP));
+ unittests::create_move_delete(&*ss);
+ }
+ #[test]
+ fn file_list() {
+ let ss = assert_ok!(start_session(TP));
+ unittests::file_list(&*ss);
+ }
+ #[test]
+ fn write_read_sequential() {
+ let ss = assert_ok!(start_session(TP));
+ unittests::write_read_sequential(&*ss);
+ }
+ }
+}
diff --git a/test/storage-unittest-aidl/manifest.json b/test/storage-unittest-aidl/manifest.json
new file mode 100644
index 0000000..6a4b307
--- /dev/null
+++ b/test/storage-unittest-aidl/manifest.json
@@ -0,0 +1,9 @@
+{
+ "app_name": "storage_unittest_aidl_lib",
+ "uuid": "c0ee7860-b88c-4903-ae0f-4f3e2c8a5638",
+ "min_heap": 65536,
+ "min_stack": 32768,
+ "mgmt_flags": {
+ "non_critical_app": true
+ }
+} \ No newline at end of file
diff --git a/test/storage-unittest-aidl/rules.mk b/test/storage-unittest-aidl/rules.mk
new file mode 100644
index 0000000..4b245b6
--- /dev/null
+++ b/test/storage-unittest-aidl/rules.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MANIFEST := $(LOCAL_DIR)/manifest.json
+
+MODULE_SRCS += \
+ $(LOCAL_DIR)/lib.rs \
+
+MODULE_CRATE_NAME := storage_unittest_aidl
+
+MODULE_LIBRARY_DEPS += \
+ $(call FIND_CRATE,libc) \
+ $(call FIND_CRATE,log) \
+ frameworks/native/libs/binder/trusty/rust \
+ frameworks/native/libs/binder/trusty/rust/binder_rpc_server \
+ frameworks/native/libs/binder/trusty/rust/rpcbinder \
+ trusty/user/base/interface/secure_storage/rust \
+ trusty/user/base/lib/trusty-std \
+ trusty/user/base/lib/trusty-log \
+ trusty/user/base/lib/tipc/rust \
+
+MODULE_RUST_TESTS := true
+
+include make/library.mk
diff --git a/test/storage-unittest-aidl/unittests/helpers.rs b/test/storage-unittest-aidl/unittests/helpers.rs
new file mode 100644
index 0000000..3eb227b
--- /dev/null
+++ b/test/storage-unittest-aidl/unittests/helpers.rs
@@ -0,0 +1,154 @@
+use android_hardware_security_see_storage::aidl::android::hardware::security::see::storage::{
+ DeleteOptions::DeleteOptions, IFile::IFile, ISecureStorage as SecureStorage,
+ IStorageSession::IStorageSession, ReadIntegrity::ReadIntegrity,
+};
+use binder::ExceptionCode;
+
+pub(crate) enum Exists {
+ Must,
+ MustNot,
+ Unknown,
+}
+
+pub(crate) fn ensure_deleted(
+ ss: &(impl IStorageSession + ?Sized),
+ fname: &str,
+ expectation: Exists,
+) -> Result<(), String> {
+ const DEFAULT_DELETE: &'static DeleteOptions = &DeleteOptions {
+ readIntegrity: ReadIntegrity::NO_TAMPER,
+ allowWritesDuringAbUpdate: false,
+ };
+
+ // Try to delete file
+ let rc = ss.deleteFile(fname, DEFAULT_DELETE);
+ match rc {
+ Ok(()) => {
+ if let Exists::MustNot = expectation {
+ return Err(format!(
+ "deleteFile succeeded, but the file (name: {}) shouldn't have exisited",
+ fname
+ ));
+ }
+ }
+ Err(e)
+ if e.exception_code() == ExceptionCode::SERVICE_SPECIFIC
+ && e.service_specific_error() == SecureStorage::ERR_NOT_FOUND =>
+ {
+ if let Exists::Must = expectation {
+ return Err(format!(
+ "deleteFile failed (for {}) with ERR_NOT_FOUND, but the file should have existed",
+ fname
+ ));
+ }
+ ss.commitChanges().map_err(|e| format!("commitChanges failed with: {}", e))?;
+ return Ok(());
+ }
+ Err(e) => {
+ return Err(format!("deleteFile failed (for {}) with unexpected error: {}", fname, e))
+ }
+ };
+ ss.commitChanges().map_err(|e| format!("commitChanges failed with: {}", e))?;
+
+ // If delete succeeded, try again to make sure it doesn't exist now
+ ensure_deleted(ss, fname, Exists::MustNot)
+ .map_err(|s| format!("while ensuring non-existence, {}", s))
+}
+
+fn check_pattern32(offset: usize, buf: &[u8]) -> Result<(), String> {
+ const U32_SIZE: usize = std::mem::size_of::<u32>();
+ let mut pattern: u32 = (offset / U32_SIZE).try_into().unwrap();
+
+ let mut chunks = buf.chunks_exact(U32_SIZE);
+ for chunk in &mut chunks {
+ let actual = u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
+ if actual != pattern {
+ return Err(format!("Expected to read {}, but found {}", pattern, actual));
+ }
+ pattern += 1;
+ }
+
+ let rem = chunks.remainder();
+ for byte in rem {
+ if *byte != 0 {
+ return Err(format!(
+ "Expected unpatterned portion of read to be zeroed; found {:x?}",
+ rem
+ ));
+ }
+ }
+ Ok(())
+}
+
+fn fill_pattern32(offset: usize, buf: &mut [u8]) {
+ const U32_SIZE: usize = std::mem::size_of::<u32>();
+ let mut pattern: u32 = (offset / U32_SIZE).try_into().unwrap();
+
+ for chunk in buf.chunks_exact_mut(U32_SIZE) {
+ let bytes = pattern.to_ne_bytes();
+ for i in 0..U32_SIZE {
+ chunk[i] = bytes[i];
+ }
+ pattern += 1;
+ }
+}
+
+fn check_valid_size(chunk_len: usize) -> Result<(), String> {
+ if chunk_len % std::mem::size_of::<u32>() != 0 {
+ return Err(format!("Chunk size ({}) not 32-bit aligned.", chunk_len));
+ }
+ Ok(())
+}
+
+pub(crate) fn write_pattern(
+ file: &dyn IFile,
+ offset: usize,
+ chunks: usize,
+ chunk_len: usize,
+) -> Result<(), String> {
+ check_valid_size(chunk_len)?;
+ let mut buf = vec![0; chunk_len];
+
+ for i in 0..chunks {
+ let chunk_offset = offset + i * chunk_len;
+ fill_pattern32(chunk_offset, &mut buf);
+ let written = file
+ .write(chunk_offset.try_into().unwrap(), &buf)
+ .map_err(|e| format!("Encountered error calling write (chunk {}): {}", i, e))?;
+ if written != chunk_len.try_into().unwrap() {
+ return Err(format!(
+ "Wrote {} bytes to chunk {}, but expected to write {}",
+ written, i, chunk_len,
+ ));
+ }
+ }
+
+ Ok(())
+}
+
+pub(crate) fn read_pattern(
+ file: &dyn IFile,
+ offset: usize,
+ chunks: usize,
+ chunk_len: usize,
+) -> Result<(), String> {
+ check_valid_size(chunk_len)?;
+
+ for i in 0..chunks {
+ let chunk_offset = offset + i * chunk_len;
+ let read = file
+ .read(chunk_len.try_into().unwrap(), chunk_offset.try_into().unwrap())
+ .map_err(|e| format!("Encountered error calling read (chunk {}): {}", i, e))?;
+ if read.len() != chunk_len {
+ return Err(format!(
+ "Read {} bytes from chunk {}, but expected to read {}",
+ read.len(),
+ i,
+ chunk_len,
+ ));
+ }
+ check_pattern32(chunk_offset, &*read).map_err(|e| format!("For chunk {}: {}", i, e))?;
+ }
+
+ Ok(())
+}
diff --git a/test/storage-unittest-aidl/unittests/mod.rs b/test/storage-unittest-aidl/unittests/mod.rs
new file mode 100644
index 0000000..23d4627
--- /dev/null
+++ b/test/storage-unittest-aidl/unittests/mod.rs
@@ -0,0 +1,203 @@
+use android_hardware_security_see_storage::aidl::android::hardware::security::see::storage::{
+ CreationMode::CreationMode, FileMode::FileMode, ISecureStorage as SecureStorage,
+ IStorageSession::IStorageSession, OpenOptions::OpenOptions, ReadIntegrity::ReadIntegrity,
+};
+use binder::ExceptionCode;
+use test::{assert_ok, expect, fail};
+
+mod helpers;
+use helpers::{ensure_deleted, Exists};
+
+const CREATE_EXCLUSIVE: &'static OpenOptions = &OpenOptions {
+ createMode: CreationMode::CREATE_EXCLUSIVE,
+ accessMode: FileMode::READ_WRITE,
+ readIntegrity: ReadIntegrity::NO_TAMPER,
+ truncateOnOpen: true,
+ allowWritesDuringAbUpdate: false,
+};
+
+const NO_CREATE: &'static OpenOptions = &OpenOptions {
+ createMode: CreationMode::NO_CREATE,
+ accessMode: FileMode::READ_WRITE,
+ readIntegrity: ReadIntegrity::NO_TAMPER,
+ truncateOnOpen: false,
+ allowWritesDuringAbUpdate: false,
+};
+
+pub(crate) fn create_delete(ss: &(impl IStorageSession + ?Sized)) {
+ let fname = "test_create_delete_file";
+ assert_ok!(ensure_deleted(ss, fname, Exists::Unknown));
+
+ {
+ // Create file
+ let _file = assert_ok!(ss.openFile(fname, CREATE_EXCLUSIVE));
+ assert_ok!(ss.commitChanges());
+
+ // Try to create file again
+ match ss.openFile(fname, CREATE_EXCLUSIVE) {
+ Ok(_) => fail!("openFile (on existing file) unexpectedly succeeded"),
+ Err(e)
+ if e.exception_code() == ExceptionCode::SERVICE_SPECIFIC
+ && e.service_specific_error() == SecureStorage::ERR_ALREADY_EXISTS =>
+ {
+ ()
+ }
+ Err(e) => fail!("openFile (on existing file) failed with: {}", e),
+ };
+ assert_ok!(ss.commitChanges());
+
+ // File closes
+ }
+
+ assert_ok!(ensure_deleted(ss, fname, Exists::Must));
+}
+
+pub(crate) fn create_move_delete(ss: &(impl IStorageSession + ?Sized)) {
+ let fname1 = "test_create_move_delete_1_file";
+ let fname2 = "test_create_move_delete_2_file";
+
+ assert_ok!(ensure_deleted(ss, fname1, Exists::Unknown));
+ assert_ok!(ensure_deleted(ss, fname2, Exists::Unknown));
+
+ {
+ // Create file
+ let file = assert_ok!(ss.openFile(fname1, CREATE_EXCLUSIVE));
+ assert_ok!(ss.commitChanges());
+
+ // Move fname1 -> fname2
+ assert_ok!(file.rename(fname2, CreationMode::CREATE_EXCLUSIVE));
+ assert_ok!(ss.commitChanges());
+
+ // Try to create fname2, with file still alive
+ match ss.openFile(fname2, CREATE_EXCLUSIVE) {
+ Ok(_) => fail!("openFile unexpectedly succeeded"),
+ Err(e)
+ if e.exception_code() == ExceptionCode::SERVICE_SPECIFIC
+ && e.service_specific_error() == SecureStorage::ERR_ALREADY_EXISTS =>
+ {
+ ()
+ }
+ Err(e) => fail!("openFile failed with unexpected error: {}", e),
+ };
+ assert_ok!(ss.commitChanges());
+
+ // file closes
+ }
+
+ // Try to create fname2, now that file is closed
+ match ss.openFile(fname2, CREATE_EXCLUSIVE) {
+ Ok(_) => fail!("openFile unexpectedly succeeded"),
+ Err(e)
+ if e.exception_code() == ExceptionCode::SERVICE_SPECIFIC
+ && e.service_specific_error() == SecureStorage::ERR_ALREADY_EXISTS =>
+ {
+ ()
+ }
+ Err(e) => fail!("openFile failed with unexpected error: {}", e),
+ };
+ assert_ok!(ss.commitChanges());
+
+ {
+ // Recreate fname1
+ let file = assert_ok!(ss.openFile(fname1, CREATE_EXCLUSIVE));
+ assert_ok!(ss.commitChanges());
+
+ // Move doesn't work now that fname2 exists
+ match file.rename(fname2, CreationMode::CREATE_EXCLUSIVE) {
+ Ok(_) => panic!("rename unexpectedly succeeded"),
+ Err(e)
+ if e.exception_code() == ExceptionCode::SERVICE_SPECIFIC
+ && e.service_specific_error() == SecureStorage::ERR_ALREADY_EXISTS =>
+ {
+ ()
+ }
+ Err(e) => panic!("rename failed with unexpected error: {}", e),
+ };
+
+ // file closes
+ }
+
+ assert_ok!(ensure_deleted(ss, fname1, Exists::Must));
+ assert_ok!(ensure_deleted(ss, fname2, Exists::Must));
+}
+
+pub(crate) fn file_list(ss: &(impl IStorageSession + ?Sized)) {
+ use core::array::from_fn;
+ use std::collections::HashSet;
+
+ let fnames: [String; 100] = from_fn(|i| format!("test_file_list_{:02}_file", i));
+ for fname in &fnames {
+ assert_ok!(ensure_deleted(ss, fname, Exists::Unknown), "file: {}", fname);
+ }
+
+ {
+ let dir = assert_ok!(ss.openDir("", ReadIntegrity::NO_TAMPER));
+ let filenames = assert_ok!(dir.readNextFilenames(0));
+ expect!(filenames.is_empty(), "Found unexpected files: {:?}", filenames);
+ }
+
+ // Create, commit, and close file #0
+ {
+ let _file = assert_ok!(ss.openFile(&fnames[0], CREATE_EXCLUSIVE));
+ assert_ok!(ss.commitChanges());
+ }
+ // Create and close (don't commit) other files
+ for fname in &fnames[1..] {
+ let _file = assert_ok!(ss.openFile(fname, CREATE_EXCLUSIVE), "for fname {}", fname);
+ }
+
+ let mut read_file_names = HashSet::new();
+ {
+ let dir = assert_ok!(ss.openDir("", ReadIntegrity::NO_TAMPER));
+ let mut filenames = assert_ok!(dir.readNextFilenames(0));
+ while !filenames.is_empty() {
+ for filename in filenames {
+ let existed = read_file_names.replace(filename);
+ if let Some(previous) = existed {
+ fail!("NextFilenameResult returned file more than once: {}", previous);
+ }
+ }
+ filenames = assert_ok!(dir.readNextFilenames(0));
+ }
+ }
+ assert_ok!(ss.commitChanges());
+
+ // Clean up
+ for fname in &fnames {
+ assert_ok!(ensure_deleted(ss, fname, Exists::Must), "file: {}", fname);
+ }
+
+ let expected = fnames.into_iter().collect::<HashSet<_>>();
+ let missing = expected.difference(&read_file_names).collect::<Vec<_>>();
+ let extra = read_file_names.difference(&expected).collect::<Vec<_>>();
+
+ expect!(missing.is_empty(), "Did not find the following expected files: {:?}", missing);
+ expect!(extra.is_empty(), "Found the following unexpected files: {:?}", extra);
+}
+
+pub(crate) fn write_read_sequential(ss: &(impl IStorageSession + ?Sized)) {
+ use helpers::{read_pattern, write_pattern};
+
+ let fname = "test_write_read_sequential";
+ let chunks = 32;
+ let chunk_len = 2048;
+
+ assert_ok!(ensure_deleted(ss, fname, Exists::Unknown));
+
+ {
+ let file = assert_ok!(ss.openFile(fname, CREATE_EXCLUSIVE));
+ assert_ok!(ss.commitChanges());
+
+ assert_ok!(write_pattern(&*file, 0, chunks, chunk_len));
+ assert_ok!(ss.commitChanges());
+
+ assert_ok!(read_pattern(&*file, 0, chunks, chunk_len));
+ } // file closes
+
+ {
+ let file = assert_ok!(ss.openFile(fname, NO_CREATE));
+ assert_ok!(read_pattern(&*file, 0, chunks, chunk_len));
+ } // file closes
+
+ assert_ok!(ensure_deleted(ss, fname, Exists::Must));
+}
diff --git a/usertests-inc.mk b/usertests-inc.mk
index 4fc661d..95be4c3 100644
--- a/usertests-inc.mk
+++ b/usertests-inc.mk
@@ -16,3 +16,9 @@
TRUSTY_USER_TESTS += \
trusty/user/app/storage/test/storage-unittest \
trusty/user/app/storage/test/storage-benchmark
+
+ifeq (true,$(call TOBOOL,$(STORAGE_AIDL_ENABLED)))
+TRUSTY_RUST_USER_TESTS += \
+ trusty/user/app/storage/test/storage-unittest-aidl \
+
+endif