diff --git a/Cargo.toml b/Cargo.toml index 49008717..d2762f19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,10 @@ version = "0.3" optional = true features = ["arbitrary-derive"] +[dependencies.log] +version = "0.4" +optional = true + [dependencies.ring] version = "0.16.11" optional = true @@ -41,13 +45,15 @@ default-features = false features = ["derive"] [dev-dependencies] +ctor = "0.1" +env_logger = "0.8" pretty_assertions = "0.6.1" ring = "0.16.11" serde_json = "1.0" testutil = { path = "testutil" } [features] -default = ["std", "ring"] +default = ["std", "ring", "log"] # Enables deriving `arbitrary::Arbitrary` for various manticore types. arbitrary-derive = ["libfuzzer-sys", "std"] diff --git a/src/cert/mod.rs b/src/cert/mod.rs index 9f0529fd..a97dddfa 100644 --- a/src/cert/mod.rs +++ b/src/cert/mod.rs @@ -95,6 +95,8 @@ impl From for Error { } } +debug_from!(Error => io::Error, untrusted::EndOfInput); + impl<'cert> Cert<'cert> { /// Parses `cert`, producing a parsed certificate in the given format. /// diff --git a/src/debug.rs b/src/debug.rs new file mode 100644 index 00000000..9fdb38b4 --- /dev/null +++ b/src/debug.rs @@ -0,0 +1,202 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +//! Debug-logging functionality. +//! +//! This module is still present when the `log` feature is disabled, but all +//! logging operations are redacted. Redaction completely compiles out log +//! statements: not even the format strings remain in the final binary. +//! +//! Manticore code *should not* call into the [`log`] crate directly outside of +//! this module. + +// TODO: Remove this once we start using these macros in Manticore. +#![allow(unused)] + +use core::fmt; + +#[cfg(doc)] +use __raw_log as log; + +/// A wrapped Manticore error. +/// +/// This type should always be referred to as `manticore::Error`. It represents +/// an error with extra (potentially redacted) information attached. This type +/// cannot be directly created by users of the library. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Error { + inner: E, +} + +impl Error { + /// Creates a new `Error`. This function is an implementation detail, + /// and should not be called by users. + #[doc(hidden)] + pub fn __new(inner: E) -> Self { + Self { inner } + } + + /// Transforms the wrapper error by way of an [`Into`] conversion. + /// + /// Generally, this function should not be necessary, because Manticore- + /// defined error types manually implement the relevant [`From`] + /// implementations for `manticore::Error`, which in turn call `cast()`. + pub fn cast(self) -> Error + where + F: From, + { + Error { + inner: self.inner.into(), + } + } + + /// Gets the wrapper error. + pub fn into_inner(self) -> E { + self.inner + } +} + +impl AsRef for Error { + fn as_ref(&self) -> &E { + &self.inner + } +} + +impl AsMut for Error { + fn as_mut(&mut self) -> &mut E { + &mut self.inner + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + struct DisplayAsDebug<'a, E>(&'a E); + impl fmt::Debug for DisplayAsDebug<'_, E> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } + } + + f.debug_struct("manticore::Error") + .field("inner", &DisplayAsDebug(&self.inner)) + .finish() + } +} + +/// Generates `From` implementations for `manticore::Error`. +/// +/// We would like to write this `impl`: +/// ```compile_fail +/// # use manticore::Error; +/// impl From> for Error where E2: From { +/// fn from(e: Error) -> Error { +/// e.cast() +/// } +/// } +/// ``` +/// +/// Unfortunately, the trait coherence rules mean that for `E1 == E2`, +/// this conflicts with the standard library's `impl From for T` +/// impl. We work around this by calling this macro for every Manticore +/// error definition that has `From` impls. +macro_rules! debug_from { + ($e:ty => $($f:ty),+ $(,)?) => {$( + impl From<$crate::Error<$f>> for $crate::Error<$e> { + fn from(e: $crate::Error<$f>) -> Self { + e.cast() + } + } + )*} +} + +/// Checks a condition, logging if it fails. +/// +/// If the condition does not hold, constructs the given error, logs it, and +/// returns out of the current function with it. +macro_rules! check { + ($cond:expr, $error:expr) => { + if !$cond { + let error = $error; + trace!( + error, + "check failure: `{}`; returned {:?}" + stringify!($cond), error, + )?; + } + } +} + +/// Logs a newly-created error value and returns it. +/// +/// This macro is the main way to generate [`Error`] values. +/// +/// For example, instead of writing `foo.ok_or(MyError)`, instead write +/// `foo.ok_or_else(|| trace!(MyError))`. +macro_rules! trace { + ($error:expr, $($format:tt)+) => {{ + error!($($format)+); + $crate::debug::Error::__new($error) + }}; + ($error:expr) => {{ + let error = $error; + error!("generated error: `{:?}`", error); + $crate::debug::Error::__new(error) + }}; +} + +/// Redactable version of [`log::info!()`]. +macro_rules! info { + ($($args:tt)*) => { + #[cfg(feature = "log")] + let _ = __raw_log::info!($($args)*); + } +} + +/// Redactable version of [`log::warn!()`]. +macro_rules! warn { + ($($args:tt)*) => { + #[cfg(feature = "log")] + let _ = __raw_log::warn!($($args)*); + } +} + +/// Redactable version of [`log::error!()`]. +macro_rules! error { + ($($args:tt)*) => { + #[cfg(feature = "log")] + let _ = __raw_log::error!($($args)*); + } +} + +/// Set up some life-before-main code that initializes a basic logger for the +/// test binary. +/// +/// This needs to happen here, since the test binary's main() cannot be +/// overriden. +#[cfg(test)] +#[ctor::ctor] +fn init_test_logger() { + env_logger::builder() + .format(move |_, record| { + use std::io::Write; + + let thread = std::thread::current(); + let name = thread.name().unwrap_or(""); + for line in record.args().to_string().trim().lines() { + // NOTE: we explicitly print to stderr, since this allows the + // Rust test harness to supress log statements originating from + // passing tests. + eprintln!( + "[{level}({thread}) {file}:{line}] {msg}", + level = record.level(), + thread = name, + file = record.file().unwrap_or(""), + line = record.line().unwrap_or(0), + msg = line, + ) + } + Ok(()) + }) + .init(); +} diff --git a/src/hardware/flash.rs b/src/hardware/flash.rs index a5f6cb5c..86927674 100644 --- a/src/hardware/flash.rs +++ b/src/hardware/flash.rs @@ -56,6 +56,7 @@ impl From for Error { Error::Internal } } +debug_from!(Error => OutOfMemory); /// Provides access to a flash-like storage device. /// diff --git a/src/lib.rs b/src/lib.rs index 28abb50d..008c439f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,8 @@ //! - `ring` (default) enables the [`crypto::ring` module], which provides //! software implementations for cryptography traits used by `manticore`. //! This feature is not intended for on-device use-cases either. +//! - `log` (default) enables debug logging throughout manticore, via the `log` +//! crate. This feature can be disabled to redact all logging. //! - `serde` enables implementations of `serde`'s (de)serialization traits. //! - `inject-alloc` makes it possible to replace borrowed content in some //! structs with allocated content. This is mostly useful for tooling that @@ -49,6 +51,14 @@ #![deny(unused)] #![deny(unsafe_code)] +// Pull in the `log` crate via a different name, to help prevent code from +// accidentally using it without going through the redactable versions in +// `debug`. +#[cfg(feature = "log")] +extern crate log as __raw_log; +#[macro_use] +mod debug; + #[macro_use] pub mod protocol; @@ -63,3 +73,12 @@ pub mod manifest; pub mod mem; pub mod net; pub mod server; + +pub use debug::Error; + +/// [`Result`] type to use throughout Manticore, which incorporates the +/// [`manticore::Error`] type. +/// +/// [`Result`]: std::result::Result +/// [`manticore::Error`]: crate::Error +pub type Result = core::result::Result>; diff --git a/src/manifest/mod.rs b/src/manifest/mod.rs index 4c84cfca..829ce697 100644 --- a/src/manifest/mod.rs +++ b/src/manifest/mod.rs @@ -218,6 +218,8 @@ impl From for Error { } } +debug_from!(Error => io::Error, flash::Error, OutOfMemory, sig::Error, sha256::Error); + /// A manifest type. /// /// A type that implements this trait is not itself a "parsed" instance of the diff --git a/src/net.rs b/src/net.rs index 60b7cc95..d3d537bf 100644 --- a/src/net.rs +++ b/src/net.rs @@ -50,6 +50,8 @@ impl From for Error { } } +debug_from!(Error => io::Error); + /// Represents a physical port that can be used to interact with host devices. /// /// This trait provides a generic mechanism for recieving and responding to diff --git a/src/protocol/wire.rs b/src/protocol/wire.rs index e8736266..fe2b4425 100644 --- a/src/protocol/wire.rs +++ b/src/protocol/wire.rs @@ -57,6 +57,8 @@ impl From for FromWireError { } } +debug_from!(FromWireError => io::Error, OutOfMemory); + /// A type which can be serialized into the Cerberus wire format. pub trait ToWire: Sized { /// Serializes `self` into `w`. @@ -76,6 +78,8 @@ impl From for ToWireError { } } +debug_from!(ToWireError => io::Error); + /// Represents a C-like enum that can be converted to and from a wire /// representation as well as to and from a string representation. /// @@ -231,7 +235,12 @@ macro_rules! wire_enum { impl core::str::FromStr for $name { type Err = $crate::protocol::wire::WireEnumFromStrError; - fn from_str(s: &str) -> Result { + fn from_str( + s: &str + ) -> core::result::Result< + Self, + $crate::protocol::wire::WireEnumFromStrError + > { use $crate::protocol::wire::WireEnum; match $name::from_name(s) { diff --git a/src/server/handler.rs b/src/server/handler.rs index 8691dfaa..1aa9f076 100644 --- a/src/server/handler.rs +++ b/src/server/handler.rs @@ -128,6 +128,8 @@ impl From for Error { } } +debug_from!(Error => FromWireError, ToWireError, net::Error); + /// A request handler builder. /// /// See the module documentation for more information.