Skip to content

Commit

Permalink
agent: Support composite events
Browse files Browse the repository at this point in the history
CRYPTO_AUDITING_*_DATA defines a single USDT probe point for each
invocation.  This causes multiple context switches to happen if they
are writen in series.  This adds a new macro, CRYPTO_AUDITING_DATA and
CRYPTO_AUDITING_DATAV, which take an array of events to limit the
number of context switches.

Signed-off-by: Daiki Ueno <[email protected]>
  • Loading branch information
ueno committed Apr 5, 2024
1 parent afff224 commit d5df5fa
Show file tree
Hide file tree
Showing 4 changed files with 380 additions and 0 deletions.
145 changes: 145 additions & 0 deletions agent/src/bpf/audit.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
bpf_trace_printk ("%s: " format, sizeof("%s: " format), \
__PRETTY_FUNCTION__, __VA_ARGS__)

#define MAX_EVENTS 16

struct crypto_auditing_data {
char *key_ptr;
void *value_ptr;
long value_size;
};

struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 4096 /* one page */);
Expand Down Expand Up @@ -226,6 +234,126 @@ record_blob_data (struct pt_regs *ctx, long context, const char *key_ptr)
return err;
}

/* This is similar to record_blob_data but always assumes VALUE_PTR is
* present.
*/
static __always_inline int
record_blob_data_explicit (struct pt_regs *ctx, long context, const char *key_ptr,
const void *value_ptr, long value_size)
{
int err;

struct audit_blob_data_event_st *event =
bpf_ringbuf_reserve (&ringbuf,
sizeof(struct audit_blob_data_event_st),
0);
if (!event)
{
DEBUG ("unable to allocate ringbuf entry: %ld\n", -ENOMEM);
return -ENOMEM;
}

populate_event_header (&event->base.header,
AUDIT_EVENT_DATA,
sizeof(*event),
context);

event->base.type = AUDIT_DATA_BLOB;
err = bpf_probe_read_user_str (event->base.key, KEY_SIZE, (void *)key_ptr);
if (err < 0)
{
DEBUG ("unable to read event key: %ld\n", err);
goto error;
}

if (value_size > 0)
{
value_size &= (VALUE_SIZE - 1);
err = bpf_probe_read_user (event->value, value_size, (void *)value_ptr);
if (err < 0)
{
DEBUG ("unable to read event data: %ld\n", err);
goto error;
}
}

event->size = value_size;

bpf_ringbuf_submit (event, 0);
return 0;

error:
bpf_ringbuf_discard (event, 0);
return err;
}

static __always_inline int
record_data (struct pt_regs *ctx,
long context,
const struct crypto_auditing_data *array_ptr,
long array_size)
{
struct crypto_auditing_data events[MAX_EVENTS];
size_t events_size = array_size & (MAX_EVENTS - 1);
int err = 0;

err = bpf_probe_read_user (events,
events_size * sizeof (struct crypto_auditing_data),
array_ptr);
if (err < 0)
{
DEBUG ("unable to read from data array: %ld\n", err);
return err;
}

for (long i = 0; i < events_size; i++)
{
switch (events[i].value_size)
{
case -2:
err = record_word_data (ctx, context,
events[i].key_ptr,
(long) events[i].value_ptr);
if (err < 0)
DEBUG ("unable to process word data: %ld\n", err);
break;

case -1:
err = record_string_data (ctx, context,
events[i].key_ptr,
(const char *) events[i].value_ptr);
if (err < 0)
DEBUG ("unable to process string data: %ld\n", err);
break;

default:
err = record_blob_data_explicit (ctx, context,
events[i].key_ptr,
events[i].value_ptr,
events[i].value_size);
if (err < 0)
DEBUG ("unable to process blob data: %ld\n", err);
break;
}
}

return err;
}

static __always_inline int
record_new_context_with_data (struct pt_regs *ctx, long context, long parent,
struct crypto_auditing_data *array_ptr,
long array_size)
{
int err;

err = record_new_context (ctx, context, parent);
if (err < 0)
return err;

return record_data (ctx, context, array_ptr, array_size);
}

SEC("usdt")
int
BPF_USDT(new_context, long context, long parent)
Expand Down Expand Up @@ -255,4 +383,21 @@ BPF_USDT(blob_data, long context, const char *key_ptr)
return record_blob_data(ctx, context, key_ptr);
}

SEC("usdt")
int
BPF_USDT(data, long context, const struct crypto_auditing_data *array_ptr,
long array_size)
{
return record_data(ctx, context, array_ptr, array_size);
}

SEC("usdt")
int
BPF_USDT(new_context_with_data, long context, long parent,
void *array_ptr, long array_size)
{
return record_new_context_with_data(ctx, context, parent, array_ptr,
array_size);
}

char LICENSE[] SEC("license") = "GPL";
18 changes: 18 additions & 0 deletions agent/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,24 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
) {
links.push(link);
}
let prog = progs.data();
if let Ok(link) = prog.attach_usdt(
-1, // any process
library,
"crypto_auditing",
"data",
) {
links.push(link);
}
let prog = progs.new_context_with_data();
if let Ok(link) = prog.attach_usdt(
-1, // any process
library,
"crypto_auditing",
"new_context_with_data",
) {
links.push(link);
}
}

let cipher = Cipher::aes_128_ecb();
Expand Down
169 changes: 169 additions & 0 deletions agent/tests/composite.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2023 The crypto-auditing developers.

extern crate agenttest;
use agenttest::*;

use crypto_auditing::types::{Event, EventGroup};
use libc::{c_long, c_uchar, c_void};
use probe::probe;
use serde_cbor::de::Deserializer;
use std::env;
use std::path::PathBuf;
use std::process::{Child, Command};
use std::thread;
use std::time::Duration;
use tempfile::tempdir;

fn target_dir() -> PathBuf {
env::current_exe()
.ok()
.map(|mut path| {
path.pop();
if path.ends_with("deps") {
path.pop();
}
path
})
.unwrap()
}

fn fixture_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.join("fixtures")
}

struct AgentProcess(Child);

impl Drop for AgentProcess {
fn drop(&mut self) {
self.0.kill().expect("unable to kill event-broker");
}
}

#[repr(C)]
struct EventData {
key_ptr: *const c_uchar,
value_ptr: *const c_void,
value_size: c_long,
}

#[test]
fn test_probe_composite() {
bump_memlock_rlimit().expect("unable to bump memlock rlimit");

let agent_path = target_dir().join("crypto-auditing-agent");
let log_dir = tempdir().expect("unable to create temporary directory");
let log_path = log_dir.path().join("agent.log");
let process = Command::new(&agent_path)
.arg("-c")
.arg(fixture_dir().join("conf").join("agent.conf"))
.arg("--log-file")
.arg(&log_path)
.arg("--library")
.arg(&env::current_exe().unwrap())
.spawn()
.expect("unable to spawn agent");

// Make sure the agent process will be killed at exit
let process = AgentProcess(process);

// Wait until the agent starts up
for _ in 0..5 {
if log_path.exists() {
break;
}
thread::sleep(Duration::from_millis(100));
}
assert!(log_path.exists());

let foo = String::from("foo\0");
let bar = String::from("bar\0");
let baz = String::from("baz\0");
let events = vec![
EventData {
key_ptr: foo.as_ptr(),
value_ptr: 3 as *const c_void,
value_size: -2,
},
EventData {
key_ptr: bar.as_ptr(),
value_ptr: bar.as_ptr() as *const c_void,
value_size: -1,
},
EventData {
key_ptr: baz.as_ptr(),
value_ptr: baz.as_ptr() as *const c_void,
value_size: baz.len() as i64,
},
];

let (_link, object) =
attach_bpf(&process.0, &agent_path).expect("unable to attach agent.bpf.o");
let map = object.map("ringbuf").expect("unable to get ringbuf map");

let timeout = Duration::from_secs(10);

let result = with_ringbuffer(
map,
|| {
probe!(crypto_auditing, new_context, 1, 2);
},
timeout,
)
.expect("unable to exercise probe points");
assert_eq!(result, 1);

let result = with_ringbuffer(
map,
|| {
probe!(crypto_auditing, data, 1, events.as_ptr(), events.len());
},
timeout,
)
.expect("unable to exercise probe points");
assert_eq!(result, 1);

let result = with_ringbuffer(
map,
|| {
probe!(crypto_auditing, new_context, 4, 5);
},
timeout,
)
.expect("unable to exercise probe points");
assert_eq!(result, 1);

let log_file = std::fs::File::open(&log_path)
.expect(&format!("unable to read file `{}`", log_path.display()));

let groups: Result<Vec<_>, _> = Deserializer::from_reader(&log_file)
.into_iter::<EventGroup>()
.collect();
let groups = groups.expect("error deserializing");
assert_eq!(groups.len(), 5);
assert_eq!(groups[0].events().len(), 1);
assert!(matches!(groups[0].events()[0], Event::NewContext { .. }));
assert_eq!(groups[1].events().len(), 1);
if let Event::Data { key, .. } = &groups[1].events()[0] {
assert_eq!(key, "foo");
} else {
unreachable!();
}
assert_eq!(groups[2].events().len(), 1);
if let Event::Data { key, .. } = &groups[2].events()[0] {
assert_eq!(key, "bar");
} else {
unreachable!();
}
assert_eq!(groups[3].events().len(), 1);
if let Event::Data { key, .. } = &groups[3].events()[0] {
assert_eq!(key, "baz");
} else {
unreachable!();
}
assert_eq!(groups[4].events().len(), 1);
assert!(matches!(groups[4].events()[0], Event::NewContext { .. }));
}
Loading

0 comments on commit d5df5fa

Please sign in to comment.