Skip to content
Draft
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
6 changes: 5 additions & 1 deletion fact-api/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ use anyhow::Context;
fn main() -> anyhow::Result<()> {
tonic_prost_build::configure()
.build_server(false)
.include_file("mod.rs")
.compile_protos(
&["../third_party/stackrox/proto/internalapi/sensor/sfa_iservice.proto"],
&[
"../third_party/stackrox/proto/internalapi/sensor/sfa_iservice.proto",
"../third_party/stackrox/proto/internalapi/sensor/signal_iservice.proto",
],
&["../third_party/stackrox/proto"],
)
.context("Failed to compile protos. Please makes sure you update your git submodules!")?;
Expand Down
2 changes: 1 addition & 1 deletion fact-api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
tonic::include_proto!("sensor");
tonic::include_proto!("mod");
10 changes: 6 additions & 4 deletions fact-ebpf/src/bpf/events.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ __always_inline static void submit_event(struct metrics_by_hook_t* m, file_activ
event->timestamp = bpf_ktime_get_boot_ns();
bpf_probe_read_str(event->filename, PATH_MAX, filename);

struct helper_t* helper = get_helper();
const char* p = get_host_path(helper->buf, dentry);
if (p != NULL) {
bpf_probe_read_str(event->host_file, PATH_MAX, p);
if (dentry != NULL) {
struct helper_t* helper = get_helper();
const char* p = get_host_path(helper->buf, dentry);
if (p != NULL) {
bpf_probe_read_str(event->host_file, PATH_MAX, p);
}
}

int64_t err = process_fill(&event->process);
Expand Down
11 changes: 11 additions & 0 deletions fact-ebpf/src/bpf/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,14 @@ int BPF_PROG(trace_path_unlink, struct path* dir, struct dentry* dentry) {
m->path_unlink.error++;
return 0;
}

SEC("lsm/bprm_check_security")
int BPF_PROG(trace_bprm_check, struct linux_binprm* bprm) {
struct metrics_t* m = get_metrics();

m->bprm_check.total++;

submit_event(&m->bprm_check, PROCESS_ACTIVITY_EXEC, "", NULL);

return 0;
}
2 changes: 2 additions & 0 deletions fact-ebpf/src/bpf/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ typedef enum file_activity_type_t {
FILE_ACTIVITY_OPEN = 0,
FILE_ACTIVITY_CREATION,
FILE_ACTIVITY_UNLINK,
PROCESS_ACTIVITY_EXEC,
} file_activity_type_t;

struct event_t {
Expand Down Expand Up @@ -73,4 +74,5 @@ struct metrics_by_hook_t {
struct metrics_t {
struct metrics_by_hook_t file_open;
struct metrics_by_hook_t path_unlink;
struct metrics_by_hook_t bprm_check;
};
19 changes: 16 additions & 3 deletions fact/src/bpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ use tokio::{
task::JoinHandle,
};

use crate::{event::Event, host_info, metrics::EventCounter};
use crate::{
event::{Activity, Event},
host_info,
metrics::EventCounter,
};

use fact_ebpf::{event_t, metrics_t, path_prefix_t, LPM_SIZE_MAX};

Expand Down Expand Up @@ -139,7 +143,8 @@ impl Bpf {
fn load_progs(&mut self) -> anyhow::Result<()> {
let btf = Btf::from_sys_fs()?;
self.load_lsm_prog("trace_file_open", "file_open", &btf)?;
self.load_lsm_prog("trace_path_unlink", "path_unlink", &btf)
self.load_lsm_prog("trace_path_unlink", "path_unlink", &btf)?;
self.load_lsm_prog("trace_bprm_check", "bprm_check_security", &btf)
}

fn attach_progs(&mut self) -> anyhow::Result<()> {
Expand Down Expand Up @@ -174,7 +179,7 @@ impl Bpf {
while let Some(event) = ringbuf.next() {
let event: &event_t = unsafe { &*(event.as_ptr() as *const _) };
let event = match Event::try_from(event) {
Ok(event) => Arc::new(event),
Ok(event) => event,
Err(e) => {
error!("Failed to parse event: '{e}'");
debug!("Event: {event:?}");
Expand All @@ -183,6 +188,14 @@ impl Bpf {
}
};

if matches!(event.activity, Activity::Process(_))
&& event.process.container_id.is_none()
{
event_counter.dropped();
continue;
}

let event = Arc::new(event);
event_counter.added();
if self.tx.send(event).is_err() {
info!("No BPF consumers left, stopping...");
Expand Down
136 changes: 109 additions & 27 deletions fact/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,54 @@ fn timestamp_to_proto(ts: u64) -> prost_types::Timestamp {
prost_types::Timestamp { seconds, nanos }
}

#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum ProcessActivity {
Exec,
}

#[derive(Debug, Clone, Serialize)]
pub enum Activity {
File(FileData),
Process(ProcessActivity),
}

#[cfg(test)]
impl PartialEq for Activity {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::File(l), Self::File(r)) => l == r,
(Self::Process(l), Self::Process(r)) => l == r,
_ => false,
}
}
}

impl Activity {
pub fn new(
event_type: file_activity_type_t,
filename: [c_char; PATH_MAX as usize],
host_file: [c_char; PATH_MAX as usize],
) -> anyhow::Result<Self> {
let activity = match event_type {
file_activity_type_t::FILE_ACTIVITY_OPEN
| file_activity_type_t::FILE_ACTIVITY_CREATION
| file_activity_type_t::FILE_ACTIVITY_UNLINK => {
Activity::File(FileData::new(event_type, filename, host_file)?)
}
file_activity_type_t::PROCESS_ACTIVITY_EXEC => Activity::Process(ProcessActivity::Exec),
invalid => unreachable!("Invalid event type: {invalid:?}"),
};

Ok(activity)
}
}

#[derive(Debug, Clone, Serialize)]
pub struct Event {
timestamp: u64,
hostname: &'static str,
process: Process,
file: FileData,
pub process: Process,
pub activity: Activity,
}

impl Event {
Expand All @@ -46,18 +88,21 @@ impl Event {
filename,
host_file,
};
let file = match event_type {
file_activity_type_t::FILE_ACTIVITY_OPEN => FileData::Open(inner),
file_activity_type_t::FILE_ACTIVITY_CREATION => FileData::Creation(inner),
file_activity_type_t::FILE_ACTIVITY_UNLINK => FileData::Unlink(inner),
let activity = match event_type {
file_activity_type_t::FILE_ACTIVITY_OPEN => Activity::File(FileData::Open(inner)),
file_activity_type_t::FILE_ACTIVITY_CREATION => {
Activity::File(FileData::Creation(inner))
}
file_activity_type_t::FILE_ACTIVITY_UNLINK => Activity::File(FileData::Unlink(inner)),
file_activity_type_t::PROCESS_ACTIVITY_EXEC => Activity::Process(ProcessActivity::Exec),
invalid => unreachable!("Invalid event type: {invalid:?}"),
};

Ok(Event {
timestamp,
hostname,
process,
file,
activity,
})
}
}
Expand All @@ -68,25 +113,60 @@ impl TryFrom<&event_t> for Event {
fn try_from(value: &event_t) -> Result<Self, Self::Error> {
let process = Process::try_from(value.process)?;
let timestamp = host_info::get_boot_time() + value.timestamp;
let file = FileData::new(value.type_, value.filename, value.host_file)?;
let activity = Activity::new(value.type_, value.filename, value.host_file)?;

Ok(Event {
timestamp,
hostname: host_info::get_hostname(),
process,
file,
activity,
})
}
}

impl From<Event> for fact_api::FileActivity {
impl From<Event> for fact_api::v1::Signal {
fn from(value: Event) -> Self {
let process_signal = fact_api::storage::ProcessSignal::from(value.process);
fact_api::v1::Signal {
signal: Some(fact_api::v1::signal::Signal::ProcessSignal(process_signal)),
}
}
}

impl From<Event> for fact_api::sensor::FileActivity {
fn from(value: Event) -> Self {
let file = fact_api::file_activity::File::from(value.file);
let file = match value.activity {
Activity::File(file) => Some(fact_api::sensor::file_activity::File::from(file)),
Activity::Process(_) => None,
};
let timestamp = timestamp_to_proto(value.timestamp);
let process = fact_api::ProcessSignal::from(value.process);
let process_signal = fact_api::storage::ProcessSignal::from(value.process.clone());
let process = fact_api::sensor::ProcessSignal {
id: process_signal.id,
container_id: process_signal.container_id,
creation_time: process_signal.time,
name: process_signal.name,
args: process_signal.args,
exec_file_path: process_signal.exec_file_path,
pid: process_signal.pid,
gid: process_signal.gid,
uid: process_signal.uid,
username: value.process.username.to_string(),
login_uid: value.process.login_uid,
in_root_mount_ns: value.process.in_root_mount_ns,
scraped: process_signal.scraped,
lineage_info: process_signal
.lineage_info
.into_iter()
.map(|l| fact_api::sensor::process_signal::LineageInfo {
parent_uid: l.parent_uid,
parent_exec_file_path: l.parent_exec_file_path,
})
.collect(),
};

Self {
file: Some(file),
file,
timestamp: Some(timestamp),
process: Some(process),
}
Expand All @@ -96,7 +176,9 @@ impl From<Event> for fact_api::FileActivity {
#[cfg(test)]
impl PartialEq for Event {
fn eq(&self, other: &Self) -> bool {
self.hostname == other.hostname && self.process == other.process && self.file == other.file
self.hostname == other.hostname
&& self.process == other.process
&& self.activity == other.activity
}
}

Expand All @@ -118,30 +200,30 @@ impl FileData {
file_activity_type_t::FILE_ACTIVITY_OPEN => FileData::Open(inner),
file_activity_type_t::FILE_ACTIVITY_CREATION => FileData::Creation(inner),
file_activity_type_t::FILE_ACTIVITY_UNLINK => FileData::Unlink(inner),
invalid => unreachable!("Invalid event type: {invalid:?}"),
invalid => unreachable!("Invalid file event type: {invalid:?}"),
};

Ok(file)
}
}

impl From<FileData> for fact_api::file_activity::File {
impl From<FileData> for fact_api::sensor::file_activity::File {
fn from(event: FileData) -> Self {
match event {
FileData::Open(event) => {
let activity = Some(fact_api::FileActivityBase::from(event));
let f_act = fact_api::FileOpen { activity };
fact_api::file_activity::File::Open(f_act)
let activity = Some(fact_api::sensor::FileActivityBase::from(event));
let f_act = fact_api::sensor::FileOpen { activity };
fact_api::sensor::file_activity::File::Open(f_act)
}
FileData::Creation(event) => {
let activity = Some(fact_api::FileActivityBase::from(event));
let f_act = fact_api::FileCreation { activity };
fact_api::file_activity::File::Creation(f_act)
let activity = Some(fact_api::sensor::FileActivityBase::from(event));
let f_act = fact_api::sensor::FileCreation { activity };
fact_api::sensor::file_activity::File::Creation(f_act)
}
FileData::Unlink(event) => {
let activity = Some(fact_api::FileActivityBase::from(event));
let f_act = fact_api::FileUnlink { activity };
fact_api::file_activity::File::Unlink(f_act)
let activity = Some(fact_api::sensor::FileActivityBase::from(event));
let f_act = fact_api::sensor::FileUnlink { activity };
fact_api::sensor::file_activity::File::Unlink(f_act)
}
}
}
Expand Down Expand Up @@ -187,9 +269,9 @@ impl PartialEq for BaseFileData {
}
}

impl From<BaseFileData> for fact_api::FileActivityBase {
impl From<BaseFileData> for fact_api::sensor::FileActivityBase {
fn from(value: BaseFileData) -> Self {
fact_api::FileActivityBase {
fact_api::sensor::FileActivityBase {
path: value.filename.to_string_lossy().to_string(),
host_path: value.host_file.to_string_lossy().to_string(),
}
Expand Down
Loading
Loading