Skip to content

Commit

Permalink
feat: user/group name in kunai events
Browse files Browse the repository at this point in the history
  • Loading branch information
qjerome committed Dec 4, 2024
1 parent 74e1364 commit 77a155e
Show file tree
Hide file tree
Showing 6 changed files with 570 additions and 14 deletions.
100 changes: 91 additions & 9 deletions kunai/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use kunai::events::{
InitModuleData, KillData, KunaiEvent, MmapExecData, MprotectData, NetworkInfo, PrctlData,
PtraceData, ScanResult, SendDataData, SockAddr, SocketInfo, TargetTask, UnlinkData, UserEvent,
};
use kunai::info::{AdditionalInfo, ProcKey, StdEventInfo};
use kunai::info::{AdditionalInfo, ProcKey, StdEventInfo, TaskAdditionalInfo};
use kunai::ioc::IoC;
use kunai::util::uname::Utsname;

Expand Down Expand Up @@ -811,9 +811,22 @@ impl<'s> EventConsumer<'s> {

#[inline(always)]
/// method acting as a central place to get the mnt namespace of a
/// task and printing out an error if not found
/// parent task and printing out an error if not found
fn task_mnt_ns(ei: &bpf_events::EventInfo) -> Option<Namespace> {
match ei.process.namespaces {
Some(ns) => Some(Namespace::mnt(ns.mnt)),
None => {
error!("parent task namespace must be known");
None
}
}
}

#[inline(always)]
/// method acting as a central place to get the mnt namespace of a
/// task and printing out an error if not found
fn parent_mnt_ns(ei: &bpf_events::EventInfo) -> Option<Namespace> {
match ei.parent.namespaces {
Some(ns) => Some(Namespace::mnt(ns.mnt)),
None => {
error!("task namespace must be known");
Expand Down Expand Up @@ -1537,6 +1550,7 @@ impl<'s> EventConsumer<'s> {
#[inline(always)]
fn build_std_event_info(&mut self, i: bpf_events::EventInfo) -> StdEventInfo {
let opt_mnt_ns = Self::task_mnt_ns(&i);
let opt_parent_ns = Self::parent_mnt_ns(&i);

let mut std_info = StdEventInfo::from_bpf(i, self.random);

Expand All @@ -1546,6 +1560,8 @@ impl<'s> EventConsumer<'s> {
};

let mut container = None;
let mut task = TaskAdditionalInfo::default();
let mut parent = TaskAdditionalInfo::default();

if let Some(mnt_ns) = opt_mnt_ns {
if mnt_ns != self.system_info.mount_ns {
Expand All @@ -1555,11 +1571,65 @@ impl<'s> EventConsumer<'s> {
ty: t.and_then(|cd| cd.container),
});
}

// getting user and group information for task
task.user = self
.cache
.get_user_by_uid(&mnt_ns, &i.process.uid)
.inspect_err(|e| {
if !e.is_unknown_ns() {
error!("failed to get task user: {e}")
}
})
.unwrap_or_default()
.cloned();

// getting group information for task
task.group = self
.cache
.get_group_by_gid(&mnt_ns, &i.process.gid)
.inspect_err(|e| {
if !e.is_unknown_ns() {
error!("failed to get task group: {e}")
}
})
.unwrap_or_default()
.cloned();
}

// getting user and group information for parent task
if let Some(parent_ns) = opt_parent_ns {
parent.user = self
.cache
.get_user_by_uid(&parent_ns, &i.parent.uid)
.inspect_err(|e| {
if !e.is_unknown_ns() {
error!("failed to get task user: {e}")
}
})
.unwrap_or_default()
.cloned();

parent.group = self
.cache
.get_group_by_gid(&parent_ns, &i.parent.gid)
.inspect_err(|e| {
if !e.is_unknown_ns() {
error!("failed to get parent task group: {e}")
}
})
.unwrap_or_default()
.cloned();
}

self.track_zombie_task(&mut std_info);

std_info.with_additional_info(AdditionalInfo { host, container })
std_info.with_additional_info(AdditionalInfo {
host,
container,
task,
parent,
})
}

#[inline(always)]
Expand Down Expand Up @@ -1788,6 +1858,23 @@ impl<'s> EventConsumer<'s> {
printed
}

#[inline(always)]
fn cache_namespaces(&mut self, i: &bpf_events::EventInfo) {
if let Some(t_mnt_ns) = Self::task_mnt_ns(i) {
let pid = i.process.pid;
if let Err(e) = self.cache.cache_ns(pid, t_mnt_ns) {
debug!("failed to cache namespace pid={pid} ns={t_mnt_ns}: {e}");
}
}

if let Some(p_mnt_ns) = Self::parent_mnt_ns(i) {
let pid = i.parent.pid;
if let Err(e) = self.cache.cache_ns(pid, p_mnt_ns) {
debug!("failed to cache namespace pid={pid} ns={p_mnt_ns}: {e}");
}
}
}

#[inline(always)]
fn handle_event(&mut self, enc_event: &mut EncodedEvent) {
let i = unsafe { enc_event.info() }.unwrap();
Expand All @@ -1799,12 +1886,7 @@ impl<'s> EventConsumer<'s> {

let etype = i.etype;

if let Some(mnt_ns) = Self::task_mnt_ns(i) {
let pid = i.process.pid;
if let Err(e) = self.cache.cache_ns(pid, mnt_ns) {
debug!("failed to cache namespace pid={pid} ns={mnt_ns}: {e}");
}
}
self.cache_namespaces(i);

let std_info = self.build_std_event_info(*i);

Expand Down
115 changes: 113 additions & 2 deletions kunai/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ use std::{
use thiserror::Error;

use crate::{
util::namespaces::{self, Kind, Namespace, Switcher},
util::{
account::{Group, Groups, User, Users},
namespaces::{self, Kind, Namespace, Switcher},
},
yara::Scanner,
};

Expand All @@ -42,6 +45,12 @@ pub enum Error {
ScanError(#[from] yara_x::errors::ScanError),
}

impl Error {
pub fn is_unknown_ns(&self) -> bool {
matches!(self, Error::UnknownNs(_))
}
}

#[derive(Debug, Default, Clone, FieldGetter, Serialize, Deserialize)]
pub struct FileMeta {
pub md5: String,
Expand Down Expand Up @@ -266,21 +275,31 @@ struct CachedNs {
switcher: namespaces::Switcher,
}

#[derive(Debug)]
struct UsersAndGroups {
users: Users,
groups: Groups,
}

pub struct Cache {
namespaces: LruHashMap<Namespace, CachedNs>,
hashes: LruHashMap<Key, Hashes>,
users_groups: LruHashMap<Namespace, UsersAndGroups>,
// since hashes and signatures are not computed
// at the same time. It seems a better option
// to separate into two HashMaps to prevent
// any race
signatures: LruHashMap<Key, Vec<String>>,
}

const NS_CACHE_SIZE: usize = 256;

impl Cache {
// Constructs a new Hcache
pub fn with_max_entries(cap: usize) -> Self {
Cache {
namespaces: LruHashMap::with_max_entries(128),
namespaces: LruHashMap::with_max_entries(NS_CACHE_SIZE),
users_groups: LruHashMap::with_max_entries(NS_CACHE_SIZE),
hashes: LruHashMap::with_max_entries(cap),
signatures: LruHashMap::with_max_entries(cap),
}
Expand All @@ -300,6 +319,98 @@ impl Cache {
Ok(())
}

/// Get a [User] structure corresponding to user id `uid`
#[inline(always)]
pub fn get_user_by_uid(&mut self, ns: &Namespace, uid: &u32) -> Result<Option<&User>, Error> {
if !ns.is_kind(Kind::Mnt) {
return Err(Error::WrongNsKind {
exp: Kind::Mnt,
got: ns.kind,
});
}

let Some(entry) = self.namespaces.get(ns) else {
return Err(Error::UnknownNs(*ns));
};

// we have already parsed users and groups
if self.users_groups.contains_key(ns) {
// we have an entry for uid so we return it
if self
.users_groups
.get(ns)
.map(|ung| ung.users.contains_uid(uid))
.unwrap_or_default()
{
return Ok(self
.users_groups
.get(ns)
.and_then(|users| users.users.get_by_uid(uid)));
}
}

// we haven't yet parsed users and groups or we don't find an entry
let users = entry.switcher.do_in_namespace(|| {
Ok(UsersAndGroups {
users: Users::from_sys().map_err(namespaces::Error::other)?,
groups: Groups::from_sys().map_err(namespaces::Error::other)?,
})
})?;

self.users_groups.insert(*ns, users);

Ok(self
.users_groups
.get(ns)
.and_then(|ung| ung.users.get_by_uid(uid)))
}

/// Get a [Group] structure corresponding to group id `gid`
#[inline(always)]
pub fn get_group_by_gid(&mut self, ns: &Namespace, gid: &u32) -> Result<Option<&Group>, Error> {
if !ns.is_kind(Kind::Mnt) {
return Err(Error::WrongNsKind {
exp: Kind::Mnt,
got: ns.kind,
});
}

let Some(entry) = self.namespaces.get(ns) else {
return Err(Error::UnknownNs(*ns));
};

// we have already parsed users and groups
if self.users_groups.contains_key(ns) {
// we have an entry for uid so we return it
if self
.users_groups
.get(ns)
.map(|ung| ung.groups.contains_gid(gid))
.unwrap_or_default()
{
return Ok(self
.users_groups
.get(ns)
.and_then(|users| users.groups.get_by_gid(gid)));
}
}

// we haven't yet parsed users and groups or we don't find an entry
let users = entry.switcher.do_in_namespace(|| {
Ok(UsersAndGroups {
users: Users::from_sys().map_err(namespaces::Error::other)?,
groups: Groups::from_sys().map_err(namespaces::Error::other)?,
})
})?;

self.users_groups.insert(*ns, users);

Ok(self
.users_groups
.get(ns)
.and_then(|ung| ung.groups.get_by_gid(gid)))
}

#[inline(always)]
pub fn get_sig_or_cache(
&mut self,
Expand Down
43 changes: 41 additions & 2 deletions kunai/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::{
cache::{FileMeta, Hashes},
containers::Container,
info::{ContainerInfo, StdEventInfo},
util::account::{Group, User},
};

#[derive(Debug, Default, Serialize, Deserialize, FieldGetter)]
Expand Down Expand Up @@ -94,13 +95,37 @@ pub struct TaskSection {
pub tgid: i32,
pub guuid: String,
pub uid: u32,
pub user: String,
pub gid: u32,
pub group: String,
pub namespaces: Option<NamespaceInfo>,
#[serde(with = "u32_hex")]
pub flags: u32,
pub zombie: bool,
}

impl TaskSection {
fn from_task_info_with_user(
ti: kunai_common::bpf_events::TaskInfo,
user: Option<User>,
group: Option<Group>,
) -> Self {
Self {
name: ti.comm_string(),
pid: ti.pid,
tgid: ti.tgid,
guuid: ti.tg_uuid.into_uuid().hyphenated().to_string(),
uid: ti.uid,
user: user.map(|u| u.name).unwrap_or("?".into()),
gid: ti.gid,
group: group.map(|g| g.name).unwrap_or("?".into()),
namespaces: ti.namespaces.map(|ns| ns.into()),
flags: ti.flags,
zombie: ti.zombie,
}
}
}

impl From<kunai_common::bpf_events::TaskInfo> for TaskSection {
fn from(value: kunai_common::bpf_events::TaskInfo) -> Self {
Self {
Expand All @@ -109,7 +134,9 @@ impl From<kunai_common::bpf_events::TaskInfo> for TaskSection {
tgid: value.tgid,
guuid: value.tg_uuid.into_uuid().hyphenated().to_string(),
uid: value.uid,
user: "?".into(),
gid: value.gid,
group: "?".into(),
namespaces: value.namespaces.map(|ns| ns.into()),
flags: value.flags,
zombie: value.zombie,
Expand Down Expand Up @@ -190,6 +217,18 @@ pub struct EventInfo {

impl From<StdEventInfo> for EventInfo {
fn from(value: StdEventInfo) -> Self {
let task = TaskSection::from_task_info_with_user(
value.bpf.process,
value.additional.task.user,
value.additional.task.group,
);

let parent_task = TaskSection::from_task_info_with_user(
value.bpf.parent,
value.additional.parent.user,
value.additional.parent.group,
);

Self {
host: HostSection {
name: value.additional.host.name,
Expand All @@ -203,8 +242,8 @@ impl From<StdEventInfo> for EventInfo {
uuid: value.bpf.uuid.into_uuid().hyphenated().to_string(),
batch: value.bpf.batch,
},
task: value.bpf.process.into(),
parent_task: value.bpf.parent.into(),
task,
parent_task,
utc_time: value.utc_timestamp.into(),
}
}
Expand Down
Loading

0 comments on commit 77a155e

Please sign in to comment.