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