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");
23 changes: 16 additions & 7 deletions fact-ebpf/src/bpf/events.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
#include <bpf/bpf_helpers.h>
#pragma once

// clang-format off
#include "vmlinux.h"

#include "maps.h"
#include "process.h"
#include "types.h"
#include "vmlinux.h"

#include <bpf/bpf_helpers.h>
// clang-format on

__always_inline static void submit_event(struct metrics_by_hook_t* m, file_activity_type_t event_type, const char filename[PATH_MAX], struct dentry* dentry) {
struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0);
Expand All @@ -14,12 +19,16 @@ __always_inline static void submit_event(struct metrics_by_hook_t* m, file_activ

event->type = event_type;
event->timestamp = bpf_ktime_get_boot_ns();
bpf_probe_read_str(event->filename, PATH_MAX, filename);
if (filename != NULL) {
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
7 changes: 7 additions & 0 deletions fact-ebpf/src/bpf/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,10 @@ 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();
submit_event(&m->bprm_check, PROCESS_EXEC, NULL, NULL);
return 0;
}
1 change: 1 addition & 0 deletions fact-ebpf/src/bpf/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ __always_inline static int64_t process_fill(process_t* p) {
p->gid = (uid_gid >> 32) & 0xFFFFFFFF;
p->login_uid = BPF_CORE_READ(task, loginuid.val);
p->pid = (bpf_get_current_pid_tgid() >> 32) & 0xFFFFFFFF;
p->start_time = BPF_CORE_READ(task, start_boottime);
u_int64_t err = bpf_get_current_comm(p->comm, TASK_COMM_LEN);
if (err != 0) {
bpf_printk("Failed to fill task comm");
Expand Down
3 changes: 3 additions & 0 deletions fact-ebpf/src/bpf/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ typedef struct process_t {
lineage_t lineage[LINEAGE_MAX];
unsigned int lineage_len;
char in_root_mount_ns;
unsigned long start_time;
} process_t;

typedef enum file_activity_type_t {
FILE_ACTIVITY_INIT = -1,
FILE_ACTIVITY_OPEN = 0,
FILE_ACTIVITY_CREATION,
FILE_ACTIVITY_UNLINK,
PROCESS_EXEC,
} file_activity_type_t;

struct event_t {
Expand Down Expand Up @@ -73,4 +75,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;
};
7 changes: 5 additions & 2 deletions fact/src/bpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,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 +175,9 @@ 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) if event.is_from_container() => Arc::new(event),
Ok(event) if event.is_file_event() => Arc::new(event),
Ok(_) => continue,
Err(e) => {
error!("Failed to parse event: '{e}'");
debug!("Event: {event:?}");
Expand Down
153 changes: 122 additions & 31 deletions fact/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ fn timestamp_to_proto(ts: u64) -> prost_types::Timestamp {
pub struct Event {
timestamp: u64,
hostname: &'static str,
process: Process,
file: FileData,
data: EventData,
}

impl Event {
Expand All @@ -42,6 +41,42 @@ impl Event {
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos() as _;
let data = match event_type {
file_activity_type_t::FILE_ACTIVITY_OPEN
| file_activity_type_t::FILE_ACTIVITY_CREATION
| file_activity_type_t::FILE_ACTIVITY_UNLINK => {
Event::new_file_event(event_type, filename, host_file, process)
}
file_activity_type_t::PROCESS_EXEC => EventData::ProcessData(process),
invalid => unreachable!("Invalid event type: {invalid:?}"),
};

Ok(Event {
timestamp,
hostname,
data,
})
}

pub fn is_file_event(&self) -> bool {
matches!(self.data, EventData::FileData { .. })
}

pub fn is_from_container(&self) -> bool {
let p = match &self.data {
EventData::FileData { process, .. } => process,
EventData::ProcessData(process) => process,
};
p.is_from_container()
}

#[cfg(test)]
fn new_file_event(
event_type: file_activity_type_t,
filename: PathBuf,
host_file: PathBuf,
process: Process,
) -> EventData {
let inner = BaseFileData {
filename,
host_file,
Expand All @@ -52,13 +87,7 @@ impl Event {
file_activity_type_t::FILE_ACTIVITY_UNLINK => FileData::Unlink(inner),
invalid => unreachable!("Invalid event type: {invalid:?}"),
};

Ok(Event {
timestamp,
hostname,
process,
file,
})
EventData::FileData { process, file }
}
}

Expand All @@ -68,35 +97,97 @@ 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 data = match value.type_ {
file_activity_type_t::FILE_ACTIVITY_OPEN
| file_activity_type_t::FILE_ACTIVITY_CREATION
| file_activity_type_t::FILE_ACTIVITY_UNLINK => {
let file = FileData::new(value.type_, value.filename, value.host_file)?;
EventData::FileData { process, file }
}
file_activity_type_t::PROCESS_EXEC => EventData::ProcessData(process),
invalid => unreachable!("Invalid event type: {invalid:?}"),
};

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

impl From<Event> for fact_api::FileActivity {
fn from(value: Event) -> Self {
let file = fact_api::file_activity::File::from(value.file);
impl TryFrom<Event> for fact_api::sensor::FileActivity {
type Error = anyhow::Error;

fn try_from(value: Event) -> Result<Self, Self::Error> {
let (process, file) = match value.data {
EventData::FileData { process, file } => (process, file),
EventData::ProcessData(_) => anyhow::bail!("Unexpected process event on file pipeline"),
};
let file = fact_api::sensor::file_activity::File::from(file);
let timestamp = timestamp_to_proto(value.timestamp);
let process = fact_api::ProcessSignal::from(value.process);
let process = fact_api::sensor::ProcessSignal::from(process);

Self {
Ok(Self {
file: Some(file),
timestamp: Some(timestamp),
process: Some(process),
}
})
}
}

impl TryFrom<Event> for fact_api::sensor::SignalStreamMessage {
type Error = anyhow::Error;

fn try_from(value: Event) -> Result<Self, Self::Error> {
let process = match value.data {
EventData::FileData { .. } => {
anyhow::bail!("Unexpected file event on process pipeline")
}
EventData::ProcessData(p) => p,
};
let signal = fact_api::storage::ProcessSignal::from(process);
let signal = fact_api::v1::signal::Signal::ProcessSignal(signal);
let signal = fact_api::v1::Signal {
signal: Some(signal),
};
let msg = fact_api::sensor::signal_stream_message::Msg::Signal(signal);

Ok(Self { msg: Some(msg) })
}
}

#[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.data == other.data
}
}

#[derive(Debug, Clone, Serialize)]
pub enum EventData {
FileData { process: Process, file: FileData },
ProcessData(Process),
}

#[cfg(test)]
impl PartialEq for EventData {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
EventData::FileData {
process: s_proc,
file: s_file,
},
EventData::FileData {
process: o_proc,
file: o_file,
},
) => s_proc == o_proc && s_file == o_file,
(EventData::ProcessData(s_proc), EventData::ProcessData(o_proc)) => s_proc == o_proc,
_ => false,
}
}
}

Expand Down Expand Up @@ -125,23 +216,23 @@ impl FileData {
}
}

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 +278,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