Skip to content

Commit

Permalink
agent: Add basic integration test
Browse files Browse the repository at this point in the history
This adds a test exercising the agent functionality.  This works by
spawning the agent to monitor the test executable itself, where
crypto-auditing probes are defined.

Signed-off-by: Daiki Ueno <[email protected]>
  • Loading branch information
ueno committed Nov 13, 2023
1 parent 923d04d commit 348090f
Show file tree
Hide file tree
Showing 12 changed files with 431 additions and 1 deletion.
9 changes: 8 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
runs-on: ubuntu-latest
container:
image: registry.fedoraproject.org/fedora:38
options: --privileged
steps:
- uses: actions/checkout@v3
- name: Install dependencies
Expand All @@ -32,6 +33,12 @@ jobs:
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
run: |
cargo test --verbose --workspace --exclude crypto-auditing-agent
- name: Run integration tests
run: |
cd agent
cargo test --verbose
cd -
- name: Run clippy
run: cargo clippy
29 changes: 29 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions agent/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.x86_64-unknown-linux-gnu]
runner = "sudo -E"
5 changes: 5 additions & 0 deletions agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,8 @@ probe = "0.3"

[build-dependencies]
libbpf-cargo = { version = "0.20", features = ["novendor"] }

[dev-dependencies]
tempfile = "3"
plain = "0.2"
agenttest = { path = "tests/agenttest" }
7 changes: 7 additions & 0 deletions agent/fixtures/agent.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# library = ["/usr/lib64/libgnutls.so.30", "/usr/lib64/libssl.so.3"]
# log_file = "/var/log/crypto-auditing/audit.cborseq"
# user = "crypto-auditing:crypto-auditing"
# coalesce_window = 100
# max_events = 1000000

coalesce_window = 0
17 changes: 17 additions & 0 deletions agent/tests/agenttest/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "agenttest"
version = "0.1.0"
edition = "2021"
license = "GPL-3.0-or-later"
authors = ["The crypto-auditing developers"]

[dependencies]
anyhow = "1.0"
libbpf-rs = { version = "0.20", features = ["novendor"] }
libc = "0.2"
nix = "0.26"
tempfile = "3"
plain = "0.2"

[build-dependencies]
libbpf-cargo = { version = "0.20", features = ["novendor"] }
17 changes: 17 additions & 0 deletions agent/tests/agenttest/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0

use libbpf_cargo::SkeletonBuilder;
use std::{env, path::PathBuf};

const SRC: &str = "src/bpf/agent.bpf.c";

fn main() {
let mut out =
PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR must be set in build script"));
out.push("agent.skel.rs");
SkeletonBuilder::new()
.source(SRC)
.build_and_generate(&out)
.unwrap();
println!("cargo:rerun-if-changed={}", SRC);
}
30 changes: 30 additions & 0 deletions agent/tests/agenttest/src/bpf/agent.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0 */

#include "vmlinux.h"
#include <bpf/usdt.bpf.h>

#define MAX_DATA_SIZE 512

struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 4096 /* one page */);
} ringbuf SEC(".maps");

SEC("usdt")
int
BPF_USDT(event_group, long count)
{
long *value;
long err;

value = bpf_ringbuf_reserve (&ringbuf, sizeof(*value), 0);
if (value)
{
*value = count;
bpf_ringbuf_submit (value, 0);
}

return 0;
}

char LICENSE[] SEC("license") = "GPL";
1 change: 1 addition & 0 deletions agent/tests/agenttest/src/bpf/vmlinux.h
88 changes: 88 additions & 0 deletions agent/tests/agenttest/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2023 The crypto-auditing developers.

use anyhow::{bail, Result};
use libbpf_rs::{Link, Map, Object, RingBufferBuilder};
use std::env;
use std::path::{Path, PathBuf};
use std::process::Child;
use std::time::Duration;

mod skel {
include!(concat!(env!("OUT_DIR"), "/agent.skel.rs"));
}
use skel::*;

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

pub fn agent_path() -> PathBuf {
target_dir().join("crypto-auditing-agent")
}

pub fn bump_memlock_rlimit() -> Result<()> {
let rlimit = libc::rlimit {
rlim_cur: 128 << 20,
rlim_max: 128 << 20,
};

if unsafe { libc::setrlimit(libc::RLIMIT_MEMLOCK, &rlimit) } != 0 {
bail!("Failed to increase rlimit");
}

Ok(())
}

pub fn attach_bpf(process: &Child, path: impl AsRef<Path>) -> Result<(Link, Object)> {
let skel_builder = AgentSkelBuilder::default();
let open_skel = skel_builder.open()?;
let mut skel = open_skel.load()?;

let mut progs = skel.progs_mut();
let prog = progs.event_group();

let link = prog
.attach_usdt(
process.id() as i32,
path.as_ref(),
"crypto_auditing_internal_agent",
"event_group",
)
.expect("unable to attach prog");

Ok((link, skel.obj))
}

// Copied from libbpf-rs/libbpf-rs/tests/test.rs
pub fn with_ringbuffer<F>(map: &Map, action: F, timeout: Duration) -> Result<i64>
where
F: FnOnce(),
{
let mut value = 0i64;
{
let callback = |data: &[u8]| {
plain::copy_from_bytes(&mut value, data).expect("Wrong size");
0
};

let mut builder = RingBufferBuilder::new();
builder.add(map, callback)?;
let mgr = builder.build()?;

action();
mgr.poll(timeout)?;
mgr.consume()?;
}

Ok(value)
}
101 changes: 101 additions & 0 deletions agent/tests/coalesce.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2023 The crypto-auditing developers.

extern crate agenttest;
use agenttest::*;

use crypto_auditing::types::EventGroup;
use probe::probe;
use serde_cbor::de::Deserializer;
use std::env;
use std::panic;
use std::path::PathBuf;
use std::process::Command;
use std::thread;
use std::time::Duration;
use tempfile::tempdir;

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

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

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

// Wait until the agent process starts up
while !log_path.exists() {
thread::sleep(Duration::from_millis(100));
}

let result = panic::catch_unwind(|| {
let foo = String::from("foo\0");
let bar = String::from("bar\0");
let baz = String::from("baz\0");

let (_link, object) =
attach_bpf(&process, &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);
probe!(crypto_auditing, word_data, 1, foo.as_ptr(), 3);
probe!(crypto_auditing, string_data, 1, bar.as_ptr(), bar.as_ptr());
probe!(
crypto_auditing,
blob_data,
1,
baz.as_ptr(),
baz.as_ptr(),
baz.len()
);
},
timeout,
)
.expect("unable to exercise probe points");
assert_eq!(result, 4);
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(), 2);
assert_eq!(groups[0].events().len(), 4);
assert_eq!(groups[1].events().len(), 1);
});

process.kill().expect("unable to kill agent");

assert!(result.is_ok());
}
Loading

0 comments on commit 348090f

Please sign in to comment.