Remove onPayloadStarted stream argument
microdroid_manager creates a vsock connection with the host and
redirects the payload's stdin/stdout/stderr streams over it. This may
not necessarily be a securiy issue if the app never writes any secrets
to its standard output, but it would be safer to not open up
a communication channel like that by default. If the payload wishes to
pass its logs to the host, it should open up the connection explicitly.
Remove the vsock connection, the virtualizationservice server and the
corresponding file descriptor argument of onPayloadStarted() callback.
Instead, provide onPayloadStdio() that the payload can optinally call
to set up the connection.
Bug: 245727626
Bug: 253221932
Test: atest -p packages/modules/Virtualization:avf-presubmit
Change-Id: I89fd3a52aae9272db7300224b88d87c6f4d8a8a7
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 68e1948..02459b2 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -240,15 +240,17 @@
struct Callback {}
impl vmclient::VmCallback for Callback {
- fn on_payload_started(&self, cid: i32, stream: Option<&File>) {
- if let Some(file) = stream {
- if let Err(e) = start_logging(file) {
- warn!("Can't log vm output: {}", e);
- };
- }
+ fn on_payload_started(&self, cid: i32) {
log::info!("VM payload started, cid = {}", cid);
}
+ fn on_payload_stdio(&self, cid: i32, stream: &File) {
+ if let Err(e) = start_logging(stream) {
+ log::warn!("Can't log vm output: {}", e);
+ };
+ log::info!("VM payload forwarded its stdio, cid = {}", cid);
+ }
+
fn on_payload_ready(&self, cid: i32) {
log::info!("VM payload ready, cid = {}", cid);
}
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index a4e3903..c280956 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -24,10 +24,10 @@
use anyhow::{bail, Result};
use compos_common::COMPOS_VSOCK_PORT;
-use log::{debug, error};
+use log::{debug, error, warn};
use rpcbinder::run_vsock_rpc_server;
use std::panic;
-use vm_payload_bindgen::AVmPayload_notifyPayloadReady;
+use vm_payload_bindgen::{AVmPayload_notifyPayloadReady, AVmPayload_setupStdioProxy};
fn main() {
if let Err(e) = try_main() {
@@ -44,6 +44,10 @@
panic::set_hook(Box::new(|panic_info| {
error!("{}", panic_info);
}));
+ // Redirect stdio to the host.
+ if !unsafe { AVmPayload_setupStdioProxy() } {
+ warn!("Failed to setup stdio proxy");
+ }
let service = compsvc::new_binder()?.as_binder();
debug!("compsvc is starting as a rpc service.");
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index b5ae3d5..ebc2bb3 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -169,13 +169,11 @@
private final ExecutorService mService = mExecutorService;
@Override
- public void onPayloadStarted(VirtualMachine vm,
- ParcelFileDescriptor stream) {
- if (stream == null) {
- mPayloadOutput.postValue("(no output available)");
- return;
- }
+ public void onPayloadStarted(VirtualMachine vm) {}
+ @Override
+ public void onPayloadStdio(VirtualMachine vm, ParcelFileDescriptor stream) {
+ mPayloadOutput.postValue("(Payload connected standard output...)");
InputStream input = new FileInputStream(stream.getFileDescriptor());
mService.execute(new Reader("payload", mPayloadOutput, input));
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 7c826b6..e30c856 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -643,9 +643,14 @@
mVirtualMachine.registerCallback(
new IVirtualMachineCallback.Stub() {
@Override
- public void onPayloadStarted(int cid, ParcelFileDescriptor stream) {
+ public void onPayloadStarted(int cid) {
+ executeCallback((cb) -> cb.onPayloadStarted(VirtualMachine.this));
+ }
+
+ @Override
+ public void onPayloadStdio(int cid, ParcelFileDescriptor stream) {
executeCallback(
- (cb) -> cb.onPayloadStarted(VirtualMachine.this, stream));
+ (cb) -> cb.onPayloadStdio(VirtualMachine.this, stream));
}
@Override
@@ -656,16 +661,20 @@
@Override
public void onPayloadFinished(int cid, int exitCode) {
executeCallback(
- (cb) -> cb.onPayloadFinished(VirtualMachine.this,
- exitCode));
+ (cb) ->
+ cb.onPayloadFinished(
+ VirtualMachine.this, exitCode));
}
@Override
public void onError(int cid, int errorCode, String message) {
int translatedError = getTranslatedError(errorCode);
executeCallback(
- (cb) -> cb.onError(VirtualMachine.this, translatedError,
- message));
+ (cb) ->
+ cb.onError(
+ VirtualMachine.this,
+ translatedError,
+ message));
}
@Override
@@ -674,18 +683,17 @@
int translatedReason = getTranslatedReason(reason);
if (onDiedCalled.compareAndSet(false, true)) {
executeCallback(
- (cb) -> cb.onStopped(VirtualMachine.this,
- translatedReason));
+ (cb) ->
+ cb.onStopped(
+ VirtualMachine.this, translatedReason));
}
}
@Override
public void onRamdump(int cid, ParcelFileDescriptor ramdump) {
- executeCallback(
- (cb) -> cb.onRamdump(VirtualMachine.this, ramdump));
+ executeCallback((cb) -> cb.onRamdump(VirtualMachine.this, ramdump));
}
- }
- );
+ });
service.asBinder().linkToDeath(deathRecipient, 0);
mVirtualMachine.start();
} catch (IOException | IllegalStateException | ServiceSpecificException e) {
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
index bb6b2b8..26b8ba2 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
@@ -18,7 +18,6 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.os.ParcelFileDescriptor;
@@ -135,11 +134,11 @@
/** The VM killed due to hangup */
int STOP_REASON_HANGUP = 16;
- /**
- * Called when the payload starts in the VM. The stream, if non-null, provides access
- * to the stdin/stdout of the VM payload.
- */
- void onPayloadStarted(@NonNull VirtualMachine vm, @Nullable ParcelFileDescriptor stream);
+ /** Called when the payload starts in the VM. */
+ void onPayloadStarted(@NonNull VirtualMachine vm);
+
+ /** Called when the payload creates a standard input/output stream. */
+ void onPayloadStdio(@NonNull VirtualMachine vm, @NonNull ParcelFileDescriptor stream);
/**
* Called when the payload in the VM is ready to serve. See
diff --git a/microdroid/vm_payload/Android.bp b/microdroid/vm_payload/Android.bp
index e153f92..dd2a937 100644
--- a/microdroid/vm_payload/Android.bp
+++ b/microdroid/vm_payload/Android.bp
@@ -14,6 +14,7 @@
"libanyhow",
"libbinder_rs",
"liblazy_static",
+ "liblibc",
"liblog_rust",
"librpcbinder_rs",
],
diff --git a/microdroid/vm_payload/include/vm_payload.h b/microdroid/vm_payload/include/vm_payload.h
index 82dbd6d..d5853a1 100644
--- a/microdroid/vm_payload/include/vm_payload.h
+++ b/microdroid/vm_payload/include/vm_payload.h
@@ -80,4 +80,13 @@
*/
const char *AVmPayload_getApkContentsPath(void);
+/**
+ * Initiates a socket connection with the host and duplicates stdin, stdout and
+ * stderr file descriptors to the socket.
+ *
+ * \return true on success and false on failure. If unsuccessful, the stdio FDs
+ * may be in an inconsistent state.
+ */
+bool AVmPayload_setupStdioProxy();
+
__END_DECLS
diff --git a/microdroid/vm_payload/src/lib.rs b/microdroid/vm_payload/src/lib.rs
index be6cf93..65b59bf 100644
--- a/microdroid/vm_payload/src/lib.rs
+++ b/microdroid/vm_payload/src/lib.rs
@@ -18,5 +18,5 @@
pub use vm_payload_service::{
AVmPayload_getDiceAttestationCdi, AVmPayload_getDiceAttestationChain,
- AVmPayload_getVmInstanceSecret, AVmPayload_notifyPayloadReady,
+ AVmPayload_getVmInstanceSecret, AVmPayload_notifyPayloadReady, AVmPayload_setupStdioProxy,
};
diff --git a/microdroid/vm_payload/src/vm_payload_service.rs b/microdroid/vm_payload/src/vm_payload_service.rs
index 098d246..e89f730 100644
--- a/microdroid/vm_payload/src/vm_payload_service.rs
+++ b/microdroid/vm_payload/src/vm_payload_service.rs
@@ -21,8 +21,11 @@
use lazy_static::lazy_static;
use log::{error, info, Level};
use rpcbinder::{get_unix_domain_rpc_interface, run_vsock_rpc_server};
+use std::io;
use std::ffi::CString;
+use std::fs::File;
use std::os::raw::{c_char, c_void};
+use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd};
lazy_static! {
static ref VM_APK_CONTENTS_PATH_C: CString =
@@ -202,6 +205,36 @@
get_vm_payload_service()?.getDiceAttestationCdi().context("Cannot get attestation CDI")
}
+/// Creates a socket connection with the host and duplicates standard I/O
+/// file descriptors of the payload to that socket. Then notifies the host.
+#[no_mangle]
+pub extern "C" fn AVmPayload_setupStdioProxy() -> bool {
+ if let Err(e) = try_setup_stdio_proxy() {
+ error!("{:?}", e);
+ false
+ } else {
+ info!("Successfully set up stdio proxy to the host");
+ true
+ }
+}
+
+fn dup2(old_fd: &File, new_fd: BorrowedFd) -> Result<(), io::Error> {
+ // SAFETY - ownership does not change, only modifies the underlying raw FDs.
+ match unsafe { libc::dup2(old_fd.as_raw_fd(), new_fd.as_raw_fd()) } {
+ -1 => Err(io::Error::last_os_error()),
+ _ => Ok(()),
+ }
+}
+
+fn try_setup_stdio_proxy() -> Result<()> {
+ let fd =
+ get_vm_payload_service()?.setupStdioProxy().context("Could not connect a host socket")?;
+ dup2(fd.as_ref(), io::stdin().as_fd()).context("Failed to dup stdin")?;
+ dup2(fd.as_ref(), io::stdout().as_fd()).context("Failed to dup stdout")?;
+ dup2(fd.as_ref(), io::stderr().as_fd()).context("Failed to dup stderr")?;
+ Ok(())
+}
+
fn get_vm_payload_service() -> Result<Strong<dyn IVmPayloadService>> {
get_unix_domain_rpc_interface(VM_PAYLOAD_SERVICE_SOCKET_NAME)
.context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_SOCKET_NAME))
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
index f8e7d34..1141965 100644
--- a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
+++ b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
@@ -16,6 +16,8 @@
package android.system.virtualization.payload;
+import android.os.ParcelFileDescriptor;
+
/**
* This interface regroups the tasks that payloads delegate to
* Microdroid Manager for execution.
@@ -61,4 +63,16 @@
* @throws SecurityException if the use of test APIs is not permitted.
*/
byte[] getDiceAttestationCdi();
+
+ /**
+ * Sets up a standard I/O proxy to the host.
+ *
+ * Creates a socket with the host and notifies its listeners that the stdio
+ * proxy is ready.
+ *
+ * Temporarily uses a random free port allocated by the OS.
+ * @return a file descriptor that the payload should dup() its standard I/O
+ * file descriptors to.
+ */
+ ParcelFileDescriptor setupStdioProxy();
}
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index c18dd26..762a149 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -26,7 +26,7 @@
use crate::vm_payload_service::register_vm_payload_service;
use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
- IVirtualMachineService, VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT,
+ IVirtualMachineService, VM_BINDER_SERVICE_PORT,
};
use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::VM_APK_CONTENTS_PATH;
use anyhow::{anyhow, bail, ensure, Context, Error, Result};
@@ -49,15 +49,13 @@
use std::borrow::Cow::{Borrowed, Owned};
use std::convert::TryInto;
use std::env;
-use std::fs::{self, create_dir, File, OpenOptions};
+use std::fs::{self, create_dir, OpenOptions};
use std::io::Write;
-use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::os::unix::process::ExitStatusExt;
use std::path::Path;
use std::process::{Child, Command, Stdio};
use std::str;
use std::time::{Duration, SystemTime};
-use vsock::VsockStream;
const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
const MAIN_APK_PATH: &str = "/dev/block/by-name/microdroid-apk";
@@ -732,7 +730,14 @@
/// virtualizationservice in the host side.
fn exec_task(task: &Task, service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
info!("executing main task {:?}...", task);
- let mut command = build_command(task)?;
+ let mut command = match task.type_ {
+ TaskType::Executable => Command::new(&task.command),
+ TaskType::MicrodroidLauncher => {
+ let mut command = Command::new("/system/bin/microdroid_launcher");
+ command.arg(find_library_path(&task.command)?);
+ command
+ }
+ };
info!("notifying payload started");
service.notifyPayloadStarted()?;
@@ -751,40 +756,6 @@
}
}
-fn build_command(task: &Task) -> Result<Command> {
- let mut command = match task.type_ {
- TaskType::Executable => Command::new(&task.command),
- TaskType::MicrodroidLauncher => {
- let mut command = Command::new("/system/bin/microdroid_launcher");
- command.arg(find_library_path(&task.command)?);
- command
- }
- };
-
- match VsockStream::connect_with_cid_port(VMADDR_CID_HOST, VM_STREAM_SERVICE_PORT as u32) {
- Ok(stream) => {
- // SAFETY: the ownership of the underlying file descriptor is transferred from stream
- // to the file object, and then into the Command object. When the command is finished,
- // the file descriptor is closed.
- let file = unsafe { File::from_raw_fd(stream.into_raw_fd()) };
- command
- .stdin(Stdio::from(file.try_clone()?))
- .stdout(Stdio::from(file.try_clone()?))
- .stderr(Stdio::from(file));
- }
- Err(e) => {
- error!("failed to connect to virtualization service: {}", e);
- // Don't fail hard here. Even if we failed to connect to the virtualizationservice,
- // we keep executing the task. This can happen if the owner of the VM doesn't register
- // callback to accept the stream. Use /dev/null as the stream so that the task can
- // make progress without waiting for someone to consume the output.
- command.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
- }
- }
-
- Ok(command)
-}
-
fn find_library_path(name: &str) -> Result<String> {
let mut watcher = PropertyWatcher::new("ro.product.cpu.abilist")?;
let value = watcher.read(|_name, value| Ok(value.trim().to_string()))?;
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index fcfc79d..249a2d8 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -18,15 +18,18 @@
use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME};
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
-use anyhow::{bail, Result};
-use binder::{Interface, BinderFeatures, ExceptionCode, Status, Strong};
+use anyhow::{bail, Context, Result};
+use binder::{Interface, BinderFeatures, ExceptionCode, ParcelFileDescriptor, Status, Strong};
use log::{error, info};
use openssl::hkdf::hkdf;
use openssl::md::Md;
use rpcbinder::run_init_unix_domain_rpc_server;
+use std::fs::File;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
+use std::os::unix::io::{FromRawFd, IntoRawFd};
+use vsock::VsockListener;
/// Implementation of `IVmPayloadService`.
struct VmPayloadService {
@@ -67,6 +70,16 @@
self.check_restricted_apis_allowed()?;
Ok(self.dice.cdi_attest.to_vec())
}
+
+ fn setupStdioProxy(&self) -> binder::Result<ParcelFileDescriptor> {
+ let f = self.setup_payload_stdio_proxy().map_err(|e| {
+ Status::new_service_specific_error_str(
+ -1,
+ Some(format!("Failed to create stdio proxy: {:?}", e)),
+ )
+ })?;
+ Ok(ParcelFileDescriptor::new(f))
+ }
}
impl Interface for VmPayloadService {}
@@ -89,6 +102,22 @@
Err(Status::new_exception_str(ExceptionCode::SECURITY, Some("Use of restricted APIs")))
}
}
+
+ fn setup_payload_stdio_proxy(&self) -> Result<File> {
+ // Instead of a predefined port in the host, we open up a port in the guest and have
+ // the host connect to it. This makes it possible to have per-app instances of VS.
+ const ANY_PORT: u32 = 0;
+ let listener = VsockListener::bind_with_cid_port(libc::VMADDR_CID_HOST, ANY_PORT)
+ .context("Failed to create vsock listener")?;
+ let addr = listener.local_addr().context("Failed to resolve listener port")?;
+ self.virtual_machine_service
+ .connectPayloadStdioProxy(addr.port() as i32)
+ .context("Failed to connect to the host")?;
+ let (stream, _) =
+ listener.accept().context("Failed to accept vsock connection from the host")?;
+ // SAFETY: ownership is transferred from stream to the new File
+ Ok(unsafe { File::from_raw_fd(stream.into_raw_fd()) })
+ }
}
/// Registers the `IVmPayloadService` service.
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 9fb7d91..1e57ff8 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -232,7 +232,10 @@
}
@Override
- public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {}
+ public void onPayloadStarted(VirtualMachine vm) {}
+
+ @Override
+ public void onPayloadStdio(VirtualMachine vm, ParcelFileDescriptor stream) {}
@Override
public void onPayloadReady(VirtualMachine vm) {}
@@ -327,7 +330,7 @@
VmEventListener listener =
new VmEventListener() {
@Override
- public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
+ public void onPayloadStarted(VirtualMachine vm) {
endTime.complete(System.nanoTime());
payloadStarted.complete(true);
forceStop(vm);
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 cc623a8..dba62b6 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -671,8 +671,9 @@
new VmEventListener() {
private void testVMService(VirtualMachine vm) {
try {
- ITestService testService = ITestService.Stub.asInterface(
- vm.connectToVsockServer(ITestService.SERVICE_PORT));
+ ITestService testService =
+ ITestService.Stub.asInterface(
+ vm.connectToVsockServer(ITestService.SERVICE_PORT));
testResults.mAddInteger = testService.addInteger(123, 456);
testResults.mAppRunProp =
testService.readProperty("debug.microdroid.app.run");
@@ -695,11 +696,16 @@
}
@Override
- public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
+ public void onPayloadStarted(VirtualMachine vm) {
Log.i(TAG, "onPayloadStarted");
payloadStarted.complete(true);
- logVmOutput(TAG, new FileInputStream(stream.getFileDescriptor()),
- "Payload");
+ }
+
+ @Override
+ public void onPayloadStdio(VirtualMachine vm, ParcelFileDescriptor stream) {
+ Log.i(TAG, "onPayloadStdio");
+ logVmOutput(
+ TAG, new FileInputStream(stream.getFileDescriptor()), "Payload");
}
};
listener.runToFinish(TAG, vm);
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 48942dc..1b18ce9 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -158,6 +158,9 @@
} // Anonymous namespace
extern "C" int AVmPayload_main() {
+ // Forward standard I/O to the host.
+ AVmPayload_setupStdioProxy();
+
// disable buffering to communicate seamlessly
setvbuf(stdin, nullptr, _IONBF, 0);
setvbuf(stdout, nullptr, _IONBF, 0);
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
index 8d6ed08..521cf12 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
@@ -24,13 +24,14 @@
*/
oneway interface IVirtualMachineCallback {
/**
- * Called when the payload starts in the VM. `stream` is the input/output port of the payload.
- *
- * <p>Note: when the virtual machine object is shared to multiple processes and they register
- * this callback to the same virtual machine object, the processes will compete to access the
- * same payload stream. Keep only one process to access the stream.
+ * Called when the payload starts in the VM.
*/
- void onPayloadStarted(int cid, in @nullable ParcelFileDescriptor stream);
+ void onPayloadStarted(int cid);
+
+ /**
+ * Called when the payload provides access to its standard input/output via a socket.
+ */
+ void onPayloadStdio(int cid, in ParcelFileDescriptor fd);
/**
* Called when the payload in the VM is ready to serve.
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
index e8c1724..deee662 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -21,12 +21,6 @@
interface IVirtualMachineService {
/**
* Port number that VirtualMachineService listens on connections from the guest VMs for the
- * payload input and output.
- */
- const int VM_STREAM_SERVICE_PORT = 3000;
-
- /**
- * Port number that VirtualMachineService listens on connections from the guest VMs for the
* VirtualMachineService binder service.
*/
const int VM_BINDER_SERVICE_PORT = 5000;
@@ -53,7 +47,12 @@
void notifyPayloadFinished(int exitCode);
/**
- * Notifies that an error has occurred inside the VM..
+ * Notifies that an error has occurred inside the VM.
*/
void notifyError(ErrorCode errorCode, in String message);
+
+ /**
+ * Notifies that the guest has started a stdio proxy on the given port.
+ */
+ void connectPayloadStdioProxy(int port);
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index bc697e3..340fc68 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -38,7 +38,7 @@
};
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
BnVirtualMachineService, IVirtualMachineService, VM_BINDER_SERVICE_PORT,
- VM_STREAM_SERVICE_PORT, VM_TOMBSTONES_SERVICE_PORT,
+ VM_TOMBSTONES_SERVICE_PORT,
};
use anyhow::{anyhow, bail, Context, Result};
use apkverify::{HashAlgorithm, V4Signature};
@@ -301,12 +301,6 @@
pub fn init() -> VirtualizationService {
let service = VirtualizationService::default();
- // server for payload output
- let state = service.state.clone(); // reference to state (not the state itself) is copied
- std::thread::spawn(move || {
- handle_stream_connection_from_vm(state).unwrap();
- });
-
std::thread::spawn(|| {
if let Err(e) = handle_stream_connection_tombstoned() {
warn!("Error receiving tombstone from guest or writing them. Error: {:?}", e);
@@ -488,33 +482,6 @@
}
}
-/// Waits for incoming connections from VM. If a new connection is made, stores the stream in the
-/// corresponding `VmInstance`.
-fn handle_stream_connection_from_vm(state: Arc<Mutex<State>>) -> Result<()> {
- let listener =
- VsockListener::bind_with_cid_port(VMADDR_CID_HOST, VM_STREAM_SERVICE_PORT as u32)?;
- for stream in listener.incoming() {
- let stream = match stream {
- Err(e) => {
- warn!("invalid incoming connection: {:?}", e);
- continue;
- }
- Ok(s) => s,
- };
- if let Ok(addr) = stream.peer_addr() {
- let cid = addr.cid();
- let port = addr.port();
- info!("payload stream connected from cid={}, port={}", cid, port);
- if let Some(vm) = state.lock().unwrap().get_vm(cid) {
- *vm.stream.lock().unwrap() = Some(stream);
- } else {
- error!("connection from cid={} is not from a guest VM", cid);
- }
- }
- }
- Ok(())
-}
-
fn write_zero_filler(zero_filler_path: &Path) -> Result<()> {
let file = OpenOptions::new()
.create_new(true)
@@ -854,11 +821,10 @@
impl VirtualMachineCallbacks {
/// Call all registered callbacks to notify that the payload has started.
- pub fn notify_payload_started(&self, cid: Cid, stream: Option<VsockStream>) {
+ pub fn notify_payload_started(&self, cid: Cid) {
let callbacks = &*self.0.lock().unwrap();
- let pfd = stream.map(vsock_stream_to_pfd);
for callback in callbacks {
- if let Err(e) = callback.onPayloadStarted(cid as i32, pfd.as_ref()) {
+ if let Err(e) = callback.onPayloadStarted(cid as i32) {
error!("Error notifying payload start event from VM CID {}: {:?}", cid, e);
}
}
@@ -894,6 +860,16 @@
}
}
+ /// Call all registered callbacks to notify that the payload has provided a standard I/O proxy.
+ pub fn notify_payload_stdio(&self, cid: Cid, fd: ParcelFileDescriptor) {
+ let callbacks = &*self.0.lock().unwrap();
+ for callback in callbacks {
+ if let Err(e) = callback.onPayloadStdio(cid as i32, &fd) {
+ error!("Error notifying payload stdio event from VM CID {}: {:?}", cid, e);
+ }
+ }
+ }
+
/// Call all registered callbacks to say that the VM has died.
pub fn callback_on_died(&self, cid: Cid, reason: DeathReason) {
let callbacks = &*self.0.lock().unwrap();
@@ -1072,8 +1048,7 @@
vm.update_payload_state(PayloadState::Started).map_err(|e| {
Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
})?;
- let stream = vm.stream.lock().unwrap().take();
- vm.callbacks.notify_payload_started(cid, stream);
+ vm.callbacks.notify_payload_started(cid);
let vm_start_timestamp = vm.vm_start_timestamp.lock().unwrap();
write_vm_booted_stats(vm.requester_uid as i32, &vm.name, *vm_start_timestamp);
@@ -1140,6 +1115,27 @@
))
}
}
+
+ fn connectPayloadStdioProxy(&self, port: i32) -> binder::Result<()> {
+ let cid = self.cid;
+ if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
+ info!("VM with CID {} started a stdio proxy", cid);
+ let stream = VsockStream::connect_with_cid_port(cid, port as u32).map_err(|e| {
+ Status::new_service_specific_error_str(
+ -1,
+ Some(format!("Failed to connect to guest stdio proxy: {:?}", e)),
+ )
+ })?;
+ vm.callbacks.notify_payload_stdio(cid, vsock_stream_to_pfd(stream));
+ Ok(())
+ } else {
+ error!("connectPayloadStdioProxy is called from an unknown CID {}", cid);
+ Err(Status::new_service_specific_error_str(
+ -1,
+ Some(format!("cannot find a VM with CID {}", cid)),
+ ))
+ }
+ }
}
impl VirtualMachineService {
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 1b8061e..29040b7 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -35,7 +35,6 @@
use std::sync::{Arc, Condvar, Mutex};
use std::time::{Duration, SystemTime};
use std::thread;
-use vsock::VsockStream;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::DeathReason::DeathReason;
use binder::Strong;
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
@@ -190,8 +189,6 @@
pub requester_debug_pid: i32,
/// Callbacks to clients of the VM.
pub callbacks: VirtualMachineCallbacks,
- /// Input/output stream of the payload run in the VM.
- pub stream: Mutex<Option<VsockStream>>,
/// VirtualMachineService binder object for the VM.
pub vm_service: Mutex<Option<Strong<dyn IVirtualMachineService>>>,
/// Recorded timestamp when the VM is started.
@@ -223,7 +220,6 @@
requester_uid,
requester_debug_pid,
callbacks: Default::default(),
- stream: Mutex::new(None),
vm_service: Mutex::new(None),
vm_start_timestamp: Mutex::new(None),
payload_state: Mutex::new(PayloadState::Starting),
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 53402da..7cd5a19 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -276,19 +276,22 @@
struct Callback {}
impl vmclient::VmCallback for Callback {
- fn on_payload_started(&self, _cid: i32, stream: Option<&File>) {
+ fn on_payload_started(&self, _cid: i32) {
+ eprintln!("payload started");
+ }
+
+ fn on_payload_stdio(&self, _cid: i32, stream: &File) {
+ eprintln!("connecting payload stdio...");
// Show the output of the payload
- if let Some(stream) = stream {
- let mut reader = BufReader::new(stream.try_clone().unwrap());
- std::thread::spawn(move || loop {
- let mut s = String::new();
- match reader.read_line(&mut s) {
- Ok(0) => break,
- Ok(_) => print!("{}", s),
- Err(e) => eprintln!("error reading from virtual machine: {}", e),
- };
- });
- }
+ let mut reader = BufReader::new(stream.try_clone().unwrap());
+ std::thread::spawn(move || loop {
+ let mut s = String::new();
+ match reader.read_line(&mut s) {
+ Ok(0) => break,
+ Ok(_) => print!("{}", s),
+ Err(e) => eprintln!("error reading from virtual machine: {}", e),
+ };
+ });
}
fn on_payload_ready(&self, _cid: i32) {
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index e6f32b4..1dd553c 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -74,12 +74,15 @@
pub trait VmCallback {
/// Called when the payload has been started within the VM. If present, `stream` is connected
/// to the stdin/stdout of the payload.
- fn on_payload_started(&self, cid: i32, stream: Option<&File>) {}
+ fn on_payload_started(&self, cid: i32) {}
/// Callend when the payload has notified Virtualization Service that it is ready to serve
/// clients.
fn on_payload_ready(&self, cid: i32) {}
+ /// Called by the payload to forward its standard I/O streams to the host.
+ fn on_payload_stdio(&self, cid: i32, fd: &File);
+
/// Called when the payload has exited in the VM. `exit_code` is the exit code of the payload
/// process.
fn on_payload_finished(&self, cid: i32, exit_code: i32) {}
@@ -269,14 +272,17 @@
impl Interface for VirtualMachineCallback {}
impl IVirtualMachineCallback for VirtualMachineCallback {
- fn onPayloadStarted(
- &self,
- cid: i32,
- stream: Option<&ParcelFileDescriptor>,
- ) -> BinderResult<()> {
+ fn onPayloadStarted(&self, cid: i32) -> BinderResult<()> {
self.state.notify_state(VirtualMachineState::STARTED);
if let Some(ref callback) = self.client_callback {
- callback.on_payload_started(cid, stream.map(ParcelFileDescriptor::as_ref));
+ callback.on_payload_started(cid);
+ }
+ Ok(())
+ }
+
+ fn onPayloadStdio(&self, cid: i32, stream: &ParcelFileDescriptor) -> BinderResult<()> {
+ if let Some(ref callback) = self.client_callback {
+ callback.on_payload_stdio(cid, stream.as_ref());
}
Ok(())
}