blob: d99128a81363c219a1d49c73103aec90e8d1ed3d [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! The broker daemon that supports Trichecus from within the Chrome OS guest machine.
#![deny(unsafe_op_in_unsafe_fn)]
use std::collections::BTreeMap as Map;
use std::env;
use std::fmt::{self, Debug, Formatter};
use std::fs::File;
use std::io::Read;
use std::os::unix::net::UnixDatagram;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use anyhow::{anyhow, bail, Context, Result};
use dbus::{
arg::OwnedFd, blocking::LocalConnection, channel::MatchingReceiver, message::MatchRule,
MethodErr,
};
use dbus_crossroads::Crossroads;
use getopts::Options;
use libsirenia::build_info::BUILD_TIMESTAMP;
use libsirenia::cli::trichechus::initialize_common_arguments;
use libsirenia::communication::trichechus::{self, AppInfo, Trichechus, TrichechusClient};
use libsirenia::rpc;
use libsirenia::transport::{Transport, TransportType, DEFAULT_CLIENT_PORT, DEFAULT_SERVER_PORT};
use sirenia::app_info::{AppManifest, ExecutableInfo};
use sirenia::server::{register_org_chromium_mana_teeinterface, OrgChromiumManaTEEInterface};
use sys_util::{error, info, syslog};
const GET_LOGS_SHORT_NAME: &str = "l";
// Arc and Mutex are used because dbus-crossroads requires the Send trait since it is designed to
// be thread safe. At the time of writing Dugong is single threaded so Arc and Mutex aren't strictly
// necessary.
// See https://github.com/diwic/dbus-rs/issues/349 for a feature request to make Send optional.
struct DugongStateInternal {
transport_type: TransportType,
trichechus_client: Mutex<TrichechusClient>,
supported_apps: Mutex<Map<String, Option<PathBuf>>>,
}
#[derive(Clone)]
struct DugongState(Arc<DugongStateInternal>);
impl DugongState {
fn new(trichechus_client: TrichechusClient, transport_type: TransportType) -> Self {
DugongState(Arc::new(DugongStateInternal {
transport_type,
trichechus_client: Mutex::new(trichechus_client),
supported_apps: Mutex::new(Default::default()),
}))
}
fn register_supported_apps<
S: Into<String>,
P: Into<PathBuf>,
I: IntoIterator<Item = (S, Option<P>)>,
>(
&mut self,
i: I,
) {
let mut supported_apps = self.0.supported_apps.lock().unwrap();
for (app_id, path) in i.into_iter() {
supported_apps.insert(app_id.into(), path.map(Into::<PathBuf>::into));
}
}
fn transport_type(&self) -> &TransportType {
&self.0.transport_type
}
fn trichechus_client(&self) -> &Mutex<TrichechusClient> {
&self.0.trichechus_client
}
fn supported_apps(&self) -> &Mutex<Map<String, Option<PathBuf>>> {
&self.0.supported_apps
}
}
impl Debug for DugongState {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "transport_type: {:?}", self.transport_type())
}
}
impl OrgChromiumManaTEEInterface for DugongState {
fn start_teeapplication(
&mut self,
app_id: String,
) -> std::result::Result<(i32, OwnedFd, OwnedFd), MethodErr> {
info!("Got request to start up: {}", &app_id);
let fds = request_start_tee_app(self, &app_id);
match fds {
Ok(fds) => Ok((0, fds.0, fds.1)),
Err(e) => Err(MethodErr::failed(&e)),
}
}
fn system_event(&mut self, event: String) -> std::result::Result<String, MethodErr> {
let api_handle = self.trichechus_client().lock().unwrap();
let status = api_handle
.system_event(event.parse().map_err(|err| MethodErr::failed(&err))?)
.map_err(|err| MethodErr::failed(&err))?;
Ok(match status {
Ok(()) => String::new(),
Err(err) => format!("{:?}", err),
})
}
}
fn load_tee_app(
api_handle: &mut TrichechusClient,
state: &DugongState,
app_id: &str,
) -> Result<()> {
let supported_apps = state.supported_apps().lock().unwrap();
let elf_path = supported_apps
.get(app_id)
.unwrap_or(&None)
.as_ref()
.ok_or_else(|| anyhow!("no entry for loading app '{0}'", app_id))?;
let mut elf = Vec::<u8>::new();
File::open(elf_path)
.with_context(|| format!("failed to open elf path '{}'", &elf_path.display()))?
.read_to_end(&mut elf)
.with_context(|| format!("failed to read elf path '{}'", &elf_path.display()))?;
info!("Transmitting TEE app.");
api_handle
.load_app(app_id.to_string(), elf)
.context("failed to call load_app rpc")?
.context("load_app rpc failed")?;
Ok(())
}
fn request_start_tee_app(state: &DugongState, app_id: &str) -> Result<(OwnedFd, OwnedFd)> {
let mut transport = state
.transport_type()
.try_into_client(None)
.context("failed to get client for transport")?;
let addr = transport.bind().context("failed to bind to socket")?;
let app_info = AppInfo {
app_id: String::from(app_id),
port_number: addr.get_port().context("failed to get port")?,
};
info!("Requesting start {:?}", &app_info);
let mut trichechus_client = state.trichechus_client().lock().unwrap();
match trichechus_client
.start_session(app_info.clone())
.context("failed to call start_session rpc")?
{
Ok(_) => (),
Err(trichechus::Error::AppNotLoaded) => {
load_tee_app(&mut trichechus_client, state, app_id)?;
trichechus_client
.start_session(app_info)
.context("failed to call start_session rpc")?
.context("start_session rpc failed")?;
}
Err(err) => {
bail!("start_session rpc failed: {}", err);
}
}
let Transport { r, w, id: _ } = transport.connect().context("failed to connect to socket")?;
// This is safe because into_raw_fd transfers the ownership to OwnedFd.
Ok((unsafe { OwnedFd::new(r.into_raw_fd()) }, unsafe {
OwnedFd::new(w.into_raw_fd())
}))
}
fn handle_manatee_logs(dugong_state: &DugongState) -> Result<()> {
const LOG_PATH: &str = "/dev/log";
let trichechus_client = dugong_state.trichechus_client().lock().unwrap();
let logs: Vec<Vec<u8>> = trichechus_client
.get_logs()
.context("failed to call get_logs rpc")?;
if logs.is_empty() {
return Ok(());
}
// TODO(b/173600313) Decide whether to write this directly to a different log file.
let raw_syslog = UnixDatagram::unbound().context("failed connect to /dev/log")?;
for entry in logs.as_slice() {
raw_syslog
.send_to(entry, LOG_PATH)
.context("failed write to /dev/log")?;
}
Ok(())
}
fn register_dbus_interface_for_app(
crossroads: &mut Crossroads,
dugong_state: DugongState,
app_id: &str,
) {
// D-Bus identifiers only allow alphanumeric and underscore characters. Since we allow hyphens
// in TEE app IDs, replace them with underscores when creating the D-Bus identifier.
let app_dbus_identifier = app_id.replace("-", "_");
let interface_token = crossroads.register(
format!("org.chromium.manatee.{}", &app_dbus_identifier),
|b| {
b.method(
"StartInstance",
(),
("error_code", "fd_in", "fd_out"),
|_, t: &mut (DugongState, String), ()| t.0.start_teeapplication(t.1.clone()),
)
.annotate("org.chromium.DBus.Method.Kind", "simple");
},
);
crossroads.insert(
format!("/org/chromium/ManaTEE1/{}", &app_dbus_identifier),
&[interface_token],
(dugong_state, app_id.to_string()),
);
}
fn start_dbus_handler(dugong_state: DugongState) -> Result<()> {
let c = LocalConnection::new_system().context("failed to open D-Bus connection")?;
c.request_name(
"org.chromium.ManaTEE",
false, /*allow_replacement*/
false, /*replace_existing*/
false, /*do_not_queue*/
)
.context("failed to register D-Bus handler")?;
let mut crossroads = Crossroads::new();
let interface_token = register_org_chromium_mana_teeinterface::<DugongState>(&mut crossroads);
crossroads.insert(
"/org/chromium/ManaTEE1",
&[interface_token],
dugong_state.clone(),
);
dugong_state
.supported_apps()
.lock()
.unwrap()
.iter()
.for_each(|(app_id, _)| {
register_dbus_interface_for_app(&mut crossroads, dugong_state.clone(), app_id)
});
c.start_receive(
MatchRule::new_method_call(),
Box::new(move |msg, conn| {
if let Err(err) = crossroads.handle_message(msg, conn) {
error!("Failed to handle message: {:?}", err);
false
} else {
true
}
}),
);
info!("Finished dbus setup, starting handler.");
loop {
if let Err(err) = handle_manatee_logs(&dugong_state) {
if matches!(
err.source().map(|a| a.downcast_ref::<rpc::Error>()),
Some(_)
) {
error!("Trichechus disconnected: {}", err);
return Err(err);
}
error!("Failed to forward syslog: {}", err);
}
c.process(Duration::from_millis(1000))
.context("failed to process the D-Bus message")?;
}
}
fn main() -> Result<()> {
let args: Vec<String> = env::args().collect();
let mut opts = Options::new();
opts.optflag(
GET_LOGS_SHORT_NAME,
"get-logs",
"connect to trichechus, get and print logs, then exit.",
);
let (config, matches) = initialize_common_arguments(opts, &args[1..]).unwrap();
let get_logs = matches.opt_present(GET_LOGS_SHORT_NAME);
let transport_type = config.connection_type;
if let Err(e) = syslog::init() {
eprintln!("failed to initialize syslog: {}", e);
bail!("failed to start up the syslog: {}", e);
}
info!("Starting dugong: {}", BUILD_TIMESTAMP);
info!("Opening connection to trichechus");
// Adjust the source port when connecting to a non-standard port to facilitate testing.
let bind_port = match transport_type.get_port().context("failed to get port")? {
DEFAULT_SERVER_PORT => DEFAULT_CLIENT_PORT,
port => port + 1,
};
let mut transport = transport_type
.try_into_client(Some(bind_port))
.context("failed to get client for transport")?;
let transport = transport.connect().map_err(|e| {
error!("transport connect failed: {}", e);
anyhow!("transport connect failed: {}", e)
})?;
info!("Starting rpc");
let client = TrichechusClient::new(transport);
if get_logs {
info!("Getting logs");
let logs = client.get_logs().unwrap();
for entry in &logs[..] {
print!("{}", String::from_utf8_lossy(entry));
}
} else {
let mut dugong_state = DugongState::new(client, transport_type);
let manifest = AppManifest::load_default().context("failed to load manifest")?;
dugong_state.register_supported_apps(manifest.iter().map(|entry| {
(
&entry.app_name,
match &entry.exec_info {
ExecutableInfo::Path(p) => {
let path = PathBuf::from(&p);
if path.exists() {
Some(path)
} else {
None
}
}
ExecutableInfo::Digest(_) => None,
},
)
}));
start_dbus_handler(dugong_state).unwrap();
unreachable!()
}
Ok(())
}