1 // Copyright 2022, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! This module handles the interaction with virtual machine payload service.
16
17 use android_system_virtualization_payload::aidl::android::system::virtualization::payload:: IVmPayloadService::{
18 IVmPayloadService, ENCRYPTEDSTORE_MOUNTPOINT, VM_APK_CONTENTS_PATH,
19 VM_PAYLOAD_SERVICE_SOCKET_NAME, AttestationResult::AttestationResult,
20 };
21 use anyhow::{bail, ensure, Context, Result};
22 use binder::{
23 unstable_api::{new_spibinder, AIBinder},
24 Strong, ExceptionCode,
25 };
26 use log::{error, info, LevelFilter};
27 use rpcbinder::{RpcServer, RpcSession};
28 use openssl::{ec::EcKey, sha::sha256, ecdsa::EcdsaSig};
29 use std::convert::Infallible;
30 use std::ffi::{CString, CStr};
31 use std::fmt::Debug;
32 use std::os::raw::{c_char, c_void};
33 use std::path::Path;
34 use std::ptr::{self, NonNull};
35 use std::sync::{
36 atomic::{AtomicBool, Ordering},
37 LazyLock,
38 Mutex,
39 };
40 use vm_payload_status_bindgen::AVmAttestationStatus;
41
42 /// Maximum size of an ECDSA signature for EC P-256 key is 72 bytes.
43 const MAX_ECDSA_P256_SIGNATURE_SIZE: usize = 72;
44
45 static VM_APK_CONTENTS_PATH_C: LazyLock<CString> =
46 LazyLock::new(|| CString::new(VM_APK_CONTENTS_PATH).expect("CString::new failed"));
47 static PAYLOAD_CONNECTION: Mutex<Option<Strong<dyn IVmPayloadService>>> = Mutex::new(None);
48 static VM_ENCRYPTED_STORAGE_PATH_C: LazyLock<CString> =
49 LazyLock::new(|| CString::new(ENCRYPTEDSTORE_MOUNTPOINT).expect("CString::new failed"));
50
51 static ALREADY_NOTIFIED: AtomicBool = AtomicBool::new(false);
52
53 /// Return a connection to the payload service in Microdroid Manager. Uses the existing connection
54 /// if there is one, otherwise attempts to create a new one.
get_vm_payload_service() -> Result<Strong<dyn IVmPayloadService>>55 fn get_vm_payload_service() -> Result<Strong<dyn IVmPayloadService>> {
56 let mut connection = PAYLOAD_CONNECTION.lock().unwrap();
57 if let Some(strong) = &*connection {
58 Ok(strong.clone())
59 } else {
60 let new_connection: Strong<dyn IVmPayloadService> = RpcSession::new()
61 .setup_unix_domain_client(VM_PAYLOAD_SERVICE_SOCKET_NAME)
62 .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_SOCKET_NAME))?;
63 *connection = Some(new_connection.clone());
64 Ok(new_connection)
65 }
66 }
67
68 /// Make sure our logging goes to logcat. It is harmless to call this more than once.
initialize_logging()69 fn initialize_logging() {
70 android_logger::init_once(
71 android_logger::Config::default().with_tag("vm_payload").with_max_level(LevelFilter::Info),
72 );
73 }
74
75 /// In many cases clients can't do anything useful if API calls fail, and the failure
76 /// generally indicates that the VM is exiting or otherwise doomed. So rather than
77 /// returning a non-actionable error indication we just log the problem and abort
78 /// the process.
unwrap_or_abort<T, E: Debug>(result: Result<T, E>) -> T79 fn unwrap_or_abort<T, E: Debug>(result: Result<T, E>) -> T {
80 result.unwrap_or_else(|e| {
81 let msg = format!("{:?}", e);
82 error!("{msg}");
83 panic!("{msg}")
84 })
85 }
86
87 /// Notifies the host that the payload is ready.
88 /// Panics on failure.
89 #[no_mangle]
AVmPayload_notifyPayloadReady()90 pub extern "C" fn AVmPayload_notifyPayloadReady() {
91 initialize_logging();
92
93 if !ALREADY_NOTIFIED.swap(true, Ordering::Relaxed) {
94 unwrap_or_abort(try_notify_payload_ready());
95
96 info!("Notified host payload ready successfully");
97 }
98 }
99
100 /// Notifies the host that the payload is ready.
101 /// Returns a `Result` containing error information if failed.
try_notify_payload_ready() -> Result<()>102 fn try_notify_payload_ready() -> Result<()> {
103 get_vm_payload_service()?.notifyPayloadReady().context("Cannot notify payload ready")
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 (it is listening on the port), `on_ready` is
110 /// called to allow appropriate action to be taken - e.g. to notify clients that they may now
111 /// attempt to connect.
112 ///
113 /// The current thread joins the binder thread pool to handle incoming messages.
114 /// This function never returns.
115 ///
116 /// Panics on error (including unexpected server exit).
117 ///
118 /// # Safety
119 ///
120 /// If present, the `on_ready` callback must be a valid function pointer, which will be called at
121 /// most once, while this function is executing, with the `param` parameter.
122 #[no_mangle]
AVmPayload_runVsockRpcServer( service: *mut AIBinder, port: u32, on_ready: Option<unsafe extern "C" fn(param: *mut c_void)>, param: *mut c_void, ) -> Infallible123 pub unsafe extern "C" fn AVmPayload_runVsockRpcServer(
124 service: *mut AIBinder,
125 port: u32,
126 on_ready: Option<unsafe extern "C" fn(param: *mut c_void)>,
127 param: *mut c_void,
128 ) -> Infallible {
129 initialize_logging();
130
131 // SAFETY: try_run_vsock_server has the same requirements as this function
132 unwrap_or_abort(unsafe { try_run_vsock_server(service, port, on_ready, param) })
133 }
134
135 /// # Safety: Same as `AVmPayload_runVsockRpcServer`.
try_run_vsock_server( service: *mut AIBinder, port: u32, on_ready: Option<unsafe extern "C" fn(param: *mut c_void)>, param: *mut c_void, ) -> Result<Infallible>136 unsafe fn try_run_vsock_server(
137 service: *mut AIBinder,
138 port: u32,
139 on_ready: Option<unsafe extern "C" fn(param: *mut c_void)>,
140 param: *mut c_void,
141 ) -> Result<Infallible> {
142 // SAFETY: AIBinder returned has correct reference count, and the ownership can
143 // safely be taken by new_spibinder.
144 let service = unsafe { new_spibinder(service) };
145 if let Some(service) = service {
146 match RpcServer::new_vsock(service, libc::VMADDR_CID_HOST, port) {
147 Ok((server, _)) => {
148 if let Some(on_ready) = on_ready {
149 // SAFETY: We're calling the callback with the parameter specified within the
150 // allowed lifetime.
151 unsafe { on_ready(param) };
152 }
153 server.join();
154 bail!("RpcServer unexpectedly terminated");
155 }
156 Err(err) => {
157 bail!("Failed to start RpcServer: {:?}", err);
158 }
159 }
160 } else {
161 bail!("Failed to convert the given service from AIBinder to SpIBinder.");
162 }
163 }
164
165 /// Get a secret that is uniquely bound to this VM instance.
166 /// Panics on failure.
167 ///
168 /// # Safety
169 ///
170 /// Behavior is undefined if any of the following conditions are violated:
171 ///
172 /// * `identifier` must be [valid] for reads of `identifier_size` bytes.
173 /// * `secret` must be [valid] for writes of `size` bytes.
174 ///
175 /// [valid]: ptr#safety
176 #[no_mangle]
AVmPayload_getVmInstanceSecret( identifier: *const u8, identifier_size: usize, secret: *mut u8, size: usize, )177 pub unsafe extern "C" fn AVmPayload_getVmInstanceSecret(
178 identifier: *const u8,
179 identifier_size: usize,
180 secret: *mut u8,
181 size: usize,
182 ) {
183 initialize_logging();
184
185 // SAFETY: See the requirements on `identifier` above.
186 let identifier = unsafe { std::slice::from_raw_parts(identifier, identifier_size) };
187 let vm_secret = unwrap_or_abort(try_get_vm_instance_secret(identifier, size));
188
189 // SAFETY: See the requirements on `secret` above; `vm_secret` is known to have length `size`,
190 // and cannot overlap `secret` because we just allocated it.
191 unsafe {
192 ptr::copy_nonoverlapping(vm_secret.as_ptr(), secret, size);
193 }
194 }
195
try_get_vm_instance_secret(identifier: &[u8], size: usize) -> Result<Vec<u8>>196 fn try_get_vm_instance_secret(identifier: &[u8], size: usize) -> Result<Vec<u8>> {
197 let vm_secret = get_vm_payload_service()?
198 .getVmInstanceSecret(identifier, i32::try_from(size)?)
199 .context("Cannot get VM instance secret")?;
200 ensure!(
201 vm_secret.len() == size,
202 "Returned secret has {} bytes, expected {}",
203 vm_secret.len(),
204 size
205 );
206 Ok(vm_secret)
207 }
208
209 /// Get the VM's attestation chain.
210 /// Panics on failure.
211 ///
212 /// # Safety
213 ///
214 /// Behavior is undefined if any of the following conditions are violated:
215 ///
216 /// * `data` must be [valid] for writes of `size` bytes, if size > 0.
217 ///
218 /// [valid]: ptr#safety
219 #[no_mangle]
AVmPayload_getDiceAttestationChain(data: *mut u8, size: usize) -> usize220 pub unsafe extern "C" fn AVmPayload_getDiceAttestationChain(data: *mut u8, size: usize) -> usize {
221 initialize_logging();
222
223 let chain = unwrap_or_abort(try_get_dice_attestation_chain());
224 if size != 0 {
225 // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
226 // the length of either buffer, and `chain` cannot overlap `data` because we just allocated
227 // it. We allow data to be null, which is never valid, but only if size == 0 which is
228 // checked above.
229 unsafe { ptr::copy_nonoverlapping(chain.as_ptr(), data, std::cmp::min(chain.len(), size)) };
230 }
231 chain.len()
232 }
233
try_get_dice_attestation_chain() -> Result<Vec<u8>>234 fn try_get_dice_attestation_chain() -> Result<Vec<u8>> {
235 get_vm_payload_service()?.getDiceAttestationChain().context("Cannot get attestation chain")
236 }
237
238 /// Get the VM's attestation CDI.
239 /// Panics on failure.
240 ///
241 /// # Safety
242 ///
243 /// Behavior is undefined if any of the following conditions are violated:
244 ///
245 /// * `data` must be [valid] for writes of `size` bytes, if size > 0.
246 ///
247 /// [valid]: ptr#safety
248 #[no_mangle]
AVmPayload_getDiceAttestationCdi(data: *mut u8, size: usize) -> usize249 pub unsafe extern "C" fn AVmPayload_getDiceAttestationCdi(data: *mut u8, size: usize) -> usize {
250 initialize_logging();
251
252 let cdi = unwrap_or_abort(try_get_dice_attestation_cdi());
253 if size != 0 {
254 // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
255 // the length of either buffer, and `cdi` cannot overlap `data` because we just allocated
256 // it. We allow data to be null, which is never valid, but only if size == 0 which is
257 // checked above.
258 unsafe { ptr::copy_nonoverlapping(cdi.as_ptr(), data, std::cmp::min(cdi.len(), size)) };
259 }
260 cdi.len()
261 }
262
try_get_dice_attestation_cdi() -> Result<Vec<u8>>263 fn try_get_dice_attestation_cdi() -> Result<Vec<u8>> {
264 get_vm_payload_service()?.getDiceAttestationCdi().context("Cannot get attestation CDI")
265 }
266
267 /// Requests the remote attestation of the client VM.
268 ///
269 /// The challenge will be included in the certificate chain in the attestation result,
270 /// serving as proof of the freshness of the result.
271 ///
272 /// # Safety
273 ///
274 /// Behavior is undefined if any of the following conditions are violated:
275 ///
276 /// * `challenge` must be [valid] for reads of `challenge_size` bytes.
277 ///
278 /// [valid]: ptr#safety
279 #[no_mangle]
AVmPayload_requestAttestation( challenge: *const u8, challenge_size: usize, res: &mut *mut AttestationResult, ) -> AVmAttestationStatus280 pub unsafe extern "C" fn AVmPayload_requestAttestation(
281 challenge: *const u8,
282 challenge_size: usize,
283 res: &mut *mut AttestationResult,
284 ) -> AVmAttestationStatus {
285 // SAFETY: The caller guarantees that `challenge` is valid for reads and `res` is valid
286 // for writes.
287 unsafe {
288 request_attestation(
289 challenge,
290 challenge_size,
291 false, // test_mode
292 res,
293 )
294 }
295 }
296
297 /// Requests the remote attestation of the client VM for testing.
298 ///
299 /// # Safety
300 ///
301 /// Behavior is undefined if any of the following conditions are violated:
302 ///
303 /// * `challenge` must be [valid] for reads of `challenge_size` bytes.
304 ///
305 /// [valid]: ptr#safety
306 #[no_mangle]
AVmPayload_requestAttestationForTesting( challenge: *const u8, challenge_size: usize, res: &mut *mut AttestationResult, ) -> AVmAttestationStatus307 pub unsafe extern "C" fn AVmPayload_requestAttestationForTesting(
308 challenge: *const u8,
309 challenge_size: usize,
310 res: &mut *mut AttestationResult,
311 ) -> AVmAttestationStatus {
312 // SAFETY: The caller guarantees that `challenge` is valid for reads and `res` is valid
313 // for writes.
314 unsafe {
315 request_attestation(
316 challenge,
317 challenge_size,
318 true, // test_mode
319 res,
320 )
321 }
322 }
323
324 /// Requests the remote attestation of the client VM.
325 ///
326 /// # Safety
327 ///
328 /// Behavior is undefined if any of the following conditions are violated:
329 ///
330 /// * `challenge` must be [valid] for reads of `challenge_size` bytes.
331 ///
332 /// [valid]: ptr#safety
request_attestation( challenge: *const u8, challenge_size: usize, test_mode: bool, res: &mut *mut AttestationResult, ) -> AVmAttestationStatus333 unsafe fn request_attestation(
334 challenge: *const u8,
335 challenge_size: usize,
336 test_mode: bool,
337 res: &mut *mut AttestationResult,
338 ) -> AVmAttestationStatus {
339 initialize_logging();
340 const MAX_CHALLENGE_SIZE: usize = 64;
341 if challenge_size > MAX_CHALLENGE_SIZE {
342 return AVmAttestationStatus::ATTESTATION_ERROR_INVALID_CHALLENGE;
343 }
344 let challenge = if challenge_size == 0 {
345 &[]
346 } else {
347 // SAFETY: The caller guarantees that `challenge` is valid for reads of
348 // `challenge_size` bytes and `challenge_size` is not zero.
349 unsafe { std::slice::from_raw_parts(challenge, challenge_size) }
350 };
351 let service = unwrap_or_abort(get_vm_payload_service());
352 match service.requestAttestation(challenge, test_mode) {
353 Ok(attestation_res) => {
354 *res = Box::into_raw(Box::new(attestation_res));
355 AVmAttestationStatus::ATTESTATION_OK
356 }
357 Err(e) => {
358 error!("Remote attestation failed: {e:?}");
359 binder_status_to_attestation_status(e)
360 }
361 }
362 }
363
binder_status_to_attestation_status(status: binder::Status) -> AVmAttestationStatus364 fn binder_status_to_attestation_status(status: binder::Status) -> AVmAttestationStatus {
365 match status.exception_code() {
366 ExceptionCode::UNSUPPORTED_OPERATION => AVmAttestationStatus::ATTESTATION_ERROR_UNSUPPORTED,
367 _ => AVmAttestationStatus::ATTESTATION_ERROR_ATTESTATION_FAILED,
368 }
369 }
370
371 /// Converts the return value from `AVmPayload_requestAttestation` to a text string
372 /// representing the error code.
373 #[no_mangle]
AVmAttestationStatus_toString(status: AVmAttestationStatus) -> *const c_char374 pub extern "C" fn AVmAttestationStatus_toString(status: AVmAttestationStatus) -> *const c_char {
375 let message = match status {
376 AVmAttestationStatus::ATTESTATION_OK => {
377 CStr::from_bytes_with_nul(b"The remote attestation completes successfully.\0").unwrap()
378 }
379 AVmAttestationStatus::ATTESTATION_ERROR_INVALID_CHALLENGE => {
380 CStr::from_bytes_with_nul(b"The challenge size is not between 0 and 64.\0").unwrap()
381 }
382 AVmAttestationStatus::ATTESTATION_ERROR_ATTESTATION_FAILED => {
383 CStr::from_bytes_with_nul(b"Failed to attest the VM. Please retry at a later time.\0")
384 .unwrap()
385 }
386 AVmAttestationStatus::ATTESTATION_ERROR_UNSUPPORTED => CStr::from_bytes_with_nul(
387 b"Remote attestation is not supported in the current environment.\0",
388 )
389 .unwrap(),
390 };
391 message.as_ptr()
392 }
393
394 /// Reads the DER-encoded ECPrivateKey structure specified in [RFC 5915 s3] for the
395 /// EC P-256 private key from the provided attestation result.
396 ///
397 /// # Safety
398 ///
399 /// Behavior is undefined if any of the following conditions are violated:
400 ///
401 /// * `data` must be [valid] for writes of `size` bytes, if size > 0.
402 /// * The region of memory beginning at `data` with `size` bytes must not overlap with the region of
403 /// memory `res` points to.
404 ///
405 /// [valid]: ptr#safety
406 /// [RFC 5915 s3]: https://datatracker.ietf.org/doc/html/rfc5915#section-3
407 #[no_mangle]
AVmAttestationResult_getPrivateKey( res: &AttestationResult, data: *mut u8, size: usize, ) -> usize408 pub unsafe extern "C" fn AVmAttestationResult_getPrivateKey(
409 res: &AttestationResult,
410 data: *mut u8,
411 size: usize,
412 ) -> usize {
413 let private_key = &res.privateKey;
414 if size != 0 {
415 let data = NonNull::new(data).expect("data must not be null when size > 0");
416 // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
417 // the length of either buffer, and the caller ensures that `private_key` cannot overlap
418 // `data`. We allow data to be null, which is never valid, but only if size == 0
419 // which is checked above.
420 unsafe {
421 ptr::copy_nonoverlapping(
422 private_key.as_ptr(),
423 data.as_ptr(),
424 std::cmp::min(private_key.len(), size),
425 )
426 };
427 }
428 private_key.len()
429 }
430
431 /// Signs the given message using ECDSA P-256, the message is first hashed with SHA-256 and
432 /// then it is signed with the attested EC P-256 private key in the attestation result.
433 ///
434 /// # Safety
435 ///
436 /// Behavior is undefined if any of the following conditions are violated:
437 ///
438 /// * `message` must be [valid] for reads of `message_size` bytes.
439 /// * `data` must be [valid] for writes of `size` bytes, if size > 0.
440 /// * The region of memory beginning at `data` with `size` bytes must not overlap with the region of
441 /// memory `res` or `message` point to.
442 ///
443 ///
444 /// [valid]: ptr#safety
445 #[no_mangle]
AVmAttestationResult_sign( res: &AttestationResult, message: *const u8, message_size: usize, data: *mut u8, size: usize, ) -> usize446 pub unsafe extern "C" fn AVmAttestationResult_sign(
447 res: &AttestationResult,
448 message: *const u8,
449 message_size: usize,
450 data: *mut u8,
451 size: usize,
452 ) -> usize {
453 // A DER-encoded ECDSA signature can have varying sizes even with the same EC Key and message,
454 // due to the encoding of the random values r and s that are part of the signature.
455 if size == 0 {
456 return MAX_ECDSA_P256_SIGNATURE_SIZE;
457 }
458 if message_size == 0 {
459 panic!("Message to be signed must not be empty.")
460 }
461 // SAFETY: See the requirements on `message` above.
462 let message = unsafe { std::slice::from_raw_parts(message, message_size) };
463 let signature = unwrap_or_abort(try_ecdsa_sign(message, &res.privateKey));
464 let data = NonNull::new(data).expect("data must not be null when size > 0");
465 // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
466 // the length of either buffer, and the caller ensures that `signature` cannot overlap
467 // `data`. We allow data to be null, which is never valid, but only if size == 0
468 // which is checked above.
469 unsafe {
470 ptr::copy_nonoverlapping(
471 signature.as_ptr(),
472 data.as_ptr(),
473 usize::min(signature.len(), size),
474 )
475 };
476 if size < signature.len() {
477 // If the buffer is too small, return the maximum size of the signature to allow the caller
478 // to allocate a buffer large enough to call this function again.
479 MAX_ECDSA_P256_SIGNATURE_SIZE
480 } else {
481 signature.len()
482 }
483 }
484
try_ecdsa_sign(message: &[u8], der_encoded_ec_private_key: &[u8]) -> Result<Vec<u8>>485 fn try_ecdsa_sign(message: &[u8], der_encoded_ec_private_key: &[u8]) -> Result<Vec<u8>> {
486 let private_key = EcKey::private_key_from_der(der_encoded_ec_private_key)?;
487 let digest = sha256(message);
488 let sig = EcdsaSig::sign(&digest, &private_key)?;
489 Ok(sig.to_der()?)
490 }
491
492 /// Gets the number of certificates in the certificate chain.
493 #[no_mangle]
AVmAttestationResult_getCertificateCount(res: &AttestationResult) -> usize494 pub extern "C" fn AVmAttestationResult_getCertificateCount(res: &AttestationResult) -> usize {
495 res.certificateChain.len()
496 }
497
498 /// Retrieves the certificate at the given `index` from the certificate chain in the provided
499 /// attestation result.
500 ///
501 /// # Safety
502 ///
503 /// Behavior is undefined if any of the following conditions are violated:
504 ///
505 /// * `data` must be [valid] for writes of `size` bytes, if size > 0.
506 /// * `index` must be within the range of [0, number of certificates). The number of certificates
507 /// can be obtained with `AVmAttestationResult_getCertificateCount`.
508 /// * The region of memory beginning at `data` with `size` bytes must not overlap with the region of
509 /// memory `res` points to.
510 ///
511 /// [valid]: ptr#safety
512 #[no_mangle]
AVmAttestationResult_getCertificateAt( res: &AttestationResult, index: usize, data: *mut u8, size: usize, ) -> usize513 pub unsafe extern "C" fn AVmAttestationResult_getCertificateAt(
514 res: &AttestationResult,
515 index: usize,
516 data: *mut u8,
517 size: usize,
518 ) -> usize {
519 let certificate =
520 &res.certificateChain.get(index).expect("The index is out of bounds.").encodedCertificate;
521 if size != 0 {
522 let data = NonNull::new(data).expect("data must not be null when size > 0");
523 // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
524 // the length of either buffer, and the caller ensures that `certificate` cannot overlap
525 // `data`. We allow data to be null, which is never valid, but only if size == 0
526 // which is checked above.
527 unsafe {
528 ptr::copy_nonoverlapping(
529 certificate.as_ptr(),
530 data.as_ptr(),
531 std::cmp::min(certificate.len(), size),
532 )
533 };
534 }
535 certificate.len()
536 }
537
538 /// Frees all the data owned by given attestation result and result itself.
539 ///
540 /// # Safety
541 ///
542 /// Behavior is undefined if any of the following conditions are violated:
543 ///
544 /// * `res` must point to a valid `AttestationResult` and has not been freed before.
545 #[no_mangle]
AVmAttestationResult_free(res: *mut AttestationResult)546 pub unsafe extern "C" fn AVmAttestationResult_free(res: *mut AttestationResult) {
547 if !res.is_null() {
548 // SAFETY: The result is only freed once is ensured by the caller.
549 let res = unsafe { Box::from_raw(res) };
550 drop(res)
551 }
552 }
553
554 /// Gets the path to the APK contents.
555 #[no_mangle]
AVmPayload_getApkContentsPath() -> *const c_char556 pub extern "C" fn AVmPayload_getApkContentsPath() -> *const c_char {
557 VM_APK_CONTENTS_PATH_C.as_ptr()
558 }
559
560 /// Gets the path to the VM's encrypted storage.
561 #[no_mangle]
AVmPayload_getEncryptedStoragePath() -> *const c_char562 pub extern "C" fn AVmPayload_getEncryptedStoragePath() -> *const c_char {
563 if Path::new(ENCRYPTEDSTORE_MOUNTPOINT).exists() {
564 VM_ENCRYPTED_STORAGE_PATH_C.as_ptr()
565 } else {
566 ptr::null()
567 }
568 }
569