Merge "Don't allow disk images labeled as app_data_file"
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 18d8ade..37350ff 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -36,9 +36,10 @@
"libmicrodroid_payload_config",
"libonce_cell",
"librustutils",
+ "libselinux_bindgen",
+ "libserde",
"libserde_json",
"libserde_xml_rs",
- "libserde",
"libshared_child",
"libvmconfig",
"libzip",
@@ -48,6 +49,7 @@
],
shared_libs: [
"libbinder_rpc_unstable",
+ "libselinux",
],
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 1c88174..af420f6 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -18,6 +18,7 @@
use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmInstance, VmState};
use crate::payload::add_microdroid_images;
use crate::{Cid, FIRST_GUEST_CID, SYSPROP_LAST_CID};
+use crate::selinux::{SeContext, getfilecon};
use ::binder::unstable_api::AsNative;
use android_os_permissions_aidl::aidl::android::os::IPermissionController;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
@@ -25,6 +26,7 @@
IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
IVirtualMachineCallback::IVirtualMachineCallback,
IVirtualizationService::IVirtualizationService,
+ Partition::Partition,
PartitionType::PartitionType,
VirtualMachineAppConfig::DebugLevel::DebugLevel,
VirtualMachineAppConfig::VirtualMachineAppConfig,
@@ -169,6 +171,8 @@
}
}
+ let is_app_config = matches!(config, VirtualMachineConfig::AppConfig(_));
+
let config = match config {
VirtualMachineConfig::AppConfig(config) => BorrowedOrOwned::Owned(
load_app_config(config, &temporary_directory).map_err(|e| {
@@ -183,6 +187,25 @@
};
let config = config.as_ref();
+ // Check if partition images are labeled incorrectly. This is to prevent random images
+ // which are not protected by the Android Verified Boot (e.g. bits downloaded by apps) from
+ // being loaded in a pVM. Specifically, for images in the raw config, nothing is allowed
+ // to be labeled as app_data_file. For images in the app config, nothing but the instance
+ // partition is allowed to be labeled as such.
+ config
+ .disks
+ .iter()
+ .flat_map(|disk| disk.partitions.iter())
+ .filter(|partition| {
+ if is_app_config {
+ partition.label != "vm-instance"
+ } else {
+ true // all partitions are checked
+ }
+ })
+ .try_for_each(check_label_for_partition)
+ .map_err(|e| new_binder_exception(ExceptionCode::SERVICE_SPECIFIC, e.to_string()))?;
+
let zero_filler_path = temporary_directory.join("zero.img");
write_zero_filler(&zero_filler_path).map_err(|e| {
error!("Failed to make composite image: {}", e);
@@ -606,6 +629,16 @@
check_permission("android.permission.MANAGE_VIRTUAL_MACHINE")
}
+/// Check if a partition has selinux labels that are not allowed
+fn check_label_for_partition(partition: &Partition) -> Result<()> {
+ let ctx = getfilecon(partition.image.as_ref().unwrap().as_ref())?;
+ if ctx == SeContext::new("u:object_r:app_data_file:s0").unwrap() {
+ Err(anyhow!("Partition {} shouldn't be labeled as {}", &partition.label, ctx))
+ } else {
+ Ok(())
+ }
+}
+
/// Implementation of the AIDL `IVirtualMachine` interface. Used as a handle to a VM.
#[derive(Debug)]
struct VirtualMachine {
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index 0e1e974..69ae076 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -18,6 +18,7 @@
mod composite;
mod crosvm;
mod payload;
+mod selinux;
use crate::aidl::{VirtualizationService, BINDER_SERVICE_IDENTIFIER, TEMPORARY_DIRECTORY};
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::BnVirtualizationService;
diff --git a/virtualizationservice/src/selinux.rs b/virtualizationservice/src/selinux.rs
new file mode 100644
index 0000000..e450dee
--- /dev/null
+++ b/virtualizationservice/src/selinux.rs
@@ -0,0 +1,104 @@
+// Copyright 2021, 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.
+
+//! Wrapper to libselinux
+
+use anyhow::{anyhow, Context, Result};
+use std::ffi::{CStr, CString};
+use std::fmt;
+use std::fs::File;
+use std::io;
+use std::ops::Deref;
+use std::os::raw::c_char;
+use std::os::unix::io::AsRawFd;
+use std::ptr;
+
+// Partially copied from system/security/keystore2/selinux/src/lib.rs
+/// SeContext represents an SELinux context string. It can take ownership of a raw
+/// s-string as allocated by `getcon` or `selabel_lookup`. In this case it uses
+/// `freecon` to free the resources when dropped. In its second variant it stores
+/// an `std::ffi::CString` that can be initialized from a Rust string slice.
+#[derive(Debug)]
+pub enum SeContext {
+ /// Wraps a raw context c-string as returned by libselinux.
+ Raw(*mut ::std::os::raw::c_char),
+ /// Stores a context string as `std::ffi::CString`.
+ CString(CString),
+}
+
+impl PartialEq for SeContext {
+ fn eq(&self, other: &Self) -> bool {
+ // We dereference both and thereby delegate the comparison
+ // to `CStr`'s implementation of `PartialEq`.
+ **self == **other
+ }
+}
+
+impl Eq for SeContext {}
+
+impl fmt::Display for SeContext {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.to_str().unwrap_or("Invalid context"))
+ }
+}
+
+impl Drop for SeContext {
+ fn drop(&mut self) {
+ if let Self::Raw(p) = self {
+ // SAFETY: SeContext::Raw is created only with a pointer that is set by libselinux and
+ // has to be freed with freecon.
+ unsafe { selinux_bindgen::freecon(*p) };
+ }
+ }
+}
+
+impl Deref for SeContext {
+ type Target = CStr;
+
+ fn deref(&self) -> &Self::Target {
+ match self {
+ // SAFETY: the non-owned C string pointed by `p` is guaranteed to be valid (non-null
+ // and shorter than i32::MAX). It is freed when SeContext is dropped.
+ Self::Raw(p) => unsafe { CStr::from_ptr(*p) },
+ Self::CString(cstr) => cstr,
+ }
+ }
+}
+
+impl SeContext {
+ /// Initializes the `SeContext::CString` variant from a Rust string slice.
+ pub fn new(con: &str) -> Result<Self> {
+ Ok(Self::CString(
+ CString::new(con)
+ .with_context(|| format!("Failed to create SeContext with \"{}\"", con))?,
+ ))
+ }
+}
+
+pub fn getfilecon(file: &File) -> Result<SeContext> {
+ let fd = file.as_raw_fd();
+ let mut con: *mut c_char = ptr::null_mut();
+ // SAFETY: the returned pointer `con` is wrapped in SeContext::Raw which is freed with
+ // `freecon` when it is dropped.
+ match unsafe { selinux_bindgen::fgetfilecon(fd, &mut con) } {
+ 1.. => {
+ if !con.is_null() {
+ Ok(SeContext::Raw(con))
+ } else {
+ Err(anyhow!("fgetfilecon returned a NULL context"))
+ }
+ }
+ _ => Err(anyhow!(io::Error::last_os_error())).context("fgetfilecon failed"),
+ }
+}