Enable exporting of tombstones out of guest VMs

Microdroid init will start the tombstone_transmit service which will
connect to a server (thread of virtualization service).

virtualizationservice listens for incoming connections from guests VMs.
The client (tombstone_transmit in  guest) sends each tombstone file
content through a separate stream.

For each received connection, handle_tombstone would send a connect
request to tombstoned(via libtombstoned_client_rust), which provide a
file  to write the content in (actually managed by the client libary).
Once write is complete notify_completion() would send completion
notification to tombstoned (which then will close the file and rename it).

Test: atest MicrodroidHostTestCases and/or crash a process in microdroid, take and check bugreport
Bug: 202153827

Change-Id: Ifcaa5da968ef39fdd05612d3e0baca4fd1c5eaf1
This commit is contained in:
Shikha Panwar 2022-03-24 08:54:43 +00:00
parent 0689e7e162
commit 7afc13991c
8 changed files with 126 additions and 10 deletions

View File

@ -77,6 +77,7 @@ android_system_image {
"linkerconfig",
"servicemanager.microdroid",
"tombstoned",
"tombstone_transmit.microdroid",
"cgroups.json",
"public.libraries.android.txt",

View File

@ -1,2 +1,6 @@
androidboot.first_stage_console = 1
androidboot.hardware = microdroid
# tombstone_transmit is enabled. Tombstones will be transmitted to the host
# TODO(b/227443903) : Make this configurable by VM owners
androidboot.tombstone_transmit.enabled=1

View File

@ -173,6 +173,13 @@ on post-fs-data
mkdir /data/local 0751 root root
mkdir /data/local/tmp 0771 shell shell
on init && property:ro.boot.tombstone_transmit.enabled=1
start tombstone_transmit
service tombstone_transmit /system/bin/tombstone_transmit.microdroid -cid 2 -port 2000
user root
group system
service apexd-vm /system/bin/apexd --vm
user root
group system

View File

@ -105,7 +105,7 @@ public abstract class VirtualizationTestCaseBase extends BaseHostJUnit4Test {
}
// Run an arbitrary command in the host side and returns the result
private static String runOnHost(String... cmd) {
public static String runOnHost(String... cmd) {
return runOnHostWithTimeout(10000, cmd);
}

View File

@ -22,6 +22,7 @@ import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@ -100,6 +101,13 @@ public class MicrodroidTestCase extends VirtualizationTestCaseBase {
false);
}
// Wait until logd-init starts. The service is one of the last services that are started in
// the microdroid boot procedure. Therefore, waiting for the service means that we wait for
// the boot to complete. TODO: we need a better marker eventually.
private void waitForLogdInit() {
tryRunOnMicrodroid("watch -e \"getprop init.svc.logd-reinit | grep '^$'\"");
}
@Test
public void testCreateVmRequiresPermission() throws Exception {
// Revoke the MANAGE_VIRTUAL_MACHINE permission for the test app
@ -349,6 +357,41 @@ public class MicrodroidTestCase extends VirtualizationTestCaseBase {
shutdownMicrodroid(getDevice(), cid);
}
@Test
public void testTombstonesAreBeingForwarded() throws Exception {
// Note this test relies on logcat values being printed by tombstone_transmit on
// and the reeceiver on host (virtualization_service)
final String configPath = "assets/vm_config.json"; // path inside the APK
final String cid =
startMicrodroid(
getDevice(),
getBuild(),
APK_NAME,
PACKAGE_NAME,
configPath,
/* debug */ true,
minMemorySize(),
Optional.of(NUM_VCPUS),
Optional.of(CPU_AFFINITY));
adbConnectToMicrodroid(getDevice(), cid);
waitForLogdInit();
runOnMicrodroid("logcat -c");
// We need root permission to write to /data/tombstones/
rootMicrodroid();
// Write a test tombstone file in /data/tombstones
runOnMicrodroid("echo -n \'Test tombstone in VM with 34 bytes\'"
+ "> /data/tombstones/transmit.txt");
// check if the tombstone have been tranferred from VM
assertNotEquals(runOnMicrodroid("timeout 15s logcat | grep -m 1 "
+ "'tombstone_transmit.microdroid:.*data/tombstones/transmit.txt'"),
"");
// Confirm that tombstone is received (from host logcat)
assertNotEquals(runOnHost("adb", "-s", getDevice().getSerialNumber(),
"logcat", "-d", "-e",
"Received 34 bytes from guest & wrote to tombstone file.*"),
"");
}
@Test
public void testMicrodroidBoots() throws Exception {
final String configPath = "assets/vm_config.json"; // path inside the APK
@ -364,12 +407,7 @@ public class MicrodroidTestCase extends VirtualizationTestCaseBase {
Optional.of(NUM_VCPUS),
Optional.of(CPU_AFFINITY));
adbConnectToMicrodroid(getDevice(), cid);
// Wait until logd-init starts. The service is one of the last services that are started in
// the microdroid boot procedure. Therefore, waiting for the service means that we wait for
// the boot to complete. TODO: we need a better marker eventually.
tryRunOnMicrodroid("watch -e \"getprop init.svc.logd-reinit | grep '^$'\"");
waitForLogdInit();
// Test writing to /data partition
runOnMicrodroid("echo MicrodroidTest > /data/local/tmp/test.txt");
assertThat(runOnMicrodroid("cat /data/local/tmp/test.txt"), is("MicrodroidTest"));

View File

@ -45,6 +45,7 @@ rust_defaults {
"libserde_xml_rs",
"libshared_child",
"libstatslog_virtualization_rust",
"libtombstoned_client_rust",
"libvmconfig",
"libzip",
"libvsock",

View File

@ -29,6 +29,12 @@ interface IVirtualMachineService {
*/
const int VM_BINDER_SERVICE_PORT = 5000;
/**
* Port number that VirtualMachineService listens on connections from the guest VMs for the
* tombtones
*/
const int VM_TOMBSTONES_SERVICE_PORT = 2000;
/**
* Notifies that the payload has started.
*/

View File

@ -42,7 +42,7 @@ use android_system_virtualizationservice::binder::{
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::{
IVirtualMachineService::{
BnVirtualMachineService, IVirtualMachineService, VM_BINDER_SERVICE_PORT,
VM_STREAM_SERVICE_PORT,
VM_STREAM_SERVICE_PORT, VM_TOMBSTONES_SERVICE_PORT,
},
};
use anyhow::{anyhow, bail, Context, Result};
@ -57,13 +57,14 @@ use statslog_virtualization_rust::vm_creation_requested::{stats_write, Hyperviso
use std::convert::TryInto;
use std::ffi::CStr;
use std::fs::{create_dir, File, OpenOptions};
use std::io::{Error, ErrorKind, Write};
use std::io::{Error, ErrorKind, Write, Read};
use std::num::NonZeroU32;
use std::os::raw;
use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::path::{Path, PathBuf};
use std::ptr::null_mut;
use std::sync::{Arc, Mutex, Weak};
use tombstoned_client::{TombstonedConnection, DebuggerdDumpType};
use vmconfig::VmConfig;
use vsock::{SockAddr, VsockListener, VsockStream};
use zip::ZipArchive;
@ -86,6 +87,8 @@ const ANDROID_VM_INSTANCE_MAGIC: &str = "Android-VM-instance";
/// Version of the instance image format
const ANDROID_VM_INSTANCE_VERSION: u16 = 1;
const CHUNK_RECV_MAX_LEN: usize = 1024;
/// Implementation of `IVirtualizationService`, the entry point of the AIDL service.
#[derive(Debug, Default)]
pub struct VirtualizationService {
@ -371,6 +374,55 @@ impl IVirtualizationService for VirtualizationService {
}
}
fn handle_stream_connection_tombstoned() -> Result<()> {
let listener =
VsockListener::bind_with_cid_port(VMADDR_CID_HOST, VM_TOMBSTONES_SERVICE_PORT as u32)?;
info!("Listening to tombstones from guests ...");
for incoming_stream in listener.incoming() {
let mut incoming_stream = match incoming_stream {
Err(e) => {
warn!("invalid incoming connection: {}", e);
continue;
}
Ok(s) => s,
};
std::thread::spawn(move || {
if let Err(e) = handle_tombstone(&mut incoming_stream) {
error!("Failed to write tombstone- {:?}", e);
}
});
}
Ok(())
}
fn handle_tombstone(stream: &mut VsockStream) -> Result<()> {
if let Ok(SockAddr::Vsock(addr)) = stream.peer_addr() {
info!("Vsock Stream connected to cid={} for tombstones", addr.cid());
}
let tb_connection =
TombstonedConnection::connect(std::process::id() as i32, DebuggerdDumpType::Tombstone)
.context("Failed to connect to tombstoned")?;
let mut text_output = tb_connection
.text_output
.as_ref()
.ok_or_else(|| anyhow!("Could not get file to write the tombstones on"))?;
let mut num_bytes_read = 0;
loop {
let mut chunk_recv = [0; CHUNK_RECV_MAX_LEN];
let n = stream
.read(&mut chunk_recv)
.context("Failed to read tombstone data from Vsock stream")?;
if n == 0 {
break;
}
num_bytes_read += n;
text_output.write_all(&chunk_recv[0..n]).context("Failed to write guests tombstones")?;
}
info!("Received {} bytes from guest & wrote to tombstone file", num_bytes_read);
tb_connection.notify_completion()?;
Ok(())
}
impl VirtualizationService {
pub fn init() -> VirtualizationService {
let service = VirtualizationService::default();
@ -381,8 +433,15 @@ impl VirtualizationService {
handle_stream_connection_from_vm(state).unwrap();
});
std::thread::spawn(|| {
if let Err(e) = handle_stream_connection_tombstoned() {
warn!("Error receiving tombstone from guest or writing them. Error: {}", e);
}
});
// binder server for vm
let mut state = service.state.clone(); // reference to state (not the state itself) is copied
// reference to state (not the state itself) is copied
let mut state = service.state.clone();
std::thread::spawn(move || {
let state_ptr = &mut state as *mut _ as *mut raw::c_void;