From f8caca59896d71e282597abb87cdc3643ab905f3 Mon Sep 17 00:00:00 2001 From: Karuna Wadhera Date: Fri, 1 May 2026 19:13:58 +0000 Subject: [PATCH 1/3] secure_env: Extract common TA loop code Move the main event loop for TAs into a common library libsecure_env_common to avoid duplication. This refactoring is done in preparation for introducing the Weaver HAL. Bug: b/432285494 Test: bazel test //... (in base/cvd) --- .../host/commands/secure_env/rust/common.rs | 142 ++++++++++++++++++ .../host/commands/secure_env/rust/lib.rs | 123 +-------------- 2 files changed, 150 insertions(+), 115 deletions(-) create mode 100644 base/cvd/cuttlefish/host/commands/secure_env/rust/common.rs diff --git a/base/cvd/cuttlefish/host/commands/secure_env/rust/common.rs b/base/cvd/cuttlefish/host/commands/secure_env/rust/common.rs new file mode 100644 index 00000000000..674508f3a71 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/secure_env/rust/common.rs @@ -0,0 +1,142 @@ +// Copyright (C) 2026 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. + +//! Common utilities for Cuttlefish host secure_env Rust TAs. + +use log::{error, trace}; +use std::fs::File; +use std::io::{Read, Write}; +use std::os::fd::AsFd; +use std::os::unix::net::UnixStream; + +// See `SnapshotSocketMessage` in suspend_resume_handler.h for docs. +const SNAPSHOT_SOCKET_MESSAGE_SUSPEND: u8 = 1; +const SNAPSHOT_SOCKET_MESSAGE_SUSPEND_ACK: u8 = 2; +const SNAPSHOT_SOCKET_MESSAGE_RESUME: u8 = 3; + +/// Runs the main loop for a Cuttlefish TA, handling requests and snapshot signals. +pub fn run_ta_loop( + mut infile: File, + mut outfile: File, + mut snapshot_socket: UnixStream, + max_size: usize, + mut process: F, +) where + F: FnMut(&[u8]) -> Vec, +{ + let mut buf = vec![0u8; max_size]; + loop { + // Wait for data from either `infile` or `snapshot_socket`. If both have data, we prioritize + // processing only `infile` until it is empty so that there is no pending state when we + // suspend the loop. + let mut fd_set = nix::sys::select::FdSet::new(); + fd_set.insert(infile.as_fd()); + fd_set.insert(snapshot_socket.as_fd()); + if let Err(e) = nix::sys::select::select( + None, + /*readfds=*/ Some(&mut fd_set), + None, + None, + /*timeout=*/ None, + ) { + error!("FATAL: Failed to select on input FDs: {:?}", e); + return; + } + + if fd_set.contains(infile.as_fd()) { + // Read a request message from the pipe, as a 4-byte BE length followed by the message. + let mut req_len_data = [0u8; 4]; + if let Err(e) = infile.read_exact(&mut req_len_data) { + error!("FATAL: Failed to read request length from connection: {:?}", e); + return; + } + let req_len = u32::from_be_bytes(req_len_data) as usize; + if req_len > max_size { + error!("FATAL: Request too long ({})", req_len); + return; + } + let req_data = &mut buf[..req_len]; + if let Err(e) = infile.read_exact(req_data) { + error!( + "FATAL: Failed to read request data of length {} from connection: {:?}", + req_len, e + ); + return; + } + + // Pass to the TA to process. + trace!("-> TA: received data: (len={})", req_data.len()); + let rsp = process(req_data); + trace!("<- TA: send data: (len={})", rsp.len()); + + // Send the response message down the pipe, as a 4-byte BE length followed by the message. + let rsp_len: u32 = match rsp.len().try_into() { + Ok(l) => l, + Err(_e) => { + error!("FATAL: Response too long (len={})", rsp.len()); + return; + } + }; + let rsp_len_data = rsp_len.to_be_bytes(); + if let Err(e) = outfile.write_all(&rsp_len_data[..]) { + error!("FATAL: Failed to write response length to connection: {:?}", e); + return; + } + if let Err(e) = outfile.write_all(&rsp) { + error!( + "FATAL: Failed to write response data of length {} to connection: {:?}", + rsp_len, e + ); + return; + } + let _ = outfile.flush(); + + continue; + } + + if fd_set.contains(snapshot_socket.as_fd()) { + // Read suspend request. + let mut suspend_request = 0u8; + if let Err(e) = snapshot_socket.read_exact(std::slice::from_mut(&mut suspend_request)) { + error!("FATAL: Failed to read suspend request: {:?}", e); + return; + } + if suspend_request != SNAPSHOT_SOCKET_MESSAGE_SUSPEND { + error!( + "FATAL: Unexpected value from snapshot socket: got {}, expected {}", + suspend_request, SNAPSHOT_SOCKET_MESSAGE_SUSPEND + ); + return; + } + // Write ACK. + if let Err(e) = snapshot_socket.write_all(&[SNAPSHOT_SOCKET_MESSAGE_SUSPEND_ACK]) { + error!("FATAL: Failed to write suspend ACK request: {:?}", e); + return; + } + // Block until we get a resume request. + let mut resume_request = 0u8; + if let Err(e) = snapshot_socket.read_exact(std::slice::from_mut(&mut resume_request)) { + error!("FATAL: Failed to read resume request: {:?}", e); + return; + } + if resume_request != SNAPSHOT_SOCKET_MESSAGE_RESUME { + error!( + "FATAL: Unexpected value from snapshot socket: got {}, expected {}", + resume_request, SNAPSHOT_SOCKET_MESSAGE_RESUME + ); + return; + } + } + } +} diff --git a/base/cvd/cuttlefish/host/commands/secure_env/rust/lib.rs b/base/cvd/cuttlefish/host/commands/secure_env/rust/lib.rs index ce5971db2a5..b204d9e3bb3 100644 --- a/base/cvd/cuttlefish/host/commands/secure_env/rust/lib.rs +++ b/base/cvd/cuttlefish/host/commands/secure_env/rust/lib.rs @@ -29,10 +29,9 @@ use kmr_ta::{HardwareInfo, KeyMintTa, RpcInfo, RpcInfoV3}; use kmr_ta_nonsecure::{rpc, soft}; use kmr_wire::keymint::SecurityLevel; use kmr_wire::rpc::MINIMUM_SUPPORTED_KEYS_IN_CSR; -use log::{error, info, trace}; +use log::{error, info}; +use secure_env_common::run_ta_loop; use std::ffi::CString; -use std::io::{Read, Write}; -use std::os::fd::AsFd; use std::os::fd::AsRawFd; use std::os::unix::ffi::OsStrExt; @@ -43,22 +42,17 @@ mod tpm; #[cfg(test)] mod tests; -// See `SnapshotSocketMessage` in suspend_resume_handler.h for docs. -const SNAPSHOT_SOCKET_MESSAGE_SUSPEND: u8 = 1; -const SNAPSHOT_SOCKET_MESSAGE_SUSPEND_ACK: u8 = 2; -const SNAPSHOT_SOCKET_MESSAGE_RESUME: u8 = 3; - /// Main routine for the KeyMint TA. Only returns if there is a fatal error. /// /// # Safety /// /// TODO: What are the preconditions for `trm`? pub unsafe fn ta_main( - mut infile: std::fs::File, - mut outfile: std::fs::File, + infile: std::fs::File, + outfile: std::fs::File, security_level: SecurityLevel, trm: *mut libc::c_void, - mut snapshot_socket: std::os::unix::net::UnixStream, + snapshot_socket: std::os::unix::net::UnixStream, ) { log::set_logger(&AndroidCppLogger).unwrap(); log::set_max_level(log::LevelFilter::Debug); // Filtering happens elsewhere @@ -152,110 +146,9 @@ pub unsafe fn ta_main( }; let mut ta = KeyMintTa::new(hw_info, RpcInfo::V3(rpc_info_v3), imp, dev); - let mut buf = [0; kmr_wire::DEFAULT_MAX_SIZE]; - loop { - // Wait for data from either `infile` or `snapshot_socket`. If both have data, we prioritize - // processing only `infile` until it is empty so that there is no pending state when we - // suspend the loop. - let mut fd_set = nix::sys::select::FdSet::new(); - fd_set.insert(infile.as_fd()); - fd_set.insert(snapshot_socket.as_fd()); - if let Err(e) = nix::sys::select::select( - None, - /*readfds=*/ Some(&mut fd_set), - None, - None, - /*timeout=*/ None, - ) { - error!("FATAL: Failed to select on input FDs: {:?}", e); - return; - } - - if fd_set.contains(infile.as_fd()) { - // Read a request message from the pipe, as a 4-byte BE length followed by the message. - let mut req_len_data = [0u8; 4]; - if let Err(e) = infile.read_exact(&mut req_len_data) { - error!("FATAL: Failed to read request length from connection: {:?}", e); - return; - } - let req_len = u32::from_be_bytes(req_len_data) as usize; - if req_len > kmr_wire::DEFAULT_MAX_SIZE { - error!("FATAL: Request too long ({})", req_len); - return; - } - let req_data = &mut buf[..req_len]; - if let Err(e) = infile.read_exact(req_data) { - error!( - "FATAL: Failed to read request data of length {} from connection: {:?}", - req_len, e - ); - return; - } - - // Pass to the TA to process. - trace!("-> TA: received data: (len={})", req_data.len()); - let rsp = ta.process(req_data); - trace!("<- TA: send data: (len={})", rsp.len()); - - // Send the response message down the pipe, as a 4-byte BE length followed by the message. - let rsp_len: u32 = match rsp.len().try_into() { - Ok(l) => l, - Err(_e) => { - error!("FATAL: Response too long (len={})", rsp.len()); - return; - } - }; - let rsp_len_data = rsp_len.to_be_bytes(); - if let Err(e) = outfile.write_all(&rsp_len_data[..]) { - error!("FATAL: Failed to write response length to connection: {:?}", e); - return; - } - if let Err(e) = outfile.write_all(&rsp) { - error!( - "FATAL: Failed to write response data of length {} to connection: {:?}", - rsp_len, e - ); - return; - } - let _ = outfile.flush(); - - continue; - } - - if fd_set.contains(snapshot_socket.as_fd()) { - // Read suspend request. - let mut suspend_request = 0u8; - if let Err(e) = snapshot_socket.read_exact(std::slice::from_mut(&mut suspend_request)) { - error!("FATAL: Failed to read suspend request: {:?}", e); - return; - } - if suspend_request != SNAPSHOT_SOCKET_MESSAGE_SUSPEND { - error!( - "FATAL: Unexpected value from snapshot socket: got {}, expected {}", - suspend_request, SNAPSHOT_SOCKET_MESSAGE_SUSPEND - ); - return; - } - // Write ACK. - if let Err(e) = snapshot_socket.write_all(&[SNAPSHOT_SOCKET_MESSAGE_SUSPEND_ACK]) { - error!("FATAL: Failed to write suspend ACK request: {:?}", e); - return; - } - // Block until we get a resume request. - let mut resume_request = 0u8; - if let Err(e) = snapshot_socket.read_exact(std::slice::from_mut(&mut resume_request)) { - error!("FATAL: Failed to read resume request: {:?}", e); - return; - } - if resume_request != SNAPSHOT_SOCKET_MESSAGE_RESUME { - error!( - "FATAL: Unexpected value from snapshot socket: got {}, expected {}", - resume_request, SNAPSHOT_SOCKET_MESSAGE_RESUME - ); - return; - } - } - } + run_ta_loop(infile, outfile, snapshot_socket, kmr_wire::DEFAULT_MAX_SIZE, |req| { + ta.process(req) + }); } // TODO(schuffelen): Use android_logger when rust works with host glibc, see aosp/1415969 From c946d76ea8a794f70af59b2f1fc9d244014c49a6 Mon Sep 17 00:00:00 2001 From: Karuna Wadhera Date: Mon, 29 Jun 2026 16:50:56 +0000 Subject: [PATCH 2/3] secure_env: Move logger to common crate and rename FFI files Move the C++ log forwarder to the common crate to enable its use by other TAs. Also rename 'tpm_ffi' files to 'secure_env_ffi' to reflect they are no longer strictly TPM-specific. This refactoring is done in preparation for introducing the Weaver HAL. Bug: 432285494 Test: bazel test //... (in base/cvd) --- .../host/commands/secure_env/rust/common.rs | 2 + .../host/commands/secure_env/rust/lib.rs | 47 +------------- .../host/commands/secure_env/rust/logger.rs | 61 +++++++++++++++++++ .../host/commands/secure_env/rust/tpm.rs | 2 +- .../{tpm_ffi.cpp => secure_env_ffi.cpp} | 2 +- .../{tpm_ffi.h => secure_env_ffi.h} | 0 6 files changed, 66 insertions(+), 48 deletions(-) create mode 100644 base/cvd/cuttlefish/host/commands/secure_env/rust/logger.rs rename base/cvd/cuttlefish/host/commands/secure_env/{tpm_ffi.cpp => secure_env_ffi.cpp} (98%) rename base/cvd/cuttlefish/host/commands/secure_env/{tpm_ffi.h => secure_env_ffi.h} (100%) diff --git a/base/cvd/cuttlefish/host/commands/secure_env/rust/common.rs b/base/cvd/cuttlefish/host/commands/secure_env/rust/common.rs index 674508f3a71..94530d64dd5 100644 --- a/base/cvd/cuttlefish/host/commands/secure_env/rust/common.rs +++ b/base/cvd/cuttlefish/host/commands/secure_env/rust/common.rs @@ -20,6 +20,8 @@ use std::io::{Read, Write}; use std::os::fd::AsFd; use std::os::unix::net::UnixStream; +/// Logger module that forwards logs to the Android C++ backend. +pub mod logger; // See `SnapshotSocketMessage` in suspend_resume_handler.h for docs. const SNAPSHOT_SOCKET_MESSAGE_SUSPEND: u8 = 1; const SNAPSHOT_SOCKET_MESSAGE_SUSPEND_ACK: u8 = 2; diff --git a/base/cvd/cuttlefish/host/commands/secure_env/rust/lib.rs b/base/cvd/cuttlefish/host/commands/secure_env/rust/lib.rs index b204d9e3bb3..a02caee62f0 100644 --- a/base/cvd/cuttlefish/host/commands/secure_env/rust/lib.rs +++ b/base/cvd/cuttlefish/host/commands/secure_env/rust/lib.rs @@ -31,9 +31,7 @@ use kmr_wire::keymint::SecurityLevel; use kmr_wire::rpc::MINIMUM_SUPPORTED_KEYS_IN_CSR; use log::{error, info}; use secure_env_common::run_ta_loop; -use std::ffi::CString; use std::os::fd::AsRawFd; -use std::os::unix::ffi::OsStrExt; mod clock; mod sdd; @@ -54,7 +52,7 @@ pub unsafe fn ta_main( trm: *mut libc::c_void, snapshot_socket: std::os::unix::net::UnixStream, ) { - log::set_logger(&AndroidCppLogger).unwrap(); + log::set_logger(&secure_env_common::logger::AndroidCppLogger).unwrap(); log::set_max_level(log::LevelFilter::Debug); // Filtering happens elsewhere info!( "KeyMint Rust TA running with infile={}, outfile={}, security_level={:?}", @@ -150,46 +148,3 @@ pub unsafe fn ta_main( ta.process(req) }); } - -// TODO(schuffelen): Use android_logger when rust works with host glibc, see aosp/1415969 -struct AndroidCppLogger; - -impl log::Log for AndroidCppLogger { - fn enabled(&self, _metadata: &log::Metadata) -> bool { - // Filtering is done in the underlying C++ logger, so indicate to the Rust code that all - // logs should be included - true - } - - fn log(&self, record: &log::Record) { - let file = record.file().unwrap_or("(no file)"); - let file_basename = - std::path::Path::new(file).file_name().unwrap_or(std::ffi::OsStr::new("(no file)")); - let file = CString::new(file_basename.as_bytes()) - .unwrap_or_else(|_| CString::new("(invalid file)").unwrap()); - let line = record.line().unwrap_or(0); - let severity = match record.level() { - log::Level::Trace => 0, - log::Level::Debug => 1, - log::Level::Info => 2, - log::Level::Warn => 3, - log::Level::Error => 4, - }; - let tag = CString::new("secure_env::".to_owned() + record.target()) - .unwrap_or_else(|_| CString::new("(invalid tag)").unwrap()); - let msg = CString::new(format!("{}", record.args())) - .unwrap_or_else(|_| CString::new("(invalid msg)").unwrap()); - // SAFETY: All pointer arguments are generated from valid owned CString instances. - unsafe { - secure_env_tpm::secure_env_log( - file.as_ptr(), - line, - severity, - tag.as_ptr(), - msg.as_ptr(), - ); - } - } - - fn flush(&self) {} -} diff --git a/base/cvd/cuttlefish/host/commands/secure_env/rust/logger.rs b/base/cvd/cuttlefish/host/commands/secure_env/rust/logger.rs new file mode 100644 index 00000000000..063da70262a --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/secure_env/rust/logger.rs @@ -0,0 +1,61 @@ +// +// Copyright (C) 2022 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. + +use std::ffi::CString; +use std::os::unix::ffi::OsStrExt; + +/// Logger implementation that forwards logs to the Android C++ backend. +/// TODO(schuffelen): Use android_logger when rust works with host glibc, see aosp/1415969 +pub struct AndroidCppLogger; + +impl log::Log for AndroidCppLogger { + fn enabled(&self, _metadata: &log::Metadata) -> bool { + // Filtering is done in the underlying C++ logger, so indicate to the Rust code that all + // logs should be included + true + } + + fn log(&self, record: &log::Record) { + let file = record.file().unwrap_or("(no file)"); + let file_basename = + std::path::Path::new(file).file_name().unwrap_or(std::ffi::OsStr::new("(no file)")); + let file = CString::new(file_basename.as_bytes()) + .unwrap_or_else(|_| CString::new("(invalid file)").unwrap()); + let line = record.line().unwrap_or(0); + let severity = match record.level() { + log::Level::Trace => 0, + log::Level::Debug => 1, + log::Level::Info => 2, + log::Level::Warn => 3, + log::Level::Error => 4, + }; + let tag = CString::new("secure_env::".to_owned() + record.target()) + .unwrap_or_else(|_| CString::new("(invalid tag)").unwrap()); + let msg = CString::new(format!("{}", record.args())) + .unwrap_or_else(|_| CString::new("(invalid msg)").unwrap()); + // SAFETY: All pointer arguments are generated from valid owned CString instances. + unsafe { + secure_env_ffi::secure_env_log( + file.as_ptr(), + line, + severity, + tag.as_ptr(), + msg.as_ptr(), + ); + } + } + + fn flush(&self) {} +} diff --git a/base/cvd/cuttlefish/host/commands/secure_env/rust/tpm.rs b/base/cvd/cuttlefish/host/commands/secure_env/rust/tpm.rs index 113829b7043..302be876b1a 100644 --- a/base/cvd/cuttlefish/host/commands/secure_env/rust/tpm.rs +++ b/base/cvd/cuttlefish/host/commands/secure_env/rust/tpm.rs @@ -43,7 +43,7 @@ impl TpmHmac { // Safety: all slices are valid with correct lengths. let rc = unsafe { - secure_env_tpm::tpm_hmac( + secure_env_ffi::tpm_hmac( self.trm, data.as_ptr(), data.len() as u32, diff --git a/base/cvd/cuttlefish/host/commands/secure_env/tpm_ffi.cpp b/base/cvd/cuttlefish/host/commands/secure_env/secure_env_ffi.cpp similarity index 98% rename from base/cvd/cuttlefish/host/commands/secure_env/tpm_ffi.cpp rename to base/cvd/cuttlefish/host/commands/secure_env/secure_env_ffi.cpp index 0865bfac4c2..4eaf47f68b5 100644 --- a/base/cvd/cuttlefish/host/commands/secure_env/tpm_ffi.cpp +++ b/base/cvd/cuttlefish/host/commands/secure_env/secure_env_ffi.cpp @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "tpm_ffi.h" +#include "secure_env_ffi.h" #include "absl/log/log.h" diff --git a/base/cvd/cuttlefish/host/commands/secure_env/tpm_ffi.h b/base/cvd/cuttlefish/host/commands/secure_env/secure_env_ffi.h similarity index 100% rename from base/cvd/cuttlefish/host/commands/secure_env/tpm_ffi.h rename to base/cvd/cuttlefish/host/commands/secure_env/secure_env_ffi.h From 454dd0954609c7ad5b056be8e91e8c9071515ecc Mon Sep 17 00:00:00 2001 From: Karuna Wadhera Date: Mon, 29 Jun 2026 17:40:34 +0000 Subject: [PATCH 3/3] secure_env: Add Weaver TA (Host side) Implement the Weaver Trusted Application in Rust on the host side. This TA will handle secure storage of Weaver slots. Bug: 432285494 Test: bazel test //... (in base/cvd) --- .../host/commands/secure_env/weaver/ffi.rs | 38 ++++++ .../host/commands/secure_env/weaver/lib.rs | 123 ++++++++++++++++++ .../commands/secure_env/weaver/weaver_ta.h | 34 +++++ 3 files changed, 195 insertions(+) create mode 100644 base/cvd/cuttlefish/host/commands/secure_env/weaver/ffi.rs create mode 100644 base/cvd/cuttlefish/host/commands/secure_env/weaver/lib.rs create mode 100644 base/cvd/cuttlefish/host/commands/secure_env/weaver/weaver_ta.h diff --git a/base/cvd/cuttlefish/host/commands/secure_env/weaver/ffi.rs b/base/cvd/cuttlefish/host/commands/secure_env/weaver/ffi.rs new file mode 100644 index 00000000000..f2919fefee3 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/secure_env/weaver/ffi.rs @@ -0,0 +1,38 @@ +// +// Copyright (C) 2024 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. + +//! Weaver TA core for Cuttlefish. + +use std::os::fd::OwnedFd; + +/// FFI wrapper around [`weaver_cf::ta_main`]. +/// +/// # Safety +/// +/// `fd_in`, `fd_out` and `snapshot_fd` must be valid and open file descriptors and the caller must +/// not use or close them after the call. `storage_path` must be a valid null-terminated C string. +#[no_mangle] +pub unsafe extern "C" fn weaver_ta_main( + fd_in: OwnedFd, + fd_out: OwnedFd, + storage_path: *const libc::c_char, + snapshot_fd: OwnedFd, +) { + // SAFETY: The caller guarantees `storage_path` is a valid null-terminated C string. + let storage_path = + unsafe { std::ffi::CStr::from_ptr(storage_path) }.to_string_lossy().into_owned(); + let snapshot_socket = std::os::unix::net::UnixStream::from(snapshot_fd); + weaver_cf::ta_main(fd_in.into(), fd_out.into(), storage_path, snapshot_socket) +} diff --git a/base/cvd/cuttlefish/host/commands/secure_env/weaver/lib.rs b/base/cvd/cuttlefish/host/commands/secure_env/weaver/lib.rs new file mode 100644 index 00000000000..3aecf92971a --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/secure_env/weaver/lib.rs @@ -0,0 +1,123 @@ +// +// Copyright (C) 2026 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. + +//! Weaver TA for Cuttlefish. + +use hal_wire::AsCborValue; +use log::{error, info}; +use secure_env_common::run_ta_loop; +use std::boxed::Box; +use std::collections::BTreeMap; +use std::fs::File; +use std::io::{self, BufReader, BufWriter}; +use std::os::fd::AsRawFd; +use subtle::ConstantTimeEq; +use weaver_ta::{traits, Error, Slot, WeaverTa}; + +const SLOT_COUNT: i32 = 64; +const KEY_SIZE: i32 = 16; +const VALUE_SIZE: i32 = 16; +const MAX_CHANNEL_MSG_SIZE: usize = 4096; + +/// Storage backend for Weaver that saves to a JSON file. +struct JsonSlotStorage { + path: String, +} + +impl JsonSlotStorage { + fn new(path: String) -> Self { + Self { path } + } + + fn load_all(&self) -> BTreeMap> { + match File::open(&self.path) { + Ok(file) => { + let reader = BufReader::new(file); + serde_json::from_reader(reader).unwrap_or_default() + } + Err(_) => BTreeMap::new(), + } + } + + fn save_all(&self, slots: &BTreeMap>) -> Result<(), io::Error> { + let file = File::create(&self.path)?; + let writer = BufWriter::new(file); + serde_json::to_writer(writer, slots) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + } +} + +impl traits::SlotStorage for JsonSlotStorage { + fn read_slot(&self, slot_id: i32) -> Result { + let all_slots = self.load_all(); + if let Some(data) = all_slots.get(&slot_id) { + hal_wire::AsCborValue::from_slice(data).map_err(Error::Cbor) + } else { + Ok(Slot::default()) + } + } + + fn write_slot(&mut self, slot_id: i32, slot: &Slot) -> Result<(), Error> { + let mut all_slots = self.load_all(); + let data = slot.clone().into_vec().map_err(Error::Cbor)?; + all_slots.insert(slot_id, data); + self.save_all(&all_slots).map_err(|e| { + error!("Failed to save Weaver storage: {:?}", e); + Error::FileNotFound // Map to a generic error + }) + } +} + +struct StdClock; +impl hal_ta::traits::MonotonicClock for StdClock { + fn now(&self) -> hal_wire::types::MillisecondsSinceEpoch { + let now = + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default(); + hal_wire::types::MillisecondsSinceEpoch(now.as_millis() as i64) + } +} + +struct Compare; +impl hal_ta::traits::ConstTimeEq for Compare { + fn eq(&self, left: &[u8], right: &[u8]) -> bool { + left.ct_eq(right).into() + } +} + +/// Main entry point for the Weaver TA. +pub fn ta_main( + infile: File, + outfile: File, + storage_path: String, + snapshot_socket: std::os::unix::net::UnixStream, +) { + log::set_logger(&secure_env_common::logger::AndroidCppLogger).unwrap(); + log::set_max_level(log::LevelFilter::Debug); // Filtering happens elsewhere + info!( + "Weaver TA running with infile={}, outfile={}, storage={}", + infile.as_raw_fd(), + outfile.as_raw_fd(), + storage_path + ); + + let imp = traits::Implementation { + clock: Box::new(StdClock), + compare: Box::new(Compare), + storage: Box::new(JsonSlotStorage::new(storage_path)), + warm_up: Box::new(traits::NoopWarmUp {}), + }; + let mut ta = WeaverTa::new(imp, SLOT_COUNT, KEY_SIZE, VALUE_SIZE); + run_ta_loop(infile, outfile, snapshot_socket, MAX_CHANNEL_MSG_SIZE, |req| ta.process(req)); +} diff --git a/base/cvd/cuttlefish/host/commands/secure_env/weaver/weaver_ta.h b/base/cvd/cuttlefish/host/commands/secure_env/weaver/weaver_ta.h new file mode 100644 index 00000000000..16cfde19774 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/secure_env/weaver/weaver_ta.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 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. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +// Main function for Rust implementation of Weaver. +// - fd_in: file descriptor for incoming serialized request messages +// - fd_out: file descriptor for outgoing serialized response messages +// - storage_path: path to the file used for persistent storage +// - snapshot_fd: file descriptor for a socket used to communicate with the +// secure_env suspend-resume handler thread. +void weaver_ta_main(int fd_in, int fd_out, const char* storage_path, + int snapshot_fd); + +#ifdef __cplusplus +} +#endif