Merge "Add --enable-earlycon option to vm tool" into main
diff --git a/android/LinuxInstaller/linux_image_builder/.gitignore b/android/LinuxInstaller/linux_image_builder/.gitignore
new file mode 100644
index 0000000..f082413
--- /dev/null
+++ b/android/LinuxInstaller/linux_image_builder/.gitignore
@@ -0,0 +1,2 @@
+debian.img
+ttyd
diff --git a/android/LinuxInstaller/linux_image_builder/commands b/android/LinuxInstaller/linux_image_builder/commands
new file mode 100644
index 0000000..8b2da45
--- /dev/null
+++ b/android/LinuxInstaller/linux_image_builder/commands
@@ -0,0 +1,11 @@
+upload init.sh:/root
+upload vsock.py:/usr/local/bin
+upload ttyd:/usr/local/bin
+upload ttyd.service:/etc/systemd/system
+upload vsockip.service:/etc/systemd/system
+chmod 0777:/root/init.sh
+firstboot-command "/root/init.sh"
+chmod 0644:/etc/systemd/system/vsockip.service
+chmod 0644:/etc/systemd/system/ttyd.service
+chmod 0777:/usr/local/bin/vsock.py
+chmod 0777:/usr/local/bin/ttyd
diff --git a/android/LinuxInstaller/linux_image_builder/init.sh b/android/LinuxInstaller/linux_image_builder/init.sh
new file mode 100644
index 0000000..bec5ac5
--- /dev/null
+++ b/android/LinuxInstaller/linux_image_builder/init.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+systemctl daemon-reload
+systemctl start ttyd && sudo systemctl enable ttyd
+systemctl start vsockip && sudo systemctl enable vsockip
diff --git a/android/LinuxInstaller/linux_image_builder/setup.sh b/android/LinuxInstaller/linux_image_builder/setup.sh
new file mode 100755
index 0000000..a9aa77d
--- /dev/null
+++ b/android/LinuxInstaller/linux_image_builder/setup.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-nocloud-arm64.raw -O debian.img
+wget https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.aarch64 -O ttyd
+virt-customize --commands-from-file commands -a debian.img
\ No newline at end of file
diff --git a/android/LinuxInstaller/linux_image_builder/ttyd.service b/android/LinuxInstaller/linux_image_builder/ttyd.service
new file mode 100644
index 0000000..3a8f181
--- /dev/null
+++ b/android/LinuxInstaller/linux_image_builder/ttyd.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=TTYD
+After=syslog.target
+After=network.target
+[Service]
+ExecStart=/usr/local/bin/ttyd -W login
+Type=simple
+Restart=always
+User=root
+Group=root
+[Install]
+WantedBy=multi-user.target
diff --git a/android/LinuxInstaller/linux_image_builder/vsock.py b/android/LinuxInstaller/linux_image_builder/vsock.py
new file mode 100644
index 0000000..292d953
--- /dev/null
+++ b/android/LinuxInstaller/linux_image_builder/vsock.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+
+import socket
+
+# Constants for vsock (from linux/vm_sockets.h)
+AF_VSOCK = 40
+SOCK_STREAM = 1
+VMADDR_CID_ANY = -1
+
+def get_local_ip():
+ """Retrieves the first IPv4 address found on the system.
+
+ Returns:
+ str: The local IPv4 address, or '127.0.0.1' if no IPv4 address is found.
+ """
+
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ try:
+ s.connect(('8.8.8.8', 80))
+ ip = s.getsockname()[0]
+ except Exception:
+ ip = '127.0.0.1'
+ finally:
+ s.close()
+ return ip
+
+def main():
+ PORT = 1024
+
+ # Create a vsock socket
+ server_socket = socket.socket(AF_VSOCK, SOCK_STREAM)
+
+ # Bind the socket to the server address
+ server_address = (VMADDR_CID_ANY, PORT)
+ server_socket.bind(server_address)
+
+ # Listen for incoming connections
+ server_socket.listen(1)
+ print(f"VSOCK server listening on port {PORT}...")
+
+ while True:
+ # Accept a connection
+ connection, client_address = server_socket.accept()
+ print(f"Connection from: {client_address}")
+
+ try:
+ # Get the local IP address
+ local_ip = get_local_ip()
+
+ # Send the IP address to the client
+ connection.sendall(local_ip.encode())
+ finally:
+ # Close the connection
+ connection.close()
+
+if __name__ == "__main__":
+ main()
diff --git a/android/LinuxInstaller/linux_image_builder/vsockip.service b/android/LinuxInstaller/linux_image_builder/vsockip.service
new file mode 100644
index 0000000..a29020b
--- /dev/null
+++ b/android/LinuxInstaller/linux_image_builder/vsockip.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=vsock ip service
+After=syslog.target
+After=network.target
+[Service]
+ExecStart=/usr/bin/python3 /usr/local/bin/vsock.py
+Type=simple
+Restart=always
+User=root
+Group=root
+[Install]
+WantedBy=multi-user.target
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index e6e56d9..2112f89 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -20,6 +20,8 @@
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@@ -53,6 +55,12 @@
});
}
+ @Override
+ protected void onDestroy() {
+ VmLauncherServices.stopVmLauncherService(this);
+ super.onDestroy();
+ }
+
private void gotoURL(String url) {
runOnUiThread(() -> mWebView.loadUrl(url));
}
@@ -79,4 +87,21 @@
new Handler(Looper.getMainLooper())
.postDelayed(() -> gotoURL("http://" + mVmIpAddr + ":7681"), 2000);
}
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.main_menu, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.stop_vm:
+ VmLauncherServices.stopVmLauncherService(this);
+ return true;
+ default:
+ return super.onMenuItemSelected(featureId, item);
+ }
+ }
}
diff --git a/android/TerminalApp/res/menu/main_menu.xml b/android/TerminalApp/res/menu/main_menu.xml
new file mode 100644
index 0000000..cc65098
--- /dev/null
+++ b/android/TerminalApp/res/menu/main_menu.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/stop_vm"
+ android:title="Stop the existing VM instance"/>
+</menu>
\ No newline at end of file
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmLauncherService.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmLauncherService.java
index ec98f4c..5e78f99 100644
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmLauncherService.java
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmLauncherService.java
@@ -76,6 +76,10 @@
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
+ if (isVmRunning()) {
+ Log.d(TAG, "there is already the running VM instance");
+ return START_NOT_STICKY;
+ }
mExecutorService = Executors.newCachedThreadPool();
ConfigJson json = ConfigJson.from(VM_CONFIG_PATH);
@@ -85,7 +89,9 @@
try {
runner = Runner.create(this, config);
} catch (VirtualMachineException e) {
- throw new RuntimeException(e);
+ Log.e(TAG, "cannot create runner", e);
+ stopSelf();
+ return START_NOT_STICKY;
}
mVirtualMachine = runner.getVm();
mResultReceiver =
@@ -117,13 +123,32 @@
@Override
public void onDestroy() {
super.onDestroy();
- mExecutorService.shutdownNow();
+ if (isVmRunning()) {
+ try {
+ mVirtualMachine.stop();
+ stopForeground(STOP_FOREGROUND_REMOVE);
+ } catch (VirtualMachineException e) {
+ Log.e(TAG, "failed to stop a VM instance", e);
+ }
+ mExecutorService.shutdownNow();
+ mExecutorService = null;
+ mVirtualMachine = null;
+ }
+ }
+
+ private boolean isVmRunning() {
+ return mVirtualMachine != null
+ && mVirtualMachine.getStatus() == VirtualMachine.STATUS_RUNNING;
}
// TODO(b/359523803): Use AVF API to get ip addr when it exists
private void gatherIpAddrFromVm(Handler handler) {
handler.postDelayed(
() -> {
+ if (!isVmRunning()) {
+ Log.d(TAG, "A virtual machine instance isn't running");
+ return;
+ }
int INTERNAL_VSOCK_SERVER_PORT = 1024;
try (ParcelFileDescriptor pfd =
mVirtualMachine.connectVsock(INTERNAL_VSOCK_SERVER_PORT)) {
diff --git a/android/fd_server/Android.bp b/android/fd_server/Android.bp
index b02c104..32a8fec 100644
--- a/android/fd_server/Android.bp
+++ b/android/fd_server/Android.bp
@@ -18,6 +18,7 @@
"liblog_rust",
"libnix",
"librpcbinder_rs",
+ "libsafe_ownedfd",
],
prefer_rlib: true,
apex_available: ["com.android.virt"],
@@ -39,6 +40,7 @@
"liblog_rust",
"libnix",
"librpcbinder_rs",
+ "libsafe_ownedfd",
],
prefer_rlib: true,
test_suites: ["general-tests"],
diff --git a/android/fd_server/src/aidl.rs b/android/fd_server/src/aidl.rs
index 5f91987..2f3697c 100644
--- a/android/fd_server/src/aidl.rs
+++ b/android/fd_server/src/aidl.rs
@@ -14,20 +14,21 @@
* limitations under the License.
*/
-use anyhow::Result;
+use anyhow::{Context, Result};
use log::error;
use nix::{
errno::Errno, fcntl::openat, fcntl::OFlag, sys::stat::fchmod, sys::stat::mkdirat,
sys::stat::mode_t, sys::stat::Mode, sys::statvfs::statvfs, sys::statvfs::Statvfs,
unistd::unlinkat, unistd::UnlinkatFlags,
};
+use safe_ownedfd::take_fd_ownership;
use std::cmp::min;
use std::collections::{btree_map, BTreeMap};
use std::convert::TryInto;
use std::fs::File;
use std::io;
use std::os::unix::fs::FileExt;
-use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd};
+use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, OwnedFd};
use std::path::{Component, Path, PathBuf, MAIN_SEPARATOR};
use std::sync::{Arc, RwLock};
@@ -38,7 +39,8 @@
get_fsverity_metadata_path, parse_fsverity_metadata, FSVerityMetadata,
};
use binder::{
- BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, StatusCode, Strong,
+ BinderFeatures, ExceptionCode, Interface, IntoBinderResult, Result as BinderResult, Status,
+ StatusCode, Strong,
};
/// Bitflags of forbidden file mode, e.g. setuid, setgid and sticky bit.
@@ -299,9 +301,11 @@
mode,
)
.map_err(new_errno_error)?;
- // SAFETY: new_fd is just created and not an error.
- let new_file = unsafe { File::from_raw_fd(new_fd) };
- Ok((new_fd, FdConfig::ReadWrite(new_file)))
+ let new_fd = take_fd_ownership(new_fd)
+ .context("Failed to take ownership of fd for file")
+ .or_service_specific_exception(-1)?;
+ let new_file = File::from(new_fd);
+ Ok((new_file.as_raw_fd(), FdConfig::ReadWrite(new_file)))
}
_ => Err(new_errno_error(Errno::ENOTDIR)),
})
@@ -327,9 +331,10 @@
Mode::empty(),
)
.map_err(new_errno_error)?;
- // SAFETY: new_dir_fd is just created and not an error.
- let fd_owner = unsafe { OwnedFd::from_raw_fd(new_dir_fd) };
- Ok((new_dir_fd, FdConfig::OutputDir(fd_owner)))
+ let fd_owner = take_fd_ownership(new_dir_fd)
+ .context("Failed to take ownership of the fd for directory")
+ .or_service_specific_exception(-1)?;
+ Ok((fd_owner.as_raw_fd(), FdConfig::OutputDir(fd_owner)))
}
_ => Err(new_errno_error(Errno::ENOTDIR)),
})
@@ -408,9 +413,11 @@
fn open_readonly_at(dir_fd: BorrowedFd, path: &Path) -> nix::Result<File> {
let new_fd = openat(Some(dir_fd.as_raw_fd()), path, OFlag::O_RDONLY, Mode::empty())?;
- // SAFETY: new_fd is just created successfully and not owned.
- let new_file = unsafe { File::from_raw_fd(new_fd) };
- Ok(new_file)
+ let new_fd = take_fd_ownership(new_fd).map_err(|e| match e {
+ safe_ownedfd::Error::Errno(e) => e,
+ _ => Errno::UnknownErrno,
+ })?;
+ Ok(File::from(new_fd))
}
fn validate_and_cast_offset(offset: i64) -> Result<u64, Status> {
diff --git a/build/apex/product_packages.mk b/build/apex/product_packages.mk
index e710021..efa8dd5 100644
--- a/build/apex/product_packages.mk
+++ b/build/apex/product_packages.mk
@@ -62,3 +62,11 @@
$(error RELEASE_AVF_ENABLE_LLPVM_CHANGES must also be enabled)
endif
endif
+
+ifdef RELEASE_AVF_ENABLE_EARLY_VM
+ # We can't query TARGET_RELEASE from here, so we use RELEASE_AIDL_USE_UNFROZEN as a proxy value of
+ # whether we are building -next release.
+ ifneq ($(RELEASE_AIDL_USE_UNFROZEN),true)
+ $(error RELEASE_AVF_ENABLE_EARLY_VM can only be enabled in trunk_staging until b/357025924 is fixed)
+ endif
+endif
diff --git a/guest/authfs_service/Android.bp b/guest/authfs_service/Android.bp
index 2101a36..e508c17 100644
--- a/guest/authfs_service/Android.bp
+++ b/guest/authfs_service/Android.bp
@@ -18,6 +18,7 @@
"libnix",
"librpcbinder_rs",
"librustutils",
+ "libsafe_ownedfd",
"libshared_child",
],
prefer_rlib: true,
diff --git a/guest/authfs_service/src/main.rs b/guest/authfs_service/src/main.rs
index 97e684d..ff2f770 100644
--- a/guest/authfs_service/src/main.rs
+++ b/guest/authfs_service/src/main.rs
@@ -26,9 +26,10 @@
use log::*;
use rpcbinder::RpcServer;
use rustutils::sockets::android_get_control_socket;
+use safe_ownedfd::take_fd_ownership;
use std::ffi::OsString;
use std::fs::{create_dir, read_dir, remove_dir_all, remove_file};
-use std::os::unix::io::{FromRawFd, OwnedFd};
+use std::os::unix::io::OwnedFd;
use std::sync::atomic::{AtomicUsize, Ordering};
use authfs_aidl_interface::aidl::com::android::virt::fs::AuthFsConfig::AuthFsConfig;
@@ -109,22 +110,9 @@
}
/// Prepares a socket file descriptor for the authfs service.
-///
-/// # Safety requirement
-///
-/// The caller must ensure that this function is the only place that claims ownership
-/// of the file descriptor and it is called only once.
-unsafe fn prepare_authfs_service_socket() -> Result<OwnedFd> {
+fn prepare_authfs_service_socket() -> Result<OwnedFd> {
let raw_fd = android_get_control_socket(AUTHFS_SERVICE_SOCKET_NAME)?;
-
- // Creating OwnedFd for stdio FDs is not safe.
- if [libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO].contains(&raw_fd) {
- bail!("File descriptor {raw_fd} is standard I/O descriptor");
- }
- // SAFETY: Initializing OwnedFd for a RawFd created by the init.
- // We checked that the integer value corresponds to a valid FD and that the caller
- // ensures that this is the only place to claim its ownership.
- Ok(unsafe { OwnedFd::from_raw_fd(raw_fd) })
+ Ok(take_fd_ownership(raw_fd)?)
}
#[allow(clippy::eq_op)]
@@ -137,8 +125,7 @@
clean_up_working_directory()?;
- // SAFETY: This is the only place we take the ownership of the fd of the authfs service.
- let socket_fd = unsafe { prepare_authfs_service_socket()? };
+ let socket_fd = prepare_authfs_service_socket()?;
let service = AuthFsService::new_binder(debuggable).as_binder();
debug!("{} is starting as a rpc service.", AUTHFS_SERVICE_SOCKET_NAME);
let server = RpcServer::new_bound_socket(service, socket_fd)?;
diff --git a/guest/rialto/tests/test.rs b/guest/rialto/tests/test.rs
index 7c0d9dc..582b69e 100644
--- a/guest/rialto/tests/test.rs
+++ b/guest/rialto/tests/test.rs
@@ -68,14 +68,9 @@
#[test]
fn process_requests_in_non_protected_vm() -> Result<()> {
- check_processing_requests(VmType::NonProtectedVm, None)
-}
-
-#[ignore] // TODO(b/360077974): Figure out why this is flaky.
-#[test]
-fn process_requests_in_non_protected_vm_with_extra_ram() -> Result<()> {
const MEMORY_MB: i32 = 300;
- check_processing_requests(VmType::NonProtectedVm, Some(MEMORY_MB))
+ check_processing_requests(VmType::NonProtectedVm, Some(MEMORY_MB))?;
+ check_processing_requests(VmType::NonProtectedVm, None)
}
fn check_processing_requests(vm_type: VmType, vm_memory_mb: Option<i32>) -> Result<()> {
@@ -288,7 +283,9 @@
}
fn check_csr(csr: Vec<u8>) -> Result<()> {
- let _csr = rkp::Csr::from_cbor(&Session::default(), &csr[..]).context("Failed to parse CSR")?;
+ let mut session = Session::default();
+ session.set_allow_any_mode(true);
+ let _csr = rkp::Csr::from_cbor(&session, &csr[..]).context("Failed to parse CSR")?;
Ok(())
}
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java
index c5bc5fb..565b793 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java
@@ -59,6 +59,11 @@
return i;
}
+ public static void stopVmLauncherService(Context context) {
+ Intent i = buildVmLauncherServiceIntent(context);
+ context.stopService(i);
+ }
+
public static void startVmLauncherService(Context context, VmLauncherServiceCallback callback) {
Intent i = buildVmLauncherServiceIntent(context);
if (i == null) {
diff --git a/tests/ComposHostTestCases/java/android/compos/test/ComposTestCase.java b/tests/ComposHostTestCases/java/android/compos/test/ComposTestCase.java
index 7a35829..6e583c0 100644
--- a/tests/ComposHostTestCases/java/android/compos/test/ComposTestCase.java
+++ b/tests/ComposHostTestCases/java/android/compos/test/ComposTestCase.java
@@ -200,6 +200,7 @@
10000,
validator.getAbsolutePath(),
"dice-chain",
+ "--allow-any-mode",
bcc_file.getAbsolutePath());
assertWithMessage("hwtrust failed").about(command_results()).that(result).isSuccess();
}
diff --git a/tests/testapk/src/java/com/android/microdroid/test/HwTrustJni.java b/tests/testapk/src/java/com/android/microdroid/test/HwTrustJni.java
index 3b237aa..0cf0606 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/HwTrustJni.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/HwTrustJni.java
@@ -25,7 +25,8 @@
* Validates a DICE chain.
*
* @param diceChain The dice chain to validate.
+ * @param allowAnyMode Allow the chain's certificates to have any mode.
* @return true if the dice chain is valid, false otherwise.
*/
- public static native boolean validateDiceChain(byte[] diceChain);
+ public static native boolean validateDiceChain(byte[] diceChain, boolean allowAnyMode);
}
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index f21e18e..d38af45 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -1318,7 +1318,7 @@
grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
VirtualMachineConfig config =
newVmConfigBuilderWithPayloadConfig("assets/vm_config.json")
- .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setDebugLevel(DEBUG_LEVEL_NONE)
.build();
VirtualMachine vm = forceCreateNewVirtualMachine("bcc_vm_for_vsr", config);
TestResults testResults =
@@ -1331,7 +1331,11 @@
testResults.assertNoException();
byte[] bccBytes = testResults.mBcc;
assertThat(bccBytes).isNotNull();
- assertThat(HwTrustJni.validateDiceChain(bccBytes)).isTrue();
+
+ String buildType = SystemProperties.get("ro.build.type");
+ boolean nonUserBuild = !buildType.isEmpty() && buildType != "user";
+
+ assertThat(HwTrustJni.validateDiceChain(bccBytes, nonUserBuild)).isTrue();
}
@Test
diff --git a/tests/testapk/src/native/hwtrust_jni.rs b/tests/testapk/src/native/hwtrust_jni.rs
index 3b00364..058b1a6 100644
--- a/tests/testapk/src/native/hwtrust_jni.rs
+++ b/tests/testapk/src/native/hwtrust_jni.rs
@@ -29,6 +29,7 @@
env: JNIEnv,
_class: JClass,
dice_chain: JByteArray,
+ allow_any_mode: jboolean,
) -> jboolean {
android_logger::init_once(
android_logger::Config::default()
@@ -36,7 +37,7 @@
.with_max_level(log::LevelFilter::Debug),
);
debug!("Starting the DICE chain validation ...");
- match validate_dice_chain(env, dice_chain) {
+ match validate_dice_chain(env, dice_chain, allow_any_mode) {
Ok(_) => {
info!("DICE chain validated successfully");
true
@@ -49,9 +50,14 @@
.into()
}
-fn validate_dice_chain(env: JNIEnv, jdice_chain: JByteArray) -> Result<()> {
+fn validate_dice_chain(
+ env: JNIEnv,
+ jdice_chain: JByteArray,
+ allow_any_mode: jboolean,
+) -> Result<()> {
let dice_chain = env.convert_byte_array(jdice_chain)?;
- let session = Session::default();
+ let mut session = Session::default();
+ session.set_allow_any_mode(allow_any_mode == jboolean::from(true));
let _chain = dice::Chain::from_cbor(&session, &dice_chain)?;
Ok(())
}