Skip to content

Commit a5ff29b

Browse files
committed
Include system boot time in the primary log as metadata
Timestamps in event entries are relative to the system boot time, meaning that it is only valid within the same boot session. This patch introduces way of recording the system boot time in the log file, in a special "metadata" section. Signed-off-by: Daiki Ueno <dueno@redhat.com>
1 parent 502393b commit a5ff29b

6 files changed

Lines changed: 111 additions & 31 deletions

File tree

agent/src/log_writer.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,21 @@ impl LogWriter {
3232
let file = File::create(&config.log_file)
3333
.await
3434
.with_context(|| format!("unable to create file `{}`", config.log_file.display()))?;
35-
Ok(Self {
35+
36+
let mut log_writer = Self {
3637
config: config.clone(),
3738
file: Some(file),
3839
offset: 0u64,
3940
instant: Instant::now(),
4041
groups: Vec::default(),
4142
written_events: 0usize,
4243
pending_events: 0usize,
43-
})
44+
};
45+
46+
let metadata = EventGroup::metadata();
47+
log_writer.write_event_group(&metadata).await?;
48+
49+
Ok(log_writer)
4450
}
4551

4652
pub fn timeout(&self) -> Duration {
@@ -164,18 +170,22 @@ impl LogWriter {
164170
Ok(())
165171
}
166172

173+
async fn write_event_group(&mut self, group: &EventGroup) -> Result<()> {
174+
let v = match self.config.format {
175+
config::Format::Normal => serde_cbor::ser::to_vec(group)?,
176+
config::Format::Packed => serde_cbor::ser::to_vec_packed(group)?,
177+
config::Format::Minimal => to_vec_minimal(group)?,
178+
};
179+
self.write_all(v).await
180+
}
181+
167182
pub async fn flush(&mut self) -> Result<()> {
168183
self.pending_events = 0;
169184
for group in self.groups.clone() {
170185
if self.should_rotate_after(self.written_events) {
171186
self.rotate().await?;
172187
}
173-
let v = match self.config.format {
174-
config::Format::Normal => serde_cbor::ser::to_vec(&group)?,
175-
config::Format::Packed => serde_cbor::ser::to_vec_packed(&group)?,
176-
config::Format::Minimal => to_vec_minimal(&group)?,
177-
};
178-
self.write_all(v).await?;
188+
self.write_event_group(&group).await?;
179189
probe!(
180190
crypto_auditing_internal_agent,
181191
event_group,

agent/tests/coalesce.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ fn test_probe_coalesce() {
120120
.into_iter::<EventGroup>()
121121
.collect();
122122
let groups = groups.expect("error deserializing");
123-
assert_eq!(groups.len(), 2);
124-
assert_eq!(groups[0].events().len(), 4);
125-
assert_eq!(groups[1].events().len(), 1);
123+
// first group is a metadata group
124+
assert_eq!(groups.len(), 3);
125+
assert_eq!(groups[1].events().len(), 4);
126+
assert_eq!(groups[2].events().len(), 1);
126127
}

agent/tests/composite.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -144,27 +144,28 @@ fn test_probe_composite() {
144144
.into_iter::<EventGroup>()
145145
.collect();
146146
let groups = groups.expect("error deserializing");
147-
assert_eq!(groups.len(), 5);
148-
assert_eq!(groups[0].events().len(), 1);
149-
assert!(matches!(groups[0].events()[0], Event::NewContext { .. }));
147+
// first group is a metadata group
148+
assert_eq!(groups.len(), 6);
150149
assert_eq!(groups[1].events().len(), 1);
151-
if let Event::Data { key, .. } = &groups[1].events()[0] {
150+
assert!(matches!(groups[1].events()[0], Event::NewContext { .. }));
151+
assert_eq!(groups[2].events().len(), 1);
152+
if let Event::Data { key, .. } = &groups[2].events()[0] {
152153
assert_eq!(key, "foo");
153154
} else {
154155
unreachable!();
155156
}
156-
assert_eq!(groups[2].events().len(), 1);
157-
if let Event::Data { key, .. } = &groups[2].events()[0] {
157+
assert_eq!(groups[3].events().len(), 1);
158+
if let Event::Data { key, .. } = &groups[3].events()[0] {
158159
assert_eq!(key, "bar");
159160
} else {
160161
unreachable!();
161162
}
162-
assert_eq!(groups[3].events().len(), 1);
163-
if let Event::Data { key, .. } = &groups[3].events()[0] {
163+
assert_eq!(groups[4].events().len(), 1);
164+
if let Event::Data { key, .. } = &groups[4].events()[0] {
164165
assert_eq!(key, "baz");
165166
} else {
166167
unreachable!();
167168
}
168-
assert_eq!(groups[4].events().len(), 1);
169-
assert!(matches!(groups[4].events()[0], Event::NewContext { .. }));
169+
assert_eq!(groups[5].events().len(), 1);
170+
assert!(matches!(groups[5].events()[0], Event::NewContext { .. }));
170171
}

agent/tests/no_coalesce.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,11 @@ fn test_probe_no_coalesce() {
142142
.into_iter::<EventGroup>()
143143
.collect();
144144
let groups = groups.expect("error deserializing");
145-
assert_eq!(groups.len(), 5);
146-
assert_eq!(groups[0].events().len(), 1);
145+
// first group is a metadata group
146+
assert_eq!(groups.len(), 6);
147147
assert_eq!(groups[1].events().len(), 1);
148148
assert_eq!(groups[2].events().len(), 1);
149149
assert_eq!(groups[3].events().len(), 1);
150150
assert_eq!(groups[4].events().len(), 1);
151+
assert_eq!(groups[5].events().len(), 1);
151152
}

crypto-auditing/src/types.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
1717

1818
pub type ContextId = [u8; 16];
1919

20+
/// Context ID associated with `EventGroup` representing metadata
21+
pub const METADATA_CONTEXT_ID: ContextId = [0; 16];
22+
2023
fn only_values<K, V, S>(source: &BTreeMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
2124
where
2225
S: Serializer,
@@ -177,6 +180,15 @@ pub enum Event {
177180
},
178181
}
179182

183+
impl Event {
184+
pub fn data(&self, needle: &str) -> Option<&EventData> {
185+
match self {
186+
Self::Data { key, value } if key == needle => Some(value),
187+
_ => None,
188+
}
189+
}
190+
}
191+
180192
#[serde_as]
181193
#[derive(Serialize, Deserialize, Clone, Debug)]
182194
pub struct EventGroup {
@@ -197,6 +209,32 @@ fn format_context_id(pid_tgid: u64, context: i64) -> ContextId {
197209
}
198210

199211
impl EventGroup {
212+
/// Returns a new `EventGroup` with metadata events
213+
pub fn metadata() -> Self {
214+
let events = vec![
215+
Event::Data {
216+
key: "version".to_string(),
217+
value: EventData::Word(1),
218+
},
219+
Event::Data {
220+
key: "boot_time".to_string(),
221+
value: EventData::Word(System::boot_time() as i64),
222+
},
223+
];
224+
225+
Self {
226+
context: METADATA_CONTEXT_ID,
227+
start: Default::default(),
228+
end: Default::default(),
229+
events,
230+
}
231+
}
232+
233+
/// Returns true if this is a metadata group
234+
pub fn is_metadata(&self) -> bool {
235+
self.context == METADATA_CONTEXT_ID
236+
}
237+
200238
/// Returns the context ID associated with the event group
201239
pub fn context(&self) -> &ContextId {
202240
&self.context

log-parser/src/query.rs

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,56 @@
22
// Copyright (C) 2022-2023 The crypto-auditing developers.
33

44
use anyhow::{Context as _, Result};
5-
use crypto_auditing::types::{ContextTracker, EventGroup};
5+
use crypto_auditing::types::{ContextTracker, EventData, EventGroup};
66
use pager::Pager;
77
use serde_cbor::de::Deserializer;
88
use std::io::{self, Write};
9-
use std::time::{Duration, UNIX_EPOCH};
9+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
1010

1111
mod config;
1212

13+
fn get_boot_time_from_metadata(group: &EventGroup) -> Option<SystemTime> {
14+
for event in group.events() {
15+
if let Some(data) = event.data("boot_time") {
16+
match data {
17+
EventData::Word(secs) => {
18+
return Some(UNIX_EPOCH + Duration::from_secs(*secs as u64));
19+
}
20+
_ => (),
21+
}
22+
}
23+
}
24+
None
25+
}
26+
1327
fn main() -> Result<(), Box<dyn std::error::Error>> {
1428
let config = config::Config::new()?;
1529
Pager::new().setup();
1630

1731
let log_file = std::fs::File::open(&config.log_file)
1832
.with_context(|| format!("unable to read file `{}`", config.log_file.display()))?;
1933

20-
let mut tracker = ContextTracker::new(
21-
config
22-
.boot_time
23-
.map(|secs| UNIX_EPOCH + Duration::from_secs(secs)),
24-
);
25-
for group in Deserializer::from_reader(&log_file).into_iter::<EventGroup>() {
34+
let mut groups = Deserializer::from_reader(&log_file)
35+
.into_iter::<EventGroup>()
36+
.peekable();
37+
38+
// Figure out the system boot time, first from the config, and
39+
// then from the metadata group in the log.
40+
let boot_time = if let Some(secs) = config.boot_time {
41+
Some(UNIX_EPOCH + Duration::from_secs(secs))
42+
} else if let Some(Ok(group)) = groups.peek()
43+
&& group.is_metadata()
44+
{
45+
let boot_time = get_boot_time_from_metadata(&group);
46+
// Skip the metadata group.
47+
groups.next();
48+
boot_time
49+
} else {
50+
None
51+
};
52+
53+
let mut tracker = ContextTracker::new(boot_time);
54+
for group in groups {
2655
tracker.handle_event_group(&group?);
2756
}
2857
let root_contexts: Vec<_> = tracker

0 commit comments

Comments
 (0)