Merge "Make enabled but inactive ports visible in port forwarding setting" into main
diff --git a/android/TerminalApp/AndroidManifest.xml b/android/TerminalApp/AndroidManifest.xml
index 7dab58d..726004c 100644
--- a/android/TerminalApp/AndroidManifest.xml
+++ b/android/TerminalApp/AndroidManifest.xml
@@ -54,7 +54,9 @@
android:label="@string/settings_port_forwarding_title" />
<activity android:name=".SettingsRecoveryActivity"
android:label="@string/settings_recovery_title" />
- <activity android:name=".ErrorActivity" />
+ <activity android:name=".ErrorActivity"
+ android:label="@string/error_title"
+ android:process=":error" />
<property
android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
android:value="true" />
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/BaseActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/BaseActivity.java
index d6521be..d6ca1e6 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/BaseActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/BaseActivity.java
@@ -39,6 +39,15 @@
NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
+
+ if (!(this instanceof ErrorActivity)) {
+ Thread currentThread = Thread.currentThread();
+ if (!(currentThread.getUncaughtExceptionHandler()
+ instanceof TerminalExceptionHandler)) {
+ currentThread.setUncaughtExceptionHandler(
+ new TerminalExceptionHandler(getApplicationContext()));
+ }
+ }
}
@Override
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/CertificateUtils.java b/android/TerminalApp/java/com/android/virtualization/terminal/CertificateUtils.java
index fa5c382..e3d1a67 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/CertificateUtils.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/CertificateUtils.java
@@ -62,9 +62,8 @@
}
return ((KeyStore.PrivateKeyEntry) ks.getEntry(ALIAS, null));
} catch (Exception e) {
- Log.e(TAG, "cannot generate or get key", e);
+ throw new RuntimeException("cannot generate or get key", e);
}
- return null;
}
private static void createKey()
@@ -95,7 +94,7 @@
+ end_cert;
writer.write(output.getBytes());
} catch (IOException | CertificateEncodingException e) {
- Log.d(TAG, "cannot write cert", e);
+ throw new RuntimeException("cannot write certs", e);
}
}
}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
index ac05d78..66ab414 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
@@ -41,7 +41,6 @@
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.file.Path;
-import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -75,6 +74,7 @@
this, /* requestCode= */ 0, intent, PendingIntent.FLAG_IMMUTABLE);
mNotification =
new Notification.Builder(this, this.getPackageName())
+ .setSilent(true)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle(getString(R.string.installer_notif_title_text))
.setContentText(getString(R.string.installer_notif_desc_text))
@@ -82,7 +82,9 @@
.setContentIntent(pendingIntent)
.build();
- mExecutorService = Executors.newSingleThreadExecutor();
+ mExecutorService =
+ Executors.newSingleThreadExecutor(
+ new TerminalThreadFactory(getApplicationContext()));
mConnectivityManager = getSystemService(ConnectivityManager.class);
Network defaultNetwork = mConnectivityManager.getBoundNetworkForProcess();
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 397a546..624d6ca 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -32,6 +32,7 @@
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Environment;
+import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
@@ -67,6 +68,8 @@
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
public class MainActivity extends BaseActivity
implements VmLauncherService.VmLauncherServiceCallback, AccessibilityStateChangeListener {
@@ -74,9 +77,11 @@
static final String KEY_DISK_SIZE = "disk_size";
private static final String VM_ADDR = "192.168.0.2";
private static final int TTYD_PORT = 7681;
+ private static final int TERMINAL_CONNECTION_TIMEOUT_MS = 10_000;
private static final int REQUEST_CODE_INSTALLER = 0x33;
private static final int FONT_SIZE_DEFAULT = 13;
+ private ExecutorService mExecutorService;
private InstalledImage mImage;
private X509Certificate[] mCertificates;
private PrivateKey mPrivateKey;
@@ -141,6 +146,11 @@
updateModifierKeysVisibility();
return insets;
});
+
+ mExecutorService =
+ Executors.newSingleThreadExecutor(
+ new TerminalThreadFactory(getApplicationContext()));
+
// if installer is launched, it will be handled in onActivityResult
if (!launchInstaller) {
if (!Environment.isExternalStorageManager()) {
@@ -323,15 +333,12 @@
handler.proceed();
}
});
- new Thread(
- () -> {
- waitUntilVmStarts();
- runOnUiThread(
- () ->
- mTerminalView.loadUrl(
- getTerminalServiceUrl().toString()));
- })
- .start();
+ mExecutorService.execute(
+ () -> {
+ // TODO(b/376793781): Remove polling
+ waitUntilVmStarts();
+ runOnUiThread(() -> mTerminalView.loadUrl(getTerminalServiceUrl().toString()));
+ });
}
private static void waitUntilVmStarts() {
@@ -341,17 +348,33 @@
} catch (UnknownHostException e) {
// this can never happen.
}
- try {
- while (!addr.isReachable(10000)) {}
- } catch (IOException e) {
- // give up on network error
- throw new RuntimeException(e);
+
+ long startTime = SystemClock.elapsedRealtime();
+ while (true) {
+ int remainingTime =
+ TERMINAL_CONNECTION_TIMEOUT_MS
+ - (int) (SystemClock.elapsedRealtime() - startTime);
+ if (remainingTime <= 0) {
+ throw new RuntimeException("Connection to terminal timedout");
+ }
+ try {
+ // Note: this quits immediately if VM is unreachable.
+ if (addr.isReachable(remainingTime)) {
+ return;
+ }
+ } catch (IOException e) {
+ // give up on network error
+ throw new RuntimeException(e);
+ }
}
- return;
}
@Override
protected void onDestroy() {
+ if (mExecutorService != null) {
+ mExecutorService.shutdown();
+ }
+
getSystemService(AccessibilityManager.class).removeAccessibilityStateChangeListener(this);
VmLauncherService.stop(this);
super.onDestroy();
@@ -471,6 +494,7 @@
Icon icon = Icon.createWithResource(getResources(), R.drawable.ic_launcher_foreground);
Notification notification =
new Notification.Builder(this, this.getPackageName())
+ .setSilent(true)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle(
getResources().getString(R.string.service_notification_title))
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalExceptionHandler.java b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalExceptionHandler.java
new file mode 100644
index 0000000..4ab2b77
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalExceptionHandler.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.terminal;
+
+import android.content.Context;
+import android.util.Log;
+
+public class TerminalExceptionHandler implements Thread.UncaughtExceptionHandler {
+ private static final String TAG = "TerminalExceptionHandler";
+
+ private final Context mContext;
+
+ public TerminalExceptionHandler(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void uncaughtException(Thread thread, Throwable throwable) {
+ Exception exception;
+ if (throwable instanceof Exception) {
+ exception = (Exception) throwable;
+ } else {
+ exception = new Exception(throwable);
+ }
+ try {
+ ErrorActivity.start(mContext, exception);
+ } catch (Exception ex) {
+ Log.wtf(TAG, "Failed to launch error activity for an exception", exception);
+ }
+
+ thread.getDefaultUncaughtExceptionHandler().uncaughtException(thread, throwable);
+ }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalThreadFactory.java b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalThreadFactory.java
new file mode 100644
index 0000000..5ee535d
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalThreadFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.terminal;
+
+import android.content.Context;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+public class TerminalThreadFactory implements ThreadFactory {
+ private final Context mContext;
+
+ public TerminalThreadFactory(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread thread = Executors.defaultThreadFactory().newThread(r);
+ thread.setUncaughtExceptionHandler(new TerminalExceptionHandler(mContext));
+ return thread;
+ }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
index 6d2c5bd..f262f1f 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
@@ -20,9 +20,11 @@
import android.app.Notification;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -143,8 +145,13 @@
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (Objects.equals(intent.getAction(), ACTION_STOP_VM_LAUNCHER_SERVICE)) {
- // If there is no Debian service or it fails to shutdown, just stop the service.
- if (mDebianService == null || !mDebianService.shutdownDebian()) {
+
+ if (mDebianService != null && mDebianService.shutdownDebian()) {
+ // During shutdown, change the notification content to indicate that it's closing
+ Notification notification = createNotificationForTerminalClose();
+ getSystemService(NotificationManager.class).notify(this.hashCode(), notification);
+ } else {
+ // If there is no Debian service or it fails to shutdown, just stop the service.
stopSelf();
}
return START_NOT_STICKY;
@@ -153,7 +160,8 @@
Log.d(TAG, "VM instance is already started");
return START_NOT_STICKY;
}
- mExecutorService = Executors.newCachedThreadPool();
+ mExecutorService =
+ Executors.newCachedThreadPool(new TerminalThreadFactory(getApplicationContext()));
InstalledImage image = InstalledImage.getDefault(this);
ConfigJson json = ConfigJson.from(this, image.getConfigPath());
@@ -172,9 +180,7 @@
android.os.Trace.endSection();
android.os.Trace.beginAsyncSection("debianBoot", 0);
} catch (VirtualMachineException e) {
- Log.e(TAG, "cannot create runner", e);
- stopSelf();
- return START_NOT_STICKY;
+ throw new RuntimeException("cannot create runner", e);
}
mVirtualMachine = runner.getVm();
mResultReceiver =
@@ -204,6 +210,32 @@
return START_NOT_STICKY;
}
+ private Notification createNotificationForTerminalClose() {
+ Intent stopIntent = new Intent();
+ stopIntent.setClass(this, VmLauncherService.class);
+ stopIntent.setAction(VmLauncherService.ACTION_STOP_VM_LAUNCHER_SERVICE);
+ PendingIntent stopPendingIntent =
+ PendingIntent.getService(
+ this,
+ 0,
+ stopIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ Icon icon = Icon.createWithResource(getResources(), R.drawable.ic_launcher_foreground);
+ String stopActionText =
+ getResources().getString(R.string.service_notification_force_quit_action);
+ String stopNotificationTitle =
+ getResources().getString(R.string.service_notification_close_title);
+ return new Notification.Builder(this, this.getPackageName())
+ .setSmallIcon(R.drawable.ic_launcher_foreground)
+ .setContentTitle(stopNotificationTitle)
+ .setOngoing(true)
+ .setSilent(true)
+ .addAction(
+ new Notification.Action.Builder(icon, stopActionText, stopPendingIntent)
+ .build())
+ .build();
+ }
+
private boolean overrideConfigIfNecessary(VirtualMachineCustomImageConfig.Builder builder) {
boolean changed = false;
// TODO: check if ANGLE is enabled for the app.
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index 493496c..9cb6e4d 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -155,6 +155,11 @@
<!-- Notification action button for closing the virtual machine [CHAR LIMIT=20] -->
<string name="service_notification_quit_action">Close</string>
+ <!-- Notification title for foreground service notification during closing [CHAR LIMIT=none] -->
+ <string name="service_notification_close_title">Terminal is closing</string>
+ <!-- Notification action button for force-closing the virtual machine [CHAR LIMIT=30] -->
+ <string name="service_notification_force_quit_action">Force close</string>
+
<!-- This string is for toast message to notify that VirGL is enabled. [CHAR LIMIT=40] -->
<string name="virgl_enabled"><xliff:g>VirGL</xliff:g> is enabled</string>
</resources>
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index e9074c6..82a5573 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -18,7 +18,7 @@
use crate::atom::{write_vm_booted_stats, write_vm_creation_stats};
use crate::composite::make_composite_image;
use crate::crosvm::{AudioConfig, CrosvmConfig, DiskFile, SharedPathConfig, DisplayConfig, GpuConfig, InputDeviceOption, PayloadState, UsbConfig, VmContext, VmInstance, VmState};
-use crate::debug_config::DebugConfig;
+use crate::debug_config::{DebugConfig, DebugPolicy};
use crate::dt_overlay::{create_device_tree_overlay, VM_DT_OVERLAY_MAX_SIZE, VM_DT_OVERLAY_PATH};
use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images, add_microdroid_vendor_image};
use crate::selinux::{check_tee_service_permission, getfilecon, getprevcon, SeContext};
@@ -319,6 +319,12 @@
Ok(Vec::from_iter(SUPPORTED_OS_NAMES.iter().cloned()))
}
+ /// Get printable debug policy for testing and debugging
+ fn getDebugPolicy(&self) -> binder::Result<String> {
+ let debug_policy = DebugPolicy::from_host();
+ Ok(format!("{debug_policy:?}"))
+ }
+
/// Returns whether given feature is enabled
fn isFeatureEnabled(&self, feature: &str) -> binder::Result<bool> {
check_manage_access()?;
diff --git a/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index 0c3f6b7..169c3dc 100644
--- a/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -82,6 +82,11 @@
*/
String[] getSupportedOSList();
+ /**
+ * Get installed debug policy for test and debugging purpose.
+ */
+ String getDebugPolicy();
+
/** Returns whether given feature is enabled. */
boolean isFeatureEnabled(in String feature);
diff --git a/android/vm/src/main.rs b/android/vm/src/main.rs
index 7bfd957..830d56c 100644
--- a/android/vm/src/main.rs
+++ b/android/vm/src/main.rs
@@ -483,6 +483,9 @@
let os_list = get_service()?.getSupportedOSList()?;
println!("Available OS list: {}", serde_json::to_string(&os_list)?);
+ let debug_policy = get_service()?.getDebugPolicy()?;
+ println!("Debug policy: {}", debug_policy);
+
Ok(())
}
diff --git a/build/debian/build.sh b/build/debian/build.sh
index 19894c2..cc38dfd 100755
--- a/build/debian/build.sh
+++ b/build/debian/build.sh
@@ -11,6 +11,7 @@
echo "Options:"
echo "-h Print usage and this help message and exit."
echo "-a ARCH Architecture of the image [default is host arch: $(uname -m)]"
+ echo "-k Build and use our custom kernel [default is cloud kernel]"
echo "-r Release mode build"
echo "-w Save temp work directory [for debugging]"
}
@@ -22,7 +23,7 @@
}
parse_options() {
- while getopts "a:hrw" option; do
+ while getopts "a:hkrw" option; do
case ${option} in
h)
show_help ; exit
@@ -30,6 +31,9 @@
a)
arch="$OPTARG"
;;
+ k)
+ use_custom_kernel=1
+ ;;
r)
mode=release
;;
@@ -117,6 +121,33 @@
linux-image-generic
)
fi
+
+ if [[ "$use_custom_kernel" -eq 1 ]]; then
+ packages+=(
+ bc
+ bison
+ debhelper
+ dh-exec
+ flex
+ gcc-12
+ kernel-wedge
+ libelf-dev
+ libpci-dev
+ lz4
+ pahole
+ python3-jinja2
+ python3-docutils
+ quilt
+ rsync
+ )
+ if [[ "$arch" == "aarch64" ]]; then
+ packages+=(
+ gcc-arm-linux-gnueabihf
+ gcc-12-aarch64-linux-gnu
+ )
+ fi
+ fi
+
DEBIAN_FRONTEND=noninteractive \
apt install --no-install-recommends --assume-yes "${packages[@]}"
@@ -195,6 +226,74 @@
build_rust_binary_and_copy shutdown_runner
}
+package_custom_kernel() {
+ if [[ "$use_custom_kernel" != 1 ]]; then
+ echo "linux-headers-generic" >> "${config_space}/package_config/AVF"
+ return
+ fi
+
+ # NOTE: 6.1 is the latest LTS kernel for which Debian's kernel build scripts
+ # work on Python 3.10, the default version on our Ubuntu 22.04 builders.
+ local debian_kver="6.1.119-1"
+ local custom_flavour="avf"
+ local ksrc_base_url="https://deb.debian.org/debian/pool/main/l/linux"
+
+ local debian_ksrc_url="${ksrc_base_url}/linux_${debian_kver}.debian.tar.xz"
+ local orig_ksrc_url="${ksrc_base_url}/linux_${debian_kver%-*}.orig.tar.xz"
+
+ # 1. Grab original kernel source, merge debian patches etc.
+ mkdir -p "${workdir}/kernel/avf-${debian_arch}"
+ pushd "${workdir}/kernel" > /dev/null
+ wget "$orig_ksrc_url"
+ pushd "avf-${debian_arch}" > /dev/null
+ wget "${debian_ksrc_url}" -O - | tar xJ
+ # TODO: Copy our own kernel patches to debian/patches
+ # and add patch file names in the desired order to debian/patches/series
+ ./debian/rules orig
+
+ local abi_kver="$(sed -nE 's;Package: linux-support-(.*);\1;p' debian/control)"
+ local debarch_flavour="${custom_flavour}-${debian_arch}"
+ local abi_flavour="${abi_kver}-${debarch_flavour}"
+
+ # 2. Define our custom flavour and regenerate control file
+ # NOTE: Our flavour extends Debian's `cloud` config on the `none` featureset.
+ cat > debian/config/${debian_arch}/config.${debarch_flavour} <<EOF
+# TODO: Add our custom kernel config to this file
+EOF
+
+ sed -z "s;\[base\]\nflavours:;[base]\nflavours:\n ${debarch_flavour};" \
+ -i debian/config/${debian_arch}/none/defines
+ cat >> debian/config/${debian_arch}/none/defines <<EOF
+[${debarch_flavour}_image]
+configs:
+ config.cloud
+ ${debian_arch}/config.${debarch_flavour}
+EOF
+ cat >> debian/config/${debian_arch}/defines <<EOF
+[${debarch_flavour}_description]
+hardware: ${arch} AVF
+hardware-long: ${arch} Android Virtualization Framework
+EOF
+ ./debian/rules debian/control || true
+
+ # 3. Build the kernel and generate Debian packages
+ ./debian/rules source
+ [[ "$arch" == "$(uname -m)" ]] || export $(dpkg-architecture -a $debian_arch)
+ make -j$(nproc) -f debian/rules.gen \
+ "binary-arch_${debian_arch}_none_${debarch_flavour}"
+
+ # 4. Copy the packages to localdebs and add their names to package_config/AVF
+ popd > /dev/null
+ cp "linux-headers-${abi_flavour}_${debian_kver}_${debian_arch}.deb" \
+ "linux-image-${abi_flavour}-unsigned_${debian_kver}_${debian_arch}.deb" \
+ "${debian_cloud_image}/localdebs/"
+ popd > /dev/null
+ cat >> "${config_space}/package_config/AVF" <<EOF
+linux-headers-${abi_flavour}
+linux-image-${abi_flavour}-unsigned
+EOF
+}
+
run_fai() {
local out="${built_image}"
make -C "${debian_cloud_image}" "image_bookworm_nocloud_${debian_arch}"
@@ -238,12 +337,14 @@
arch="$(uname -m)"
mode=debug
save_workdir=0
+use_custom_kernel=0
parse_options "$@"
check_sudo
install_prerequisites
download_debian_cloud_image
copy_android_config
+package_custom_kernel
run_fai
fdisk -l "${built_image}"
images=()
diff --git a/build/debian/build_in_container.sh b/build/debian/build_in_container.sh
index 5028b74..967f5ab 100755
--- a/build/debian/build_in_container.sh
+++ b/build/debian/build_in_container.sh
@@ -6,17 +6,19 @@
echo "Options:"
echo "-h Print usage and this help message and exit."
echo "-a ARCH Architecture of the image [default is host arch: $(uname -m)]"
+ echo "-k Build and use our custom kernel [default is cloud kernel]"
echo "-r Release mode build"
echo "-s Leave a shell open [default: only if the build fails]"
echo "-w Save temp work directory in the container [for debugging]"
}
arch="$(uname -m)"
+kernel_flag=
release_flag=
save_workdir_flag=
shell_condition="||"
-while getopts "a:rsw" option; do
+while getopts "a:hkrsw" option; do
case ${option} in
a)
arch="$OPTARG"
@@ -24,6 +26,9 @@
h)
show_help ; exit
;;
+ k)
+ kernel_flag="-k"
+ ;;
r)
release_flag="-r"
;;
@@ -53,4 +58,4 @@
-v "$ANDROID_BUILD_TOP/packages/modules/Virtualization:/root/Virtualization" \
--workdir /root/Virtualization/build/debian \
ubuntu:22.04 \
- bash -c "/root/Virtualization/build/debian/build.sh -a $arch $release_flag $save_workdir_flag $shell_condition bash"
+ bash -c "/root/Virtualization/build/debian/build.sh -a $arch $release_flag $kernel_flag $save_workdir_flag $shell_condition bash"
diff --git a/build/debian/fai_config/package_config/AVF b/build/debian/fai_config/package_config/AVF
index 2e55e90..a91c354 100644
--- a/build/debian/fai_config/package_config/AVF
+++ b/build/debian/fai_config/package_config/AVF
@@ -1,5 +1,4 @@
PACKAGES install
bpfcc-tools
-linux-headers-generic
procps
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
index 22ac595..a935591 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
+++ b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
@@ -5,7 +5,7 @@
cd "${KOKORO_ARTIFACTS_DIR}/git/avf/build/debian/"
sudo losetup -D
grep vmx /proc/cpuinfo || true
-sudo ./build.sh -r -a x86_64
+sudo ./build.sh -a x86_64 -k -r
sudo mv images.tar.gz ${KOKORO_ARTIFACTS_DIR} || true
mkdir -p ${KOKORO_ARTIFACTS_DIR}/logs
sudo cp -r /var/log/fai/* ${KOKORO_ARTIFACTS_DIR}/logs || true
diff --git a/build/microdroid/Android.bp b/build/microdroid/Android.bp
index 68b715d..dea0bf3 100644
--- a/build/microdroid/Android.bp
+++ b/build/microdroid/Android.bp
@@ -598,6 +598,12 @@
src: ":microdroid_kernel_prebuilt-x86_64",
},
},
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "16",
+ },
+ ],
include_descriptors_from_images: [
":microdroid_16k_initrd_normal_hashdesc",
":microdroid_16k_initrd_debug_hashdesc",
diff --git a/guest/pvmfw/README.md b/guest/pvmfw/README.md
index 8c8314d..766a923 100644
--- a/guest/pvmfw/README.md
+++ b/guest/pvmfw/README.md
@@ -461,6 +461,7 @@
- `secretkeeper_protection`: pvmfw defers rollback protection to the guest
- `supports_uefi_boot`: pvmfw boots the VM as a EFI payload (experimental)
- `trusty_security_vm`: pvmfw skips rollback protection
+- `"com.android.virt.page_size"`: the guest page size in KiB (optional, defaults to 4)
## Development
diff --git a/guest/pvmfw/avb/Android.bp b/guest/pvmfw/avb/Android.bp
index a1ee626..0294322 100644
--- a/guest/pvmfw/avb/Android.bp
+++ b/guest/pvmfw/avb/Android.bp
@@ -37,10 +37,16 @@
":test_image_with_one_hashdesc",
":test_image_with_non_initrd_hashdesc",
":test_image_with_initrd_and_non_initrd_desc",
- ":test_image_with_prop_desc",
+ ":test_image_with_invalid_page_size",
+ ":test_image_with_negative_page_size",
+ ":test_image_with_overflow_page_size",
+ ":test_image_with_0k_page_size",
+ ":test_image_with_1k_page_size",
+ ":test_image_with_4k_page_size",
+ ":test_image_with_9k_page_size",
+ ":test_image_with_16k_page_size",
":test_image_with_service_vm_prop",
":test_image_with_unknown_vm_type_prop",
- ":test_image_with_multiple_props",
":test_image_with_duplicated_capability",
":test_image_with_rollback_index_5",
":test_image_with_multiple_capabilities",
@@ -117,15 +123,113 @@
}
avb_add_hash_footer {
- name: "test_image_with_prop_desc",
+ name: "test_image_with_invalid_page_size",
src: ":unsigned_test_image",
partition_name: "boot",
private_key: ":pvmfw_sign_key",
salt: "2134",
props: [
{
- name: "mock_prop",
- value: "3333",
+ name: "com.android.virt.page_size",
+ value: "invalid",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_negative_page_size",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "-16",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_overflow_page_size",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "18014398509481983",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_0k_page_size",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "0",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_1k_page_size",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "1",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_4k_page_size",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "4",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_9k_page_size",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "9",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_16k_page_size",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "16",
},
],
}
@@ -159,24 +263,6 @@
}
avb_add_hash_footer {
- name: "test_image_with_multiple_props",
- src: ":unsigned_test_image",
- partition_name: "boot",
- private_key: ":pvmfw_sign_key",
- salt: "2133",
- props: [
- {
- name: "com.android.virt.cap",
- value: "remote_attest",
- },
- {
- name: "another_vm_type",
- value: "foo_vm",
- },
- ],
-}
-
-avb_add_hash_footer {
name: "test_image_with_duplicated_capability",
src: ":unsigned_test_image",
partition_name: "boot",
diff --git a/guest/pvmfw/avb/src/error.rs b/guest/pvmfw/avb/src/error.rs
index 2e1950a..1307e15 100644
--- a/guest/pvmfw/avb/src/error.rs
+++ b/guest/pvmfw/avb/src/error.rs
@@ -28,6 +28,8 @@
InvalidDescriptors(DescriptorError),
/// Unknown vbmeta property.
UnknownVbmetaProperty,
+ /// VBMeta has invalid page_size property.
+ InvalidPageSize,
}
impl From<SlotVerifyError<'_>> for PvmfwVerifyError {
@@ -51,6 +53,7 @@
write!(f, "VBMeta has invalid descriptors. Error: {:?}", e)
}
Self::UnknownVbmetaProperty => write!(f, "Unknown vbmeta property"),
+ Self::InvalidPageSize => write!(f, "Invalid page_size property"),
}
}
}
diff --git a/guest/pvmfw/avb/src/verify.rs b/guest/pvmfw/avb/src/verify.rs
index a073502..8810696 100644
--- a/guest/pvmfw/avb/src/verify.rs
+++ b/guest/pvmfw/avb/src/verify.rs
@@ -17,12 +17,12 @@
use crate::ops::{Ops, Payload};
use crate::partition::PartitionName;
use crate::PvmfwVerifyError;
-use alloc::vec;
use alloc::vec::Vec;
use avb::{
- Descriptor, DescriptorError, DescriptorResult, HashDescriptor, PartitionData,
- PropertyDescriptor, SlotVerifyError, SlotVerifyNoDataResult, VbmetaData,
+ Descriptor, DescriptorError, DescriptorResult, HashDescriptor, PartitionData, SlotVerifyError,
+ SlotVerifyNoDataResult, VbmetaData,
};
+use core::str;
// We use this for the rollback_index field if SlotVerifyData has empty rollback_indexes
const DEFAULT_ROLLBACK_INDEX: u64 = 0;
@@ -45,6 +45,8 @@
pub capabilities: Vec<Capability>,
/// Rollback index of kernel.
pub rollback_index: u64,
+ /// Page size of kernel, if present.
+ pub page_size: Option<usize>,
}
impl VerifiedBootData<'_> {
@@ -91,14 +93,14 @@
/// Returns the capabilities indicated in `descriptor`, or error if the descriptor has
/// unexpected contents.
- fn get_capabilities(descriptor: &PropertyDescriptor) -> Result<Vec<Self>, PvmfwVerifyError> {
- if descriptor.key != Self::KEY {
- return Err(PvmfwVerifyError::UnknownVbmetaProperty);
- }
+ fn get_capabilities(vbmeta_data: &VbmetaData) -> Result<Vec<Self>, PvmfwVerifyError> {
+ let Some(value) = vbmeta_data.get_property_value(Self::KEY) else {
+ return Ok(Vec::new());
+ };
let mut res = Vec::new();
- for v in descriptor.value.split(|b| *b == Self::SEPARATOR) {
+ for v in value.split(|b| *b == Self::SEPARATOR) {
let cap = match v {
Self::REMOTE_ATTEST => Self::RemoteAttest,
Self::TRUSTY_SECURITY_VM => Self::TrustySecurityVm,
@@ -153,30 +155,6 @@
}
}
-/// Verifies that the vbmeta contains at most one property descriptor and it indicates the
-/// vm type is service VM.
-fn verify_property_and_get_capabilities(
- descriptors: &[Descriptor],
-) -> Result<Vec<Capability>, PvmfwVerifyError> {
- let mut iter = descriptors.iter().filter_map(|d| match d {
- Descriptor::Property(p) => Some(p),
- _ => None,
- });
-
- let descriptor = match iter.next() {
- // No property descriptors -> no capabilities.
- None => return Ok(vec![]),
- Some(d) => d,
- };
-
- // Multiple property descriptors -> error.
- if iter.next().is_some() {
- return Err(DescriptorError::InvalidContents.into());
- }
-
- Capability::get_capabilities(descriptor)
-}
-
/// Hash descriptors extracted from a vbmeta image.
///
/// We always have a kernel hash descriptor and may have initrd normal or debug descriptors.
@@ -243,6 +221,23 @@
Ok(digest)
}
+/// Returns the indicated payload page size, if present.
+fn read_page_size(vbmeta_data: &VbmetaData) -> Result<Option<usize>, PvmfwVerifyError> {
+ let Some(property) = vbmeta_data.get_property_value("com.android.virt.page_size") else {
+ return Ok(None);
+ };
+ let size = str::from_utf8(property)
+ .or(Err(PvmfwVerifyError::InvalidPageSize))?
+ .parse::<usize>()
+ .or(Err(PvmfwVerifyError::InvalidPageSize))?
+ .checked_mul(1024)
+ // TODO(stable(unsigned_is_multiple_of)): use .is_multiple_of()
+ .filter(|sz| sz % (4 << 10) == 0 && *sz != 0)
+ .ok_or(PvmfwVerifyError::InvalidPageSize)?;
+
+ Ok(Some(size))
+}
+
/// Verifies the given initrd partition, and checks that the resulting contents looks like expected.
fn verify_initrd(
ops: &mut Ops,
@@ -278,7 +273,8 @@
verify_vbmeta_is_from_kernel_partition(vbmeta_image)?;
let descriptors = vbmeta_image.descriptors()?;
let hash_descriptors = HashDescriptors::get(&descriptors)?;
- let capabilities = verify_property_and_get_capabilities(&descriptors)?;
+ let capabilities = Capability::get_capabilities(vbmeta_image)?;
+ let page_size = read_page_size(vbmeta_image)?;
if initrd.is_none() {
hash_descriptors.verify_no_initrd()?;
@@ -289,6 +285,7 @@
public_key: trusted_public_key,
capabilities,
rollback_index,
+ page_size,
});
}
@@ -309,5 +306,6 @@
public_key: trusted_public_key,
capabilities,
rollback_index,
+ page_size,
})
}
diff --git a/guest/pvmfw/avb/tests/api_test.rs b/guest/pvmfw/avb/tests/api_test.rs
index 430c4b3..0ed0279 100644
--- a/guest/pvmfw/avb/tests/api_test.rs
+++ b/guest/pvmfw/avb/tests/api_test.rs
@@ -28,11 +28,17 @@
use utils::*;
const TEST_IMG_WITH_ONE_HASHDESC_PATH: &str = "test_image_with_one_hashdesc.img";
+const TEST_IMG_WITH_INVALID_PAGE_SIZE_PATH: &str = "test_image_with_invalid_page_size.img";
+const TEST_IMG_WITH_NEGATIVE_PAGE_SIZE_PATH: &str = "test_image_with_negative_page_size.img";
+const TEST_IMG_WITH_OVERFLOW_PAGE_SIZE_PATH: &str = "test_image_with_overflow_page_size.img";
+const TEST_IMG_WITH_0K_PAGE_SIZE_PATH: &str = "test_image_with_0k_page_size.img";
+const TEST_IMG_WITH_1K_PAGE_SIZE_PATH: &str = "test_image_with_1k_page_size.img";
+const TEST_IMG_WITH_4K_PAGE_SIZE_PATH: &str = "test_image_with_4k_page_size.img";
+const TEST_IMG_WITH_9K_PAGE_SIZE_PATH: &str = "test_image_with_9k_page_size.img";
+const TEST_IMG_WITH_16K_PAGE_SIZE_PATH: &str = "test_image_with_16k_page_size.img";
const TEST_IMG_WITH_ROLLBACK_INDEX_5: &str = "test_image_with_rollback_index_5.img";
-const TEST_IMG_WITH_PROP_DESC_PATH: &str = "test_image_with_prop_desc.img";
const TEST_IMG_WITH_SERVICE_VM_PROP_PATH: &str = "test_image_with_service_vm_prop.img";
const TEST_IMG_WITH_UNKNOWN_VM_TYPE_PROP_PATH: &str = "test_image_with_unknown_vm_type_prop.img";
-const TEST_IMG_WITH_MULTIPLE_PROPS_PATH: &str = "test_image_with_multiple_props.img";
const TEST_IMG_WITH_DUPLICATED_CAP_PATH: &str = "test_image_with_duplicated_capability.img";
const TEST_IMG_WITH_NON_INITRD_HASHDESC_PATH: &str = "test_image_with_non_initrd_hashdesc.img";
const TEST_IMG_WITH_INITRD_AND_NON_INITRD_DESC_PATH: &str =
@@ -51,6 +57,7 @@
&load_latest_initrd_normal()?,
b"initrd_normal",
DebugLevel::None,
+ None,
)
}
@@ -63,6 +70,7 @@
salt,
expected_rollback_index,
vec![Capability::TrustySecurityVm],
+ None,
)
}
@@ -72,6 +80,7 @@
&load_latest_initrd_debug()?,
b"initrd_debug",
DebugLevel::Full,
+ None,
)
}
@@ -93,6 +102,7 @@
public_key: &public_key,
capabilities: vec![],
rollback_index: 0,
+ page_size: None,
};
assert_eq!(expected_boot_data, verified_boot_data);
@@ -137,6 +147,7 @@
public_key: &public_key,
capabilities: vec![Capability::RemoteAttest],
rollback_index: 0,
+ page_size: None,
};
assert_eq!(expected_boot_data, verified_boot_data);
@@ -154,16 +165,6 @@
}
#[test]
-fn payload_with_multiple_props_fails_verification_with_no_initrd() -> Result<()> {
- assert_payload_verification_fails(
- &fs::read(TEST_IMG_WITH_MULTIPLE_PROPS_PATH)?,
- /* initrd= */ None,
- &load_trusted_public_key()?,
- PvmfwVerifyError::InvalidDescriptors(DescriptorError::InvalidContents),
- )
-}
-
-#[test]
fn payload_with_duplicated_capability_fails_verification_with_no_initrd() -> Result<()> {
assert_payload_verification_fails(
&fs::read(TEST_IMG_WITH_DUPLICATED_CAP_PATH)?,
@@ -174,16 +175,6 @@
}
#[test]
-fn payload_with_prop_descriptor_fails_verification_with_no_initrd() -> Result<()> {
- assert_payload_verification_fails(
- &fs::read(TEST_IMG_WITH_PROP_DESC_PATH)?,
- /* initrd= */ None,
- &load_trusted_public_key()?,
- PvmfwVerifyError::UnknownVbmetaProperty,
- )
-}
-
-#[test]
fn payload_expecting_initrd_fails_verification_with_no_initrd() -> Result<()> {
assert_payload_verification_fails(
&load_latest_signed_kernel()?,
@@ -257,6 +248,60 @@
}
#[test]
+fn kernel_has_expected_page_size_invalid() {
+ let kernel = fs::read(TEST_IMG_WITH_INVALID_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Err(PvmfwVerifyError::InvalidPageSize));
+}
+
+#[test]
+fn kernel_has_expected_page_size_negative() {
+ let kernel = fs::read(TEST_IMG_WITH_NEGATIVE_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Err(PvmfwVerifyError::InvalidPageSize));
+}
+
+#[test]
+fn kernel_has_expected_page_size_overflow() {
+ let kernel = fs::read(TEST_IMG_WITH_OVERFLOW_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Err(PvmfwVerifyError::InvalidPageSize));
+}
+
+#[test]
+fn kernel_has_expected_page_size_none() {
+ let kernel = fs::read(TEST_IMG_WITH_ONE_HASHDESC_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Ok(None));
+}
+
+#[test]
+fn kernel_has_expected_page_size_0k() {
+ let kernel = fs::read(TEST_IMG_WITH_0K_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Err(PvmfwVerifyError::InvalidPageSize));
+}
+
+#[test]
+fn kernel_has_expected_page_size_1k() {
+ let kernel = fs::read(TEST_IMG_WITH_1K_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Err(PvmfwVerifyError::InvalidPageSize));
+}
+
+#[test]
+fn kernel_has_expected_page_size_4k() {
+ let kernel = fs::read(TEST_IMG_WITH_4K_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Ok(Some(4usize << 10)));
+}
+
+#[test]
+fn kernel_has_expected_page_size_9k() {
+ let kernel = fs::read(TEST_IMG_WITH_9K_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Err(PvmfwVerifyError::InvalidPageSize));
+}
+
+#[test]
+fn kernel_has_expected_page_size_16k() {
+ let kernel = fs::read(TEST_IMG_WITH_16K_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Ok(Some(16usize << 10)));
+}
+
+#[test]
fn kernel_footer_with_vbmeta_offset_overwritten_fails_verification() -> Result<()> {
// Arrange.
let mut kernel = load_latest_signed_kernel()?;
@@ -412,6 +457,7 @@
public_key: &public_key,
capabilities: vec![],
rollback_index: 5,
+ page_size: None,
};
assert_eq!(expected_boot_data, verified_boot_data);
Ok(())
diff --git a/guest/pvmfw/avb/tests/utils.rs b/guest/pvmfw/avb/tests/utils.rs
index 61bfbf2..79552b5 100644
--- a/guest/pvmfw/avb/tests/utils.rs
+++ b/guest/pvmfw/avb/tests/utils.rs
@@ -114,6 +114,7 @@
initrd: &[u8],
initrd_salt: &[u8],
expected_debug_level: DebugLevel,
+ page_size: Option<usize>,
) -> Result<()> {
let public_key = load_trusted_public_key()?;
let kernel = load_latest_signed_kernel()?;
@@ -133,6 +134,7 @@
public_key: &public_key,
capabilities,
rollback_index: if cfg!(llpvm_changes) { 1 } else { 0 },
+ page_size,
};
assert_eq!(expected_boot_data, verified_boot_data);
@@ -144,6 +146,7 @@
salt: &[u8],
expected_rollback_index: u64,
capabilities: Vec<Capability>,
+ page_size: Option<usize>,
) -> Result<()> {
let public_key = load_trusted_public_key()?;
let verified_boot_data = verify_payload(
@@ -163,12 +166,23 @@
public_key: &public_key,
capabilities,
rollback_index: expected_rollback_index,
+ page_size,
};
assert_eq!(expected_boot_data, verified_boot_data);
Ok(())
}
+pub fn read_page_size(kernel: &[u8]) -> Result<Option<usize>, PvmfwVerifyError> {
+ let public_key = load_trusted_public_key().unwrap();
+ let verified_boot_data = verify_payload(
+ kernel,
+ None, // initrd
+ &public_key,
+ )?;
+ Ok(verified_boot_data.page_size)
+}
+
pub fn hash(inputs: &[&[u8]]) -> Digest {
let mut digester = sha::Sha256::new();
inputs.iter().for_each(|input| digester.update(input));
diff --git a/guest/pvmfw/src/dice.rs b/guest/pvmfw/src/dice.rs
index b597309..6694881 100644
--- a/guest/pvmfw/src/dice.rs
+++ b/guest/pvmfw/src/dice.rs
@@ -200,6 +200,7 @@
public_key: b"public key",
capabilities: vec![],
rollback_index: 42,
+ page_size: None,
};
const HASH: Hash = *b"sixtyfourbyteslongsentencearerarebutletsgiveitatrycantbethathard";
diff --git a/guest/pvmfw/src/entry.rs b/guest/pvmfw/src/entry.rs
index 343c2fc..7c46515 100644
--- a/guest/pvmfw/src/entry.rs
+++ b/guest/pvmfw/src/entry.rs
@@ -74,22 +74,36 @@
configure_heap!(SIZE_128KB);
limit_stack_size!(SIZE_4KB * 12);
+#[derive(Debug)]
+enum NextStage {
+ LinuxBoot(usize),
+ LinuxBootWithUart(usize),
+}
+
/// Entry point for pVM firmware.
pub fn start(fdt_address: u64, payload_start: u64, payload_size: u64, _arg3: u64) {
- // Limitations in this function:
- // - can't access non-pvmfw memory (only statically-mapped memory)
- // - can't access MMIO (except the console, already configured by vmbase)
+ let fdt_address = fdt_address.try_into().unwrap();
+ let payload_start = payload_start.try_into().unwrap();
+ let payload_size = payload_size.try_into().unwrap();
- match main_wrapper(fdt_address as usize, payload_start as usize, payload_size as usize) {
- Ok((entry, bcc, keep_uart)) => {
- jump_to_payload(fdt_address, entry.try_into().unwrap(), bcc, keep_uart)
- }
- Err(e) => {
- const REBOOT_REASON_CONSOLE: usize = 1;
- console_writeln!(REBOOT_REASON_CONSOLE, "{}", e.as_avf_reboot_string());
- reboot()
- }
- }
+ let reboot_reason = match main_wrapper(fdt_address, payload_start, payload_size) {
+ Err(r) => r,
+ Ok((next_stage, bcc)) => match next_stage {
+ NextStage::LinuxBootWithUart(ep) => jump_to_payload(fdt_address, ep, bcc),
+ NextStage::LinuxBoot(ep) => {
+ if let Err(e) = unshare_uart() {
+ error!("Failed to unmap UART: {e}");
+ RebootReason::InternalError
+ } else {
+ jump_to_payload(fdt_address, ep, bcc)
+ }
+ }
+ },
+ };
+
+ const REBOOT_REASON_CONSOLE: usize = 1;
+ console_writeln!(REBOOT_REASON_CONSOLE, "{}", reboot_reason.as_avf_reboot_string());
+ reboot()
// if we reach this point and return, vmbase::entry::rust_entry() will call power::shutdown().
}
@@ -102,7 +116,7 @@
fdt: usize,
payload: usize,
payload_size: usize,
-) -> Result<(usize, Range<usize>, bool), RebootReason> {
+) -> Result<(NextStage, Range<usize>), RebootReason> {
// Limitations in this function:
// - only access MMIO once (and while) it has been mapped and configured
// - only perform logging once the logger has been initialized
@@ -122,13 +136,7 @@
let config_entries = appended.get_entries();
- let slices = memory::MemorySlices::new(
- fdt,
- payload,
- payload_size,
- config_entries.vm_dtbo,
- config_entries.vm_ref_dt,
- )?;
+ let slices = memory::MemorySlices::new(fdt, payload, payload_size)?;
// This wrapper allows main() to be blissfully ignorant of platform details.
let (next_bcc, debuggable_payload) = crate::main(
@@ -137,6 +145,8 @@
slices.ramdisk,
config_entries.bcc,
config_entries.debug_policy,
+ config_entries.vm_dtbo,
+ config_entries.vm_ref_dt,
)?;
// Keep UART MMIO_GUARD-ed for debuggable payloads, to enable earlycon.
let keep_uart = cfg!(debuggable_vms_improvements) && debuggable_payload;
@@ -150,14 +160,20 @@
})?;
unshare_all_memory();
- Ok((slices.kernel.as_ptr() as usize, next_bcc, keep_uart))
+ let next_stage = select_next_stage(slices.kernel, keep_uart);
+
+ Ok((next_stage, next_bcc))
}
-fn jump_to_payload(fdt_address: u64, payload_start: u64, bcc: Range<usize>, keep_uart: bool) -> ! {
- if !keep_uart {
- unshare_uart().unwrap();
+fn select_next_stage(kernel: &[u8], keep_uart: bool) -> NextStage {
+ if keep_uart {
+ NextStage::LinuxBootWithUart(kernel.as_ptr() as _)
+ } else {
+ NextStage::LinuxBoot(kernel.as_ptr() as _)
}
+}
+fn jump_to_payload(fdt_address: usize, payload_start: usize, bcc: Range<usize>) -> ! {
deactivate_dynamic_page_tables();
const ASM_STP_ALIGN: usize = size_of::<u64>() * 2;
@@ -296,8 +312,8 @@
eh_stack = in(reg) u64::try_from(eh_stack.start.0).unwrap(),
eh_stack_end = in(reg) u64::try_from(eh_stack.end.0).unwrap(),
dcache_line_size = in(reg) u64::try_from(min_dcache_line_size()).unwrap(),
- in("x0") fdt_address,
- in("x30") payload_start,
+ in("x0") u64::try_from(fdt_address).unwrap(),
+ in("x30") u64::try_from(payload_start).unwrap(),
options(noreturn),
);
};
diff --git a/guest/pvmfw/src/fdt.rs b/guest/pvmfw/src/fdt.rs
index 027f163..bfbd2e6 100644
--- a/guest/pvmfw/src/fdt.rs
+++ b/guest/pvmfw/src/fdt.rs
@@ -16,7 +16,6 @@
use crate::bootargs::BootArgsIterator;
use crate::device_assignment::{self, DeviceAssignmentInfo, VmDtbo};
-use crate::helpers::GUEST_PAGE_SIZE;
use crate::Box;
use crate::RebootReason;
use alloc::collections::BTreeMap;
@@ -83,7 +82,7 @@
/// Extract from /config the address range containing the pre-loaded kernel. Absence of /config is
/// not an error.
-fn read_kernel_range_from(fdt: &Fdt) -> libfdt::Result<Option<Range<usize>>> {
+pub fn read_kernel_range_from(fdt: &Fdt) -> libfdt::Result<Option<Range<usize>>> {
let addr = cstr!("kernel-address");
let size = cstr!("kernel-size");
@@ -101,7 +100,7 @@
/// Extract from /chosen the address range containing the pre-loaded ramdisk. Absence is not an
/// error as there can be initrd-less VM.
-fn read_initrd_range_from(fdt: &Fdt) -> libfdt::Result<Option<Range<usize>>> {
+pub fn read_initrd_range_from(fdt: &Fdt) -> libfdt::Result<Option<Range<usize>>> {
let start = cstr!("linux,initrd-start");
let end = cstr!("linux,initrd-end");
@@ -147,7 +146,10 @@
/// Reads and validates the memory range in the DT.
///
/// Only one memory range is expected with the crosvm setup for now.
-fn read_and_validate_memory_range(fdt: &Fdt) -> Result<Range<usize>, RebootReason> {
+fn read_and_validate_memory_range(
+ fdt: &Fdt,
+ guest_page_size: usize,
+) -> Result<Range<usize>, RebootReason> {
let mut memory = fdt.memory().map_err(|e| {
error!("Failed to read memory range from DT: {e}");
RebootReason::InvalidFdt
@@ -169,8 +171,8 @@
}
let size = range.len();
- if size % GUEST_PAGE_SIZE != 0 {
- error!("Memory size {:#x} is not a multiple of page size {:#x}", size, GUEST_PAGE_SIZE);
+ if size % guest_page_size != 0 {
+ error!("Memory size {:#x} is not a multiple of page size {:#x}", size, guest_page_size);
return Err(RebootReason::InvalidFdt);
}
@@ -854,16 +856,17 @@
fn validate_swiotlb_info(
swiotlb_info: &SwiotlbInfo,
memory: &Range<usize>,
+ guest_page_size: usize,
) -> Result<(), RebootReason> {
let size = swiotlb_info.size;
let align = swiotlb_info.align;
- if size == 0 || (size % GUEST_PAGE_SIZE) != 0 {
+ if size == 0 || (size % guest_page_size) != 0 {
error!("Invalid swiotlb size {:#x}", size);
return Err(RebootReason::InvalidFdt);
}
- if let Some(align) = align.filter(|&a| a % GUEST_PAGE_SIZE != 0) {
+ if let Some(align) = align.filter(|&a| a % guest_page_size != 0) {
error!("Invalid swiotlb alignment {:#x}", align);
return Err(RebootReason::InvalidFdt);
}
@@ -989,7 +992,6 @@
#[derive(Debug)]
pub struct DeviceTreeInfo {
- pub kernel_range: Option<Range<usize>>,
pub initrd_range: Option<Range<usize>>,
pub memory_range: Range<usize>,
bootargs: Option<CString>,
@@ -1015,15 +1017,11 @@
}
pub fn sanitize_device_tree(
- fdt: &mut [u8],
+ fdt: &mut Fdt,
vm_dtbo: Option<&mut [u8]>,
vm_ref_dt: Option<&[u8]>,
+ guest_page_size: usize,
) -> Result<DeviceTreeInfo, RebootReason> {
- let fdt = Fdt::from_mut_slice(fdt).map_err(|e| {
- error!("Failed to load FDT: {e}");
- RebootReason::InvalidFdt
- })?;
-
let vm_dtbo = match vm_dtbo {
Some(vm_dtbo) => Some(VmDtbo::from_mut_slice(vm_dtbo).map_err(|e| {
error!("Failed to load VM DTBO: {e}");
@@ -1032,7 +1030,7 @@
None => None,
};
- let info = parse_device_tree(fdt, vm_dtbo.as_deref())?;
+ let info = parse_device_tree(fdt, vm_dtbo.as_deref(), guest_page_size)?;
fdt.clone_from(FDT_TEMPLATE).map_err(|e| {
error!("Failed to instantiate FDT from the template DT: {e}");
@@ -1085,18 +1083,17 @@
Ok(info)
}
-fn parse_device_tree(fdt: &Fdt, vm_dtbo: Option<&VmDtbo>) -> Result<DeviceTreeInfo, RebootReason> {
- let kernel_range = read_kernel_range_from(fdt).map_err(|e| {
- error!("Failed to read kernel range from DT: {e}");
- RebootReason::InvalidFdt
- })?;
-
+fn parse_device_tree(
+ fdt: &Fdt,
+ vm_dtbo: Option<&VmDtbo>,
+ guest_page_size: usize,
+) -> Result<DeviceTreeInfo, RebootReason> {
let initrd_range = read_initrd_range_from(fdt).map_err(|e| {
error!("Failed to read initrd range from DT: {e}");
RebootReason::InvalidFdt
})?;
- let memory_range = read_and_validate_memory_range(fdt)?;
+ let memory_range = read_and_validate_memory_range(fdt, guest_page_size)?;
let bootargs = read_bootargs_from(fdt).map_err(|e| {
error!("Failed to read bootargs from DT: {e}");
@@ -1149,7 +1146,7 @@
error!("Swiotlb info missing from DT");
RebootReason::InvalidFdt
})?;
- validate_swiotlb_info(&swiotlb_info, &memory_range)?;
+ validate_swiotlb_info(&swiotlb_info, &memory_range, guest_page_size)?;
let device_assignment = match vm_dtbo {
Some(vm_dtbo) => {
@@ -1194,7 +1191,6 @@
})?;
Ok(DeviceTreeInfo {
- kernel_range,
initrd_range,
memory_range,
bootargs,
diff --git a/guest/pvmfw/src/helpers.rs b/guest/pvmfw/src/helpers.rs
deleted file mode 100644
index 0552640..0000000
--- a/guest/pvmfw/src/helpers.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2022, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! Miscellaneous helper functions.
-
-use vmbase::memory::SIZE_4KB;
-
-pub const GUEST_PAGE_SIZE: usize = SIZE_4KB;
diff --git a/guest/pvmfw/src/main.rs b/guest/pvmfw/src/main.rs
index bde03ff..d04db06 100644
--- a/guest/pvmfw/src/main.rs
+++ b/guest/pvmfw/src/main.rs
@@ -28,18 +28,15 @@
mod exceptions;
mod fdt;
mod gpt;
-mod helpers;
mod instance;
mod memory;
+mod rollback;
use crate::bcc::Bcc;
use crate::dice::PartialInputs;
use crate::entry::RebootReason;
-use crate::fdt::modify_for_next_stage;
-use crate::helpers::GUEST_PAGE_SIZE;
-use crate::instance::EntryBody;
-use crate::instance::Error as InstanceError;
-use crate::instance::{get_recorded_entry, record_instance_entry};
+use crate::fdt::{modify_for_next_stage, sanitize_device_tree};
+use crate::rollback::perform_rollback_protection;
use alloc::borrow::Cow;
use alloc::boxed::Box;
use bssl_avf::Digester;
@@ -49,26 +46,25 @@
use libfdt::{Fdt, FdtNode};
use log::{debug, error, info, trace, warn};
use pvmfw_avb::verify_payload;
-use pvmfw_avb::Capability;
use pvmfw_avb::DebugLevel;
use pvmfw_embedded_key::PUBLIC_KEY;
use vmbase::fdt::pci::{PciError, PciInfo};
use vmbase::heap;
-use vmbase::memory::flush;
+use vmbase::memory::{flush, init_shared_pool, SIZE_4KB};
use vmbase::rand;
use vmbase::virtio::pci;
-const NEXT_BCC_SIZE: usize = GUEST_PAGE_SIZE;
-
fn main(
- fdt: &mut Fdt,
+ untrusted_fdt: &mut Fdt,
signed_kernel: &[u8],
ramdisk: Option<&[u8]>,
current_bcc_handover: &[u8],
mut debug_policy: Option<&[u8]>,
+ vm_dtbo: Option<&mut [u8]>,
+ vm_ref_dt: Option<&[u8]>,
) -> Result<(Range<usize>, bool), RebootReason> {
info!("pVM firmware");
- debug!("FDT: {:?}", fdt.as_ptr());
+ debug!("FDT: {:?}", untrusted_fdt.as_ptr());
debug!("Signed kernel: {:?} ({:#x} bytes)", signed_kernel.as_ptr(), signed_kernel.len());
debug!("AVB public key: addr={:?}, size={:#x} ({1})", PUBLIC_KEY.as_ptr(), PUBLIC_KEY.len());
if let Some(rd) = ramdisk {
@@ -97,14 +93,6 @@
debug_policy = None;
}
- // Set up PCI bus for VirtIO devices.
- let pci_info = PciInfo::from_fdt(fdt).map_err(handle_pci_error)?;
- debug!("PCI: {:#x?}", pci_info);
- let mut pci_root = pci::initialize(pci_info).map_err(|e| {
- error!("Failed to initialize PCI: {e}");
- RebootReason::InternalError
- })?;
-
let verified_boot_data = verify_payload(signed_kernel, ramdisk, PUBLIC_KEY).map_err(|e| {
error!("Failed to verify the payload: {e}");
RebootReason::PayloadVerificationError
@@ -115,7 +103,23 @@
info!("Please disregard any previous libavb ERROR about initrd_normal.");
}
- let next_bcc = heap::aligned_boxed_slice(NEXT_BCC_SIZE, GUEST_PAGE_SIZE).ok_or_else(|| {
+ let guest_page_size = verified_boot_data.page_size.unwrap_or(SIZE_4KB);
+ let fdt_info = sanitize_device_tree(untrusted_fdt, vm_dtbo, vm_ref_dt, guest_page_size)?;
+ let fdt = untrusted_fdt; // DT has now been sanitized.
+ let pci_info = PciInfo::from_fdt(fdt).map_err(handle_pci_error)?;
+ debug!("PCI: {:#x?}", pci_info);
+ // Set up PCI bus for VirtIO devices.
+ let mut pci_root = pci::initialize(pci_info).map_err(|e| {
+ error!("Failed to initialize PCI: {e}");
+ RebootReason::InternalError
+ })?;
+ init_shared_pool(fdt_info.swiotlb_info.fixed_range()).map_err(|e| {
+ error!("Failed to initialize shared pool: {e}");
+ RebootReason::InternalError
+ })?;
+
+ let next_bcc_size = guest_page_size;
+ let next_bcc = heap::aligned_boxed_slice(next_bcc_size, guest_page_size).ok_or_else(|| {
error!("Failed to allocate the next-stage BCC");
RebootReason::InternalError
})?;
@@ -128,65 +132,14 @@
})?;
let instance_hash = if cfg!(llpvm_changes) { Some(salt_from_instance_id(fdt)?) } else { None };
- let defer_rollback_protection = should_defer_rollback_protection(fdt)?
- && verified_boot_data.has_capability(Capability::SecretkeeperProtection);
- let (new_instance, salt) = if defer_rollback_protection {
- info!("Guest OS is capable of Secretkeeper protection, deferring rollback protection");
- // rollback_index of the image is used as security_version and is expected to be > 0 to
- // discourage implicit allocation.
- if verified_boot_data.rollback_index == 0 {
- error!("Expected positive rollback_index, found 0");
- return Err(RebootReason::InvalidPayload);
- };
- (false, instance_hash.unwrap())
- } else if verified_boot_data.has_capability(Capability::RemoteAttest) {
- info!("Service VM capable of remote attestation detected, performing version checks");
- if service_vm_version::VERSION != verified_boot_data.rollback_index {
- // For RKP VM, we only boot if the version in the AVB footer of its kernel matches
- // the one embedded in pvmfw at build time.
- // This prevents the pvmfw from booting a roll backed RKP VM.
- error!(
- "Service VM version mismatch: expected {}, found {}",
- service_vm_version::VERSION,
- verified_boot_data.rollback_index
- );
- return Err(RebootReason::InvalidPayload);
- }
- (false, instance_hash.unwrap())
- } else if verified_boot_data.has_capability(Capability::TrustySecurityVm) {
- // The rollback protection of Trusty VMs are handled by AuthMgr, so we don't need to
- // handle it here.
- info!("Trusty Security VM detected");
- (false, instance_hash.unwrap())
- } else {
- info!("Fallback to instance.img based rollback checks");
- let (recorded_entry, mut instance_img, header_index) =
- get_recorded_entry(&mut pci_root, cdi_seal).map_err(|e| {
- error!("Failed to get entry from instance.img: {e}");
- RebootReason::InternalError
- })?;
- let (new_instance, salt) = if let Some(entry) = recorded_entry {
- check_dice_measurements_match_entry(&dice_inputs, &entry)?;
- let salt = instance_hash.unwrap_or(entry.salt);
- (false, salt)
- } else {
- // New instance!
- let salt = instance_hash.map_or_else(rand::random_array, Ok).map_err(|e| {
- error!("Failed to generated instance.img salt: {e}");
- RebootReason::InternalError
- })?;
-
- let entry = EntryBody::new(&dice_inputs, &salt);
- record_instance_entry(&entry, cdi_seal, &mut instance_img, header_index).map_err(
- |e| {
- error!("Failed to get recorded entry in instance.img: {e}");
- RebootReason::InternalError
- },
- )?;
- (true, salt)
- };
- (new_instance, salt)
- };
+ let (new_instance, salt, defer_rollback_protection) = perform_rollback_protection(
+ fdt,
+ &verified_boot_data,
+ &dice_inputs,
+ &mut pci_root,
+ cdi_seal,
+ instance_hash,
+ )?;
trace!("Got salt for instance: {salt:x?}");
let new_bcc_handover = if cfg!(dice_changes) {
@@ -257,36 +210,6 @@
Ok((bcc_range, debuggable))
}
-fn check_dice_measurements_match_entry(
- dice_inputs: &PartialInputs,
- entry: &EntryBody,
-) -> Result<(), RebootReason> {
- ensure_dice_measurements_match_entry(dice_inputs, entry).map_err(|e| {
- error!(
- "Dice measurements do not match recorded entry. \
- This may be because of update: {e}"
- );
- RebootReason::InternalError
- })?;
-
- Ok(())
-}
-
-fn ensure_dice_measurements_match_entry(
- dice_inputs: &PartialInputs,
- entry: &EntryBody,
-) -> Result<(), InstanceError> {
- if entry.code_hash != dice_inputs.code_hash {
- Err(InstanceError::RecordedCodeHashMismatch)
- } else if entry.auth_hash != dice_inputs.auth_hash {
- Err(InstanceError::RecordedAuthHashMismatch)
- } else if entry.mode() != dice_inputs.mode {
- Err(InstanceError::RecordedDiceModeMismatch)
- } else {
- Ok(())
- }
-}
-
// Get the "salt" which is one of the input for DICE derivation.
// This provides differentiation of secrets for different VM instances with same payloads.
fn salt_from_instance_id(fdt: &Fdt) -> Result<Hidden, RebootReason> {
@@ -314,18 +237,6 @@
})
}
-fn should_defer_rollback_protection(fdt: &Fdt) -> Result<bool, RebootReason> {
- let node = avf_untrusted_node(fdt)?;
- let defer_rbp = node
- .getprop(cstr!("defer-rollback-protection"))
- .map_err(|e| {
- error!("Failed to get defer-rollback-protection property in DT: {e}");
- RebootReason::InvalidFdt
- })?
- .is_some();
- Ok(defer_rbp)
-}
-
fn avf_untrusted_node(fdt: &Fdt) -> Result<FdtNode, RebootReason> {
let node = fdt.node(cstr!("/avf/untrusted")).map_err(|e| {
error!("Failed to get /avf/untrusted node: {e}");
diff --git a/guest/pvmfw/src/memory.rs b/guest/pvmfw/src/memory.rs
index 35bfd3a..d2f63b5 100644
--- a/guest/pvmfw/src/memory.rs
+++ b/guest/pvmfw/src/memory.rs
@@ -15,7 +15,7 @@
//! Low-level allocation and tracking of main memory.
use crate::entry::RebootReason;
-use crate::fdt;
+use crate::fdt::{read_initrd_range_from, read_kernel_range_from};
use core::num::NonZeroUsize;
use core::slice;
use log::debug;
@@ -24,7 +24,7 @@
use log::warn;
use vmbase::{
layout::crosvm,
- memory::{init_shared_pool, map_data, map_rodata, resize_available_memory},
+ memory::{map_data, map_rodata, resize_available_memory},
};
pub(crate) struct MemorySlices<'a> {
@@ -34,13 +34,7 @@
}
impl<'a> MemorySlices<'a> {
- pub fn new(
- fdt: usize,
- kernel: usize,
- kernel_size: usize,
- vm_dtbo: Option<&mut [u8]>,
- vm_ref_dt: Option<&[u8]>,
- ) -> Result<Self, RebootReason> {
+ pub fn new(fdt: usize, kernel: usize, kernel_size: usize) -> Result<Self, RebootReason> {
let fdt_size = NonZeroUsize::new(crosvm::FDT_MAX_SIZE).unwrap();
// TODO - Only map the FDT as read-only, until we modify it right before jump_to_payload()
// e.g. by generating a DTBO for a template DT in main() and, on return, re-map DT as RW,
@@ -51,44 +45,39 @@
})?;
// SAFETY: map_data validated the range to be in main memory, mapped, and not overlap.
- let fdt = unsafe { slice::from_raw_parts_mut(fdt as *mut u8, fdt_size.into()) };
-
- let info = fdt::sanitize_device_tree(fdt, vm_dtbo, vm_ref_dt)?;
- let fdt = libfdt::Fdt::from_mut_slice(fdt).map_err(|e| {
- error!("Failed to load sanitized FDT: {e}");
+ let untrusted_fdt = unsafe { slice::from_raw_parts_mut(fdt as *mut u8, fdt_size.into()) };
+ let untrusted_fdt = libfdt::Fdt::from_mut_slice(untrusted_fdt).map_err(|e| {
+ error!("Failed to load input FDT: {e}");
RebootReason::InvalidFdt
})?;
- debug!("Fdt passed validation!");
- let memory_range = info.memory_range;
+ let memory_range = untrusted_fdt.first_memory_range().map_err(|e| {
+ error!("Failed to read memory range from DT: {e}");
+ RebootReason::InvalidFdt
+ })?;
debug!("Resizing MemoryTracker to range {memory_range:#x?}");
resize_available_memory(&memory_range).map_err(|e| {
error!("Failed to use memory range value from DT: {memory_range:#x?}: {e}");
RebootReason::InvalidFdt
})?;
- init_shared_pool(info.swiotlb_info.fixed_range()).map_err(|e| {
- error!("Failed to initialize shared pool: {e}");
- RebootReason::InternalError
+ let kernel_range = read_kernel_range_from(untrusted_fdt).map_err(|e| {
+ error!("Failed to read kernel range: {e}");
+ RebootReason::InvalidFdt
})?;
-
- let (kernel_start, kernel_size) = if let Some(r) = info.kernel_range {
- let size = r.len().try_into().map_err(|_| {
- error!("Invalid kernel size: {:#x}", r.len());
- RebootReason::InternalError
- })?;
- (r.start, size)
+ let (kernel_start, kernel_size) = if let Some(r) = kernel_range {
+ (r.start, r.len())
} else if cfg!(feature = "legacy") {
warn!("Failed to find the kernel range in the DT; falling back to legacy ABI");
- let size = NonZeroUsize::new(kernel_size).ok_or_else(|| {
- error!("Invalid kernel size: {kernel_size:#x}");
- RebootReason::InvalidPayload
- })?;
- (kernel, size)
+ (kernel, kernel_size)
} else {
error!("Failed to locate the kernel from the DT");
return Err(RebootReason::InvalidPayload);
};
+ let kernel_size = kernel_size.try_into().map_err(|_| {
+ error!("Invalid kernel size: {kernel_size:#x}");
+ RebootReason::InvalidPayload
+ })?;
map_rodata(kernel_start, kernel_size).map_err(|e| {
error!("Failed to map kernel range: {e}");
@@ -99,7 +88,11 @@
// SAFETY: map_rodata validated the range to be in main memory, mapped, and not overlap.
let kernel = unsafe { slice::from_raw_parts(kernel, kernel_size.into()) };
- let ramdisk = if let Some(r) = info.initrd_range {
+ let initrd_range = read_initrd_range_from(untrusted_fdt).map_err(|e| {
+ error!("Failed to read initrd range: {e}");
+ RebootReason::InvalidFdt
+ })?;
+ let ramdisk = if let Some(r) = initrd_range {
debug!("Located ramdisk at {r:?}");
let ramdisk_size = r.len().try_into().map_err(|_| {
error!("Invalid ramdisk size: {:#x}", r.len());
@@ -118,6 +111,6 @@
None
};
- Ok(Self { fdt, kernel, ramdisk })
+ Ok(Self { fdt: untrusted_fdt, kernel, ramdisk })
}
}
diff --git a/guest/pvmfw/src/rollback.rs b/guest/pvmfw/src/rollback.rs
new file mode 100644
index 0000000..bc16332
--- /dev/null
+++ b/guest/pvmfw/src/rollback.rs
@@ -0,0 +1,159 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Support for guest-specific rollback protection (RBP).
+
+use crate::dice::PartialInputs;
+use crate::entry::RebootReason;
+use crate::instance::EntryBody;
+use crate::instance::Error as InstanceError;
+use crate::instance::{get_recorded_entry, record_instance_entry};
+use cstr::cstr;
+use diced_open_dice::Hidden;
+use libfdt::{Fdt, FdtNode};
+use log::{error, info};
+use pvmfw_avb::Capability;
+use pvmfw_avb::VerifiedBootData;
+use virtio_drivers::transport::pci::bus::PciRoot;
+use vmbase::rand;
+
+/// Performs RBP based on the input payload, current DICE chain, and host-controlled platform.
+///
+/// On success, returns a tuple containing:
+/// - `new_instance`: true if a new entry was created using the legacy instance.img solution;
+/// - `salt`: the salt representing the instance, to be used during DICE derivation;
+/// - `defer_rollback_protection`: if RBP is being deferred.
+pub fn perform_rollback_protection(
+ fdt: &Fdt,
+ verified_boot_data: &VerifiedBootData,
+ dice_inputs: &PartialInputs,
+ pci_root: &mut PciRoot,
+ cdi_seal: &[u8],
+ instance_hash: Option<Hidden>,
+) -> Result<(bool, Hidden, bool), RebootReason> {
+ let defer_rollback_protection = should_defer_rollback_protection(fdt)?
+ && verified_boot_data.has_capability(Capability::SecretkeeperProtection);
+ let (new_instance, salt) = if defer_rollback_protection {
+ info!("Guest OS is capable of Secretkeeper protection, deferring rollback protection");
+ // rollback_index of the image is used as security_version and is expected to be > 0 to
+ // discourage implicit allocation.
+ if verified_boot_data.rollback_index == 0 {
+ error!("Expected positive rollback_index, found 0");
+ return Err(RebootReason::InvalidPayload);
+ };
+ (false, instance_hash.unwrap())
+ } else if verified_boot_data.has_capability(Capability::RemoteAttest) {
+ info!("Service VM capable of remote attestation detected, performing version checks");
+ if service_vm_version::VERSION != verified_boot_data.rollback_index {
+ // For RKP VM, we only boot if the version in the AVB footer of its kernel matches
+ // the one embedded in pvmfw at build time.
+ // This prevents the pvmfw from booting a roll backed RKP VM.
+ error!(
+ "Service VM version mismatch: expected {}, found {}",
+ service_vm_version::VERSION,
+ verified_boot_data.rollback_index
+ );
+ return Err(RebootReason::InvalidPayload);
+ }
+ (false, instance_hash.unwrap())
+ } else if verified_boot_data.has_capability(Capability::TrustySecurityVm) {
+ // The rollback protection of Trusty VMs are handled by AuthMgr, so we don't need to
+ // handle it here.
+ info!("Trusty Security VM detected");
+ (false, instance_hash.unwrap())
+ } else {
+ info!("Fallback to instance.img based rollback checks");
+ let (recorded_entry, mut instance_img, header_index) =
+ get_recorded_entry(pci_root, cdi_seal).map_err(|e| {
+ error!("Failed to get entry from instance.img: {e}");
+ RebootReason::InternalError
+ })?;
+ let (new_instance, salt) = if let Some(entry) = recorded_entry {
+ check_dice_measurements_match_entry(dice_inputs, &entry)?;
+ let salt = instance_hash.unwrap_or(entry.salt);
+ (false, salt)
+ } else {
+ // New instance!
+ let salt = instance_hash.map_or_else(rand::random_array, Ok).map_err(|e| {
+ error!("Failed to generated instance.img salt: {e}");
+ RebootReason::InternalError
+ })?;
+
+ let entry = EntryBody::new(dice_inputs, &salt);
+ record_instance_entry(&entry, cdi_seal, &mut instance_img, header_index).map_err(
+ |e| {
+ error!("Failed to get recorded entry in instance.img: {e}");
+ RebootReason::InternalError
+ },
+ )?;
+ (true, salt)
+ };
+ (new_instance, salt)
+ };
+
+ Ok((new_instance, salt, defer_rollback_protection))
+}
+
+fn check_dice_measurements_match_entry(
+ dice_inputs: &PartialInputs,
+ entry: &EntryBody,
+) -> Result<(), RebootReason> {
+ ensure_dice_measurements_match_entry(dice_inputs, entry).map_err(|e| {
+ error!(
+ "Dice measurements do not match recorded entry. \
+ This may be because of update: {e}"
+ );
+ RebootReason::InternalError
+ })?;
+
+ Ok(())
+}
+
+fn ensure_dice_measurements_match_entry(
+ dice_inputs: &PartialInputs,
+ entry: &EntryBody,
+) -> Result<(), InstanceError> {
+ if entry.code_hash != dice_inputs.code_hash {
+ Err(InstanceError::RecordedCodeHashMismatch)
+ } else if entry.auth_hash != dice_inputs.auth_hash {
+ Err(InstanceError::RecordedAuthHashMismatch)
+ } else if entry.mode() != dice_inputs.mode {
+ Err(InstanceError::RecordedDiceModeMismatch)
+ } else {
+ Ok(())
+ }
+}
+
+fn should_defer_rollback_protection(fdt: &Fdt) -> Result<bool, RebootReason> {
+ let node = avf_untrusted_node(fdt)?;
+ let defer_rbp = node
+ .getprop(cstr!("defer-rollback-protection"))
+ .map_err(|e| {
+ error!("Failed to get defer-rollback-protection property in DT: {e}");
+ RebootReason::InvalidFdt
+ })?
+ .is_some();
+ Ok(defer_rbp)
+}
+
+fn avf_untrusted_node(fdt: &Fdt) -> Result<FdtNode, RebootReason> {
+ let node = fdt.node(cstr!("/avf/untrusted")).map_err(|e| {
+ error!("Failed to get /avf/untrusted node: {e}");
+ RebootReason::InvalidFdt
+ })?;
+ node.ok_or_else(|| {
+ error!("/avf/untrusted node is missing in DT");
+ RebootReason::InvalidFdt
+ })
+}
diff --git a/libs/libavf/include/android/virtualization.h b/libs/libavf/include/android/virtualization.h
index f33ee75..ac46fca 100644
--- a/libs/libavf/include/android/virtualization.h
+++ b/libs/libavf/include/android/virtualization.h
@@ -18,6 +18,8 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
+#include <sys/cdefs.h>
+#include <time.h>
__BEGIN_DECLS
@@ -40,9 +42,9 @@
* VM by calling {@link AVirtualMachine_createRaw} or releasing it by calling
* {@link AVirtualMachineRawConfig_destroy}.
*
- * \return A new virtual machine raw config object.
+ * \return A new virtual machine raw config object. On failure (such as out of memory), it aborts.
*/
-AVirtualMachineRawConfig* AVirtualMachineRawConfig_create();
+AVirtualMachineRawConfig* _Nonnull AVirtualMachineRawConfig_create() __INTRODUCED_IN(36);
/**
* Destroy a virtual machine config object.
@@ -52,61 +54,69 @@
* `AVirtualMachineRawConfig_destroy` does nothing if `config` is null. A destroyed config object
* must not be reused.
*/
-void AVirtualMachineRawConfig_destroy(AVirtualMachineRawConfig* config);
+void AVirtualMachineRawConfig_destroy(AVirtualMachineRawConfig* _Nullable config)
+ __INTRODUCED_IN(36);
/**
* Set a name of a virtual machine.
*
* \param config a virtual machine config object.
- * \param name a pointer to a null-terminated string for the name.
+ * \param name a pointer to a null-terminated, UTF-8 encoded string for the name.
*
- * \return If successful, it returns 0.
+ * \return If successful, it returns 0. If `name` is not a null-terminated UTF-8 encoded string,
+ * it returns -EINVAL.
*/
-int AVirtualMachineRawConfig_setName(AVirtualMachineRawConfig* config, const char* name);
+int AVirtualMachineRawConfig_setName(AVirtualMachineRawConfig* _Nonnull config,
+ const char* _Nonnull name) __INTRODUCED_IN(36);
/**
* Set an instance ID of a virtual machine.
*
* \param config a virtual machine config object.
* \param instanceId a pointer to a 64-byte buffer for the instance ID.
+ * \param instanceIdSize the number of bytes in `instanceId`.
*
- * \return If successful, it returns 0.
+ * \return If successful, it returns 0. If `instanceIdSize` is incorrect, it returns -EINVAL.
*/
-int AVirtualMachineRawConfig_setInstanceId(AVirtualMachineRawConfig* config,
- const int8_t* instanceId);
+int AVirtualMachineRawConfig_setInstanceId(AVirtualMachineRawConfig* _Nonnull config,
+ const int8_t* _Nonnull instanceId, size_t instanceIdSize)
+ __INTRODUCED_IN(36);
/**
* Set a kernel image of a virtual machine.
*
* \param config a virtual machine config object.
- * \param fd a readable file descriptor containing the kernel image, or -1 to unset.
- * `AVirtualMachineRawConfig_setKernel` takes ownership of `fd`.
- *
- * \return If successful, it returns 0.
+ * \param fd a readable, seekable, and sized (i.e. report a valid size using fstat()) file
+ * descriptor containing the kernel image, or -1 to unset. `AVirtualMachineRawConfig_setKernel`
+ * takes ownership of `fd`.
*/
-int AVirtualMachineRawConfig_setKernel(AVirtualMachineRawConfig* config, int fd);
+void AVirtualMachineRawConfig_setKernel(AVirtualMachineRawConfig* _Nonnull config, int fd)
+ __INTRODUCED_IN(36);
/**
* Set an init rd of a virtual machine.
*
* \param config a virtual machine config object.
- * \param fd a readable file descriptor containing the kernel image, or -1 to unset.
- * `AVirtualMachineRawConfig_setInitRd` takes ownership of `fd`.
- *
- * \return If successful, it returns 0.
+ * \param fd a readable, seekable, and sized (i.e. report a valid size using fstat()) file
+ * descriptor containing the init rd image, or -1 to unset. `AVirtualMachineRawConfig_setInitRd`
+ * takes ownership of `fd`.
*/
-int AVirtualMachineRawConfig_setInitRd(AVirtualMachineRawConfig* config, int fd);
+void AVirtualMachineRawConfig_setInitRd(AVirtualMachineRawConfig* _Nonnull config, int fd)
+ __INTRODUCED_IN(36);
/**
* Add a disk for a virtual machine.
*
* \param config a virtual machine config object.
- * \param fd a readable file descriptor containing the disk image.
- * `AVirtualMachineRawConfig_addDisk` takes ownership of `fd`.
+ * \param fd a readable, seekable, and sized (i.e. report a valid size using fstat()) file
+ * descriptor containing the disk. `fd` must be writable if If `writable` is true.
+ * `AVirtualMachineRawConfig_addDisk` takes ownership of `fd`.
+ * \param writable whether this disk should be writable by the virtual machine.
*
* \return If successful, it returns 0. If `fd` is invalid, it returns -EINVAL.
*/
-int AVirtualMachineRawConfig_addDisk(AVirtualMachineRawConfig* config, int fd);
+int AVirtualMachineRawConfig_addDisk(AVirtualMachineRawConfig* _Nonnull config, int fd,
+ bool writable) __INTRODUCED_IN(36);
/**
* Set how much memory will be given to a virtual machine.
@@ -114,40 +124,41 @@
* \param config a virtual machine config object.
* \param memoryMib the amount of RAM to give the virtual machine, in MiB. 0 or negative to use the
* default.
- *
- * \return If successful, it returns 0.
*/
-int AVirtualMachineRawConfig_setMemoryMib(AVirtualMachineRawConfig* config, int32_t memoryMib);
+void AVirtualMachineRawConfig_setMemoryMib(AVirtualMachineRawConfig* _Nonnull config,
+ int32_t memoryMib) __INTRODUCED_IN(36);
/**
- * Set whether a virtual machine is protected or not.
+ * Set whether the virtual machine's memory will be protected from the host, so the host can't
+ * access its memory.
*
* \param config a virtual machine config object.
* \param protectedVm whether the virtual machine should be protected.
- *
- * \return If successful, it returns 0.
*/
-int AVirtualMachineRawConfig_setProtectedVm(AVirtualMachineRawConfig* config, bool protectedVm);
+void AVirtualMachineRawConfig_setProtectedVm(AVirtualMachineRawConfig* _Nonnull config,
+ bool protectedVm) __INTRODUCED_IN(36);
/**
- * Set whether a virtual machine uses memory ballooning or not.
+ * Set whether a virtual machine uses memory ballooning.
*
* \param config a virtual machine config object.
* \param balloon whether the virtual machine should use memory ballooning.
- *
- * \return If successful, it returns 0.
*/
-int AVirtualMachineRawConfig_setBalloon(AVirtualMachineRawConfig* config, bool balloon);
+void AVirtualMachineRawConfig_setBalloon(AVirtualMachineRawConfig* _Nonnull config, bool balloon)
+ __INTRODUCED_IN(36);
/**
* Set whether to use an alternate, hypervisor-specific authentication method
- * for protected VMs. You don't want to use this.
+ * for protected VMs.
+ *
+ * This option is discouraged. Prefer to use the default authentication method, which is better
+ * tested and integrated into Android. This option must only be used from the vendor partition.
*
* \return If successful, it returns 0. It returns `-ENOTSUP` if the hypervisor doesn't have an
* alternate auth mode.
*/
-int AVirtualMachineRawConfig_setHypervisorSpecificAuthMethod(AVirtualMachineRawConfig* config,
- bool enable);
+int AVirtualMachineRawConfig_setHypervisorSpecificAuthMethod(
+ AVirtualMachineRawConfig* _Nonnull config, bool enable) __INTRODUCED_IN(36);
/**
* Use the specified fd as the backing memfd for a range of the guest
@@ -155,14 +166,15 @@
*
* \param config a virtual machine config object.
* \param fd a memfd
- * \param rangeStart range start IPA
- * \param rangeEnd range end IPA
+ * \param rangeStart range start of guest memory addresses
+ * \param rangeEnd range end of guest memory addresses
*
* \return If successful, it returns 0. It returns `-ENOTSUP` if the hypervisor doesn't support
* backing memfd.
*/
-int AVirtualMachineRawConfig_addCustomMemoryBackingFile(AVirtualMachineRawConfig* config, int fd,
- size_t rangeStart, size_t rangeEnd);
+int AVirtualMachineRawConfig_addCustomMemoryBackingFile(AVirtualMachineRawConfig* _Nonnull config,
+ int fd, uint64_t rangeStart,
+ uint64_t rangeEnd) __INTRODUCED_IN(36);
/**
* Represents a handle on a virtualization service, responsible for managing virtual machines.
@@ -176,8 +188,10 @@
* The caller takes ownership of the returned service object, and is responsible for releasing it
* by calling {@link AVirtualizationService_destroy}.
*
- * \param early set to true when running a service for early virtual machines. See
- * [`early_vm.md`](../../../../docs/early_vm.md) for more details on early virtual machines.
+ * \param early set to true when running a service for early virtual machines. Early VMs are
+ * specialized virtual machines that can run even before the `/data` partition is mounted.
+ * Early VMs must be pre-defined in XML files located at `{partition}/etc/avf/early_vms*.xml`, and
+ * clients of early VMs must be pre-installed under the same partition.
* \param service an out parameter that will be set to the service handle.
*
* \return
@@ -187,7 +201,8 @@
* - If it fails to connect to the spawned `virtmgr`, it leaves `service` untouched and returns
* `-ECONNREFUSED`.
*/
-int AVirtualizationService_create(AVirtualizationService** service, bool early);
+int AVirtualizationService_create(AVirtualizationService* _Null_unspecified* _Nonnull service,
+ bool early) __INTRODUCED_IN(36);
/**
* Destroy a VirtualizationService object.
@@ -197,7 +212,7 @@
*
* \param service a handle on a virtualization service.
*/
-void AVirtualizationService_destroy(AVirtualizationService* service);
+void AVirtualizationService_destroy(AVirtualizationService* _Nullable service) __INTRODUCED_IN(36);
/**
* Represents a handle on a virtual machine.
@@ -208,55 +223,55 @@
* The reason why a virtual machine stopped.
* @see AVirtualMachine_waitForStop
*/
-enum StopReason : int32_t {
+enum AVirtualMachineStopReason : int32_t {
/**
* VirtualizationService died.
*/
- VIRTUALIZATION_SERVICE_DIED = 1,
+ AVIRTUAL_MACHINE_VIRTUALIZATION_SERVICE_DIED = 1,
/**
* There was an error waiting for the virtual machine.
*/
- INFRASTRUCTURE_ERROR = 2,
+ AVIRTUAL_MACHINE_INFRASTRUCTURE_ERROR = 2,
/**
* The virtual machine was killed.
*/
- KILLED = 3,
+ AVIRTUAL_MACHINE_KILLED = 3,
/**
* The virtual machine stopped for an unknown reason.
*/
- UNKNOWN = 4,
+ AVIRTUAL_MACHINE_UNKNOWN = 4,
/**
* The virtual machine requested to shut down.
*/
- SHUTDOWN = 5,
+ AVIRTUAL_MACHINE_SHUTDOWN = 5,
/**
* crosvm had an error starting the virtual machine.
*/
- START_FAILED = 6,
+ AVIRTUAL_MACHINE_START_FAILED = 6,
/**
* The virtual machine requested to reboot, possibly as the result of a kernel panic.
*/
- REBOOT = 7,
+ AVIRTUAL_MACHINE_REBOOT = 7,
/**
* The virtual machine or crosvm crashed.
*/
- CRASH = 8,
+ AVIRTUAL_MACHINE_CRASH = 8,
/**
* The pVM firmware failed to verify the VM because the public key doesn't match.
*/
- PVM_FIRMWARE_PUBLIC_KEY_MISMATCH = 9,
+ AVIRTUAL_MACHINE_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH = 9,
/**
* The pVM firmware failed to verify the VM because the instance image changed.
*/
- PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED = 10,
+ AVIRTUAL_MACHINE_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED = 10,
/**
* The virtual machine was killed due to hangup.
*/
- HANGUP = 11,
+ AVIRTUAL_MACHINE_HANGUP = 11,
/**
* VirtualizationService sent a stop reason which was not recognised by the client library.
*/
- UNRECOGNISED = 0,
+ AVIRTUAL_MACHINE_UNRECOGNISED = 0,
};
/**
@@ -282,45 +297,67 @@
* \return If successful, it sets `vm` and returns 0. Otherwise, it leaves `vm` untouched and
* returns `-EIO`.
*/
-int AVirtualMachine_createRaw(const AVirtualizationService* service,
- AVirtualMachineRawConfig* config, int consoleOutFd, int consoleInFd,
- int logFd, AVirtualMachine** vm);
+int AVirtualMachine_createRaw(const AVirtualizationService* _Nonnull service,
+ AVirtualMachineRawConfig* _Nonnull config, int consoleOutFd,
+ int consoleInFd, int logFd,
+ AVirtualMachine* _Null_unspecified* _Nonnull vm) __INTRODUCED_IN(36);
/**
- * Start a virtual machine.
+ * Start a virtual machine. `AVirtualMachine_start` is synchronous and blocks until the virtual
+ * machine is initialized and free to start executing code, or until an error happens.
*
* \param vm a handle on a virtual machine.
*
* \return If successful, it returns 0. Otherwise, it returns `-EIO`.
*/
-int AVirtualMachine_start(AVirtualMachine* vm);
+int AVirtualMachine_start(AVirtualMachine* _Nonnull vm) __INTRODUCED_IN(36);
/**
- * Stop a virtual machine.
+ * Stop a virtual machine. Stopping a virtual machine is like pulling the plug on a real computer;
+ * the machine halts immediately. Software running on the virtual machine is not notified of the
+ * event, the instance might be left in an inconsistent state.
+ *
+ * For a graceful shutdown, you could request the virtual machine to exit itself, and wait for the
+ * virtual machine to stop by `AVirtualMachine_waitForStop`.
+ *
+ * A stopped virtual machine can be re-started by calling `AVirtualMachine_start`.
+ *
+ * `AVirtualMachine_stop` stops a virtual machine by sending a signal to the process. This operation
+ * is synchronous and `AVirtualMachine_stop` may block.
*
* \param vm a handle on a virtual machine.
*
* \return If successful, it returns 0. Otherwise, it returns `-EIO`.
*/
-int AVirtualMachine_stop(AVirtualMachine* vm);
+int AVirtualMachine_stop(AVirtualMachine* _Nonnull vm) __INTRODUCED_IN(36);
/**
- * Wait until a virtual machine stops.
+ * Wait until a virtual machine stops or the given timeout elapses.
*
* \param vm a handle on a virtual machine.
+ * \param timeout the timeout, or null to wait indefinitely.
+ * \param reason An out parameter that will be set to the reason why the virtual machine stopped.
*
- * \return The reason why the virtual machine stopped.
+ * \return
+ * - If the virtual machine stops within the timeout (or indefinitely if `timeout` is null), it
+ * sets `reason` and returns true.
+ * - If the timeout expired, it returns `false`.
*/
-enum StopReason AVirtualMachine_waitForStop(AVirtualMachine* vm);
+bool AVirtualMachine_waitForStop(AVirtualMachine* _Nonnull vm,
+ const struct timespec* _Nullable timeout,
+ enum AVirtualMachineStopReason* _Nonnull reason)
+ __INTRODUCED_IN(36);
/**
- * Destroy a virtual machine.
+ * Destroy a virtual machine object. If the virtual machine is still running,
+ * `AVirtualMachine_destroy` first stops the virtual machine by sending a signal to the process.
+ * This operation is synchronous and `AVirtualMachine_destroy` may block.
*
* `AVirtualMachine_destroy` does nothing if `vm` is null. A destroyed virtual machine must not be
* reused.
*
* \param vm a handle on a virtual machine.
*/
-void AVirtualMachine_destroy(AVirtualMachine* vm);
+void AVirtualMachine_destroy(AVirtualMachine* _Nullable vm) __INTRODUCED_IN(36);
__END_DECLS
diff --git a/libs/libavf/libavf.map.txt b/libs/libavf/libavf.map.txt
index ecb4cc9..05a5b35 100644
--- a/libs/libavf/libavf.map.txt
+++ b/libs/libavf/libavf.map.txt
@@ -1,4 +1,4 @@
-LIBAVF {
+LIBAVF { # introduced=36
global:
AVirtualMachineRawConfig_create; # apex llndk
AVirtualMachineRawConfig_destroy; # apex llndk
diff --git a/libs/libavf/src/lib.rs b/libs/libavf/src/lib.rs
index 0a8f891..1d7861f 100644
--- a/libs/libavf/src/lib.rs
+++ b/libs/libavf/src/lib.rs
@@ -19,6 +19,7 @@
use std::os::fd::FromRawFd;
use std::os::raw::{c_char, c_int};
use std::ptr;
+use std::time::Duration;
use android_system_virtualizationservice::{
aidl::android::system::virtualizationservice::{
@@ -28,7 +29,8 @@
},
binder::{ParcelFileDescriptor, Strong},
};
-use avf_bindgen::StopReason;
+use avf_bindgen::AVirtualMachineStopReason;
+use libc::timespec;
use vmclient::{DeathReason, VirtualizationService, VmInstance};
/// Create a new virtual machine config object with no properties.
@@ -70,8 +72,14 @@
// AVirtualMachineRawConfig_create. It's the only reference to the object.
let config = unsafe { &mut *config };
// SAFETY: `name` is assumed to be a pointer to a valid C string.
- config.name = unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned();
- 0
+ let name = unsafe { CStr::from_ptr(name) };
+ match name.to_str() {
+ Ok(name) => {
+ config.name = name.to_owned();
+ 0
+ }
+ Err(_) => -libc::EINVAL,
+ }
}
/// Set an instance ID of a virtual machine.
@@ -83,7 +91,12 @@
pub unsafe extern "C" fn AVirtualMachineRawConfig_setInstanceId(
config: *mut VirtualMachineRawConfig,
instance_id: *const u8,
+ instance_id_size: usize,
) -> c_int {
+ if instance_id_size != 64 {
+ return -libc::EINVAL;
+ }
+
// SAFETY: `config` is assumed to be a valid, non-null pointer returned by
// AVirtualMachineRawConfig_create. It's the only reference to the object.
let config = unsafe { &mut *config };
@@ -91,7 +104,7 @@
// is assumed to be a valid object returned by AVirtuaMachineConfig_create.
// Both never overlap.
unsafe {
- ptr::copy_nonoverlapping(instance_id, config.instanceId.as_mut_ptr(), 64);
+ ptr::copy_nonoverlapping(instance_id, config.instanceId.as_mut_ptr(), instance_id_size);
}
0
}
@@ -106,13 +119,12 @@
pub unsafe extern "C" fn AVirtualMachineRawConfig_setKernel(
config: *mut VirtualMachineRawConfig,
fd: c_int,
-) -> c_int {
+) {
let file = get_file_from_fd(fd);
// SAFETY: `config` is assumed to be a valid, non-null pointer returned by
// AVirtualMachineRawConfig_create. It's the only reference to the object.
let config = unsafe { &mut *config };
config.kernel = file.map(ParcelFileDescriptor::new);
- 0
}
/// Set an init rd of a virtual machine.
@@ -125,13 +137,12 @@
pub unsafe extern "C" fn AVirtualMachineRawConfig_setInitRd(
config: *mut VirtualMachineRawConfig,
fd: c_int,
-) -> c_int {
+) {
let file = get_file_from_fd(fd);
// SAFETY: `config` is assumed to be a valid, non-null pointer returned by
// AVirtualMachineRawConfig_create. It's the only reference to the object.
let config = unsafe { &mut *config };
config.initrd = file.map(ParcelFileDescriptor::new);
- 0
}
/// Add a disk for a virtual machine.
@@ -172,12 +183,11 @@
pub unsafe extern "C" fn AVirtualMachineRawConfig_setMemoryMib(
config: *mut VirtualMachineRawConfig,
memory_mib: i32,
-) -> c_int {
+) {
// SAFETY: `config` is assumed to be a valid, non-null pointer returned by
// AVirtualMachineRawConfig_create. It's the only reference to the object.
let config = unsafe { &mut *config };
config.memoryMib = memory_mib;
- 0
}
/// Set whether a virtual machine is protected or not.
@@ -188,12 +198,11 @@
pub unsafe extern "C" fn AVirtualMachineRawConfig_setProtectedVm(
config: *mut VirtualMachineRawConfig,
protected_vm: bool,
-) -> c_int {
+) {
// SAFETY: `config` is assumed to be a valid, non-null pointer returned by
// AVirtualMachineRawConfig_create. It's the only reference to the object.
let config = unsafe { &mut *config };
config.protectedVm = protected_vm;
- 0
}
/// Set whether a virtual machine uses memory ballooning or not.
@@ -204,12 +213,11 @@
pub unsafe extern "C" fn AVirtualMachineRawConfig_setBalloon(
config: *mut VirtualMachineRawConfig,
balloon: bool,
-) -> c_int {
+) {
// SAFETY: `config` is assumed to be a valid, non-null pointer returned by
// AVirtualMachineRawConfig_create. It's the only reference to the object.
let config = unsafe { &mut *config };
config.noBalloon = !balloon;
- 0
}
/// NOT IMPLEMENTED.
@@ -232,8 +240,8 @@
pub extern "C" fn AVirtualMachineRawConfig_addCustomMemoryBackingFile(
_config: *mut VirtualMachineRawConfig,
_fd: c_int,
- _range_start: usize,
- _range_end: usize,
+ _range_start: u64,
+ _range_end: u64,
) -> c_int {
-libc::ENOTSUP
}
@@ -360,31 +368,64 @@
}
}
-/// Wait until a virtual machine stops.
+fn death_reason_to_stop_reason(death_reason: DeathReason) -> AVirtualMachineStopReason {
+ match death_reason {
+ DeathReason::VirtualizationServiceDied => {
+ AVirtualMachineStopReason::AVIRTUAL_MACHINE_VIRTUALIZATION_SERVICE_DIED
+ }
+ DeathReason::InfrastructureError => {
+ AVirtualMachineStopReason::AVIRTUAL_MACHINE_INFRASTRUCTURE_ERROR
+ }
+ DeathReason::Killed => AVirtualMachineStopReason::AVIRTUAL_MACHINE_KILLED,
+ DeathReason::Unknown => AVirtualMachineStopReason::AVIRTUAL_MACHINE_UNKNOWN,
+ DeathReason::Shutdown => AVirtualMachineStopReason::AVIRTUAL_MACHINE_SHUTDOWN,
+ DeathReason::StartFailed => AVirtualMachineStopReason::AVIRTUAL_MACHINE_START_FAILED,
+ DeathReason::Reboot => AVirtualMachineStopReason::AVIRTUAL_MACHINE_REBOOT,
+ DeathReason::Crash => AVirtualMachineStopReason::AVIRTUAL_MACHINE_CRASH,
+ DeathReason::PvmFirmwarePublicKeyMismatch => {
+ AVirtualMachineStopReason::AVIRTUAL_MACHINE_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH
+ }
+ DeathReason::PvmFirmwareInstanceImageChanged => {
+ AVirtualMachineStopReason::AVIRTUAL_MACHINE_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED
+ }
+ DeathReason::Hangup => AVirtualMachineStopReason::AVIRTUAL_MACHINE_HANGUP,
+ _ => AVirtualMachineStopReason::AVIRTUAL_MACHINE_UNRECOGNISED,
+ }
+}
+
+/// Wait until a virtual machine stops or the timeout elapses.
///
/// # Safety
-/// `vm` must be a pointer returned by `AVirtualMachine_createRaw`.
+/// `vm` must be a pointer returned by `AVirtualMachine_createRaw`. `timeout` must be a valid
+/// pointer to a `struct timespec` object or null. `reason` must be a valid, non-null pointer to an
+/// AVirtualMachineStopReason object.
#[no_mangle]
-pub unsafe extern "C" fn AVirtualMachine_waitForStop(vm: *const VmInstance) -> StopReason {
+pub unsafe extern "C" fn AVirtualMachine_waitForStop(
+ vm: *const VmInstance,
+ timeout: *const timespec,
+ reason: *mut AVirtualMachineStopReason,
+) -> bool {
// SAFETY: `vm` is assumed to be a valid, non-null pointer returned by
// AVirtualMachine_create. It's the only reference to the object.
let vm = unsafe { &*vm };
- match vm.wait_for_death() {
- DeathReason::VirtualizationServiceDied => StopReason::VIRTUALIZATION_SERVICE_DIED,
- DeathReason::InfrastructureError => StopReason::INFRASTRUCTURE_ERROR,
- DeathReason::Killed => StopReason::KILLED,
- DeathReason::Unknown => StopReason::UNKNOWN,
- DeathReason::Shutdown => StopReason::SHUTDOWN,
- DeathReason::StartFailed => StopReason::START_FAILED,
- DeathReason::Reboot => StopReason::REBOOT,
- DeathReason::Crash => StopReason::CRASH,
- DeathReason::PvmFirmwarePublicKeyMismatch => StopReason::PVM_FIRMWARE_PUBLIC_KEY_MISMATCH,
- DeathReason::PvmFirmwareInstanceImageChanged => {
- StopReason::PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED
+
+ let death_reason = if timeout.is_null() {
+ vm.wait_for_death()
+ } else {
+ // SAFETY: `timeout` is assumed to be a valid pointer to a `struct timespec` object if
+ // non-null.
+ let timeout = unsafe { &*timeout };
+ let timeout = Duration::new(timeout.tv_sec as u64, timeout.tv_nsec as u32);
+ match vm.wait_for_death_with_timeout(timeout) {
+ Some(death_reason) => death_reason,
+ None => return false,
}
- DeathReason::Hangup => StopReason::HANGUP,
- _ => StopReason::UNRECOGNISED,
- }
+ };
+
+ // SAFETY: `reason` is assumed to be a valid, non-null pointer to an
+ // AVirtualMachineStopReason object.
+ unsafe { *reason = death_reason_to_stop_reason(death_reason) };
+ true
}
/// Destroy a virtual machine.
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 8314f43..01af51c 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -15,11 +15,6 @@
java_defaults {
name: "MicrodroidTestAppsDefaults",
- test_suites: [
- "cts",
- "vts",
- "general-tests",
- ],
static_libs: [
"com.android.microdroid.testservice-java",
"com.android.microdroid.test.vmshare_service-java",
@@ -64,17 +59,60 @@
min_sdk_version: "33",
}
+DATA = [
+ ":MicrodroidTestAppUpdated",
+ ":MicrodroidVmShareApp",
+ ":test_microdroid_vendor_image",
+ ":test_microdroid_vendor_image_unsigned",
+]
+
android_test {
name: "MicrodroidTestApp",
defaults: ["MicrodroidVersionsTestAppDefaults"],
manifest: "AndroidManifestV5.xml",
- // Defined in ../vmshareapp/Android.bp
- data: [
- ":MicrodroidTestAppUpdated",
- ":MicrodroidVmShareApp",
- ":test_microdroid_vendor_image",
- ":test_microdroid_vendor_image_unsigned",
- ],
+ test_suites: ["general-tests"],
+ test_config: "AndroidTest.xml",
+ data: DATA,
+}
+
+android_test {
+ name: "MicrodroidTestApp.CTS",
+ defaults: ["MicrodroidVersionsTestAppDefaults"],
+ manifest: "AndroidManifestV5.xml",
+ test_suites: ["cts"],
+ test_config: ":MicrodroidTestApp.CTS.config",
+ data: DATA,
+}
+
+android_test {
+ name: "MicrodroidTestApp.VTS",
+ defaults: ["MicrodroidVersionsTestAppDefaults"],
+ manifest: "AndroidManifestV5.xml",
+ test_suites: ["vts"],
+ test_config: ":MicrodroidTestApp.VTS.config",
+ data: DATA,
+}
+
+genrule {
+ name: "MicrodroidTestApp.CTS.config",
+ srcs: ["AndroidTest.xml"],
+ out: ["out.xml"],
+ cmd: "sed " +
+ "-e 's/<!-- PLACEHOLDER_FOR_ANNOTATION -->/" +
+ "<option name=\"include-annotation\" value=\"com.android.compatibility.common.util.CddTest\" \\/>/' " +
+ "-e 's/MicrodroidTestApp.apk/MicrodroidTestApp.CTS.apk/' " +
+ "$(in) > $(out)",
+}
+
+genrule {
+ name: "MicrodroidTestApp.VTS.config",
+ srcs: ["AndroidTest.xml"],
+ out: ["out.xml"],
+ cmd: "sed " +
+ "-e 's/<!-- PLACEHOLDER_FOR_ANNOTATION -->/" +
+ "<option name=\"include-annotation\" value=\"com.android.compatibility.common.util.VsrTest\" \\/>/' " +
+ "-e 's/MicrodroidTestApp.apk/MicrodroidTestApp.VTS.apk/' " +
+ "$(in) > $(out)",
}
android_test_helper_app {
diff --git a/tests/testapk/AndroidTest.xml b/tests/testapk/AndroidTest.xml
index e490da4..221c25c 100644
--- a/tests/testapk/AndroidTest.xml
+++ b/tests/testapk/AndroidTest.xml
@@ -43,4 +43,6 @@
<!-- Controller that will skip the module if a native bridge situation is detected -->
<!-- For example: module wants to run arm and device is x86 -->
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.NativeBridgeModuleController" />
+
+ <!-- PLACEHOLDER_FOR_ANNOTATION -->
</configuration>
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 b1485e3..97a5e78 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -83,7 +83,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
@@ -1931,30 +1930,27 @@
assertThat(checkVmOutputIsRedirectedToLogcat(true)).isTrue();
}
- private boolean setSystemProperties(String name, String value) {
+ private boolean isDebugPolicyEnabled(String entry) {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
UiAutomation uiAutomation = instrumentation.getUiAutomation();
- String cmd = "setprop " + name + " " + (value.isEmpty() ? "\"\"" : value);
- return runInShellWithStderr(TAG, uiAutomation, cmd).trim().isEmpty();
+ String cmd = "/apex/com.android.virt/bin/vm info";
+ String output = runInShellWithStderr(TAG, uiAutomation, cmd).trim();
+ for (String line : output.split("\\v")) {
+ if (line.matches("^.*Debug policy.*" + entry + ": true.*$")) {
+ return true;
+ }
+ }
+ return false;
}
@Test
- @Ignore("b/372874464")
public void outputIsNotRedirectedToLogcatIfNotDebuggable() throws Exception {
assumeSupportedDevice();
- // Disable debug policy to ensure no log output.
- String sysprop = "hypervisor.virtualizationmanager.debug_policy.path";
- String old = SystemProperties.get(sysprop);
- assumeTrue(
- "Can't disable debug policy. Perhapse user build?",
- setSystemProperties(sysprop, ""));
+ // Debug policy shouldn't enable log
+ assumeFalse(isDebugPolicyEnabled("log"));
- try {
- assertThat(checkVmOutputIsRedirectedToLogcat(false)).isFalse();
- } finally {
- assertThat(setSystemProperties(sysprop, old)).isTrue();
- }
+ assertThat(checkVmOutputIsRedirectedToLogcat(false)).isFalse();
}
@Test