Alan Stokes | 9fd57b0 | 2024-05-28 09:50:22 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2024 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | //! Rust wrapper for the VM Payload API, allowing virtual machine payload code to be written in |
| 18 | //! Rust. This wraps the raw C API, accessed via bindgen, into a more idiomatic Rust interface. |
| 19 | //! |
Jiyong Park | b51c028 | 2024-07-22 12:58:26 +0900 | [diff] [blame^] | 20 | //! See `https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/libs/libvm_payload/README.md` |
Alan Stokes | 9fd57b0 | 2024-05-28 09:50:22 +0100 | [diff] [blame] | 21 | //! for more information on the VM Payload API. |
| 22 | |
| 23 | mod attestation; |
| 24 | |
Alan Stokes | c874cd2 | 2024-06-11 16:23:18 +0100 | [diff] [blame] | 25 | pub use attestation::{request_attestation, AttestationError, AttestationResult}; |
Alan Stokes | 9fd57b0 | 2024-05-28 09:50:22 +0100 | [diff] [blame] | 26 | use binder::unstable_api::AsNative; |
| 27 | use binder::{FromIBinder, Strong}; |
| 28 | use std::ffi::{c_void, CStr, OsStr}; |
| 29 | use std::os::unix::ffi::OsStrExt; |
| 30 | use std::path::Path; |
| 31 | use std::ptr; |
| 32 | use vm_payload_bindgen::{ |
| 33 | AIBinder, AVmPayload_getApkContentsPath, AVmPayload_getEncryptedStoragePath, |
| 34 | AVmPayload_getVmInstanceSecret, AVmPayload_notifyPayloadReady, AVmPayload_runVsockRpcServer, |
| 35 | }; |
| 36 | |
Alan Stokes | c874cd2 | 2024-06-11 16:23:18 +0100 | [diff] [blame] | 37 | /// The functions declared here are restricted to VMs created with a config file; |
| 38 | /// they will fail, or panic, if called in other VMs. The ability to create such VMs |
| 39 | /// requires the android.permission.USE_CUSTOM_VIRTUAL_MACHINE permission, and is |
| 40 | /// therefore not available to privileged or third party apps. |
| 41 | /// |
| 42 | /// These functions can be used by tests, if the permission is granted via shell. |
| 43 | pub mod restricted { |
| 44 | pub use crate::attestation::request_attestation_for_testing; |
| 45 | } |
| 46 | |
Alan Stokes | 9fd57b0 | 2024-05-28 09:50:22 +0100 | [diff] [blame] | 47 | /// Marks the main function of the VM payload. |
| 48 | /// |
| 49 | /// When the VM is run, this function is called. If it returns, the VM ends normally with a 0 exit |
| 50 | /// code. |
| 51 | /// |
| 52 | /// Example: |
| 53 | /// |
| 54 | /// ```rust |
| 55 | /// use log::info; |
| 56 | /// |
| 57 | /// vm_payload::main!(vm_main); |
| 58 | /// |
| 59 | /// fn vm_main() { |
| 60 | /// android_logger::init_once( |
| 61 | /// android_logger::Config::default() |
| 62 | /// .with_tag("example_vm_payload") |
| 63 | /// .with_max_level(log::LevelFilter::Info), |
| 64 | /// ); |
| 65 | /// info!("Hello world"); |
| 66 | /// } |
| 67 | /// ``` |
| 68 | #[macro_export] |
| 69 | macro_rules! main { |
| 70 | ($name:path) => { |
| 71 | // Export a symbol with a name matching the extern declaration below. |
| 72 | #[export_name = "rust_main"] |
| 73 | fn __main() { |
| 74 | // Ensure that the main function provided by the application has the correct type. |
| 75 | $name() |
| 76 | } |
| 77 | }; |
| 78 | } |
| 79 | |
| 80 | // This is the real C entry point for the VM; we just forward to the Rust entry point. |
| 81 | #[allow(non_snake_case)] |
| 82 | #[no_mangle] |
| 83 | extern "C" fn AVmPayload_main() { |
| 84 | extern "Rust" { |
| 85 | fn rust_main(); |
| 86 | } |
| 87 | |
| 88 | // SAFETY: rust_main is provided by the application using the `main!` macro above, which makes |
| 89 | // sure it has the right type. |
| 90 | unsafe { rust_main() } |
| 91 | } |
| 92 | |
| 93 | /// Notifies the host that the payload is ready. |
| 94 | /// |
| 95 | /// If the host app has set a `VirtualMachineCallback` for the VM, its |
| 96 | /// `onPayloadReady` method will be called. |
| 97 | /// |
| 98 | /// Note that subsequent calls to this function after the first have no effect; |
| 99 | /// `onPayloadReady` is never called more than once. |
| 100 | pub fn notify_payload_ready() { |
| 101 | // SAFETY: Invokes a method from the bindgen library `vm_payload_bindgen` which is safe to |
| 102 | // call at any time. |
| 103 | unsafe { AVmPayload_notifyPayloadReady() }; |
| 104 | } |
| 105 | |
| 106 | /// Runs a binder RPC server, serving the supplied binder service implementation on the given vsock |
| 107 | /// port. |
| 108 | /// |
| 109 | /// If and when the server is ready for connections (i.e. it is listening on the port), |
| 110 | /// [`notify_payload_ready`] is called to notify the host that the server is ready. This is |
| 111 | /// appropriate for VM payloads that serve a single binder service - which is common. |
| 112 | /// |
| 113 | /// Note that this function does not return. The calling thread joins the binder |
| 114 | /// thread pool to handle incoming messages. |
| 115 | pub fn run_single_vsock_service<T>(service: Strong<T>, port: u32) -> ! |
| 116 | where |
| 117 | T: FromIBinder + ?Sized, |
| 118 | { |
| 119 | extern "C" fn on_ready(_param: *mut c_void) { |
| 120 | notify_payload_ready(); |
| 121 | } |
| 122 | |
| 123 | let mut service = service.as_binder(); |
| 124 | // The cast here is needed because the compiler doesn't know that our vm_payload_bindgen |
| 125 | // AIBinder is the same type as binder_ndk_sys::AIBinder. |
| 126 | let service = service.as_native_mut() as *mut AIBinder; |
| 127 | let param = ptr::null_mut(); |
| 128 | // SAFETY: We have a strong reference to the service, so the raw pointer remains valid. It is |
| 129 | // safe for on_ready to be invoked at any time, with any parameter. |
| 130 | unsafe { AVmPayload_runVsockRpcServer(service, port, Some(on_ready), param) } |
| 131 | } |
| 132 | |
| 133 | /// Gets the path to the contents of the APK containing the VM payload. It is a directory, under |
| 134 | /// which are the unzipped contents of the APK containing the payload, all read-only |
| 135 | /// but accessible to the payload. |
| 136 | pub fn apk_contents_path() -> &'static Path { |
| 137 | // SAFETY: AVmPayload_getApkContentsPath always returns a non-null pointer to a |
| 138 | // nul-terminated C string with static lifetime. |
| 139 | let c_str = unsafe { CStr::from_ptr(AVmPayload_getApkContentsPath()) }; |
| 140 | Path::new(OsStr::from_bytes(c_str.to_bytes())) |
| 141 | } |
| 142 | |
| 143 | /// Gets the path to the encrypted persistent storage for the VM, if any. This is |
| 144 | /// a directory under which any files or directories created will be stored on |
| 145 | /// behalf of the VM by the host app. All data is encrypted using a key known |
| 146 | /// only to the VM, so the host cannot decrypt it, but may delete it. |
| 147 | /// |
| 148 | /// Returns `None` if no encrypted storage was requested in the VM configuration. |
| 149 | pub fn encrypted_storage_path() -> Option<&'static Path> { |
| 150 | // SAFETY: AVmPayload_getEncryptedStoragePath returns either null or a pointer to a |
| 151 | // nul-terminated C string with static lifetime. |
| 152 | let ptr = unsafe { AVmPayload_getEncryptedStoragePath() }; |
| 153 | if ptr.is_null() { |
| 154 | None |
| 155 | } else { |
| 156 | // SAFETY: We know the pointer is not null, and so it is a valid C string. |
| 157 | let c_str = unsafe { CStr::from_ptr(ptr) }; |
| 158 | Some(Path::new(OsStr::from_bytes(c_str.to_bytes()))) |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | /// Retrieves all or part of a 32-byte secret that is bound to this unique VM |
| 163 | /// instance and the supplied identifier. The secret can be used e.g. as an |
| 164 | /// encryption key. |
| 165 | /// |
| 166 | /// Every VM has a secret that is derived from a device-specific value known to |
| 167 | /// the hypervisor, the code that runs in the VM and its non-modifiable |
| 168 | /// configuration; it is not made available to the host OS. |
| 169 | /// |
| 170 | /// This function performs a further derivation from the VM secret and the |
| 171 | /// supplied identifier. As long as the VM identity doesn't change the same value |
| 172 | /// will be returned for the same identifier, even if the VM is stopped & |
| 173 | /// restarted or the device rebooted. |
| 174 | /// |
| 175 | /// If multiple secrets are required for different purposes, a different |
| 176 | /// identifier should be used for each. The identifiers otherwise are arbitrary |
| 177 | /// byte sequences and do not need to be kept secret; typically they are |
| 178 | /// hardcoded in the calling code. |
| 179 | /// |
| 180 | /// The secret is returned in [`secret`], truncated to its size, which must be between |
| 181 | /// 1 and 32 bytes (inclusive) or the function will panic. |
Alan Stokes | c874cd2 | 2024-06-11 16:23:18 +0100 | [diff] [blame] | 182 | pub fn get_vm_instance_secret(identifier: &[u8], secret: &mut [u8]) { |
Alan Stokes | 9fd57b0 | 2024-05-28 09:50:22 +0100 | [diff] [blame] | 183 | let secret_size = secret.len(); |
| 184 | assert!((1..=32).contains(&secret_size), "VM instance secrets can be up to 32 bytes long"); |
| 185 | |
| 186 | // SAFETY: The function only reads from `[identifier]` within its bounds, and only writes to |
| 187 | // `[secret]` within its bounds. Neither reference is retained, and we know neither is null. |
| 188 | unsafe { |
| 189 | AVmPayload_getVmInstanceSecret( |
| 190 | identifier.as_ptr() as *const c_void, |
| 191 | identifier.len(), |
| 192 | secret.as_mut_ptr() as *mut c_void, |
| 193 | secret_size, |
| 194 | ) |
| 195 | } |
| 196 | } |