Skip to content

Commit

Permalink
feat: Allow filtering interest by container engines
Browse files Browse the repository at this point in the history
It allows to categorize processes in the interest tracked either
by the fact of being containerized or by the specific container
engine.
  • Loading branch information
vadorovsky committed Feb 7, 2024
1 parent e5bc078 commit dfc8ac4
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 20 deletions.
21 changes: 21 additions & 0 deletions crates/bpf-builder/include/interest_tracking.bpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ struct bpf_map_def_aya {
__uint(max_entries, 100); \
} map_cgroup_rules SEC(".maps");

// Map of containers to target
#define MAP_CONTAINER_RULES(map_container_rules) \
struct { \
__uint(type, BPF_MAP_TYPE_HASH); \
__type(key, int); \
__type(value, u8); \
__uint(max_entries, 100); \
} map_container_rules SEC(".maps");

// Define a map containing the interest for each process on the system.
// > map_interest[<process pid>] = <INTEREST BITMAP>
// TRACK_CHILDREN TRACK_SELF Description
Expand Down Expand Up @@ -164,6 +173,18 @@ tracker_check_cgroup_rules(struct bpf_map_def_aya *tracker, void *rules,
}
}

static __always_inline void
tracker_check_container_rules(struct bpf_map_def_aya *tracker, void *rules,
struct task_struct *p, int container_engine) {
int all_containers = 0;
if (bpf_map_lookup_elem(rules, &all_containers) ||
bpf_map_lookup_elem(rules, &container_engine)) {
pid_t tgid = BPF_CORE_READ(p, tgid);
u8 policy = INTEREST_TRACK_SELF | INTEREST_TRACK_CHILDREN;
long res = bpf_map_update_elem(tracker, &tgid, &policy, BPF_ANY);
}
}

// Returns true if an element was removed from the map.
// Does nothing if any process threat is still alive.
static __always_inline bool tracker_remove(struct bpf_map_def_aya *tracker,
Expand Down
83 changes: 82 additions & 1 deletion crates/bpf-filtering/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,79 @@
use std::{collections::HashSet, str::FromStr};

use bpf_common::Pid;
use pulsar_core::pdk::{ConfigError, ModuleConfig};

use crate::maps::{DEFAULT_CGROUP_RULES, DEFAULT_INTEREST, DEFAULT_RULES};
use crate::maps::{DEFAULT_CGROUP_RULES, DEFAULT_CONTAINER_RULES, DEFAULT_INTEREST, DEFAULT_RULES};

use super::maps::Image;

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[repr(i32)]
pub enum ContainerEngine {
Docker = 1,
Podman = 2,
}

impl From<&ContainerEngine> for i32 {
fn from(value: &ContainerEngine) -> Self {
match value {
ContainerEngine::Docker => 1,
ContainerEngine::Podman => 2,
}
}
}

impl FromStr for ContainerEngine {
type Err = ConfigError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"docker" => Ok(Self::Docker),
"podman" => Ok(Self::Podman),
_ => Err(ConfigError::ContainerEngine {
engine: s.to_owned(),
}),
}
}
}

#[derive(Clone, Debug)]
pub enum ContainerTargets {
All,
Engine(HashSet<ContainerEngine>),
}

impl Default for ContainerTargets {
fn default() -> Self {
Self::All
}
}

impl FromStr for ContainerTargets {
type Err = ConfigError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"all" => Ok(Self::All),
_ => {
let engines: Result<HashSet<_>, _> = s
.split(',')
.filter_map(|item| {
let trimmed = item.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed)
}
})
.map(ContainerEngine::from_str)
.collect();
engines.map(Self::Engine)
}
}
}
}

/// [`Config`] is the user configuration of a list of rules
/// for determining what constitutes an interesting eBPF event.
#[derive(Clone, Debug, Default)]
Expand All @@ -13,6 +82,10 @@ pub struct Config {
pub pid_targets: Vec<PidRule>,
/// List of image-based rules
pub rules: Vec<Rule>,
/// List of containers to target.
/// Processes belonging to these containers are considered of interest,
/// despite what `pid_targets` ans `rules` say.
pub container_targets: Option<ContainerTargets>,
/// List of cgroup paths to target.
/// Processes belonging to these cgroups are considered of interest,
/// despite what `pid_targets` and `rules` say.
Expand All @@ -22,6 +95,8 @@ pub struct Config {
/// Map name of the rules map
pub rule_map_name: String,
pub cgroup_rule_map_name: String,
/// Map name of the container rule map
pub container_rule_map_name: String,
/// Sets the default tracking status for Pid 1 and when finding missing entries.
pub track_by_default: bool,
/// Whitelist the current process
Expand Down Expand Up @@ -82,13 +157,19 @@ impl TryFrom<&ModuleConfig> for Config {
}));
}

let container_targets = config
.get_raw("container_targets")
.and_then(|s| ContainerTargets::from_str(s).ok());

Ok(Config {
pid_targets,
rules,
container_targets,
cgroup_targets: config.get_list("cgroup_targets")?,
interest_map_name: DEFAULT_INTEREST.to_string(),
rule_map_name: DEFAULT_RULES.to_string(),
cgroup_rule_map_name: DEFAULT_CGROUP_RULES.to_string(),
container_rule_map_name: DEFAULT_CONTAINER_RULES.to_string(),
track_by_default: config.with_default("track_by_default", true)?,
ignore_self: config.with_default("ignore_self", true)?,
})
Expand Down
26 changes: 25 additions & 1 deletion crates/bpf-filtering/src/initializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use pulsar_core::{
};
use tokio::sync::mpsc;

use crate::maps::{Cgroup, Map};
use crate::{
config::ContainerTargets,
maps::{Cgroup, Map},
};

use super::{
config::{Config, Rule},
Expand Down Expand Up @@ -61,6 +64,27 @@ pub async fn setup_events_filter(
.context("Error inserting in cgroup rule map")?;
}

// setup container rule map
let mut container_map = Map::<i32, u8>::load(bpf, &config.container_rule_map_name)?;
container_map.clear()?;
if let Some(ref container_targets) = config.container_targets {
match container_targets {
ContainerTargets::All => container_map
.map
.insert(0, 0, 0)
.context("Error inserting in container rule map")?,
ContainerTargets::Engine(engines) => {
for engine in engines {
let engine: i32 = engine.into();
container_map
.map
.insert(engine, 0, 0)
.context("Error inserting in container rule map")?;
}
}
}
}

// load process list from procfs
let mut process_tree = ProcessTree::load_from_procfs()?;

Expand Down
2 changes: 2 additions & 0 deletions crates/bpf-filtering/src/maps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub const DEFAULT_INTEREST: &str = "m_interest";
/// Default name for rules map
pub const DEFAULT_RULES: &str = "m_rules";
pub const DEFAULT_CGROUP_RULES: &str = "m_cgroup_rules";
/// Default name for container rules map
pub const DEFAULT_CONTAINER_RULES: &str = "m_container_rules";

impl InterestMap {
/// Try to load the map from eBPF
Expand Down
41 changes: 23 additions & 18 deletions crates/modules/process-monitor/probes.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ char LICENSE[] SEC("license") = "GPL v2";

#define CONTAINER_ID_MAX_BUF 72

#define DOCKER_CONTAINER_ENGINE 0
#define PODMAN_CONTAINER_ENGINE 1
#define UNKNOWN_CONTAINER_ENGINE -1
#define FAILED_READ_MEMORY_CGROUP_ID -2
#define FAILED_READ_CGROUP_NAME -3
#define FAILED_READ_PARENT_CGROUP_NAME -4
#define FAILED_PARSE_LIBPOD_CGROUP_NAME -5
#define DOCKER_CONTAINER_ENGINE 1
#define PODMAN_CONTAINER_ENGINE 2
#define UNKNOWN_CONTAINER_ENGINE 0
#define FAILED_READ_MEMORY_CGROUP_ID -1
#define FAILED_READ_CGROUP_NAME -2
#define FAILED_READ_PARENT_CGROUP_NAME -3
#define FAILED_PARSE_LIBPOD_CGROUP_NAME -4

struct namespaces
{
Expand Down Expand Up @@ -111,6 +111,7 @@ struct cgroup_attach_event
GLOBAL_INTEREST_MAP_DECLARATION;
MAP_RULES(m_rules);
MAP_CGROUP_RULES(m_cgroup_rules);
MAP_CONTAINER_RULES(m_container_rules);

OUTPUT_MAP(process_event, {
struct fork_event fork;
Expand Down Expand Up @@ -156,20 +157,20 @@ The array size MUST be greater than 72.
### Success:
the return value is:
- `0`: it's a docker container id
- `1`: it's a podman container id
- `1`: it's a docker container id
- `2`: it's a podman container id
and the characters array `buf` contains the id of the container
at the position specified in the value pointed by `offset`.
### Error:
if return value is less than `0`, in particular:
- `-1`: no container or unknown container engine
- `-2`: failed to read the id of the memory cgroup for the
if return value is less or equal to `0`, in particular:
- `0`: no container or unknown container engine
- `-1`: failed to read the id of the memory cgroup for the
current kernel
- `-3`: failed to read the cgroup name from the kernel memory
- `-4`: failed to read the cgroup name of the the parent of
- `-2`: failed to read the cgroup name from the kernel memory
- `-3`: failed to read the cgroup name of the the parent of
the given process in the case of podman container
- `-5`: failed to parse a `libpod-` cgroup name of the parent
- `-4`: failed to parse a `libpod-` cgroup name of the parent
of the process after a successful parse of a `container`
cgroup name for the given process
*/
Expand Down Expand Up @@ -243,8 +244,13 @@ int BPF_PROG(sched_process_fork, struct task_struct *parent,
{
return 0;
}

int id_offset;
int container_engine = get_container_info(child, buf, CONTAINER_ID_MAX_BUF, &id_offset);

// Propagate whitelist to child
tracker_fork(&GLOBAL_INTEREST_MAP, parent, child);
tracker_check_container_rules(&GLOBAL_INTEREST_MAP, &m_container_rules, parent, container_engine);
LOG_DEBUG("fork %d %d", parent_tgid, child_tgid);

struct process_event *event = init_process_event(EVENT_FORK, child_tgid);
Expand All @@ -263,9 +269,7 @@ int BPF_PROG(sched_process_fork, struct task_struct *parent,
event->fork.namespaces.time = BPF_CORE_READ(child, nsproxy, time_ns, ns.inum);
event->fork.namespaces.cgroup = BPF_CORE_READ(child, nsproxy, cgroup_ns, ns.inum);

int id_offset;
int container_engine = get_container_info(child, buf, CONTAINER_ID_MAX_BUF, &id_offset);
if (container_engine < 0)
if (container_engine <= 0)
{
// TODO: print error ??
event->fork.option_index.discriminant = OPTION_NONE;
Expand Down Expand Up @@ -369,6 +373,7 @@ int BPF_PROG(sched_process_exec, struct task_struct *p, pid_t old_pid,

// Check target and whitelist
tracker_check_rules(&GLOBAL_INTEREST_MAP, &m_rules, p, image);
tracker_check_container_rules(&GLOBAL_INTEREST_MAP, &m_container_rules, p, container_engine);

struct task_struct *task = (struct task_struct *)bpf_get_current_task();
struct mm_struct *mm = BPF_CORE_READ(task, mm);
Expand Down
2 changes: 2 additions & 0 deletions crates/pulsar-core/src/pdk/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub enum ConfigError {
value: String,
err: String,
},
#[error("unknown container engine {engine}")]
ContainerEngine { engine: String },
}

impl ModuleConfig {
Expand Down

0 comments on commit dfc8ac4

Please sign in to comment.