Skip to content

Commit aa0399a

Browse files
committed
ROX-30257: implement permission change tracking
This is done via the path_chmod LSM hook. WIP: the old mode is not quite working yet.
1 parent 087a210 commit aa0399a

File tree

7 files changed

+158
-15
lines changed

7 files changed

+158
-15
lines changed

fact-ebpf/src/bpf/events.h

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,12 @@
1111
#include <bpf/bpf_helpers.h>
1212
// clang-format on
1313

14-
__always_inline static void submit_event(struct metrics_by_hook_t* m,
15-
file_activity_type_t event_type,
16-
const char filename[PATH_MAX],
17-
inode_key_t* inode,
18-
bool use_bpf_d_path) {
19-
struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0);
20-
if (event == NULL) {
21-
m->ringbuffer_full++;
22-
return;
23-
}
24-
14+
__always_inline static void __submit_event(struct event_t* event,
15+
struct metrics_by_hook_t* m,
16+
file_activity_type_t event_type,
17+
const char filename[PATH_MAX],
18+
inode_key_t* inode,
19+
bool use_bpf_d_path) {
2520
event->type = event_type;
2621
event->timestamp = bpf_ktime_get_boot_ns();
2722
inode_copy_or_reset(&event->inode, inode);
@@ -46,3 +41,35 @@ __always_inline static void submit_event(struct metrics_by_hook_t* m,
4641
m->error++;
4742
bpf_ringbuf_discard(event, 0);
4843
}
44+
45+
__always_inline static void submit_event(struct metrics_by_hook_t* m,
46+
file_activity_type_t event_type,
47+
const char filename[PATH_MAX],
48+
inode_key_t* inode,
49+
bool use_bpf_d_path) {
50+
struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0);
51+
if (event == NULL) {
52+
m->ringbuffer_full++;
53+
return;
54+
}
55+
56+
__submit_event(event, m, event_type, filename, inode, use_bpf_d_path);
57+
}
58+
59+
__always_inline static void submit_mode_event(struct metrics_by_hook_t* m,
60+
const char filename[PATH_MAX],
61+
inode_key_t* inode,
62+
umode_t mode,
63+
umode_t old_mode,
64+
bool use_bpf_d_path) {
65+
struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0);
66+
if (event == NULL) {
67+
m->ringbuffer_full++;
68+
return;
69+
}
70+
71+
event->chmod.new = mode;
72+
event->chmod.old = old_mode;
73+
74+
__submit_event(event, m, FILE_ACTIVITY_CHMOD, filename, inode, use_bpf_d_path);
75+
}

fact-ebpf/src/bpf/main.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "maps.h"
88
#include "events.h"
99
#include "bound_path.h"
10+
#include "vmlinux/x86_64.h"
1011

1112
#include <bpf/bpf_helpers.h>
1213
#include <bpf/bpf_tracing.h>
@@ -125,3 +126,40 @@ int BPF_PROG(trace_path_unlink, struct path* dir, struct dentry* dentry) {
125126
m->path_unlink.error++;
126127
return 0;
127128
}
129+
130+
SEC("lsm/path_chmod")
131+
int BPF_PROG(trace_path_chmod, struct path* path, umode_t mode) {
132+
struct metrics_t* m = get_metrics();
133+
if (m == NULL) {
134+
return 0;
135+
}
136+
137+
m->path_chmod.total++;
138+
139+
struct bound_path_t* bound_path = path_read(path);
140+
if (bound_path == NULL) {
141+
bpf_printk("Failed to read path");
142+
m->path_chmod.error++;
143+
return 0;
144+
}
145+
146+
inode_key_t inode_key = inode_to_key(path->dentry->d_inode);
147+
const inode_value_t* inode = inode_get(&inode_key);
148+
149+
switch (inode_is_monitored(inode)) {
150+
case NOT_MONITORED:
151+
if (!is_monitored(bound_path)) {
152+
m->path_chmod.ignored++;
153+
return 0;
154+
}
155+
break;
156+
157+
case MONITORED:
158+
break;
159+
}
160+
161+
umode_t old_mode = BPF_CORE_READ(path, dentry, d_inode, i_mode);
162+
submit_mode_event(&m->path_chmod, bound_path->path, &inode_key, mode, old_mode, true);
163+
164+
return 0;
165+
}

fact-ebpf/src/bpf/types.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ typedef enum file_activity_type_t {
4747
FILE_ACTIVITY_OPEN = 0,
4848
FILE_ACTIVITY_CREATION,
4949
FILE_ACTIVITY_UNLINK,
50+
FILE_ACTIVITY_CHMOD,
5051
} file_activity_type_t;
5152

5253
struct event_t {
@@ -55,6 +56,12 @@ struct event_t {
5556
char filename[PATH_MAX];
5657
inode_key_t inode;
5758
file_activity_type_t type;
59+
union {
60+
struct {
61+
short unsigned int new;
62+
short unsigned int old;
63+
} chmod;
64+
};
5865
};
5966

6067
/**
@@ -83,4 +90,5 @@ struct metrics_by_hook_t {
8390
struct metrics_t {
8491
struct metrics_by_hook_t file_open;
8592
struct metrics_by_hook_t path_unlink;
93+
struct metrics_by_hook_t path_chmod;
8694
};

fact-ebpf/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ impl metrics_t {
111111
let mut m = metrics_t { ..*self };
112112
m.file_open = m.file_open.accumulate(&other.file_open);
113113
m.path_unlink = m.path_unlink.accumulate(&other.path_unlink);
114+
m.path_chmod = m.path_chmod.accumulate(&other.path_chmod);
114115
m
115116
}
116117
}

fact/src/bpf/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use aya::{
88
};
99
use checks::Checks;
1010
use libc::c_char;
11-
use log::{debug, error, info};
11+
use log::{error, info};
1212
use tokio::{
1313
io::unix::AsyncFd,
1414
sync::{mpsc, watch},
@@ -154,7 +154,8 @@ impl Bpf {
154154

155155
fn load_progs(&mut self, btf: &Btf) -> anyhow::Result<()> {
156156
self.load_lsm_prog("trace_file_open", "file_open", btf)?;
157-
self.load_lsm_prog("trace_path_unlink", "path_unlink", btf)
157+
self.load_lsm_prog("trace_path_unlink", "path_unlink", btf)?;
158+
self.load_lsm_prog("trace_path_chmod", "path_chmod", btf)
158159
}
159160

160161
fn attach_progs(&mut self) -> anyhow::Result<()> {
@@ -192,7 +193,6 @@ impl Bpf {
192193
Ok(event) => event,
193194
Err(e) => {
194195
error!("Failed to parse event: '{e}'");
195-
debug!("Event: {event:?}");
196196
event_counter.dropped();
197197
continue;
198198
}

fact/src/event/mod.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ impl Event {
6767
FileData::Open(data) => &data.inode,
6868
FileData::Creation(data) => &data.inode,
6969
FileData::Unlink(data) => &data.inode,
70+
FileData::Chmod(data) => &data.file.inode,
7071
}
7172
}
7273

@@ -75,6 +76,7 @@ impl Event {
7576
FileData::Open(data) => data.host_file = host_path,
7677
FileData::Creation(data) => data.host_file = host_path,
7778
FileData::Unlink(data) => data.host_file = host_path,
79+
FileData::Chmod(data) => data.file.host_file = host_path,
7880
}
7981
}
8082
}
@@ -85,7 +87,12 @@ impl TryFrom<&event_t> for Event {
8587
fn try_from(value: &event_t) -> Result<Self, Self::Error> {
8688
let process = Process::try_from(value.process)?;
8789
let timestamp = host_info::get_boot_time() + value.timestamp;
88-
let file = FileData::new(value.type_, value.filename, value.inode)?;
90+
let file = FileData::new(
91+
value.type_,
92+
value.filename,
93+
value.inode,
94+
value.__bindgen_anon_1,
95+
)?;
8996

9097
Ok(Event {
9198
timestamp,
@@ -123,19 +130,29 @@ pub enum FileData {
123130
Open(BaseFileData),
124131
Creation(BaseFileData),
125132
Unlink(BaseFileData),
133+
Chmod(ChmodFileData),
126134
}
127135

128136
impl FileData {
129137
pub fn new(
130138
event_type: file_activity_type_t,
131139
filename: [c_char; PATH_MAX as usize],
132140
inode: inode_key_t,
141+
extra_data: fact_ebpf::event_t__bindgen_ty_1,
133142
) -> anyhow::Result<Self> {
134143
let inner = BaseFileData::new(filename, inode)?;
135144
let file = match event_type {
136145
file_activity_type_t::FILE_ACTIVITY_OPEN => FileData::Open(inner),
137146
file_activity_type_t::FILE_ACTIVITY_CREATION => FileData::Creation(inner),
138147
file_activity_type_t::FILE_ACTIVITY_UNLINK => FileData::Unlink(inner),
148+
file_activity_type_t::FILE_ACTIVITY_CHMOD => {
149+
let data = ChmodFileData {
150+
file: inner,
151+
new_mode: unsafe { extra_data.chmod.new },
152+
old_mode: unsafe { extra_data.chmod.old },
153+
};
154+
FileData::Chmod(data)
155+
}
139156
invalid => unreachable!("Invalid event type: {invalid:?}"),
140157
};
141158

@@ -161,6 +178,10 @@ impl From<FileData> for fact_api::file_activity::File {
161178
let f_act = fact_api::FileUnlink { activity };
162179
fact_api::file_activity::File::Unlink(f_act)
163180
}
181+
FileData::Chmod(event) => {
182+
let f_act = fact_api::FilePermissionChange::from(event);
183+
fact_api::file_activity::File::Permission(f_act)
184+
}
164185
}
165186
}
166187
}
@@ -211,3 +232,42 @@ impl From<BaseFileData> for fact_api::FileActivityBase {
211232
}
212233
}
213234
}
235+
236+
#[derive(Debug, Clone, Serialize)]
237+
pub struct ChmodFileData {
238+
file: BaseFileData,
239+
new_mode: u16,
240+
old_mode: u16,
241+
}
242+
243+
impl ChmodFileData {
244+
pub fn new(
245+
filename: [c_char; PATH_MAX as usize],
246+
inode: inode_key_t,
247+
new_mode: u16,
248+
old_mode: u16,
249+
) -> anyhow::Result<Self> {
250+
let file = BaseFileData::new(filename, inode)?;
251+
252+
Ok(ChmodFileData {
253+
file,
254+
new_mode,
255+
old_mode,
256+
})
257+
}
258+
}
259+
260+
impl From<ChmodFileData> for fact_api::FilePermissionChange {
261+
fn from(value: ChmodFileData) -> Self {
262+
let ChmodFileData {
263+
file,
264+
new_mode,
265+
old_mode: _,
266+
} = value;
267+
let activity = fact_api::FileActivityBase::from(file);
268+
fact_api::FilePermissionChange {
269+
activity: Some(activity),
270+
mode: new_mode as u32,
271+
}
272+
}
273+
}

fact/src/metrics/kernel_metrics.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use super::{EventCounter, LabelValues};
1010
pub struct KernelMetrics {
1111
file_open: EventCounter,
1212
path_unlink: EventCounter,
13+
path_chmod: EventCounter,
1314
map: PerCpuArray<MapData, metrics_t>,
1415
}
1516

@@ -25,13 +26,20 @@ impl KernelMetrics {
2526
"Events processed by the path_unlink LSM hook",
2627
&[], // Labels are not needed since `collect` will add them all
2728
);
29+
let path_chmod = EventCounter::new(
30+
"kernel_path_chmod_events",
31+
"Events processed by the path_chmod LSM hook",
32+
&[], // Labels are not needed since `collect` will add them all
33+
);
2834

2935
file_open.register(reg);
3036
path_unlink.register(reg);
37+
path_chmod.register(reg);
3138

3239
KernelMetrics {
3340
file_open,
3441
path_unlink,
42+
path_chmod,
3543
map: kernel_metrics,
3644
}
3745
}
@@ -78,6 +86,7 @@ impl KernelMetrics {
7886

7987
KernelMetrics::refresh_labels(&self.file_open, &metrics.file_open);
8088
KernelMetrics::refresh_labels(&self.path_unlink, &metrics.path_unlink);
89+
KernelMetrics::refresh_labels(&self.path_chmod, &metrics.path_chmod);
8190

8291
Ok(())
8392
}

0 commit comments

Comments
 (0)