Skip to content

Commit 48d6e13

Browse files
committed
feat(events): add process execution tracking
This change introduces the capability to monitor process execution events by adding a new eBPF program attached to the `bprm_check_security` LSM hook. A `PROCESS_EXEC` event type is added to the event pipeline. The gRPC API is extended with a new `SignalService` to handle these process-related signals, separate from the existing file activity service. The event handling logic is refactored to differentiate between file and process events, routing them to their respective gRPC streams.
1 parent 5bca887 commit 48d6e13

File tree

10 files changed

+271
-52
lines changed

10 files changed

+271
-52
lines changed

fact-api/build.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ use anyhow::Context;
33
fn main() -> anyhow::Result<()> {
44
tonic_prost_build::configure()
55
.build_server(false)
6+
.include_file("mod.rs")
67
.compile_protos(
7-
&["../third_party/stackrox/proto/internalapi/sensor/sfa_iservice.proto"],
8+
&[
9+
"../third_party/stackrox/proto/internalapi/sensor/sfa_iservice.proto",
10+
"../third_party/stackrox/proto/internalapi/sensor/signal_iservice.proto",
11+
],
812
&["../third_party/stackrox/proto"],
913
)
1014
.context("Failed to compile protos. Please makes sure you update your git submodules!")?;

fact-api/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
tonic::include_proto!("sensor");
1+
tonic::include_proto!("mod");

fact-ebpf/src/bpf/events.h

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
#include <bpf/bpf_helpers.h>
1+
#pragma once
2+
3+
// clang-format off
4+
#include "vmlinux.h"
25

36
#include "maps.h"
47
#include "process.h"
58
#include "types.h"
6-
#include "vmlinux.h"
9+
10+
#include <bpf/bpf_helpers.h>
11+
// clang-format on
712

813
__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) {
914
struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0);
@@ -14,12 +19,16 @@ __always_inline static void submit_event(struct metrics_by_hook_t* m, file_activ
1419

1520
event->type = event_type;
1621
event->timestamp = bpf_ktime_get_boot_ns();
17-
bpf_probe_read_str(event->filename, PATH_MAX, filename);
22+
if (filename != NULL) {
23+
bpf_probe_read_str(event->filename, PATH_MAX, filename);
24+
}
1825

19-
struct helper_t* helper = get_helper();
20-
const char* p = get_host_path(helper->buf, dentry);
21-
if (p != NULL) {
22-
bpf_probe_read_str(event->host_file, PATH_MAX, p);
26+
if (dentry != NULL) {
27+
struct helper_t* helper = get_helper();
28+
const char* p = get_host_path(helper->buf, dentry);
29+
if (p != NULL) {
30+
bpf_probe_read_str(event->host_file, PATH_MAX, p);
31+
}
2332
}
2433

2534
int64_t err = process_fill(&event->process);

fact-ebpf/src/bpf/main.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,10 @@ int BPF_PROG(trace_path_unlink, struct path* dir, struct dentry* dentry) {
9191
m->path_unlink.error++;
9292
return 0;
9393
}
94+
95+
SEC("lsm/bprm_check_security")
96+
int BPF_PROG(trace_bprm_check, struct linux_binprm* bprm) {
97+
struct metrics_t* m = get_metrics();
98+
submit_event(&m->bprm_check, PROCESS_EXEC, NULL, NULL);
99+
return 0;
100+
}

fact-ebpf/src/bpf/process.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ __always_inline static int64_t process_fill(process_t* p) {
112112
p->gid = (uid_gid >> 32) & 0xFFFFFFFF;
113113
p->login_uid = BPF_CORE_READ(task, loginuid.val);
114114
p->pid = (bpf_get_current_pid_tgid() >> 32) & 0xFFFFFFFF;
115+
p->start_time = BPF_CORE_READ(task, start_boottime);
115116
u_int64_t err = bpf_get_current_comm(p->comm, TASK_COMM_LEN);
116117
if (err != 0) {
117118
bpf_printk("Failed to fill task comm");

fact-ebpf/src/bpf/types.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ typedef struct process_t {
3030
lineage_t lineage[LINEAGE_MAX];
3131
unsigned int lineage_len;
3232
char in_root_mount_ns;
33+
unsigned long start_time;
3334
} process_t;
3435

3536
typedef enum file_activity_type_t {
3637
FILE_ACTIVITY_INIT = -1,
3738
FILE_ACTIVITY_OPEN = 0,
3839
FILE_ACTIVITY_CREATION,
3940
FILE_ACTIVITY_UNLINK,
41+
PROCESS_EXEC,
4042
} file_activity_type_t;
4143

4244
struct event_t {
@@ -73,4 +75,5 @@ struct metrics_by_hook_t {
7375
struct metrics_t {
7476
struct metrics_by_hook_t file_open;
7577
struct metrics_by_hook_t path_unlink;
78+
struct metrics_by_hook_t bprm_check;
7679
};

fact/src/bpf.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ impl Bpf {
139139
fn load_progs(&mut self) -> anyhow::Result<()> {
140140
let btf = Btf::from_sys_fs()?;
141141
self.load_lsm_prog("trace_file_open", "file_open", &btf)?;
142-
self.load_lsm_prog("trace_path_unlink", "path_unlink", &btf)
142+
self.load_lsm_prog("trace_path_unlink", "path_unlink", &btf)?;
143+
self.load_lsm_prog("trace_bprm_check", "bprm_check_security", &btf)
143144
}
144145

145146
fn attach_progs(&mut self) -> anyhow::Result<()> {
@@ -174,7 +175,9 @@ impl Bpf {
174175
while let Some(event) = ringbuf.next() {
175176
let event: &event_t = unsafe { &*(event.as_ptr() as *const _) };
176177
let event = match Event::try_from(event) {
177-
Ok(event) => Arc::new(event),
178+
Ok(event) if event.is_from_container() => Arc::new(event),
179+
Ok(event) if event.is_file_event() => Arc::new(event),
180+
Ok(_) => continue,
178181
Err(e) => {
179182
error!("Failed to parse event: '{e}'");
180183
debug!("Event: {event:?}");

fact/src/event/mod.rs

Lines changed: 122 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ fn timestamp_to_proto(ts: u64) -> prost_types::Timestamp {
2525
pub struct Event {
2626
timestamp: u64,
2727
hostname: &'static str,
28-
process: Process,
29-
file: FileData,
28+
data: EventData,
3029
}
3130

3231
impl Event {
@@ -42,6 +41,42 @@ impl Event {
4241
.duration_since(UNIX_EPOCH)
4342
.unwrap()
4443
.as_nanos() as _;
44+
let data = match event_type {
45+
file_activity_type_t::FILE_ACTIVITY_OPEN
46+
| file_activity_type_t::FILE_ACTIVITY_CREATION
47+
| file_activity_type_t::FILE_ACTIVITY_UNLINK => {
48+
Event::new_file_event(event_type, filename, host_file, process)
49+
}
50+
file_activity_type_t::PROCESS_EXEC => EventData::ProcessData(process),
51+
invalid => unreachable!("Invalid event type: {invalid:?}"),
52+
};
53+
54+
Ok(Event {
55+
timestamp,
56+
hostname,
57+
data,
58+
})
59+
}
60+
61+
pub fn is_file_event(&self) -> bool {
62+
matches!(self.data, EventData::FileData { .. })
63+
}
64+
65+
pub fn is_from_container(&self) -> bool {
66+
let p = match &self.data {
67+
EventData::FileData { process, .. } => process,
68+
EventData::ProcessData(process) => process,
69+
};
70+
p.is_from_container()
71+
}
72+
73+
#[cfg(test)]
74+
fn new_file_event(
75+
event_type: file_activity_type_t,
76+
filename: PathBuf,
77+
host_file: PathBuf,
78+
process: Process,
79+
) -> EventData {
4580
let inner = BaseFileData {
4681
filename,
4782
host_file,
@@ -52,13 +87,7 @@ impl Event {
5287
file_activity_type_t::FILE_ACTIVITY_UNLINK => FileData::Unlink(inner),
5388
invalid => unreachable!("Invalid event type: {invalid:?}"),
5489
};
55-
56-
Ok(Event {
57-
timestamp,
58-
hostname,
59-
process,
60-
file,
61-
})
90+
EventData::FileData { process, file }
6291
}
6392
}
6493

@@ -68,35 +97,97 @@ impl TryFrom<&event_t> for Event {
6897
fn try_from(value: &event_t) -> Result<Self, Self::Error> {
6998
let process = Process::try_from(value.process)?;
7099
let timestamp = host_info::get_boot_time() + value.timestamp;
71-
let file = FileData::new(value.type_, value.filename, value.host_file)?;
100+
101+
let data = match value.type_ {
102+
file_activity_type_t::FILE_ACTIVITY_OPEN
103+
| file_activity_type_t::FILE_ACTIVITY_CREATION
104+
| file_activity_type_t::FILE_ACTIVITY_UNLINK => {
105+
let file = FileData::new(value.type_, value.filename, value.host_file)?;
106+
EventData::FileData { process, file }
107+
}
108+
file_activity_type_t::PROCESS_EXEC => EventData::ProcessData(process),
109+
invalid => unreachable!("Invalid event type: {invalid:?}"),
110+
};
72111

73112
Ok(Event {
74113
timestamp,
75114
hostname: host_info::get_hostname(),
76-
process,
77-
file,
115+
data,
78116
})
79117
}
80118
}
81119

82-
impl From<Event> for fact_api::FileActivity {
83-
fn from(value: Event) -> Self {
84-
let file = fact_api::file_activity::File::from(value.file);
120+
impl TryFrom<Event> for fact_api::sensor::FileActivity {
121+
type Error = anyhow::Error;
122+
123+
fn try_from(value: Event) -> Result<Self, Self::Error> {
124+
let (process, file) = match value.data {
125+
EventData::FileData { process, file } => (process, file),
126+
EventData::ProcessData(_) => anyhow::bail!("Unexpected process event on file pipeline"),
127+
};
128+
let file = fact_api::sensor::file_activity::File::from(file);
85129
let timestamp = timestamp_to_proto(value.timestamp);
86-
let process = fact_api::ProcessSignal::from(value.process);
130+
let process = fact_api::sensor::ProcessSignal::from(process);
87131

88-
Self {
132+
Ok(Self {
89133
file: Some(file),
90134
timestamp: Some(timestamp),
91135
process: Some(process),
92-
}
136+
})
137+
}
138+
}
139+
140+
impl TryFrom<Event> for fact_api::sensor::SignalStreamMessage {
141+
type Error = anyhow::Error;
142+
143+
fn try_from(value: Event) -> Result<Self, Self::Error> {
144+
let process = match value.data {
145+
EventData::FileData { .. } => {
146+
anyhow::bail!("Unexpected file event on process pipeline")
147+
}
148+
EventData::ProcessData(p) => p,
149+
};
150+
let signal = fact_api::storage::ProcessSignal::from(process);
151+
let signal = fact_api::v1::signal::Signal::ProcessSignal(signal);
152+
let signal = fact_api::v1::Signal {
153+
signal: Some(signal),
154+
};
155+
let msg = fact_api::sensor::signal_stream_message::Msg::Signal(signal);
156+
157+
Ok(Self { msg: Some(msg) })
93158
}
94159
}
95160

96161
#[cfg(test)]
97162
impl PartialEq for Event {
98163
fn eq(&self, other: &Self) -> bool {
99-
self.hostname == other.hostname && self.process == other.process && self.file == other.file
164+
self.hostname == other.hostname && self.data == other.data
165+
}
166+
}
167+
168+
#[derive(Debug, Clone, Serialize)]
169+
pub enum EventData {
170+
FileData { process: Process, file: FileData },
171+
ProcessData(Process),
172+
}
173+
174+
#[cfg(test)]
175+
impl PartialEq for EventData {
176+
fn eq(&self, other: &Self) -> bool {
177+
match (self, other) {
178+
(
179+
EventData::FileData {
180+
process: s_proc,
181+
file: s_file,
182+
},
183+
EventData::FileData {
184+
process: o_proc,
185+
file: o_file,
186+
},
187+
) => s_proc == o_proc && s_file == o_file,
188+
(EventData::ProcessData(s_proc), EventData::ProcessData(o_proc)) => s_proc == o_proc,
189+
_ => false,
190+
}
100191
}
101192
}
102193

@@ -125,23 +216,23 @@ impl FileData {
125216
}
126217
}
127218

128-
impl From<FileData> for fact_api::file_activity::File {
219+
impl From<FileData> for fact_api::sensor::file_activity::File {
129220
fn from(event: FileData) -> Self {
130221
match event {
131222
FileData::Open(event) => {
132-
let activity = Some(fact_api::FileActivityBase::from(event));
133-
let f_act = fact_api::FileOpen { activity };
134-
fact_api::file_activity::File::Open(f_act)
223+
let activity = Some(fact_api::sensor::FileActivityBase::from(event));
224+
let f_act = fact_api::sensor::FileOpen { activity };
225+
fact_api::sensor::file_activity::File::Open(f_act)
135226
}
136227
FileData::Creation(event) => {
137-
let activity = Some(fact_api::FileActivityBase::from(event));
138-
let f_act = fact_api::FileCreation { activity };
139-
fact_api::file_activity::File::Creation(f_act)
228+
let activity = Some(fact_api::sensor::FileActivityBase::from(event));
229+
let f_act = fact_api::sensor::FileCreation { activity };
230+
fact_api::sensor::file_activity::File::Creation(f_act)
140231
}
141232
FileData::Unlink(event) => {
142-
let activity = Some(fact_api::FileActivityBase::from(event));
143-
let f_act = fact_api::FileUnlink { activity };
144-
fact_api::file_activity::File::Unlink(f_act)
233+
let activity = Some(fact_api::sensor::FileActivityBase::from(event));
234+
let f_act = fact_api::sensor::FileUnlink { activity };
235+
fact_api::sensor::file_activity::File::Unlink(f_act)
145236
}
146237
}
147238
}
@@ -187,9 +278,9 @@ impl PartialEq for BaseFileData {
187278
}
188279
}
189280

190-
impl From<BaseFileData> for fact_api::FileActivityBase {
281+
impl From<BaseFileData> for fact_api::sensor::FileActivityBase {
191282
fn from(value: BaseFileData) -> Self {
192-
fact_api::FileActivityBase {
283+
fact_api::sensor::FileActivityBase {
193284
path: value.filename.to_string_lossy().to_string(),
194285
host_path: value.host_file.to_string_lossy().to_string(),
195286
}

0 commit comments

Comments
 (0)