Merge "pvmfw: Zeroize BCC before jumping to payload"
diff --git a/authfs/fd_server/src/main.rs b/authfs/fd_server/src/main.rs
index 21d0e64..9d97423 100644
--- a/authfs/fd_server/src/main.rs
+++ b/authfs/fd_server/src/main.rs
@@ -37,7 +37,8 @@
use aidl::{FdConfig, FdService};
use authfs_fsverity_metadata::parse_fsverity_metadata;
-const RPC_SERVICE_PORT: u32 = 3264; // TODO: support dynamic port for multiple fd_server instances
+// TODO(b/259920193): support dynamic port for multiple fd_server instances
+const RPC_SERVICE_PORT: u32 = 3264;
fn is_fd_valid(fd: i32) -> bool {
// SAFETY: a query-only syscall
@@ -137,7 +138,8 @@
debug!("fd_server is starting as a rpc service.");
let service = FdService::new_binder(fd_pool).as_binder();
- let server = RpcServer::new_vsock(service, RPC_SERVICE_PORT)?;
+ // TODO(b/259920193): Only accept connections from the intended guest VM.
+ let server = RpcServer::new_vsock(service, libc::VMADDR_CID_ANY, RPC_SERVICE_PORT)?;
debug!("fd_server is ready");
// Close the ready-fd if we were given one to signal our readiness.
diff --git a/compos/common/lib.rs b/compos/common/lib.rs
index c9555d5..8d49ff0 100644
--- a/compos/common/lib.rs
+++ b/compos/common/lib.rs
@@ -21,9 +21,6 @@
pub mod odrefresh;
pub mod timeouts;
-/// Special CID indicating "any".
-pub const VMADDR_CID_ANY: u32 = -1i32 as u32;
-
/// VSock port that the CompOS server listens on for RPC binder connections. This should be out of
/// future port range (if happens) that microdroid may reserve for system components.
pub const COMPOS_VSOCK_PORT: u32 = 6432;
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index 77f2ee7..54d7420 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -238,13 +238,6 @@
mService.shutdownNow();
mStatus.postValue(VirtualMachine.STATUS_STOPPED);
}
-
- @Override
- public void onRamdump(VirtualMachine vm, ParcelFileDescriptor ramdump) {
- if (!mService.isShutdown()) {
- mPayloadOutput.postValue("(Kernel panic. Ramdump created)");
- }
- }
};
try {
diff --git a/docs/debug/ramdump.md b/docs/debug/ramdump.md
index a0d9bf2..771c608 100644
--- a/docs/debug/ramdump.md
+++ b/docs/debug/ramdump.md
@@ -1,6 +1,6 @@
# Doing RAM dump of a Microdroid VM and analyzing it
-A Microdroid VM creates a RAM dump of itself when the kernel panics. This
+A debuggable Microdroid VM creates a RAM dump of itself when the kernel panics. This
document explains how the dump can be obtained and analyzed.
## Force triggering a RAM dump
@@ -49,7 +49,7 @@
## Obtaining the RAM dump
-By default, RAM dumps are sent to tombstone. To see which tombstone file is for
+RAM dumps are sent to tombstone. To see which tombstone file is for
the RAM dump, look into the log.
```shell
@@ -64,15 +64,6 @@
$ adb root && adb pull /data/tombstones/tombstone_47 ramdump && adb unroot
```
-Alternatively, you can specify the path to where RAM dump is stored when
-launching the VM using the `--ramdump` option of the `vm` tool.
-
-```shell
-$ adb shelll /apex/com.android.virt/bin/vm run-app --ramdump /data/local/tmp/virt/ramdump ...
-```
-
-In the above example, the RAM dump is saved to `/data/local/tmp/virt/ramdump`.
-
## Analyzing the RAM dump
### Building the crash(8) tool
@@ -151,9 +142,3 @@
actually triggered a crash in the kernel.
For more commands of crash(8), refer to the man page, or embedded `help` command.
-
-
-
-
-
-
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
index 9c8311d..7140ae2 100644
--- a/encryptedstore/src/main.rs
+++ b/encryptedstore/src/main.rs
@@ -137,7 +137,10 @@
fn mount(source: &Path, mountpoint: &Path) -> Result<()> {
create_dir_all(mountpoint).context(format!("Failed to create {:?}", &mountpoint))?;
- let mount_options = CString::new("").unwrap();
+ let mount_options = CString::new(
+ "fscontext=u:object_r:encryptedstore_fs:s0,context=u:object_r:encryptedstore_file:s0",
+ )
+ .unwrap();
let source = CString::new(source.as_os_str().as_bytes())?;
let mountpoint = CString::new(mountpoint.as_os_str().as_bytes())?;
let fstype = CString::new("ext4").unwrap();
diff --git a/javalib/api/system-current.txt b/javalib/api/system-current.txt
index fb7c98c..d14d83c 100644
--- a/javalib/api/system-current.txt
+++ b/javalib/api/system-current.txt
@@ -4,9 +4,8 @@
public class VirtualMachine implements java.lang.AutoCloseable {
method public void clearCallback();
method public void close();
- method @NonNull public android.os.IBinder connectToVsockServer(int) throws android.system.virtualmachine.VirtualMachineException;
- method @NonNull public android.os.ParcelFileDescriptor connectVsock(int) throws android.system.virtualmachine.VirtualMachineException;
- method public int getCid() throws android.system.virtualmachine.VirtualMachineException;
+ method @NonNull public android.os.IBinder connectToVsockServer(@IntRange(from=android.system.virtualmachine.VirtualMachine.MIN_VSOCK_PORT, to=android.system.virtualmachine.VirtualMachine.MAX_VSOCK_PORT) long) throws android.system.virtualmachine.VirtualMachineException;
+ method @NonNull public android.os.ParcelFileDescriptor connectVsock(@IntRange(from=android.system.virtualmachine.VirtualMachine.MIN_VSOCK_PORT, to=android.system.virtualmachine.VirtualMachine.MAX_VSOCK_PORT) long) throws android.system.virtualmachine.VirtualMachineException;
method @NonNull public android.system.virtualmachine.VirtualMachineConfig getConfig();
method @NonNull public java.io.InputStream getConsoleOutput() throws android.system.virtualmachine.VirtualMachineException;
method @NonNull public java.io.InputStream getLogOutput() throws android.system.virtualmachine.VirtualMachineException;
@@ -18,6 +17,8 @@
method public void stop() throws android.system.virtualmachine.VirtualMachineException;
method @NonNull public android.system.virtualmachine.VirtualMachineDescriptor toDescriptor() throws android.system.virtualmachine.VirtualMachineException;
field public static final String MANAGE_VIRTUAL_MACHINE_PERMISSION = "android.permission.MANAGE_VIRTUAL_MACHINE";
+ field public static final long MAX_VSOCK_PORT = 4294967295L; // 0xffffffffL
+ field public static final long MIN_VSOCK_PORT = 1024L; // 0x400L
field public static final int STATUS_DELETED = 2; // 0x2
field public static final int STATUS_RUNNING = 1; // 0x1
field public static final int STATUS_STOPPED = 0; // 0x0
@@ -29,7 +30,6 @@
method public void onPayloadFinished(@NonNull android.system.virtualmachine.VirtualMachine, int);
method public void onPayloadReady(@NonNull android.system.virtualmachine.VirtualMachine);
method public void onPayloadStarted(@NonNull android.system.virtualmachine.VirtualMachine);
- method public void onRamdump(@NonNull android.system.virtualmachine.VirtualMachine, @NonNull android.os.ParcelFileDescriptor);
method public void onStopped(@NonNull android.system.virtualmachine.VirtualMachine, int);
field public static final int ERROR_PAYLOAD_CHANGED = 2; // 0x2
field public static final int ERROR_PAYLOAD_INVALID_CONFIG = 3; // 0x3
@@ -58,10 +58,12 @@
public final class VirtualMachineConfig {
method @NonNull public String getApkPath();
method @NonNull public int getDebugLevel();
+ method @IntRange(from=0) public long getEncryptedStorageKib();
method @IntRange(from=0) public int getMemoryMib();
method @IntRange(from=1) public int getNumCpus();
method @Nullable public String getPayloadBinaryPath();
method public boolean isCompatibleWith(@NonNull android.system.virtualmachine.VirtualMachineConfig);
+ method public boolean isEncryptedStorageEnabled();
method public boolean isProtectedVm();
field public static final int DEBUG_LEVEL_FULL = 1; // 0x1
field public static final int DEBUG_LEVEL_NONE = 0; // 0x0
@@ -72,7 +74,8 @@
method @NonNull public android.system.virtualmachine.VirtualMachineConfig build();
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setApkPath(@NonNull String);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setDebugLevel(int);
- method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setMemoryMib(@IntRange(from=0) int);
+ method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setEncryptedStorageKib(@IntRange(from=1) long);
+ method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setMemoryMib(@IntRange(from=1) int);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setNumCpus(@IntRange(from=1) int);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadBinaryPath(@NonNull String);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setProtectedVm(boolean);
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index b8be703..1ea6714 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -45,9 +45,11 @@
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.ComponentCallbacks2;
@@ -121,6 +123,24 @@
"android.permission.USE_CUSTOM_VIRTUAL_MACHINE";
/**
+ * The lowest port number that can be used to communicate with the virtual machine payload.
+ *
+ * @see #connectToVsockServer
+ * @see #connectVsock
+ */
+ @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock.
+ public static final long MIN_VSOCK_PORT = 1024;
+
+ /**
+ * The highest port number that can be used to communicate with the virtual machine payload.
+ *
+ * @see #connectToVsockServer
+ * @see #connectVsock
+ */
+ @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock.
+ public static final long MAX_VSOCK_PORT = (1L << 32) - 1;
+
+ /**
* Status of a virtual machine
*
* @hide
@@ -169,6 +189,9 @@
/** Size of the instance image. 10 MB. */
private static final long INSTANCE_FILE_SIZE = 10 * 1024 * 1024;
+ /** Name of the file backing the encrypted storage */
+ private static final String ENCRYPTED_STORE_FILE = "storage.img";
+
/** The package which owns this VM. */
@NonNull private final String mPackageName;
@@ -191,6 +214,9 @@
/** Path to the idsig file for this VM. */
@NonNull private final File mIdsigFilePath;
+ /** File that backs the encrypted storage - Will be null if not enabled. */
+ @Nullable private final File mEncryptedStoreFilePath;
+
/**
* Unmodifiable list of extra apks. Apks are specified by the vm config, and corresponding
* idsigs are to be generated.
@@ -324,6 +350,10 @@
mExtraApks = setupExtraApks(context, config, thisVmDir);
mMemoryManagementCallbacks = new MemoryManagementCallbacks();
mContext = context;
+ mEncryptedStoreFilePath =
+ (config.isEncryptedStorageEnabled())
+ ? new File(thisVmDir, ENCRYPTED_STORE_FILE)
+ : null;
}
/**
@@ -354,6 +384,16 @@
throw new VirtualMachineException("failed to create instance image", e);
}
vm.importInstanceFrom(vmDescriptor.getInstanceImgFd());
+
+ if (vmDescriptor.getEncryptedStoreFd() != null) {
+ try {
+ vm.mEncryptedStoreFilePath.createNewFile();
+ } catch (IOException e) {
+ throw new VirtualMachineException(
+ "failed to create encrypted storage image", e);
+ }
+ vm.importEncryptedStoreFrom(vmDescriptor.getEncryptedStoreFd());
+ }
return vm;
} catch (VirtualMachineException | RuntimeException e) {
// If anything goes wrong, delete any files created so far and the VM's directory
@@ -386,6 +426,14 @@
} catch (IOException e) {
throw new VirtualMachineException("failed to create instance image", e);
}
+ if (config.isEncryptedStorageEnabled()) {
+ try {
+ vm.mEncryptedStoreFilePath.createNewFile();
+ } catch (IOException e) {
+ throw new VirtualMachineException(
+ "failed to create encrypted storage image", e);
+ }
+ }
IVirtualizationService service =
IVirtualizationService.Stub.asInterface(
@@ -403,6 +451,22 @@
} catch (ServiceSpecificException | IllegalArgumentException e) {
throw new VirtualMachineException("failed to create instance partition", e);
}
+
+ if (config.isEncryptedStorageEnabled()) {
+ try {
+ service.initializeWritablePartition(
+ ParcelFileDescriptor.open(vm.mEncryptedStoreFilePath, MODE_READ_WRITE),
+ config.getEncryptedStorageKib() * 1024L,
+ PartitionType.ENCRYPTEDSTORE);
+ } catch (FileNotFoundException e) {
+ throw new VirtualMachineException("encrypted storage image missing", e);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (ServiceSpecificException | IllegalArgumentException e) {
+ throw new VirtualMachineException(
+ "failed to create encrypted storage partition", e);
+ }
+ }
return vm;
} catch (VirtualMachineException | RuntimeException e) {
// If anything goes wrong, delete any files created so far and the VM's directory
@@ -432,7 +496,9 @@
if (!vm.mInstanceFilePath.exists()) {
throw new VirtualMachineException("instance image missing");
}
-
+ if (config.isEncryptedStorageEnabled() && !vm.mEncryptedStoreFilePath.exists()) {
+ throw new VirtualMachineException("Storage image missing");
+ }
return vm;
}
@@ -564,9 +630,22 @@
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
+ // It's stopped, but we still have a reference to it - we can fix that.
+ dropVm();
}
- // If we have an IVirtualMachine in the running state return it, otherwise throw.
+ /**
+ * This should only be called when we know our VM has stopped; we no longer need to hold a
+ * reference to it (this allows resources to be GC'd) and we no longer need to be informed of
+ * memory pressure.
+ */
+ @GuardedBy("mLock")
+ private void dropVm() {
+ mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks);
+ mVirtualMachine = null;
+ }
+
+ /** If we have an IVirtualMachine in the running state return it, otherwise throw. */
@GuardedBy("mLock")
private IVirtualMachine getRunningVm() throws VirtualMachineException {
try {
@@ -681,8 +760,12 @@
// Re-open idsig file in read-only mode
appConfig.idsig = ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_ONLY);
- appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath,
- MODE_READ_WRITE);
+ appConfig.instanceImage =
+ ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE);
+ if (mEncryptedStoreFilePath != null) {
+ appConfig.encryptedStorageImage =
+ ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_WRITE);
+ }
List<ParcelFileDescriptor> extraIdsigs = new ArrayList<>();
for (ExtraApkSpec extraApk : mExtraApks) {
extraIdsigs.add(ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_ONLY));
@@ -746,11 +829,6 @@
VirtualMachine.this, translatedReason));
}
}
-
- @Override
- public void onRamdump(int cid, ParcelFileDescriptor ramdump) {
- executeCallback((cb) -> cb.onRamdump(VirtualMachine.this, ramdump));
- }
});
mContext.registerComponentCallbacks(mMemoryManagementCallbacks);
service.asBinder().linkToDeath(deathRecipient, 0);
@@ -829,8 +907,7 @@
}
try {
mVirtualMachine.stop();
- mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks);
- mVirtualMachine = null;
+ dropVm();
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} catch (ServiceSpecificException e) {
@@ -855,8 +932,7 @@
try {
if (stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) {
mVirtualMachine.stop();
- mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks);
- mVirtualMachine = null;
+ dropVm();
}
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
@@ -890,23 +966,6 @@
}
/**
- * Returns the CID of this virtual machine, if it is running.
- *
- * @throws VirtualMachineException if the virtual machine is not running.
- * @hide
- */
- @SystemApi
- public int getCid() throws VirtualMachineException {
- synchronized (mLock) {
- try {
- return getRunningVm().getCid();
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
- }
-
- /**
* Changes the config of this virtual machine to a new one. This can be used to adjust things
* like the number of CPU and size of the RAM, depending on the situation (e.g. the size of the
* application to run on the virtual machine, etc.)
@@ -954,9 +1013,13 @@
*/
@SystemApi
@NonNull
- public IBinder connectToVsockServer(int port) throws VirtualMachineException {
+ public IBinder connectToVsockServer(
+ @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
+ throws VirtualMachineException {
+
synchronized (mLock) {
- IBinder iBinder = nativeConnectToVsockServer(getRunningVm().asBinder(), port);
+ IBinder iBinder =
+ nativeConnectToVsockServer(getRunningVm().asBinder(), validatePort(port));
if (iBinder == null) {
throw new VirtualMachineException("Failed to connect to vsock server");
}
@@ -972,10 +1035,12 @@
*/
@SystemApi
@NonNull
- public ParcelFileDescriptor connectVsock(int port) throws VirtualMachineException {
+ public ParcelFileDescriptor connectVsock(
+ @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
+ throws VirtualMachineException {
synchronized (mLock) {
try {
- return getRunningVm().connectVsock(port);
+ return getRunningVm().connectVsock(validatePort(port));
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} catch (ServiceSpecificException e) {
@@ -984,6 +1049,16 @@
}
}
+ private int validatePort(long port) {
+ // Ports below 1024 are "privileged" (payload code can't bind to these), and port numbers
+ // are 32-bit unsigned numbers at the OS level, even though we pass them as 32-bit signed
+ // numbers internally.
+ if (port < MIN_VSOCK_PORT || port > MAX_VSOCK_PORT) {
+ throw new IllegalArgumentException("Bad port " + port);
+ }
+ return (int) port;
+ }
+
/**
* Returns the root directory where all files related to this {@link VirtualMachine} (e.g.
* {@code instance.img}, {@code apk.idsig}, etc) are stored.
@@ -1017,7 +1092,10 @@
try {
return new VirtualMachineDescriptor(
ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY),
- ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY));
+ ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY),
+ mEncryptedStoreFilePath != null
+ ? ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_ONLY)
+ : null);
} catch (IOException e) {
throw new VirtualMachineException(e);
}
@@ -1183,4 +1261,14 @@
throw new VirtualMachineException("failed to transfer instance image", e);
}
}
+
+ private void importEncryptedStoreFrom(@NonNull ParcelFileDescriptor encryptedStoreFd)
+ throws VirtualMachineException {
+ try (FileChannel storeOutput = new FileOutputStream(mEncryptedStoreFilePath).getChannel();
+ FileChannel storeInput = new AutoCloseInputStream(encryptedStoreFd).getChannel()) {
+ storeOutput.transferFrom(storeInput, /*position=*/ 0, storeInput.size());
+ } catch (IOException e) {
+ throw new VirtualMachineException("failed to transfer encryptedstore image", e);
+ }
+ }
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
index fad2fa9..9aaecf0 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.os.ParcelFileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -155,7 +154,4 @@
/** Called when the VM has stopped. */
void onStopped(@NonNull VirtualMachine vm, @StopReason int reason);
-
- /** Called when kernel panic occurs and as a result ramdump is generated from the VM. */
- void onRamdump(@NonNull VirtualMachine vm, @NonNull ParcelFileDescriptor ramdump);
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index a9e062a..75e5414 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -55,6 +55,8 @@
*/
@SystemApi
public final class VirtualMachineConfig {
+ private static final String[] EMPTY_STRING_ARRAY = {};
+
// These define the schema of the config file persisted on disk.
private static final int VERSION = 2;
private static final String KEY_VERSION = "version";
@@ -65,6 +67,7 @@
private static final String KEY_PROTECTED_VM = "protectedVm";
private static final String KEY_MEMORY_MIB = "memoryMib";
private static final String KEY_NUM_CPUS = "numCpus";
+ private static final String KEY_ENCRYPTED_STORAGE_KIB = "encryptedStorageKib";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -120,6 +123,9 @@
*/
@Nullable private final String mPayloadBinaryPath;
+ /** The size of storage in KiB. 0 indicates that encryptedStorage is not required */
+ private final long mEncryptedStorageKib;
+
private VirtualMachineConfig(
@NonNull String apkPath,
@Nullable String payloadConfigPath,
@@ -127,47 +133,9 @@
@DebugLevel int debugLevel,
boolean protectedVm,
int memoryMib,
- int numCpus) {
- requireNonNull(apkPath);
- if (!apkPath.startsWith("/")) {
- throw new IllegalArgumentException("APK path must be an absolute path");
- }
-
- if (memoryMib < 0) {
- throw new IllegalArgumentException("Memory size cannot be negative");
- }
-
- int availableCpus = Runtime.getRuntime().availableProcessors();
- if (numCpus < 1 || numCpus > availableCpus) {
- throw new IllegalArgumentException("Number of vCPUs (" + numCpus + ") is out of "
- + "range [1, " + availableCpus + "]");
- }
-
- if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) {
- throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel);
- }
-
- if (payloadBinaryPath == null) {
- if (payloadConfigPath == null) {
- throw new IllegalStateException("setPayloadBinaryPath must be called");
- }
- } else {
- if (payloadConfigPath != null) {
- throw new IllegalStateException(
- "setPayloadBinaryPath and setPayloadConfigPath may not both be called");
- }
- }
-
- if (protectedVm
- && !HypervisorProperties.hypervisor_protected_vm_supported().orElse(false)) {
- throw new UnsupportedOperationException(
- "Protected VMs are not supported on this device.");
- }
- if (!protectedVm && !HypervisorProperties.hypervisor_vm_supported().orElse(false)) {
- throw new UnsupportedOperationException(
- "Unprotected VMs are not supported on this device.");
- }
-
+ int numCpus,
+ long encryptedStorageKib) {
+ // This is only called from Builder.build(); the builder handles parameter validation.
mApkPath = apkPath;
mPayloadConfigPath = payloadConfigPath;
mPayloadBinaryPath = payloadBinaryPath;
@@ -175,6 +143,7 @@
mProtectedVm = protectedVm;
mMemoryMib = memoryMib;
mNumCpus = numCpus;
+ mEncryptedStorageKib = encryptedStorageKib;
}
/** Loads a config from a file. */
@@ -203,32 +172,48 @@
private static VirtualMachineConfig fromInputStream(@NonNull InputStream input)
throws IOException, VirtualMachineException {
PersistableBundle b = PersistableBundle.readFromStream(input);
+ try {
+ return fromPersistableBundle(b);
+ } catch (NullPointerException | IllegalArgumentException | IllegalStateException e) {
+ throw new VirtualMachineException("Persisted VM config is invalid", e);
+ }
+ }
+
+ @NonNull
+ private static VirtualMachineConfig fromPersistableBundle(PersistableBundle b) {
int version = b.getInt(KEY_VERSION);
if (version > VERSION) {
- throw new VirtualMachineException("Version too high");
+ throw new IllegalArgumentException(
+ "Version " + version + " too high; current is " + VERSION);
}
- String apkPath = b.getString(KEY_APKPATH);
- if (apkPath == null) {
- throw new VirtualMachineException("No apkPath");
+
+ Builder builder = new Builder();
+ builder.setApkPath(b.getString(KEY_APKPATH));
+
+ String payloadConfigPath = b.getString(KEY_PAYLOADCONFIGPATH);
+ if (payloadConfigPath == null) {
+ builder.setPayloadBinaryPath(b.getString(KEY_PAYLOADBINARYPATH));
+ } else {
+ builder.setPayloadConfigPath(payloadConfigPath);
}
- String payloadBinaryPath = b.getString(KEY_PAYLOADBINARYPATH);
- String payloadConfigPath = null;
- if (payloadBinaryPath == null) {
- payloadConfigPath = b.getString(KEY_PAYLOADCONFIGPATH);
- if (payloadConfigPath == null) {
- throw new VirtualMachineException("No payloadBinaryPath");
- }
- }
+
@DebugLevel int debugLevel = b.getInt(KEY_DEBUGLEVEL);
if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) {
- throw new VirtualMachineException("Invalid debugLevel: " + debugLevel);
+ throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel);
}
- boolean protectedVm = b.getBoolean(KEY_PROTECTED_VM);
+ builder.setDebugLevel(debugLevel);
+ builder.setProtectedVm(b.getBoolean(KEY_PROTECTED_VM));
int memoryMib = b.getInt(KEY_MEMORY_MIB);
- int numCpus = b.getInt(KEY_NUM_CPUS);
+ if (memoryMib != 0) {
+ builder.setMemoryMib(memoryMib);
+ }
+ builder.setNumCpus(b.getInt(KEY_NUM_CPUS));
+ long encryptedStorageKib = b.getLong(KEY_ENCRYPTED_STORAGE_KIB);
+ if (encryptedStorageKib != 0) {
+ builder.setEncryptedStorageKib(encryptedStorageKib);
+ }
- return new VirtualMachineConfig(apkPath, payloadConfigPath, payloadBinaryPath, debugLevel,
- protectedVm, memoryMib, numCpus);
+ return builder.build();
}
/** Persists this config to a file. */
@@ -253,6 +238,9 @@
if (mMemoryMib > 0) {
b.putInt(KEY_MEMORY_MIB, mMemoryMib);
}
+ if (mEncryptedStorageKib > 0) {
+ b.putLong(KEY_ENCRYPTED_STORAGE_KIB, mEncryptedStorageKib);
+ }
b.writeToStream(output);
}
@@ -315,7 +303,8 @@
}
/**
- * Returns the amount of RAM that will be made available to the VM.
+ * Returns the amount of RAM that will be made available to the VM, or 0 if the default size
+ * will be used.
*
* @hide
*/
@@ -337,6 +326,28 @@
}
/**
+ * Returns whether encrypted storage is enabled or not.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isEncryptedStorageEnabled() {
+ return mEncryptedStorageKib > 0;
+ }
+
+ /**
+ * Returns the size of encrypted storage (in KiB) available in the VM, or 0 if encrypted storage
+ * is not enabled
+ *
+ * @hide
+ */
+ @SystemApi
+ @IntRange(from = 0)
+ public long getEncryptedStorageKib() {
+ return mEncryptedStorageKib;
+ }
+
+ /**
* Tests if this config is compatible with other config. Being compatible means that the configs
* can be interchangeably used for the same virtual machine. Compatible changes includes the
* number of CPUs and the size of the RAM. All other changes (e.g. using a different payload,
@@ -348,6 +359,7 @@
public boolean isCompatibleWith(@NonNull VirtualMachineConfig other) {
return this.mDebugLevel == other.mDebugLevel
&& this.mProtectedVm == other.mProtectedVm
+ && this.mEncryptedStorageKib == other.mEncryptedStorageKib
&& Objects.equals(this.mPayloadConfigPath, other.mPayloadConfigPath)
&& Objects.equals(this.mPayloadBinaryPath, other.mPayloadBinaryPath)
&& this.mApkPath.equals(other.mApkPath);
@@ -383,9 +395,8 @@
vsConfig.protectedVm = mProtectedVm;
vsConfig.memoryMib = mMemoryMib;
vsConfig.numCpus = mNumCpus;
- // Don't allow apps to set task profiles ... at last for now. Also, don't forget to
- // validate the string because these are appended to the cmdline argument.
- vsConfig.taskProfiles = new String[0];
+ // Don't allow apps to set task profiles ... at least for now.
+ vsConfig.taskProfiles = EMPTY_STRING_ARRAY;
return vsConfig;
}
@@ -396,15 +407,16 @@
*/
@SystemApi
public static final class Builder {
- private final Context mContext;
+ @Nullable private final Context mContext;
@Nullable private String mApkPath;
@Nullable private String mPayloadConfigPath;
@Nullable private String mPayloadBinaryPath;
- @DebugLevel private int mDebugLevel;
+ @DebugLevel private int mDebugLevel = DEBUG_LEVEL_NONE;
private boolean mProtectedVm;
private boolean mProtectedVmSet;
private int mMemoryMib;
- private int mNumCpus;
+ private int mNumCpus = 1;
+ private long mEncryptedStorageKib;
/**
* Creates a builder for the given context.
@@ -414,8 +426,14 @@
@SystemApi
public Builder(@NonNull Context context) {
mContext = requireNonNull(context, "context must not be null");
- mDebugLevel = DEBUG_LEVEL_NONE;
- mNumCpus = 1;
+ }
+
+ /**
+ * Creates a builder with no associated context; {@link #setApkPath} must be called to
+ * specify which APK contains the payload.
+ */
+ private Builder() {
+ mContext = null;
}
/**
@@ -426,15 +444,40 @@
@SystemApi
@NonNull
public VirtualMachineConfig build() {
- String apkPath = (mApkPath == null) ? mContext.getPackageCodePath() : mApkPath;
+ String apkPath;
+ if (mApkPath == null) {
+ if (mContext == null) {
+ throw new IllegalStateException("apkPath must be specified");
+ }
+ apkPath = mContext.getPackageCodePath();
+ } else {
+ apkPath = mApkPath;
+ }
+
+ if (mPayloadBinaryPath == null) {
+ if (mPayloadConfigPath == null) {
+ throw new IllegalStateException("setPayloadBinaryPath must be called");
+ }
+ } else {
+ if (mPayloadConfigPath != null) {
+ throw new IllegalStateException(
+ "setPayloadBinaryPath and setPayloadConfigPath may not both be called");
+ }
+ }
if (!mProtectedVmSet) {
throw new IllegalStateException("setProtectedVm must be called explicitly");
}
return new VirtualMachineConfig(
- apkPath, mPayloadConfigPath, mPayloadBinaryPath, mDebugLevel, mProtectedVm,
- mMemoryMib, mNumCpus);
+ apkPath,
+ mPayloadConfigPath,
+ mPayloadBinaryPath,
+ mDebugLevel,
+ mProtectedVm,
+ mMemoryMib,
+ mNumCpus,
+ mEncryptedStorageKib);
}
/**
@@ -446,7 +489,11 @@
@SystemApi
@NonNull
public Builder setApkPath(@NonNull String apkPath) {
- mApkPath = requireNonNull(apkPath);
+ requireNonNull(apkPath, "apkPath must not be null");
+ if (!apkPath.startsWith("/")) {
+ throw new IllegalArgumentException("APK path must be an absolute path");
+ }
+ mApkPath = apkPath;
return this;
}
@@ -461,7 +508,8 @@
@TestApi
@NonNull
public Builder setPayloadConfigPath(@NonNull String payloadConfigPath) {
- mPayloadConfigPath = requireNonNull(payloadConfigPath);
+ mPayloadConfigPath =
+ requireNonNull(payloadConfigPath, "payloadConfigPath must not be null");
return this;
}
@@ -474,7 +522,8 @@
@SystemApi
@NonNull
public Builder setPayloadBinaryPath(@NonNull String payloadBinaryPath) {
- mPayloadBinaryPath = requireNonNull(payloadBinaryPath);
+ mPayloadBinaryPath =
+ requireNonNull(payloadBinaryPath, "payloadBinaryPath must not be null");
return this;
}
@@ -486,6 +535,9 @@
@SystemApi
@NonNull
public Builder setDebugLevel(@DebugLevel int debugLevel) {
+ if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) {
+ throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel);
+ }
mDebugLevel = debugLevel;
return this;
}
@@ -500,20 +552,34 @@
@SystemApi
@NonNull
public Builder setProtectedVm(boolean protectedVm) {
+ if (protectedVm) {
+ if (!HypervisorProperties.hypervisor_protected_vm_supported().orElse(false)) {
+ throw new UnsupportedOperationException(
+ "Protected VMs are not supported on this device.");
+ }
+ } else {
+ if (!HypervisorProperties.hypervisor_vm_supported().orElse(false)) {
+ throw new UnsupportedOperationException(
+ "Unprotected VMs are not supported on this device.");
+ }
+ }
mProtectedVm = protectedVm;
mProtectedVmSet = true;
return this;
}
/**
- * Sets the amount of RAM to give the VM, in mebibytes. If zero or not explicitly set then a
- * default size will be used.
+ * Sets the amount of RAM to give the VM, in mebibytes. If not explicitly set then a default
+ * size will be used.
*
* @hide
*/
@SystemApi
@NonNull
- public Builder setMemoryMib(@IntRange(from = 0) int memoryMib) {
+ public Builder setMemoryMib(@IntRange(from = 1) int memoryMib) {
+ if (memoryMib <= 0) {
+ throw new IllegalArgumentException("Memory size must be positive");
+ }
mMemoryMib = memoryMib;
return this;
}
@@ -526,8 +592,44 @@
*/
@SystemApi
@NonNull
- public Builder setNumCpus(@IntRange(from = 1) int num) {
- mNumCpus = num;
+ public Builder setNumCpus(@IntRange(from = 1) int numCpus) {
+ int availableCpus = Runtime.getRuntime().availableProcessors();
+ if (numCpus < 1 || numCpus > availableCpus) {
+ throw new IllegalArgumentException(
+ "Number of vCPUs ("
+ + numCpus
+ + ") is out of "
+ + "range [1, "
+ + availableCpus
+ + "]");
+ }
+ mNumCpus = numCpus;
+ return this;
+ }
+
+ /**
+ * Sets the size (in KiB) of encrypted storage available to the VM. If not set, no encrypted
+ * storage is provided.
+ *
+ * <p>The storage is encrypted with a key deterministically derived from the VM identity
+ *
+ * <p>The encrypted storage is persistent across VM reboots as well as device reboots. The
+ * backing file (containing encrypted data) is stored in the app's private data directory.
+ *
+ * <p>Note - There is no integrity guarantee or rollback protection on the storage in case
+ * the encrypted data is modified.
+ *
+ * <p>Deleting the VM will delete the encrypted data - there is no way to recover that data.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public Builder setEncryptedStorageKib(@IntRange(from = 1) long encryptedStorageKib) {
+ if (encryptedStorageKib <= 0) {
+ throw new IllegalArgumentException("Encrypted Storage size must be positive");
+ }
+ mEncryptedStorageKib = encryptedStorageKib;
return this;
}
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
index edaf5b4..c9718aa 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
@@ -19,6 +19,7 @@
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
@@ -37,7 +38,9 @@
public final class VirtualMachineDescriptor implements Parcelable {
@NonNull private final ParcelFileDescriptor mConfigFd;
@NonNull private final ParcelFileDescriptor mInstanceImgFd;
- // TODO(b/243129654): Add trusted storage fd once it is available.
+ // File descriptor of the image backing the encrypted storage - Will be null if encrypted
+ // storage is not enabled. */
+ @Nullable private final ParcelFileDescriptor mEncryptedStoreFd;
@Override
public int describeContents() {
@@ -48,6 +51,7 @@
public void writeToParcel(@NonNull Parcel out, int flags) {
mConfigFd.writeToParcel(out, flags);
mInstanceImgFd.writeToParcel(out, flags);
+ if (mEncryptedStoreFd != null) mEncryptedStoreFd.writeToParcel(out, flags);
}
@NonNull
@@ -78,14 +82,27 @@
return mInstanceImgFd;
}
+ /**
+ * @return File descriptor of image backing the encrypted storage.
+ * <p>This method will return null if encrypted storage is not enabled.
+ */
+ @Nullable
+ ParcelFileDescriptor getEncryptedStoreFd() {
+ return mEncryptedStoreFd;
+ }
+
VirtualMachineDescriptor(
- @NonNull ParcelFileDescriptor configFd, @NonNull ParcelFileDescriptor instanceImgFd) {
+ @NonNull ParcelFileDescriptor configFd,
+ @NonNull ParcelFileDescriptor instanceImgFd,
+ @Nullable ParcelFileDescriptor encryptedStoreFd) {
mConfigFd = configFd;
mInstanceImgFd = instanceImgFd;
+ mEncryptedStoreFd = encryptedStoreFd;
}
private VirtualMachineDescriptor(Parcel in) {
mConfigFd = requireNonNull(in.readFileDescriptor());
mInstanceImgFd = requireNonNull(in.readFileDescriptor());
+ mEncryptedStoreFd = in.readFileDescriptor();
}
}
diff --git a/libs/apkverify/src/v4.rs b/libs/apkverify/src/v4.rs
index 6c085f6..94abf99 100644
--- a/libs/apkverify/src/v4.rs
+++ b/libs/apkverify/src/v4.rs
@@ -146,6 +146,11 @@
/// Read a stream for an APK file and creates a corresponding `V4Signature` struct that digests
/// the APK file. Note that the signing is not done.
+ /// Important: callers of this function are expected to verify the validity of the passed |apk|.
+ /// To be more specific, they should check that |apk| corresponds to a regular file, as calling
+ /// lseek on directory fds is not defined in the standard, and on ext4 it will return (off_t)-1
+ /// (see: https://bugzilla.kernel.org/show_bug.cgi?id=200043), which will result in this
+ /// function OOMing.
pub fn create(
mut apk: &mut R,
block_size: usize,
diff --git a/libs/avb/Android.bp b/libs/avb/Android.bp
index 28e969d..a19a538 100644
--- a/libs/avb/Android.bp
+++ b/libs/avb/Android.bp
@@ -39,7 +39,7 @@
rust_library_rlib {
name: "libavb_nostd",
- crate_name: "avb_nostd",
+ crate_name: "avb",
srcs: ["src/lib.rs"],
no_stdlibs: true,
prefer_rlib: true,
diff --git a/libs/avb/src/lib.rs b/libs/avb/src/lib.rs
index 81b554d..21b7d2a 100644
--- a/libs/avb/src/lib.rs
+++ b/libs/avb/src/lib.rs
@@ -16,6 +16,8 @@
#![no_std]
-mod avb_ops;
+extern crate alloc;
-pub use avb_ops::{verify_image, AvbImageVerifyError};
+mod ops;
+
+pub use ops::{verify_image, AvbImageVerifyError};
diff --git a/libs/avb/src/avb_ops.rs b/libs/avb/src/ops.rs
similarity index 99%
rename from libs/avb/src/avb_ops.rs
rename to libs/avb/src/ops.rs
index 900e152..429c980 100644
--- a/libs/avb/src/avb_ops.rs
+++ b/libs/avb/src/ops.rs
@@ -19,8 +19,6 @@
#![allow(dead_code)]
#![allow(unused_imports)]
-extern crate alloc;
-
use alloc::ffi::CString;
use avb_bindgen::{
avb_slot_verify, AvbHashtreeErrorMode_AVB_HASHTREE_ERROR_MODE_EIO,
diff --git a/libs/vbmeta/src/lib.rs b/libs/vbmeta/src/lib.rs
index 8e81ea4..c273973 100644
--- a/libs/vbmeta/src/lib.rs
+++ b/libs/vbmeta/src/lib.rs
@@ -28,11 +28,10 @@
};
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom};
-use std::mem::{size_of, MaybeUninit};
+use std::mem::{size_of, transmute, MaybeUninit};
use std::os::raw::c_uint;
use std::path::Path;
use std::ptr::null_mut;
-use std::slice;
use thiserror::Error;
pub use crate::descriptor::{Descriptor, Descriptors};
@@ -96,14 +95,17 @@
) -> Result<Self, VbMetaImageVerificationError> {
// Check for a footer in the image or assume it's an entire VBMeta image.
image.seek(SeekFrom::Start(offset + size)).map_err(VbMetaImageParseError::Io)?;
- let footer = read_avb_footer(&mut image).map_err(VbMetaImageParseError::Io)?;
- let (vbmeta_offset, vbmeta_size) = if let Some(footer) = footer {
- if footer.vbmeta_offset > size || footer.vbmeta_size > size - footer.vbmeta_offset {
- return Err(VbMetaImageParseError::InvalidFooter.into());
+ let (vbmeta_offset, vbmeta_size) = match read_avb_footer(&mut image) {
+ Ok(footer) => {
+ if footer.vbmeta_offset > size || footer.vbmeta_size > size - footer.vbmeta_offset {
+ return Err(VbMetaImageParseError::InvalidFooter.into());
+ }
+ (footer.vbmeta_offset, footer.vbmeta_size)
}
- (footer.vbmeta_offset, footer.vbmeta_size)
- } else {
- (0, size)
+ Err(VbMetaImageParseError::InvalidFooter) => (0, size),
+ Err(e) => {
+ return Err(e.into());
+ }
};
image.seek(SeekFrom::Start(offset + vbmeta_offset)).map_err(VbMetaImageParseError::Io)?;
// Verify the image before examining it to check the size.
@@ -187,26 +189,17 @@
}
/// Read the AVB footer, if present, given a reader that's positioned at the end of the image.
-fn read_avb_footer<R: Read + Seek>(image: &mut R) -> io::Result<Option<AvbFooter>> {
+fn read_avb_footer<R: Read + Seek>(image: &mut R) -> Result<AvbFooter, VbMetaImageParseError> {
image.seek(SeekFrom::Current(-(size_of::<AvbFooter>() as i64)))?;
+ let mut raw_footer = [0u8; size_of::<AvbFooter>()];
+ image.read_exact(&mut raw_footer)?;
// SAFETY: the slice is the same size as the struct which only contains simple data types.
- let mut footer = unsafe {
- let mut footer = MaybeUninit::<AvbFooter>::uninit();
- let footer_slice =
- slice::from_raw_parts_mut(&mut footer as *mut _ as *mut u8, size_of::<AvbFooter>());
- image.read_exact(footer_slice)?;
- footer.assume_init()
- };
- // Check the magic matches "AVBf" to suppress misleading logs from libavb.
- const AVB_FOOTER_MAGIC: [u8; 4] = [0x41, 0x56, 0x42, 0x66];
- if footer.magic != AVB_FOOTER_MAGIC {
- return Ok(None);
- }
+ let mut footer = unsafe { transmute::<[u8; size_of::<AvbFooter>()], AvbFooter>(raw_footer) };
// SAFETY: the function updates the struct in-place.
if unsafe { avb_footer_validate_and_byteswap(&footer, &mut footer) } {
- Ok(Some(footer))
+ Ok(footer)
} else {
- Ok(None)
+ Err(VbMetaImageParseError::InvalidFooter)
}
}
@@ -220,7 +213,7 @@
use tempfile::TempDir;
#[test]
- fn test_unsigned_image() -> Result<()> {
+ fn unsigned_image_does_not_have_public_key() -> Result<()> {
let test_dir = TempDir::new().unwrap();
let test_file = test_dir.path().join("test.img");
let mut cmd = Command::new("./avbtool");
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 028ac1f..2b8e03f 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -71,7 +71,6 @@
"atrace",
"debuggerd",
"linker",
- "linkerconfig",
"tombstoned.microdroid",
"tombstone_transmit.microdroid",
"cgroups.json",
@@ -83,7 +82,7 @@
"microdroid_manifest",
"microdroid_plat_sepolicy_and_mapping.sha256",
"microdroid_property_contexts",
- "mke2fs",
+ "mke2fs.microdroid",
// TODO(b/195425111) these should be added automatically
"libcrypto", // used by many (init_second_stage, microdroid_manager, toybox, etc)
diff --git a/microdroid/init.rc b/microdroid/init.rc
index a48ba4b..7402481 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -17,6 +17,10 @@
start ueventd
+ # Generate empty linker config to suppress warnings
+ write /linkerconfig/ld.config.txt \#
+ chmod 644 /linkerconfig/ld.config.txt
+
# If VM is debuggable, send logs to outside ot the VM via the serial console.
# If non-debuggable, logs are internally consumed at /dev/null
on early-init && property:ro.boot.microdroid.debuggable=1
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 3c490f4..6a37b88 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -77,7 +77,6 @@
const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
const DEBUGGABLE_PROP: &str = "ro.boot.microdroid.debuggable";
-const APK_MOUNT_DONE_PROP: &str = "microdroid_manager.apk.mounted";
// SYNC WITH virtualizationservice/src/crosvm.rs
const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
@@ -383,15 +382,16 @@
None
};
+ let mut zipfuse = Zipfuse::default();
+
// Before reading a file from the APK, start zipfuse
- run_zipfuse(
+ zipfuse.mount(
MountForExec::Allowed,
"fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
Path::new("/dev/block/mapper/microdroid-apk"),
Path::new(VM_APK_CONTENTS_PATH),
- Some(APK_MOUNT_DONE_PROP),
- )
- .context("Failed to run zipfuse")?;
+ "microdroid_manager.apk.mounted".to_owned(),
+ )?;
// Restricted APIs are only allowed to be used by platform or test components. Infer this from
// the use of a VM config file since those can only be used by platform and test components.
@@ -414,7 +414,7 @@
verified_data.extra_apks_data.len()
));
}
- mount_extra_apks(&config)?;
+ mount_extra_apks(&config, &mut zipfuse)?;
// Wait until apex config is done. (e.g. linker configuration for apexes)
wait_for_apex_config_done()?;
@@ -428,8 +428,8 @@
control_service("stop", "tombstoned")?;
}
- // Wait until zipfuse has mounted the APK so we can access the payload
- wait_for_property_true(APK_MOUNT_DONE_PROP).context("Failed waiting for APK mount done")?;
+ // Wait until zipfuse has mounted the APKs so we can access the payload
+ zipfuse.wait_until_done()?;
register_vm_payload_service(allow_restricted_apis, service.clone(), dice_context)?;
@@ -480,21 +480,40 @@
Disallowed,
}
-fn run_zipfuse(
- noexec: MountForExec,
- option: &str,
- zip_path: &Path,
- mount_dir: &Path,
- ready_prop: Option<&str>,
-) -> Result<Child> {
- let mut cmd = Command::new(ZIPFUSE_BIN);
- if let MountForExec::Disallowed = noexec {
- cmd.arg("--noexec");
+#[derive(Default)]
+struct Zipfuse {
+ ready_properties: Vec<String>,
+}
+
+impl Zipfuse {
+ fn mount(
+ &mut self,
+ noexec: MountForExec,
+ option: &str,
+ zip_path: &Path,
+ mount_dir: &Path,
+ ready_prop: String,
+ ) -> Result<Child> {
+ let mut cmd = Command::new(ZIPFUSE_BIN);
+ if let MountForExec::Disallowed = noexec {
+ cmd.arg("--noexec");
+ }
+ cmd.args(["-p", &ready_prop, "-o", option]);
+ cmd.arg(zip_path).arg(mount_dir);
+ self.ready_properties.push(ready_prop);
+ cmd.spawn().with_context(|| format!("Failed to run zipfuse for {mount_dir:?}"))
}
- if let Some(property_name) = ready_prop {
- cmd.args(["-p", property_name]);
+
+ fn wait_until_done(self) -> Result<()> {
+ // We check the last-started check first in the hope that by the time it is done
+ // all or most of the others will also be done, minimising the number of times we
+ // block on a property.
+ for property in self.ready_properties.into_iter().rev() {
+ wait_for_property_true(&property)
+ .with_context(|| format!("Failed waiting for {property}"))?;
+ }
+ Ok(())
}
- cmd.arg("-o").arg(option).arg(zip_path).arg(mount_dir).spawn().context("Spawn zipfuse")
}
fn write_apex_payload_data(
@@ -664,21 +683,20 @@
})
}
-fn mount_extra_apks(config: &VmPayloadConfig) -> Result<()> {
+fn mount_extra_apks(config: &VmPayloadConfig, zipfuse: &mut Zipfuse) -> Result<()> {
// For now, only the number of apks is important, as the mount point and dm-verity name is fixed
for i in 0..config.extra_apks.len() {
- let mount_dir = format!("/mnt/extra-apk/{}", i);
+ let mount_dir = format!("/mnt/extra-apk/{i}");
create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
// don't wait, just detach
- run_zipfuse(
+ zipfuse.mount(
MountForExec::Disallowed,
"fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
- Path::new(&format!("/dev/block/mapper/extra-apk-{}", i)),
+ Path::new(&format!("/dev/block/mapper/extra-apk-{i}")),
Path::new(&mount_dir),
- None,
- )
- .context("Failed to zipfuse extra apks")?;
+ format!("microdroid_manager.extra_apk.mounted.{i}"),
+ )?;
}
Ok(())
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 356e58f..318c7fe 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -20,6 +20,7 @@
"liblog_rust_nostd",
"libpvmfw_embedded_key",
"libtinyvec_nostd",
+ "libvirtio_drivers",
"libvmbase",
"libzeroize_nostd",
],
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
index 0f2a39c..b633745 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -17,8 +17,7 @@
use crate::helpers;
use core::fmt;
use core::mem;
-use core::num::NonZeroUsize;
-use core::ops;
+use core::ops::Range;
use core::result;
#[repr(C, packed)]
@@ -43,8 +42,10 @@
InvalidFlags(u32),
/// Header describes configuration data that doesn't fit in the expected buffer.
InvalidSize(usize),
+ /// Header entry is missing.
+ MissingEntry(Entry),
/// Header entry is invalid.
- InvalidEntry(Entry),
+ InvalidEntry(Entry, EntryError),
}
impl fmt::Display for Error {
@@ -55,13 +56,38 @@
Self::UnsupportedVersion(x, y) => write!(f, "Version {x}.{y} not supported"),
Self::InvalidFlags(v) => write!(f, "Flags value {v:#x} is incorrect or reserved"),
Self::InvalidSize(sz) => write!(f, "Total size ({sz:#x}) overflows reserved region"),
- Self::InvalidEntry(e) => write!(f, "Entry {e:?} is invalid"),
+ Self::MissingEntry(entry) => write!(f, "Mandatory {entry:?} entry is missing"),
+ Self::InvalidEntry(entry, e) => write!(f, "Invalid {entry:?} entry: {e}"),
}
}
}
pub type Result<T> = result::Result<T, Error>;
+#[derive(Debug)]
+pub enum EntryError {
+ /// Offset isn't between the fixed minimum value and size of configuration data.
+ InvalidOffset(usize),
+ /// Size must be zero when offset is and not be when it isn't.
+ InvalidSize(usize),
+ /// Entry isn't fully within the configuration data structure.
+ OutOfBounds { offset: usize, size: usize, limit: usize },
+}
+
+impl fmt::Display for EntryError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::InvalidOffset(offset) => write!(f, "Invalid offset: {offset:#x?}"),
+ Self::InvalidSize(sz) => write!(f, "Invalid size: {sz:#x?}"),
+ Self::OutOfBounds { offset, size, limit } => {
+ let range = Header::PADDED_SIZE..*limit;
+ let entry = *offset..(*offset + *size);
+ write!(f, "Out of bounds: {entry:#x?} must be within range {range:#x?}")
+ }
+ }
+ }
+}
+
impl Header {
const MAGIC: u32 = u32::from_ne_bytes(*b"pvmf");
const PADDED_SIZE: usize =
@@ -83,8 +109,43 @@
self.total_size() - Self::PADDED_SIZE
}
- fn get(&self, entry: Entry) -> HeaderEntry {
- self.entries[entry as usize]
+ fn get_body_range(&self, entry: Entry) -> Result<Option<Range<usize>>> {
+ let e = self.entries[entry as usize];
+ let offset = e.offset as usize;
+ let size = e.size as usize;
+
+ match self._get_body_range(offset, size) {
+ Ok(r) => Ok(r),
+ Err(EntryError::InvalidSize(0)) => {
+ // As our bootloader currently uses this (non-compliant) case, permit it for now.
+ log::warn!("Config entry {entry:?} uses non-zero offset with zero size");
+ // TODO(b/262181812): Either make this case valid or fix the bootloader.
+ Ok(None)
+ }
+ Err(e) => Err(Error::InvalidEntry(entry, e)),
+ }
+ }
+
+ fn _get_body_range(
+ &self,
+ offset: usize,
+ size: usize,
+ ) -> result::Result<Option<Range<usize>>, EntryError> {
+ match (offset, size) {
+ (0, 0) => Ok(None),
+ (0, size) | (_, size @ 0) => Err(EntryError::InvalidSize(size)),
+ _ => {
+ let start = offset
+ .checked_sub(Header::PADDED_SIZE)
+ .ok_or(EntryError::InvalidOffset(offset))?;
+ let end = start
+ .checked_add(size)
+ .filter(|x| *x <= self.body_size())
+ .ok_or(EntryError::OutOfBounds { offset, size, limit: self.total_size() })?;
+
+ Ok(Some(start..end))
+ }
+ }
}
}
@@ -105,38 +166,11 @@
size: u32,
}
-impl HeaderEntry {
- pub fn is_empty(&self) -> bool {
- self.offset() == 0 && self.size() == 0
- }
-
- pub fn fits_in(&self, max_size: usize) -> bool {
- (Header::PADDED_SIZE..max_size).contains(&self.offset())
- && NonZeroUsize::new(self.size())
- .and_then(|s| s.checked_add(self.offset()))
- .filter(|&x| x.get() <= max_size)
- .is_some()
- }
-
- pub fn as_body_range(&self) -> ops::Range<usize> {
- let start = self.offset() - Header::PADDED_SIZE;
-
- start..(start + self.size())
- }
-
- pub fn offset(&self) -> usize {
- self.offset as usize
- }
-
- pub fn size(&self) -> usize {
- self.size as usize
- }
-}
-
#[derive(Debug)]
pub struct Config<'a> {
- header: &'a Header,
body: &'a mut [u8],
+ bcc_range: Range<usize>,
+ dp_range: Option<Range<usize>>,
}
impl<'a> Config<'a> {
@@ -161,40 +195,26 @@
return Err(Error::InvalidFlags(header.flags));
}
- let total_size = header.total_size();
-
- // BCC is a mandatory entry of the configuration data.
- if !header.get(Entry::Bcc).fits_in(total_size) {
- return Err(Error::InvalidEntry(Entry::Bcc));
- }
-
- // Debug policy is optional.
- let dp = header.get(Entry::DebugPolicy);
- if !dp.is_empty() && !dp.fits_in(total_size) {
- return Err(Error::InvalidEntry(Entry::DebugPolicy));
- }
+ let bcc_range =
+ header.get_body_range(Entry::Bcc)?.ok_or(Error::MissingEntry(Entry::Bcc))?;
+ let dp_range = header.get_body_range(Entry::DebugPolicy)?;
let body = data
.get_mut(Header::PADDED_SIZE..)
.ok_or(Error::BufferTooSmall)?
.get_mut(..header.body_size())
- .ok_or(Error::InvalidSize(total_size))?;
+ .ok_or_else(|| Error::InvalidSize(header.total_size()))?;
- Ok(Self { header, body })
+ Ok(Self { body, bcc_range, dp_range })
}
/// Get slice containing the platform BCC.
pub fn get_bcc_mut(&mut self) -> &mut [u8] {
- &mut self.body[self.header.get(Entry::Bcc).as_body_range()]
+ &mut self.body[self.bcc_range.clone()]
}
/// Get slice containing the platform debug policy.
pub fn get_debug_policy(&mut self) -> Option<&mut [u8]> {
- let entry = self.header.get(Entry::DebugPolicy);
- if entry.is_empty() {
- None
- } else {
- Some(&mut self.body[entry.as_body_range()])
- }
+ self.dp_range.as_ref().map(|r| &mut self.body[r.clone()])
}
}
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index c527e22..45a8459 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -48,6 +48,8 @@
InvalidRamdisk,
/// Failed to verify the payload.
PayloadVerificationError,
+ /// Error interacting with a VirtIO PCI device.
+ PciError,
}
main!(start);
@@ -330,6 +332,12 @@
slice::from_raw_parts_mut(base as *mut u8, size)
}
+enum AppendedConfigType {
+ Valid,
+ Invalid,
+ NotFound,
+}
+
enum AppendedPayload<'a> {
/// Configuration data.
Config(config::Config<'a>),
@@ -340,24 +348,32 @@
impl<'a> AppendedPayload<'a> {
/// SAFETY - 'data' should respect the alignment of config::Header.
unsafe fn new(data: &'a mut [u8]) -> Option<Self> {
- if Self::is_valid_config(data) {
- Some(Self::Config(config::Config::new(data).unwrap()))
- } else if cfg!(feature = "legacy") {
- const BCC_SIZE: usize = helpers::SIZE_4KB;
- warn!("Assuming the appended data at {:?} to be a raw BCC", data.as_ptr());
- Some(Self::LegacyBcc(&mut data[..BCC_SIZE]))
- } else {
- None
+ match Self::guess_config_type(data) {
+ AppendedConfigType::Valid => Some(Self::Config(config::Config::new(data).unwrap())),
+ AppendedConfigType::NotFound if cfg!(feature = "legacy") => {
+ const BCC_SIZE: usize = helpers::SIZE_4KB;
+ warn!("Assuming the appended data at {:?} to be a raw BCC", data.as_ptr());
+ Some(Self::LegacyBcc(&mut data[..BCC_SIZE]))
+ }
+ _ => None,
}
}
- unsafe fn is_valid_config(data: &mut [u8]) -> bool {
+ unsafe fn guess_config_type(data: &mut [u8]) -> AppendedConfigType {
// This function is necessary to prevent the borrow checker from getting confused
// about the ownership of data in new(); see https://users.rust-lang.org/t/78467.
let addr = data.as_ptr();
- config::Config::new(data)
- .map_err(|e| warn!("Invalid configuration data at {addr:?}: {e}"))
- .is_ok()
+ match config::Config::new(data) {
+ Err(config::Error::InvalidMagic) => {
+ warn!("No configuration data found at {addr:?}");
+ AppendedConfigType::NotFound
+ }
+ Err(e) => {
+ error!("Invalid configuration data at {addr:?}: {e}");
+ AppendedConfigType::Invalid
+ }
+ Ok(_) => AppendedConfigType::Valid,
+ }
}
#[allow(dead_code)] // TODO(b/232900974)
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 7222a0d..79b6f57 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -32,9 +32,13 @@
mod pci;
mod smccc;
-use crate::{entry::RebootReason, memory::MemoryTracker, pci::PciInfo};
-use avb::PUBLIC_KEY;
-use avb_nostd::verify_image;
+use crate::{
+ avb::PUBLIC_KEY,
+ entry::RebootReason,
+ memory::MemoryTracker,
+ pci::{allocate_all_virtio_bars, PciError, PciInfo, PciMemory32Allocator},
+};
+use ::avb::verify_image;
use dice::bcc;
use libfdt::Fdt;
use log::{debug, error, info, trace};
@@ -57,9 +61,15 @@
trace!("BCC: {bcc:x?}");
// Set up PCI bus for VirtIO devices.
- let pci_info = PciInfo::from_fdt(fdt)?;
- info!("PCI: {:#x?}", pci_info);
+ let pci_info = PciInfo::from_fdt(fdt).map_err(handle_pci_error)?;
+ debug!("PCI: {:#x?}", pci_info);
pci_info.map(memory)?;
+ let mut bar_allocator = PciMemory32Allocator::new(&pci_info);
+ debug!("Allocator: {:#x?}", bar_allocator);
+ // Safety: This is the only place where we call make_pci_root, and this main function is only
+ // called once.
+ let mut pci_root = unsafe { pci_info.make_pci_root() };
+ allocate_all_virtio_bars(&mut pci_root, &mut bar_allocator).map_err(handle_pci_error)?;
verify_image(signed_kernel, PUBLIC_KEY).map_err(|e| {
error!("Failed to verify the payload: {e}");
@@ -68,3 +78,24 @@
info!("Starting payload...");
Ok(())
}
+
+/// Logs the given PCI error and returns the appropriate `RebootReason`.
+fn handle_pci_error(e: PciError) -> RebootReason {
+ error!("{}", e);
+ match e {
+ PciError::FdtErrorPci(_)
+ | PciError::FdtNoPci
+ | PciError::FdtErrorReg(_)
+ | PciError::FdtMissingReg
+ | PciError::FdtRegEmpty
+ | PciError::FdtRegMissingSize
+ | PciError::CamWrongSize(_)
+ | PciError::FdtErrorRanges(_)
+ | PciError::FdtMissingRanges
+ | PciError::RangeAddressMismatch { .. }
+ | PciError::NoSuitableRange => RebootReason::InvalidFdt,
+ PciError::BarInfoFailed(_)
+ | PciError::BarAllocationFailed { .. }
+ | PciError::UnsupportedBarType(_) => RebootReason::PciError,
+ }
+}
diff --git a/pvmfw/src/pci.rs b/pvmfw/src/pci.rs
index 3e6915a..d971c7b 100644
--- a/pvmfw/src/pci.rs
+++ b/pvmfw/src/pci.rs
@@ -18,13 +18,78 @@
entry::RebootReason,
memory::{MemoryRange, MemoryTracker},
};
-use core::{ffi::CStr, ops::Range};
-use libfdt::{AddressRange, Fdt, FdtNode};
+use core::{
+ ffi::CStr,
+ fmt::{self, Display, Formatter},
+ ops::Range,
+};
+use libfdt::{AddressRange, Fdt, FdtError, FdtNode};
use log::{debug, error};
+use virtio_drivers::pci::{
+ bus::{self, BarInfo, Cam, Command, DeviceFunction, MemoryBarType, PciRoot},
+ virtio_device_type,
+};
/// PCI MMIO configuration region size.
const PCI_CFG_SIZE: usize = 0x100_0000;
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum PciError {
+ FdtErrorPci(FdtError),
+ FdtNoPci,
+ FdtErrorReg(FdtError),
+ FdtMissingReg,
+ FdtRegEmpty,
+ FdtRegMissingSize,
+ CamWrongSize(usize),
+ FdtErrorRanges(FdtError),
+ FdtMissingRanges,
+ RangeAddressMismatch { bus_address: u64, cpu_physical: u64 },
+ NoSuitableRange,
+ BarInfoFailed(bus::PciError),
+ BarAllocationFailed { size: u32, device_function: DeviceFunction },
+ UnsupportedBarType(MemoryBarType),
+}
+
+impl Display for PciError {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ match self {
+ Self::FdtErrorPci(e) => write!(f, "Error getting PCI node from FDT: {}", e),
+ Self::FdtNoPci => write!(f, "Failed to find PCI bus in FDT."),
+ Self::FdtErrorReg(e) => write!(f, "Error getting reg property from PCI node: {}", e),
+ Self::FdtMissingReg => write!(f, "PCI node missing reg property."),
+ Self::FdtRegEmpty => write!(f, "Empty reg property on PCI node."),
+ Self::FdtRegMissingSize => write!(f, "PCI reg property missing size."),
+ Self::CamWrongSize(cam_size) => write!(
+ f,
+ "FDT says PCI CAM is {} bytes but we expected {}.",
+ cam_size, PCI_CFG_SIZE
+ ),
+ Self::FdtErrorRanges(e) => {
+ write!(f, "Error getting ranges property from PCI node: {}", e)
+ }
+ Self::FdtMissingRanges => write!(f, "PCI node missing ranges property."),
+ Self::RangeAddressMismatch { bus_address, cpu_physical } => {
+ write!(
+ f,
+ "bus address {:#018x} != CPU physical address {:#018x}",
+ bus_address, cpu_physical
+ )
+ }
+ Self::NoSuitableRange => write!(f, "No suitable PCI memory range found."),
+ Self::BarInfoFailed(e) => write!(f, "Error getting PCI BAR information: {}", e),
+ Self::BarAllocationFailed { size, device_function } => write!(
+ f,
+ "Failed to allocate memory BAR of size {} for PCI device {}.",
+ size, device_function
+ ),
+ Self::UnsupportedBarType(address_type) => {
+ write!(f, "Memory BAR address type {:?} not supported.", address_type)
+ }
+ }
+ }
+}
+
/// Information about the PCI bus parsed from the device tree.
#[derive(Debug)]
pub struct PciInfo {
@@ -36,7 +101,7 @@
impl PciInfo {
/// Finds the PCI node in the FDT, parses its properties and validates it.
- pub fn from_fdt(fdt: &Fdt) -> Result<Self, RebootReason> {
+ pub fn from_fdt(fdt: &Fdt) -> Result<Self, PciError> {
let pci_node = pci_node(fdt)?;
let cam_range = parse_cam_range(&pci_node)?;
@@ -61,48 +126,44 @@
Ok(())
}
+
+ /// Returns the `PciRoot` for the memory-mapped CAM found in the FDT. The CAM should be mapped
+ /// before this is called, by calling [`PciInfo::map`].
+ ///
+ /// # Safety
+ ///
+ /// To prevent concurrent access, only one `PciRoot` should exist in the program. Thus this
+ /// method must only be called once, and there must be no other `PciRoot` constructed using the
+ /// same CAM.
+ pub unsafe fn make_pci_root(&self) -> PciRoot {
+ PciRoot::new(self.cam_range.start as *mut u8, Cam::MmioCam)
+ }
}
/// Finds an FDT node with compatible=pci-host-cam-generic.
-fn pci_node(fdt: &Fdt) -> Result<FdtNode, RebootReason> {
+fn pci_node(fdt: &Fdt) -> Result<FdtNode, PciError> {
fdt.compatible_nodes(CStr::from_bytes_with_nul(b"pci-host-cam-generic\0").unwrap())
- .map_err(|e| {
- error!("Failed to find PCI bus in FDT: {}", e);
- RebootReason::InvalidFdt
- })?
+ .map_err(PciError::FdtErrorPci)?
.next()
- .ok_or(RebootReason::InvalidFdt)
+ .ok_or(PciError::FdtNoPci)
}
/// Parses the "reg" property of the given PCI FDT node to find the MMIO CAM range.
-fn parse_cam_range(pci_node: &FdtNode) -> Result<MemoryRange, RebootReason> {
+fn parse_cam_range(pci_node: &FdtNode) -> Result<MemoryRange, PciError> {
let pci_reg = pci_node
.reg()
- .map_err(|e| {
- error!("Error getting reg property from PCI node: {}", e);
- RebootReason::InvalidFdt
- })?
- .ok_or_else(|| {
- error!("PCI node missing reg property.");
- RebootReason::InvalidFdt
- })?
+ .map_err(PciError::FdtErrorReg)?
+ .ok_or(PciError::FdtMissingReg)?
.next()
- .ok_or_else(|| {
- error!("Empty reg property on PCI node.");
- RebootReason::InvalidFdt
- })?;
+ .ok_or(PciError::FdtRegEmpty)?;
let cam_addr = pci_reg.addr as usize;
- let cam_size = pci_reg.size.ok_or_else(|| {
- error!("PCI reg property missing size.");
- RebootReason::InvalidFdt
- })? as usize;
+ let cam_size = pci_reg.size.ok_or(PciError::FdtRegMissingSize)? as usize;
debug!("Found PCI CAM at {:#x}-{:#x}", cam_addr, cam_addr + cam_size);
// Check that the CAM is the size we expect, so we don't later try accessing it beyond its
// bounds. If it is a different size then something is very wrong and we shouldn't continue to
// access it; maybe there is some new version of PCI we don't know about.
if cam_size != PCI_CFG_SIZE {
- error!("FDT says PCI CAM is {} bytes but we expected {}.", cam_size, PCI_CFG_SIZE);
- return Err(RebootReason::InvalidFdt);
+ return Err(PciError::CamWrongSize(cam_size));
}
Ok(cam_addr..cam_addr + cam_size)
@@ -110,20 +171,14 @@
/// Parses the "ranges" property of the given PCI FDT node, and returns the largest suitable range
/// to use for non-prefetchable 32-bit memory BARs.
-fn parse_ranges(pci_node: &FdtNode) -> Result<Range<u32>, RebootReason> {
+fn parse_ranges(pci_node: &FdtNode) -> Result<Range<u32>, PciError> {
let mut memory_address = 0;
let mut memory_size = 0;
for AddressRange { addr: (flags, bus_address), parent_addr: cpu_physical, size } in pci_node
.ranges::<(u32, u64), u64, u64>()
- .map_err(|e| {
- error!("Error getting ranges property from PCI node: {}", e);
- RebootReason::InvalidFdt
- })?
- .ok_or_else(|| {
- error!("PCI node missing ranges property.");
- RebootReason::InvalidFdt
- })?
+ .map_err(PciError::FdtErrorRanges)?
+ .ok_or(PciError::FdtMissingRanges)?
{
let flags = PciMemoryFlags(flags);
let prefetchable = flags.prefetchable();
@@ -145,11 +200,7 @@
&& bus_address + size < u32::MAX.into()
{
if bus_address != cpu_physical {
- error!(
- "bus address {:#018x} != CPU physical address {:#018x}",
- bus_address, cpu_physical
- );
- return Err(RebootReason::InvalidFdt);
+ return Err(PciError::RangeAddressMismatch { bus_address, cpu_physical });
}
memory_address = u32::try_from(cpu_physical).unwrap();
memory_size = u32::try_from(size).unwrap();
@@ -157,8 +208,7 @@
}
if memory_size == 0 {
- error!("No suitable PCI memory range found.");
- return Err(RebootReason::InvalidFdt);
+ return Err(PciError::NoSuitableRange);
}
Ok(memory_address..memory_address + memory_size)
@@ -196,3 +246,101 @@
}
}
}
+
+/// Allocates BARs for all VirtIO PCI devices.
+pub fn allocate_all_virtio_bars(
+ pci_root: &mut PciRoot,
+ allocator: &mut PciMemory32Allocator,
+) -> Result<(), PciError> {
+ for (device_function, info) in pci_root.enumerate_bus(0) {
+ let (status, command) = pci_root.get_status_command(device_function);
+ debug!(
+ "Found PCI device {} at {}, status {:?} command {:?}",
+ info, device_function, status, command
+ );
+ if let Some(virtio_type) = virtio_device_type(&info) {
+ debug!(" VirtIO {:?}", virtio_type);
+ allocate_bars(pci_root, device_function, allocator)?;
+ }
+ }
+
+ Ok(())
+}
+
+/// Allocates 32-bit memory addresses for PCI BARs.
+#[derive(Debug)]
+pub struct PciMemory32Allocator {
+ /// The start of the available (not yet allocated) address space for PCI BARs.
+ start: u32,
+ /// The end of the available address space.
+ end: u32,
+}
+
+impl PciMemory32Allocator {
+ pub fn new(pci_info: &PciInfo) -> Self {
+ Self { start: pci_info.bar_range.start, end: pci_info.bar_range.end }
+ }
+
+ /// Allocates a 32-bit memory address region for a PCI BAR of the given power-of-2 size.
+ ///
+ /// It will have alignment matching the size. The size must be a power of 2.
+ pub fn allocate_memory_32(&mut self, size: u32) -> Option<u32> {
+ assert!(size.is_power_of_two());
+ let allocated_address = align_up(self.start, size);
+ if allocated_address + size <= self.end {
+ self.start = allocated_address + size;
+ Some(allocated_address)
+ } else {
+ None
+ }
+ }
+}
+
+/// Allocates appropriately-sized memory regions and assigns them to the device's BARs.
+fn allocate_bars(
+ root: &mut PciRoot,
+ device_function: DeviceFunction,
+ allocator: &mut PciMemory32Allocator,
+) -> Result<(), PciError> {
+ let mut bar_index = 0;
+ while bar_index < 6 {
+ let info = root.bar_info(device_function, bar_index).map_err(PciError::BarInfoFailed)?;
+ debug!("BAR {}: {}", bar_index, info);
+ // Ignore I/O bars, as they aren't required for the VirtIO driver.
+ if let BarInfo::Memory { address_type, size, .. } = info {
+ match address_type {
+ _ if size == 0 => {}
+ MemoryBarType::Width32 => {
+ let address = allocator
+ .allocate_memory_32(size)
+ .ok_or(PciError::BarAllocationFailed { size, device_function })?;
+ debug!("Allocated address {:#010x}", address);
+ root.set_bar_32(device_function, bar_index, address);
+ }
+ _ => {
+ return Err(PciError::UnsupportedBarType(address_type));
+ }
+ }
+ }
+
+ bar_index += 1;
+ if info.takes_two_entries() {
+ bar_index += 1;
+ }
+ }
+
+ // Enable the device to use its BARs.
+ root.set_command(
+ device_function,
+ Command::IO_SPACE | Command::MEMORY_SPACE | Command::BUS_MASTER,
+ );
+ let (status, command) = root.get_status_command(device_function);
+ debug!("Allocated BARs and enabled device, status {:?} command {:?}", status, command);
+
+ Ok(())
+}
+
+// TODO: Make the alignment functions in the helpers module generic once const_trait_impl is stable.
+const fn align_up(value: u32, alignment: u32) -> u32 {
+ ((value - 1) | (alignment - 1)) + 1
+}
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index 077c74f..a4ecc45 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -17,9 +17,9 @@
/** {@hide} */
interface ITestService {
- const int SERVICE_PORT = 5678;
+ const long SERVICE_PORT = 5678;
- const int ECHO_REVERSE_PORT = 6789;
+ const long ECHO_REVERSE_PORT = 0x80000001L; // Deliberately chosen to be > 2^31, < 2^32
/* add two integers. */
int addInteger(int a, int b);
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 72a0090..9aed34d 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
@@ -248,9 +248,6 @@
vm.clearCallback();
mExecutorService.shutdown();
}
-
- @Override
- public void onRamdump(VirtualMachine vm, ParcelFileDescriptor ramdump) {}
}
public static class BootResult {
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 8b0d6d2..25f2310 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -264,8 +264,9 @@
@Test
@CddTest(requirements = {"9.17/C-1-1"})
public void vmConfigUnitTests() {
- VirtualMachineConfig minimal =
- newVmConfigBuilder().setPayloadBinaryPath("binary/path").build();
+
+ VirtualMachineConfig.Builder minimalBuilder = newVmConfigBuilder();
+ VirtualMachineConfig minimal = minimalBuilder.setPayloadBinaryPath("binary/path").build();
assertThat(minimal.getApkPath()).isEqualTo(getContext().getPackageCodePath());
assertThat(minimal.getDebugLevel()).isEqualTo(DEBUG_LEVEL_NONE);
@@ -274,6 +275,8 @@
assertThat(minimal.getPayloadBinaryPath()).isEqualTo("binary/path");
assertThat(minimal.getPayloadConfigPath()).isNull();
assertThat(minimal.isProtectedVm()).isEqualTo(isProtectedVm());
+ assertThat(minimal.isEncryptedStorageEnabled()).isFalse();
+ assertThat(minimal.getEncryptedStorageKib()).isEqualTo(0);
int maxCpus = Runtime.getRuntime().availableProcessors();
VirtualMachineConfig.Builder maximalBuilder =
@@ -282,7 +285,8 @@
.setApkPath("/apk/path")
.setNumCpus(maxCpus)
.setDebugLevel(DEBUG_LEVEL_FULL)
- .setMemoryMib(42);
+ .setMemoryMib(42)
+ .setEncryptedStorageKib(1024);
VirtualMachineConfig maximal = maximalBuilder.build();
assertThat(maximal.getApkPath()).isEqualTo("/apk/path");
@@ -292,6 +296,8 @@
assertThat(maximal.getPayloadBinaryPath()).isNull();
assertThat(maximal.getPayloadConfigPath()).isEqualTo("config/path");
assertThat(maximal.isProtectedVm()).isEqualTo(isProtectedVm());
+ assertThat(maximal.isEncryptedStorageEnabled()).isTrue();
+ assertThat(maximal.getEncryptedStorageKib()).isEqualTo(1024);
assertThat(minimal.isCompatibleWith(maximal)).isFalse();
assertThat(minimal.isCompatibleWith(minimal)).isTrue();
@@ -299,6 +305,10 @@
VirtualMachineConfig compatible = maximalBuilder.setNumCpus(1).setMemoryMib(99).build();
assertThat(compatible.isCompatibleWith(maximal)).isTrue();
+
+ // Assert that different encrypted storage size would imply the configs are incompatible
+ VirtualMachineConfig incompatible = minimalBuilder.setEncryptedStorageKib(1048).build();
+ assertThat(incompatible.isCompatibleWith(minimal)).isFalse();
}
@Test
@@ -448,10 +458,10 @@
VirtualMachineConfig.Builder builder =
newVmConfigBuilder()
.setPayloadBinaryPath("MicrodroidTestNativeLib.so")
- .setApkPath("relative/path/to.apk")
.setDebugLevel(DEBUG_LEVEL_FULL)
.setMemoryMib(minMemoryRequired());
- assertThrows(IllegalArgumentException.class, () -> builder.build());
+ assertThrows(
+ IllegalArgumentException.class, () -> builder.setApkPath("relative/path/to.apk"));
}
@Test
@@ -927,13 +937,28 @@
}
@Test
- public void importedVmIsEqualToTheOriginalVm() throws Exception {
+ @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ public void importedVmIsEqualToTheOriginalVm_WithoutStorage() throws Exception {
+ TestResults testResults = importedVmIsEqualToTheOriginalVm(false);
+ assertThat(testResults.mEncryptedStoragePath).isEqualTo("");
+ }
+
+ @Test
+ @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ public void importedVmIsEqualToTheOriginalVm_WithStorage() throws Exception {
+ TestResults testResults = importedVmIsEqualToTheOriginalVm(true);
+ assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
+ }
+
+ private TestResults importedVmIsEqualToTheOriginalVm(boolean encryptedStoreEnabled)
+ throws Exception {
// Arrange
- VirtualMachineConfig config =
+ VirtualMachineConfig.Builder builder =
newVmConfigBuilder()
.setPayloadBinaryPath("MicrodroidTestNativeLib.so")
- .setDebugLevel(DEBUG_LEVEL_FULL)
- .build();
+ .setDebugLevel(DEBUG_LEVEL_FULL);
+ if (encryptedStoreEnabled) builder = builder.setEncryptedStorageKib(4096);
+ VirtualMachineConfig config = builder.build();
String vmNameOrig = "test_vm_orig";
String vmNameImport = "test_vm_import";
VirtualMachine vmOrig = forceCreateNewVirtualMachine(vmNameOrig, config);
@@ -953,12 +978,34 @@
// Asserts
assertFileContentsAreEqualInTwoVms("config.xml", vmNameOrig, vmNameImport);
assertFileContentsAreEqualInTwoVms("instance.img", vmNameOrig, vmNameImport);
+ if (encryptedStoreEnabled) {
+ assertFileContentsAreEqualInTwoVms("storage.img", vmNameOrig, vmNameImport);
+ }
assertThat(vmImport).isNotEqualTo(vmOrig);
vmm.delete(vmNameOrig);
assertThat(vmImport).isEqualTo(vmm.get(vmNameImport));
TestResults testResults = runVmTestService(vmImport);
assertThat(testResults.mException).isNull();
assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
+ return testResults;
+ }
+
+ @Test
+ @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ public void encryptedStorageAvailable() throws Exception {
+ assumeSupportedKernel();
+
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setMemoryMib(minMemoryRequired())
+ .setEncryptedStorageKib(4096)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+
+ TestResults testResults = runVmTestService(vm);
+ assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
}
private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 8a0019d..b6a7aa2 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -112,7 +112,7 @@
}
struct sockaddr_vm server_sa = (struct sockaddr_vm){
.svm_family = AF_VSOCK,
- .svm_port = BnTestService::ECHO_REVERSE_PORT,
+ .svm_port = static_cast<uint32_t>(BnTestService::ECHO_REVERSE_PORT),
.svm_cid = VMADDR_CID_ANY,
};
int ret = TEMP_FAILURE_RETRY(bind(server_fd, (struct sockaddr*)&server_sa, sizeof(server_sa)));
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
index a329fa6..34b6fa5 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
@@ -50,9 +50,4 @@
* also use `link_to_death` to handle that.
*/
void onDied(int cid, in DeathReason reason);
-
- /**
- * Called when kernel panic occurs and as a result ramdump is generated from the VM.
- */
- void onRamdump(int cid, in ParcelFileDescriptor ramdump);
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 7d24a32..a35c2ac 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -115,6 +115,24 @@
}
}
+fn create_or_update_idsig_file(
+ input_fd: &ParcelFileDescriptor,
+ idsig_fd: &ParcelFileDescriptor,
+) -> Result<()> {
+ let mut input = clone_file(input_fd)?;
+ let metadata = input.metadata().context("failed to get input metadata")?;
+ if !metadata.is_file() {
+ bail!("input is not a regular file");
+ }
+ let mut sig = V4Signature::create(&mut input, 4096, &[], HashAlgorithm::SHA256)
+ .context("failed to create idsig")?;
+
+ let mut output = clone_file(idsig_fd)?;
+ output.set_len(0).context("failed to set_len on the idsig output")?;
+ sig.write_into(&mut output).context("failed to write idsig")?;
+ Ok(())
+}
+
/// Singleton service for allocating globally-unique VM resources, such as the CID, and running
/// singleton servers, like tombstone receiver.
#[derive(Debug, Default)]
@@ -345,12 +363,8 @@
check_manage_access()?;
- let mut input = clone_file(input_fd)?;
- let mut sig = V4Signature::create(&mut input, 4096, &[], HashAlgorithm::SHA256).unwrap();
-
- let mut output = clone_file(idsig_fd)?;
- output.set_len(0).unwrap();
- sig.write_into(&mut output).unwrap();
+ create_or_update_idsig_file(input_fd, idsig_fd)
+ .map_err(|e| Status::new_service_specific_error_str(-1, Some(format!("{:?}", e))))?;
Ok(())
}
@@ -463,9 +477,8 @@
let service = VirtualMachineService::new_binder(self.state.clone(), cid).as_binder();
// Start VM service listening for connections from the new CID on port=CID.
- // TODO(b/245727626): Only accept connections from the new VM.
let port = cid;
- match RpcServer::new_vsock(service, port) {
+ match RpcServer::new_vsock(service, cid, port) {
Ok(vm_server) => {
vm_server.start();
return Ok((VmContext::new(global_context, vm_server), cid));
@@ -881,8 +894,9 @@
// Return whether a partition is exempt from selinux label checks, because we know that it does
// not contain code and is likely to be generated in an app-writable directory.
fn is_safe_app_partition(label: &str) -> bool {
- // See make_payload_disk in payload.rs.
+ // See add_microdroid_system_images & add_microdroid_payload_images in payload.rs.
label == "vm-instance"
+ || label == "encryptedstore"
|| label == "microdroid-apk-idsig"
|| label == "payload-metadata"
|| label.starts_with("extra-idsig-")
@@ -898,7 +912,7 @@
match ctx.selinux_type()? {
| "system_file" // immutable dm-verity protected partition
| "apk_data_file" // APKs of an installed app
- | "staging_data_file" // updated/staged APEX imagess
+ | "staging_data_file" // updated/staged APEX images
| "shell_data_file" // test files created via adb shell
=> Ok(()),
_ => bail!("Label {} is not allowed", ctx),
@@ -973,13 +987,16 @@
if !matches!(&*self.instance.vm_state.lock().unwrap(), VmState::Running { .. }) {
return Err(Status::new_service_specific_error_str(-1, Some("VM is not running")));
}
- let stream =
- VsockStream::connect_with_cid_port(self.instance.cid, port as u32).map_err(|e| {
- Status::new_service_specific_error_str(
- -1,
- Some(format!("Failed to connect: {:?}", e)),
- )
- })?;
+ let port = port as u32;
+ if port < 1024 {
+ return Err(Status::new_service_specific_error_str(
+ -1,
+ Some(format!("Can't connect to privileged port {port}")),
+ ));
+ }
+ let stream = VsockStream::connect_with_cid_port(self.instance.cid, port).map_err(|e| {
+ Status::new_service_specific_error_str(-1, Some(format!("Failed to connect: {:?}", e)))
+ })?;
Ok(vsock_stream_to_pfd(stream))
}
}
@@ -1049,17 +1066,6 @@
}
}
- /// Call all registered callbacks to say that there was a ramdump to download.
- pub fn callback_on_ramdump(&self, cid: Cid, ramdump: File) {
- let callbacks = &*self.0.lock().unwrap();
- let pfd = ParcelFileDescriptor::new(ramdump);
- for callback in callbacks {
- if let Err(e) = callback.onRamdump(cid as i32, &pfd) {
- error!("Error notifying ramdump of VM CID {}: {:?}", cid, e);
- }
- }
- }
-
/// Add a new callback to the set.
fn add(&self, callback: Strong<dyn IVirtualMachineCallback>) {
self.0.lock().unwrap().push(callback);
@@ -1302,4 +1308,50 @@
}
Ok(())
}
+
+ #[test]
+ fn test_create_or_update_idsig_file_empty_apk() -> Result<()> {
+ let apk = tempfile::tempfile().unwrap();
+ let idsig = tempfile::tempfile().unwrap();
+
+ let ret = create_or_update_idsig_file(
+ &ParcelFileDescriptor::new(apk),
+ &ParcelFileDescriptor::new(idsig),
+ );
+ assert!(ret.is_err(), "should fail");
+ Ok(())
+ }
+
+ #[test]
+ fn test_create_or_update_idsig_dir_instead_of_file_for_apk() -> Result<()> {
+ let tmp_dir = tempfile::TempDir::new().unwrap();
+ let apk = File::open(tmp_dir.path()).unwrap();
+ let idsig = tempfile::tempfile().unwrap();
+
+ let ret = create_or_update_idsig_file(
+ &ParcelFileDescriptor::new(apk),
+ &ParcelFileDescriptor::new(idsig),
+ );
+ assert!(ret.is_err(), "should fail");
+ Ok(())
+ }
+
+ /// Verifies that create_or_update_idsig_file won't oom if a fd that corresponds to a directory
+ /// on ext4 filesystem is passed.
+ /// On ext4 lseek on a directory fd will return (off_t)-1 (see:
+ /// https://bugzilla.kernel.org/show_bug.cgi?id=200043), which will result in
+ /// create_or_update_idsig_file ooming while attempting to allocate petabytes of memory.
+ #[test]
+ fn test_create_or_update_idsig_does_not_crash_dir_on_ext4() -> Result<()> {
+ // APEXes are backed by the ext4.
+ let apk = File::open("/apex/com.android.virt/").unwrap();
+ let idsig = tempfile::tempfile().unwrap();
+
+ let ret = create_or_update_idsig_file(
+ &ParcelFileDescriptor::new(apk),
+ &ParcelFileDescriptor::new(idsig),
+ );
+ assert!(ret.is_err(), "should fail");
+ Ok(())
+ }
}
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index fc85ca5..5125f19 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -520,15 +520,10 @@
Ok(())
}
- /// Checks if ramdump has been created. If so, send a notification to the user with the handle
- /// to read the ramdump.
+ /// Checks if ramdump has been created. If so, send it to tombstoned.
fn handle_ramdump(&self) -> Result<(), Error> {
let ramdump_path = self.temporary_directory.join("ramdump");
if std::fs::metadata(&ramdump_path)?.len() > 0 {
- let ramdump = File::open(&ramdump_path)
- .context(format!("Failed to open ramdump {:?} for reading", &ramdump_path))?;
- self.callbacks.callback_on_ramdump(self.cid, ramdump);
-
Self::send_ramdump_to_tombstoned(&ramdump_path)?;
}
Ok(())
@@ -536,7 +531,7 @@
fn send_ramdump_to_tombstoned(ramdump_path: &Path) -> Result<(), Error> {
let mut input = File::open(ramdump_path)
- .context(format!("Failed to open raudmp {:?} for reading", ramdump_path))?;
+ .context(format!("Failed to open ramdump {:?} for reading", ramdump_path))?;
let pid = std::process::id() as i32;
let conn = TombstonedConnection::connect(pid, DebuggerdDumpType::Tombstone)
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 32b165b..3d2fc00 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -81,10 +81,6 @@
#[clap(long)]
log: Option<PathBuf>,
- /// Path to file where ramdump is recorded on kernel panic
- #[clap(long)]
- ramdump: Option<PathBuf>,
-
/// Debug level of the VM. Supported values: "none" (default), and "full".
#[clap(long, default_value = "none", value_parser = parse_debug_level)]
debug: DebugLevel,
@@ -144,10 +140,6 @@
#[clap(long)]
log: Option<PathBuf>,
- /// Path to file where ramdump is recorded on kernel panic
- #[clap(long)]
- ramdump: Option<PathBuf>,
-
/// Debug level of the VM. Supported values: "none" (default), and "full".
#[clap(long, default_value = "full", value_parser = parse_debug_level)]
debug: DebugLevel,
@@ -268,7 +260,6 @@
daemonize,
console,
log,
- ramdump,
debug,
protected,
mem,
@@ -288,7 +279,6 @@
daemonize,
console.as_deref(),
log.as_deref(),
- ramdump.as_deref(),
debug,
protected,
mem,
@@ -304,7 +294,6 @@
daemonize,
console,
log,
- ramdump,
debug,
protected,
mem,
@@ -319,7 +308,6 @@
daemonize,
console.as_deref(),
log.as_deref(),
- ramdump.as_deref(),
debug,
protected,
mem,
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 3f25bba..6096913 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -52,7 +52,6 @@
daemonize: bool,
console_path: Option<&Path>,
log_path: Option<&Path>,
- ramdump_path: Option<&Path>,
debug_level: DebugLevel,
protected: bool,
mem: Option<u32>,
@@ -144,7 +143,7 @@
numCpus: cpus.unwrap_or(1) as i32,
taskProfiles: task_profiles,
});
- run(service, &config, &payload_config_str, daemonize, console_path, log_path, ramdump_path)
+ run(service, &config, &payload_config_str, daemonize, console_path, log_path)
}
const EMPTY_PAYLOAD_APK: &str = "com.android.microdroid.empty_payload";
@@ -182,7 +181,6 @@
daemonize: bool,
console_path: Option<&Path>,
log_path: Option<&Path>,
- ramdump_path: Option<&Path>,
debug_level: DebugLevel,
protected: bool,
mem: Option<u32>,
@@ -214,7 +212,6 @@
daemonize,
console_path,
log_path,
- ramdump_path,
debug_level,
protected,
mem,
@@ -259,7 +256,6 @@
daemonize,
console_path,
log_path,
- /* ramdump_path */ None,
)
}
@@ -282,7 +278,6 @@
daemonize: bool,
console_path: Option<&Path>,
log_path: Option<&Path>,
- ramdump_path: Option<&Path>,
) -> Result<(), Error> {
let console = if let Some(console_path) = console_path {
Some(
@@ -325,27 +320,12 @@
// Wait until the VM or VirtualizationService dies. If we just returned immediately then the
// IVirtualMachine Binder object would be dropped and the VM would be killed.
let death_reason = vm.wait_for_death();
-
- if let Some(path) = ramdump_path {
- save_ramdump_if_available(path, &vm)?;
- }
println!("VM ended: {:?}", death_reason);
}
Ok(())
}
-fn save_ramdump_if_available(path: &Path, vm: &VmInstance) -> Result<(), Error> {
- if let Some(mut ramdump) = vm.get_ramdump() {
- let mut file =
- File::create(path).context(format!("Failed to create ramdump file {:?}", path))?;
- let size = std::io::copy(&mut ramdump, &mut file)
- .context(format!("Failed to save ramdump to file {:?}", path))?;
- eprintln!("Ramdump ({} bytes) saved to {:?}", size, path);
- }
- Ok(())
-}
-
fn parse_extra_apk_list(apk: &Path, config_path: &str) -> Result<Vec<String>, Error> {
let mut archive = ZipArchive::new(File::open(apk)?)?;
let config_file = archive.by_name(config_path)?;
diff --git a/vm_payload/src/api.rs b/vm_payload/src/api.rs
index 28b440e..66c8ef7 100644
--- a/vm_payload/src/api.rs
+++ b/vm_payload/src/api.rs
@@ -136,7 +136,7 @@
// safely be taken by new_spibinder.
let service = unsafe { new_spibinder(service) };
if let Some(service) = service {
- match RpcServer::new_vsock(service, port) {
+ match RpcServer::new_vsock(service, libc::VMADDR_CID_HOST, port) {
Ok(server) => {
if let Some(on_ready) = on_ready {
// SAFETY: We're calling the callback with the parameter specified within the
diff --git a/vmbase/Android.bp b/vmbase/Android.bp
index 7a36a0a..5ed436c 100644
--- a/vmbase/Android.bp
+++ b/vmbase/Android.bp
@@ -68,7 +68,7 @@
"libspin_nostd",
],
whole_static_libs: [
- "libarm-optimized-routines-mem",
+ "librust_baremetal",
],
apex_available: ["com.android.virt"],
}
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index 20b7f02..7c05545 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -190,11 +190,6 @@
}
})
}
-
- /// Get ramdump
- pub fn get_ramdump(&self) -> Option<File> {
- self.state.get_ramdump()
- }
}
impl Debug for VmInstance {
@@ -222,7 +217,6 @@
struct VmState {
death_reason: Option<DeathReason>,
reported_state: VirtualMachineState,
- ramdump: Option<File>,
}
impl Monitor<VmState> {
@@ -239,14 +233,6 @@
self.state.lock().unwrap().reported_state = state;
self.cv.notify_all();
}
-
- fn set_ramdump(&self, ramdump: File) {
- self.state.lock().unwrap().ramdump = Some(ramdump);
- }
-
- fn get_ramdump(&self) -> Option<File> {
- self.state.lock().unwrap().ramdump.as_ref().and_then(|f| f.try_clone().ok())
- }
}
struct VirtualMachineCallback {
@@ -302,12 +288,6 @@
Ok(())
}
- fn onRamdump(&self, _cid: i32, ramdump: &ParcelFileDescriptor) -> BinderResult<()> {
- let ramdump: File = ramdump.as_ref().try_clone().unwrap();
- self.state.set_ramdump(ramdump);
- Ok(())
- }
-
fn onDied(&self, cid: i32, reason: AidlDeathReason) -> BinderResult<()> {
let reason = reason.into();
self.state.notify_death(reason);