Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ let opts = ParseOptions::default()
.with_template_hash_algorithm(HashAlgorithm::Sha1);
for event in EventLogParser::new(bytes.as_slice(), opts) {
let event = event?;
println!("PCR {} {}", event.pcr_index, event.template_name);
println!("PCR {} {}", event.pcr_index, event.template);
}
```

Expand All @@ -67,7 +67,7 @@ use ima_parser::log::parse_ascii_log;
let line = "10 91f34b5c671d73504b274a919661cf80dab1e127 ima-ng sha1:1801e1be3e65ef1eaa5c16617bec8f1274eaf6b3 boot_aggregate\n";
let events = parse_ascii_log(line).unwrap();
assert_eq!(events.len(), 1);
assert_eq!(events[0].template_name, "ima-ng");
assert_eq!(events[0].template.as_str(), "ima-ng");
```

#### Recomputing a template hash
Expand Down
11 changes: 9 additions & 2 deletions examples/parse_ascii_log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ fn main() -> ExitCode {
}
TemplateData::ImaBuf(e) => format!("{} [{}] buf={}B", e.name, e.digest, e.buf.len()),
TemplateData::Unknown(fields) => format!("{} unknown-field(s)", fields.len()),
_ => "other built-in template".to_owned(),
};
if args.verify {
#[cfg(feature = "hash")]
Expand All @@ -108,10 +109,16 @@ fn main() -> ExitCode {
let ok = "no-hash-feature";
println!(
"PCR={:>2} {:<8} hash-ok={} {hint}",
ev.pcr_index, ev.template_name, ok
ev.pcr_index,
ev.template.as_str(),
ok
);
} else {
println!("PCR={:>2} {:<8} {hint}", ev.pcr_index, ev.template_name);
println!(
"PCR={:>2} {:<8} {hint}",
ev.pcr_index,
ev.template.as_str()
);
}
}
ExitCode::SUCCESS
Expand Down
11 changes: 9 additions & 2 deletions examples/parse_binary_log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ fn main() -> ExitCode {
}
TemplateData::ImaBuf(e) => format!("{} [{}] buf={}B", e.name, e.digest, e.buf.len()),
TemplateData::Unknown(fields) => format!("{} unknown-field(s)", fields.len()),
_ => "other built-in template".to_owned(),
};
if args.verify {
#[cfg(feature = "hash")]
Expand All @@ -119,10 +120,16 @@ fn main() -> ExitCode {
let ok = "no-hash-feature";
println!(
"PCR={:>2} {:<8} hash-ok={} {hint}",
ev.pcr_index, ev.template_name, ok
ev.pcr_index,
ev.template.as_str(),
ok
);
} else {
println!("PCR={:>2} {:<8} {hint}", ev.pcr_index, ev.template_name);
println!(
"PCR={:>2} {:<8} {hint}",
ev.pcr_index,
ev.template.as_str()
);
}
}
ExitCode::SUCCESS
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
//! .with_template_hash_algorithm(HashAlgorithm::Sha1);
//! for event in EventLogParser::new(bytes.as_slice(), opts) {
//! let event = event?;
//! println!("PCR {} {}", event.pcr_index, event.template_name);
//! println!("PCR {} {}", event.pcr_index, event.template);
//! }
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
Expand All @@ -62,7 +62,7 @@
//! let line = "10 91f34b5c671d73504b274a919661cf80dab1e127 ima-ng sha1:1801e1be3e65ef1eaa5c16617bec8f1274eaf6b3 boot_aggregate\n";
//! let events = parse_ascii_log(line).unwrap();
//! assert_eq!(events.len(), 1);
//! assert_eq!(events[0].template_name, "ima-ng");
//! assert_eq!(events[0].template.as_str(), "ima-ng");
//! ```
//!
//! ### Recomputing a template hash
Expand Down
13 changes: 3 additions & 10 deletions src/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ pub use self::ascii::{parse_ascii_line, parse_ascii_log};
pub use self::event::Event;
pub use self::parser::{Endianness, EventLogParser, ParseOptions};
pub use self::template::{
Digest, ImaBufEntry, ImaEntry, ImaNgEntry, ImaSigEntry, TemplateData, TemplateField,
Digest, DigestType, DigestV2, EvmSigEntry, ImaBufEntry, ImaEntry, ImaModsigEntry, ImaNgEntry,
ImaNgV2Entry, ImaSigEntry, ImaSigV2Entry, Template, TemplateData, TemplateField,
TemplateFieldId,
};

/// Default PCR index used by IMA (`CONFIG_IMA_MEASURE_PCR_IDX`).
Expand All @@ -36,12 +38,3 @@ pub const DEFAULT_IMA_PCR: u32 = 10;
/// The legacy `ima` template pads the `n` field to this size plus one when
/// computing the template hash.
pub const IMA_EVENT_NAME_LEN_MAX: usize = 255;

/// Template name of the legacy fixed-format template.
pub const IMA_TEMPLATE_NAME: &str = "ima";
/// Template name of the `ima-ng` (next-generation) template.
pub const IMA_NG_TEMPLATE_NAME: &str = "ima-ng";
/// Template name of the `ima-sig` template.
pub const IMA_SIG_TEMPLATE_NAME: &str = "ima-sig";
/// Template name of the `ima-buf` template.
pub const IMA_BUF_TEMPLATE_NAME: &str = "ima-buf";
151 changes: 136 additions & 15 deletions src/log/ascii.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! The format of `/sys/kernel/security/ima/ascii_runtime_measurements_*` is:
//!
//! ```text
//! <pcr> <template_hash_hex> <template_name> <template_specific fields…>
//! <pcr> <template_hash_hex> <template> <template_specific fields…>
//! ```
//!
//! with SP (`' '`) as the token separator and LF (`'\n'`) as the record
Expand All @@ -23,10 +23,8 @@ use crate::hash::HashAlgorithm;

use super::event::Event;
use super::template::{
Digest, ImaBufEntry, ImaEntry, ImaNgEntry, ImaSigEntry, TemplateData, TemplateField,
};
use super::{
IMA_BUF_TEMPLATE_NAME, IMA_NG_TEMPLATE_NAME, IMA_SIG_TEMPLATE_NAME, IMA_TEMPLATE_NAME,
Digest, DigestType, DigestV2, EvmSigEntry, ImaBufEntry, ImaEntry, ImaModsigEntry, ImaNgEntry,
ImaNgV2Entry, ImaSigEntry, ImaSigV2Entry, Template, TemplateData, TemplateField,
};

/// Parse an entire ASCII measurement log (one event per line).
Expand Down Expand Up @@ -68,25 +66,30 @@ pub fn parse_ascii_line(line: &str) -> Result<Event> {
.next()
.ok_or_else(|| Error::malformed("missing template name"))?
.to_owned();
let template = Template::parse(&template_name);

let rest: Vec<&str> = toks.collect();
let (template_data, template_data_raw) = parse_template_payload(&template_name, &rest)?;
let (template_data, template_data_raw) = parse_template_payload(&template, &rest)?;

Ok(Event {
pcr_index,
template_hash,
template_name,
template,
template_data,
template_data_raw,
})
}

fn parse_template_payload(template_name: &str, fields: &[&str]) -> Result<(TemplateData, Vec<u8>)> {
match template_name {
n if n == IMA_NG_TEMPLATE_NAME => parse_ima_ng(fields),
n if n == IMA_SIG_TEMPLATE_NAME => parse_ima_sig(fields),
n if n == IMA_BUF_TEMPLATE_NAME => parse_ima_buf(fields),
n if n == IMA_TEMPLATE_NAME => parse_legacy_ima(fields),
fn parse_template_payload(template: &Template, fields: &[&str]) -> Result<(TemplateData, Vec<u8>)> {
match template {
Template::ImaNg => parse_ima_ng(fields),
Template::ImaSig => parse_ima_sig(fields),
Template::ImaBuf => parse_ima_buf(fields),
Template::ImaModsig => parse_ima_modsig(fields),
Template::ImaNgV2 => parse_ima_ngv2(fields),
Template::ImaSigV2 => parse_ima_sigv2(fields),
Template::EvmSig => parse_evm_sig(fields),
Template::Ima => parse_legacy_ima(fields),
_ => {
// Unknown template: preserve every whitespace-separated token as
// its own opaque field so callers can still inspect the payload.
Expand All @@ -107,6 +110,107 @@ fn parse_template_payload(template_name: &str, fields: &[&str]) -> Result<(Templ
}
}

fn parse_ima_ngv2(fields: &[&str]) -> Result<(TemplateData, Vec<u8>)> {
if fields.len() < 2 {
return Err(Error::malformed(
"ima-ngv2 expects <dtype:algo:hex> <filename>",
));
}
let digest = parse_prefixed_digest_v2(fields[0])?;
let filename = unescape_filename(&fields[1..].join(" "));
let raw = Vec::new();
Ok((
TemplateData::ImaNgV2(ImaNgV2Entry { digest, filename }),
raw,
))
}

fn parse_ima_sigv2(fields: &[&str]) -> Result<(TemplateData, Vec<u8>)> {
if fields.len() < 2 {
return Err(Error::malformed(
"ima-sigv2 expects <dtype:algo:hex> <filename> [<sig>]",
));
}
let digest = parse_prefixed_digest_v2(fields[0])?;
let (filename, signature) = if fields.len() >= 3 && looks_like_hex(fields[fields.len() - 1]) {
(
unescape_filename(&fields[1..fields.len() - 1].join(" ")),
decode_hex(fields[fields.len() - 1])?,
)
} else {
(unescape_filename(&fields[1..].join(" ")), Vec::new())
};
Ok((
TemplateData::ImaSigV2(ImaSigV2Entry {
digest,
filename,
signature,
}),
Vec::new(),
))
}

fn parse_ima_modsig(fields: &[&str]) -> Result<(TemplateData, Vec<u8>)> {
if fields.len() < 3 {
return Err(Error::malformed(
"ima-modsig expects <digest> <filename> <sig> [<d-modsig>] [<modsig>]",
));
}
let digest = parse_prefixed_digest(fields[0])?;
let filename = unescape_filename(fields[1]);
let signature = decode_hex(fields[2])?;
let modsig_digest = fields
.get(3)
.map(|s| decode_hex(s))
.transpose()?
Comment thread
hyperfinitism marked this conversation as resolved.
.unwrap_or_default();
let modsig = fields
.get(4)
.map(|s| decode_hex(s))
.transpose()?
.unwrap_or_default();
Ok((
TemplateData::ImaModsig(ImaModsigEntry {
digest,
filename,
signature,
modsig_digest,
modsig,
}),
Vec::new(),
))
}

fn parse_evm_sig(fields: &[&str]) -> Result<(TemplateData, Vec<u8>)> {
if fields.len() < 9 {
return Err(Error::malformed("evm-sig expects 9 template fields"));
}
let digest = parse_prefixed_digest(fields[0])?;
let iuid: u32 = fields[6]
.parse()
.map_err(|_| Error::malformed("invalid iuid"))?;
let igid: u32 = fields[7]
.parse()
.map_err(|_| Error::malformed("invalid igid"))?;
let imode: u16 = fields[8]
.parse()
.map_err(|_| Error::malformed("invalid imode"))?;
Ok((
TemplateData::EvmSig(EvmSigEntry {
digest,
filename: unescape_filename(fields[1]),
evmsig: decode_hex(fields[2])?,
xattrnames: unescape_filename(fields[3]),
xattrlengths: decode_hex(fields[4])?,
xattrvalues: decode_hex(fields[5])?,
iuid,
igid,
imode,
}),
Vec::new(),
))
}

fn parse_ima_ng(fields: &[&str]) -> Result<(TemplateData, Vec<u8>)> {
if fields.len() < 2 {
return Err(Error::malformed("ima-ng expects <digest> <filename>"));
Expand Down Expand Up @@ -206,6 +310,23 @@ fn parse_prefixed_digest(s: &str) -> Result<Digest> {
}
Ok(Digest::new(algo, bytes))
}
fn parse_prefixed_digest_v2(s: &str) -> Result<DigestV2> {
let mut it = s.splitn(3, ':');
let dtype = it
.next()
.ok_or_else(|| Error::malformed("missing digest type"))?;
let algo_name = it
.next()
.ok_or_else(|| Error::malformed("missing algorithm"))?;
let hex = it
.next()
.ok_or_else(|| Error::malformed("missing digest hex"))?;
let digest = parse_prefixed_digest(&format!("{algo_name}:{hex}"))?;
Ok(DigestV2 {
digest_type: DigestType::parse(dtype),
digest,
})
}

fn decode_hex(s: &str) -> Result<Vec<u8>> {
if !s.len().is_multiple_of(2) {
Expand Down Expand Up @@ -326,7 +447,7 @@ mod tests {
sha1:1801e1be3e65ef1eaa5c16617bec8f1274eaf6b3 boot_aggregate";
let ev = parse_ascii_line(line).unwrap();
assert_eq!(ev.pcr_index, 10);
assert_eq!(ev.template_name, "ima-ng");
assert_eq!(ev.template.as_str(), "ima-ng");
match &ev.template_data {
TemplateData::ImaNg(e) => {
assert_eq!(e.digest.algorithm, HashAlgorithm::Sha1);
Expand Down Expand Up @@ -363,7 +484,7 @@ sha1:1801e1be3e65ef1eaa5c16617bec8f1274eaf6b3 /init\n\
deadbeefdeadbeefdeadbeefdeadbeefdeadbeef \
weird-template foo bar baz";
let ev = parse_ascii_line(line).unwrap();
assert_eq!(ev.template_name, "weird-template");
assert_eq!(ev.template.as_str(), "weird-template");
match &ev.template_data {
TemplateData::Unknown(fields) => {
let strs: Vec<&[u8]> = fields.iter().map(|f| f.data.as_slice()).collect();
Expand Down
6 changes: 3 additions & 3 deletions src/log/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#[cfg(feature = "hash")]
use crate::hash::HashAlgorithm;

use super::template::TemplateData;
use super::template::{Template, TemplateData};
use super::template_hash;

/// A single IMA measurement event.
Expand All @@ -17,7 +17,7 @@ use super::template_hash;
/// 2. `template_hash` — digest covering the template's data bytes, computed
/// with the algorithm recorded in
/// [`ParseOptions::template_hash_algorithm`](super::parser::ParseOptions::with_template_hash_algorithm).
/// 3. `template_name` — ASCII identifier of the template (`"ima"`,
/// 3. `template` — ASCII identifier of the template (`"ima"`,
/// `"ima-ng"`, `"ima-sig"`, `"ima-buf"`, …).
/// 4. `template_data` — decoded payload; see [`TemplateData`].
/// 5. `template_data_raw` — unparsed template-data bytes, retained so the
Expand All @@ -32,7 +32,7 @@ pub struct Event {
pub template_hash: Vec<u8>,

/// Template name (e.g. `"ima-ng"`).
pub template_name: String,
pub template: Template,

/// Decoded template payload.
pub template_data: TemplateData,
Expand Down
Loading
Loading