Projects STRLCPY ebpfguard Commits 1423ad2f
🤬
  • Send alerts through channels

    Requiring users to deal with `AsyncPerfEventArray` and read events
    manually is not the best API. It's better to expose alerts with `mpsc`
    channels.
  • Loading...
  • Michal Rostecki committed with vadorovsky 1 year ago
    1423ad2f
    1 parent d5cd64e5
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■
    guardity/Cargo.toml
    skipped 14 lines
    15 15  serde = { version = "1.0", features = ["derive"] }
    16 16  serde_json = "1.0"
    17 17  serde_yaml = "0.9"
    18  -tokio = { version = "1.25", features = ["macros", "rt", "rt-multi-thread", "net", "signal"] }
     18 +tokio = { version = "1.25", features = ["macros", "rt", "rt-multi-thread", "net", "signal", "sync"] }
    19 19  thiserror = "1.0"
    20 20   
    21 21  [[bin]]
    skipped 3 lines
  • ■ ■ ■ ■ ■
    guardity/src/lib.rs
    1  -use std::path::Path;
     1 +use std::{fmt::Debug, marker::PhantomData, path::Path};
    2 2   
    3 3  use aya::{
    4 4   include_bytes_aligned,
     5 + maps::{AsyncPerfEventArray, MapData},
    5 6   programs::{lsm::LsmLink, Lsm},
     7 + util::online_cpus,
    6 8   Bpf, BpfLoader, Btf,
    7 9  };
     10 +use bytes::BytesMut;
     11 +use guardity_common::{
     12 + Alert, AlertBprmCheckSecurity, AlertFileOpen, AlertSetuid, AlertSocketBind, AlertSocketConnect,
     13 +};
     14 +use tokio::{
     15 + sync::mpsc::{self, Receiver},
     16 + task,
     17 +};
    8 18   
    9 19  pub mod fs;
    10 20  pub mod policy;
    11 21   
    12 22  pub struct PolicyManager {
    13  - pub bpf: Bpf,
    14  - pub bprm_check_security: Option<Hook>,
    15  - pub file_open: Option<Hook>,
    16  - pub setuid: Option<Hook>,
    17  - pub socket_bind: Option<Hook>,
    18  - pub socket_connect: Option<Hook>,
     23 + bpf: Bpf,
     24 + bprm_check_security: Option<BprmCheckSecurityHook>,
     25 + file_open: Option<FileOpenHook>,
     26 + task_fix_setuid: Option<TaskFixSetuidHook>,
     27 + socket_bind: Option<SocketBindHook>,
     28 + socket_connect: Option<SocketConnectHook>,
    19 29  }
    20  - 
    21  -pub type Foo = Option<u32>;
    22 30   
    23 31  impl PolicyManager {
    24 32   pub fn new<P: AsRef<Path>>(bpf_path: P) -> anyhow::Result<Self> {
    skipped 14 lines
    39 47   bpf,
    40 48   bprm_check_security: None,
    41 49   file_open: None,
    42  - setuid: None,
     50 + task_fix_setuid: None,
    43 51   socket_bind: None,
    44 52   socket_connect: None,
    45 53   })
    skipped 1 lines
    47 55   
    48 56   pub fn attach_bprm_check_security(&mut self) -> anyhow::Result<()> {
    49 57   let link = attach_program(&mut self.bpf, "bprm_check_security")?;
    50  - let bprm_check_security = Hook::new(link)?;
     58 + let perf_array = perf_array(&mut self.bpf, "ALERT_BPRM_CHECK_SECURITY")?;
     59 + let bprm_check_security = Hook::new(link, perf_array)?;
    51 60   self.bprm_check_security = Some(bprm_check_security);
    52 61   
    53 62   Ok(())
    54 63   }
    55 64   
     65 + pub fn bprm_check_security(&mut self) -> anyhow::Result<&mut BprmCheckSecurityHook> {
     66 + match self.bprm_check_security {
     67 + Some(ref mut bprm_check_security) => Ok(bprm_check_security),
     68 + None => Err(anyhow::anyhow!("bprm_check_security is not attached")),
     69 + }
     70 + }
     71 + 
    56 72   pub fn attach_file_open(&mut self) -> anyhow::Result<()> {
    57 73   let link = attach_program(&mut self.bpf, "file_open")?;
    58  - let file_open = Hook::new(link)?;
     74 + let perf_array = perf_array(&mut self.bpf, "ALERT_FILE_OPEN")?;
     75 + let file_open = Hook::new(link, perf_array)?;
    59 76   self.file_open = Some(file_open);
    60 77   
    61 78   Ok(())
    62 79   }
    63 80   
    64  - pub fn file_open(&mut self) -> anyhow::Result<&mut Hook> {
     81 + pub fn file_open(&mut self) -> anyhow::Result<&mut FileOpenHook> {
    65 82   match self.file_open {
    66 83   Some(ref mut file_open) => Ok(file_open),
    67 84   None => Err(anyhow::anyhow!("file_open is not attached")),
    skipped 2 lines
    70 87   
    71 88   pub fn attach_task_fix_setuid(&mut self) -> anyhow::Result<()> {
    72 89   let link = attach_program(&mut self.bpf, "task_fix_setuid")?;
    73  - let setuid = Hook::new(link)?;
    74  - self.setuid = Some(setuid);
     90 + let perf_array = perf_array(&mut self.bpf, "ALERT_SETUID")?;
     91 + let setuid = Hook::new(link, perf_array)?;
     92 + self.task_fix_setuid = Some(setuid);
    75 93   
    76 94   Ok(())
    77 95   }
    78 96   
    79  - pub fn setuid(&mut self) -> anyhow::Result<&mut Hook> {
    80  - match self.setuid {
     97 + pub fn task_fix_setuid(&mut self) -> anyhow::Result<&mut TaskFixSetuidHook> {
     98 + match self.task_fix_setuid {
    81 99   Some(ref mut setuid) => Ok(setuid),
    82 100   None => Err(anyhow::anyhow!("setuid is not attached")),
    83 101   }
    skipped 1 lines
    85 103   
    86 104   pub fn attach_socket_bind(&mut self) -> anyhow::Result<()> {
    87 105   let link = attach_program(&mut self.bpf, "socket_bind")?;
    88  - let socket_bind = Hook::new(link)?;
     106 + let perf_array = perf_array(&mut self.bpf, "ALERT_SOCKET_BIND")?;
     107 + let socket_bind = Hook::new(link, perf_array)?;
    89 108   self.socket_bind = Some(socket_bind);
    90 109   
    91 110   Ok(())
    92 111   }
    93 112   
    94  - pub fn socket_bind(&mut self) -> anyhow::Result<&mut Hook> {
     113 + pub fn socket_bind(&mut self) -> anyhow::Result<&mut SocketBindHook> {
    95 114   match self.socket_bind {
    96 115   Some(ref mut socket_bind) => Ok(socket_bind),
    97 116   None => Err(anyhow::anyhow!("socket_bind is not attached")),
    skipped 2 lines
    100 119   
    101 120   pub fn attach_socket_connect(&mut self) -> anyhow::Result<()> {
    102 121   let link = attach_program(&mut self.bpf, "socket_connect")?;
    103  - let socket_connect = Hook::new(link)?;
     122 + let perf_array = perf_array(&mut self.bpf, "ALERT_SOCKET_CONNECT")?;
     123 + let socket_connect = Hook::new(link, perf_array)?;
    104 124   self.socket_connect = Some(socket_connect);
    105 125   
    106 126   Ok(())
    107 127   }
    108 128   
    109  - pub fn socket_connect(&mut self) -> anyhow::Result<&mut Hook> {
     129 + pub fn socket_connect(&mut self) -> anyhow::Result<&mut SocketConnectHook> {
    110 130   match self.socket_connect {
    111 131   Some(ref mut socket_connect) => Ok(socket_connect),
    112 132   None => Err(anyhow::anyhow!("socket_connect is not attached")),
    skipped 11 lines
    124 144   Ok(link)
    125 145  }
    126 146   
    127  -pub struct Hook {
     147 +fn perf_array(bpf: &mut Bpf, name: &str) -> anyhow::Result<AsyncPerfEventArray<MapData>> {
     148 + let perf_array = bpf.take_map(name).unwrap().try_into()?;
     149 + Ok(perf_array)
     150 +}
     151 + 
     152 +pub struct Hook<T>
     153 +where
     154 + T: Alert,
     155 +{
    128 156   #[allow(dead_code)]
    129 157   program_link: LsmLink,
     158 + perf_array: AsyncPerfEventArray<MapData>,
     159 + phantom: PhantomData<T>,
    130 160  }
    131 161   
    132  -impl Hook {
    133  - pub fn new(program_link: LsmLink) -> anyhow::Result<Self> {
    134  - Ok(Self { program_link })
     162 +impl<T> Hook<T>
     163 +where
     164 + T: Alert + Debug + Send + 'static,
     165 +{
     166 + fn new(
     167 + program_link: LsmLink,
     168 + perf_array: AsyncPerfEventArray<MapData>,
     169 + ) -> anyhow::Result<Self> {
     170 + Ok(Self {
     171 + program_link,
     172 + perf_array,
     173 + phantom: PhantomData,
     174 + })
     175 + }
     176 + 
     177 + pub async fn alerts(&mut self) -> anyhow::Result<Receiver<T>> {
     178 + let (tx, rx) = mpsc::channel(32);
     179 + 
     180 + let cpus = online_cpus()?;
     181 + for cpu_id in cpus {
     182 + let tx = tx.clone();
     183 + let mut buf = self.perf_array.open(cpu_id, None)?;
     184 + 
     185 + task::spawn(async move {
     186 + let mut buffers = (0..10)
     187 + .map(|_| BytesMut::with_capacity(1024))
     188 + .collect::<Vec<_>>();
     189 + loop {
     190 + let events = buf.read_events(&mut buffers).await.unwrap();
     191 + for buf in buffers.iter_mut().take(events.read) {
     192 + let alert = {
     193 + let ptr = buf.as_ptr() as *const T;
     194 + unsafe { ptr.read_unaligned() }
     195 + };
     196 + tx.send(alert).await.unwrap();
     197 + }
     198 + }
     199 + });
     200 + }
     201 + 
     202 + Ok(rx)
    135 203   }
    136 204  }
    137 205   
     206 +pub type BprmCheckSecurityHook = Hook<AlertBprmCheckSecurity>;
     207 +pub type FileOpenHook = Hook<AlertFileOpen>;
     208 +pub type TaskFixSetuidHook = Hook<AlertSetuid>;
     209 +pub type SocketBindHook = Hook<AlertSocketBind>;
     210 +pub type SocketConnectHook = Hook<AlertSocketConnect>;
     211 + 
  • ■ ■ ■ ■ ■ ■
    guardity/src/main.rs
    1 1  use std::fs::create_dir_all;
    2 2  use std::path::PathBuf;
    3 3   
    4  -use aya::maps::{AsyncPerfEventArray, MapData};
    5  -use aya::util::online_cpus;
    6  -use bytes::BytesMut;
    7 4  use clap::Parser;
    8 5  use guardity::PolicyManager;
    9  -use guardity_common::{AlertFileOpen, AlertSetuid, AlertSocketBind, AlertSocketConnect};
    10 6  use log::info;
    11  -use serde::Serialize;
    12 7  use tokio::signal;
    13 8   
    14 9  #[derive(Debug, Parser)]
    skipped 6 lines
    21 16   policy: Vec<PathBuf>,
    22 17  }
    23 18   
    24  -async fn read_alerts<T>(mut map: AsyncPerfEventArray<MapData>)
    25  -where
    26  - T: Serialize + Send + Sync + 'static,
    27  -{
    28  - let cpus = online_cpus().unwrap();
    29  - for cpu_id in cpus {
    30  - let mut buf = map.open(cpu_id, None).unwrap();
    31  - 
    32  - tokio::spawn(async move {
    33  - let mut buffers = (0..10)
    34  - .map(|_| BytesMut::with_capacity(1024))
    35  - .collect::<Vec<_>>();
    36  - 
    37  - loop {
    38  - let events = buf.read_events(&mut buffers).await.unwrap();
    39  - for buf in buffers.iter_mut().take(events.read) {
    40  - let ptr = buf.as_ptr() as *const T;
    41  - let data = unsafe { ptr.read_unaligned() };
    42  - eprintln!("{}", serde_json::to_string(&data).unwrap());
    43  - }
    44  - }
    45  - });
    46  - }
    47  -}
    48  - 
    49 19  #[tokio::main]
    50 20  async fn main() -> Result<(), anyhow::Error> {
    51 21   let opt = Opt::parse();
    52 22   
    53 23   env_logger::init();
    54 24   
    55  - // This will include your eBPF object file as raw bytes at compile-time and load it at
    56  - // runtime. This approach is recommended for most real-world use cases. If you would
    57  - // like to specify the eBPF program at runtime rather than at compile-time, you can
    58  - // reach for `Bpf::load_file` instead.
    59 25   let bpf_path = opt.bpffs_path.join(opt.bpffs_dir);
    60 26   create_dir_all(&bpf_path)?;
    61 27   
    62 28   let mut policy_manager = PolicyManager::new(bpf_path)?;
     29 + 
    63 30   policy_manager.attach_bprm_check_security()?;
    64 31   policy_manager.attach_file_open()?;
    65 32   policy_manager.attach_task_fix_setuid()?;
    66 33   policy_manager.attach_socket_bind()?;
    67 34   policy_manager.attach_socket_connect()?;
    68 35   
    69  - read_alerts::<AlertFileOpen>(
    70  - policy_manager
    71  - .bpf
    72  - .take_map("ALERT_FILE_OPEN")
    73  - .unwrap()
    74  - .try_into()?,
    75  - )
    76  - .await;
    77  - read_alerts::<AlertSetuid>(
    78  - policy_manager
    79  - .bpf
    80  - .take_map("ALERT_SETUID")
    81  - .unwrap()
    82  - .try_into()?,
    83  - )
    84  - .await;
    85  - read_alerts::<AlertSocketBind>(
    86  - policy_manager
    87  - .bpf
    88  - .take_map("ALERT_SOCKET_BIND")
    89  - .unwrap()
    90  - .try_into()?,
    91  - )
    92  - .await;
    93  - read_alerts::<AlertSocketConnect>(
    94  - policy_manager
    95  - .bpf
    96  - .take_map("ALERT_SOCKET_CONNECT")
    97  - .unwrap()
    98  - .try_into()?,
    99  - )
    100  - .await;
     36 + let mut rx_bprm_check_security = policy_manager.bprm_check_security()?.alerts().await?;
     37 + let mut rx_file_open = policy_manager.file_open()?.alerts().await?;
     38 + let mut rx_task_fix_setuid = policy_manager.task_fix_setuid()?.alerts().await?;
     39 + let mut rx_socket_bind = policy_manager.socket_bind()?.alerts().await?;
     40 + let mut rx_socket_connect = policy_manager.socket_connect()?.alerts().await?;
    101 41   
    102 42   info!("Waiting for Ctrl-C...");
    103  - signal::ctrl_c().await?;
     43 + 
     44 + loop {
     45 + tokio::select! {
     46 + Some(alert) = rx_bprm_check_security.recv() => {
     47 + info!("bprm_check_security: {}", alert.pid);
     48 + }
     49 + Some(alert) = rx_file_open.recv() => {
     50 + info!("file_open: {}", alert.pid);
     51 + }
     52 + Some(alert) = rx_task_fix_setuid.recv() => {
     53 + info!("task_fix_setuid: pid={} binprm_inode={}", alert.pid, alert.binprm_inode);
     54 + }
     55 + Some(alert) = rx_socket_bind.recv() => {
     56 + info!("socket_bind: pid={}", alert.pid);
     57 + }
     58 + Some(alert) = rx_socket_connect.recv() => {
     59 + if alert.addr_v4 != 0 {
     60 + info!(
     61 + "socket_connect: pid={} binprm_inode={} addr={}",
     62 + alert.pid,
     63 + alert.binprm_inode,
     64 + alert.addr_v4
     65 + );
     66 + } else {
     67 + info!(
     68 + "socket_connect: pid={} binprm_inode={} addr={:?}",
     69 + alert.pid,
     70 + alert.binprm_inode,
     71 + alert.addr_v6
     72 + );
     73 + }
     74 + }
     75 + _ = signal::ctrl_c() => {
     76 + break;
     77 + }
     78 + }
     79 + }
     80 + 
    104 81   info!("Exiting...");
    105 82   
    106 83   Ok(())
    skipped 2 lines
  • ■ ■ ■ ■ ■ ■
    guardity-common/src/lib.rs
    skipped 13 lines
    14 14  pub trait Alert {}
    15 15   
    16 16  #[repr(C)]
    17  -#[cfg_attr(feature = "user", derive(serde::Serialize, serde::Deserialize))]
     17 +#[cfg_attr(feature = "user", derive(Debug, serde::Serialize, serde::Deserialize))]
     18 +#[derive(Copy, Clone)]
     19 +pub struct AlertBprmCheckSecurity {
     20 + pub pid: u32,
     21 + #[cfg_attr(feature = "user", serde(skip))]
     22 + _padding: u32,
     23 + pub binprm_inode: u64,
     24 +}
     25 + 
     26 +impl AlertBprmCheckSecurity {
     27 + pub fn new(pid: u32, binprm_inode: u64) -> Self {
     28 + Self {
     29 + pid,
     30 + _padding: 0,
     31 + binprm_inode,
     32 + }
     33 + }
     34 +}
     35 + 
     36 +impl Alert for AlertBprmCheckSecurity {}
     37 + 
     38 +#[repr(C)]
     39 +#[cfg_attr(feature = "user", derive(Debug, serde::Serialize, serde::Deserialize))]
    18 40  #[derive(Copy, Clone)]
    19 41  pub struct AlertFileOpen {
    20 42   pub pid: u32,
    21 43   #[cfg_attr(feature = "user", serde(skip))]
    22  - pub _padding: u32,
     44 + _padding: u32,
    23 45   pub binprm_inode: u64,
    24 46   pub inode: u64,
    25 47  }
    skipped 12 lines
    38 60  impl Alert for AlertFileOpen {}
    39 61   
    40 62  #[repr(C)]
    41  -#[cfg_attr(feature = "user", derive(serde::Serialize, serde::Deserialize))]
     63 +#[cfg_attr(feature = "user", derive(Debug, serde::Serialize, serde::Deserialize))]
    42 64  #[derive(Copy, Clone)]
    43 65  pub struct AlertSetuid {
    44 66   pub pid: u32,
    45 67   #[cfg_attr(feature = "user", serde(skip))]
    46  - pub _padding: u32,
     68 + _padding: u32,
    47 69   pub binprm_inode: u64,
    48 70   pub old_uid: u32,
    49 71   pub old_gid: u32,
    skipped 25 lines
    75 97  impl Alert for AlertSetuid {}
    76 98   
    77 99  #[repr(C)]
    78  -#[cfg_attr(feature = "user", derive(serde::Serialize, serde::Deserialize))]
     100 +#[cfg_attr(feature = "user", derive(Debug, serde::Serialize, serde::Deserialize))]
    79 101  #[derive(Copy, Clone)]
    80 102  pub struct AlertSocketBind {
    81 103   pub pid: u32,
    82 104   #[cfg_attr(feature = "user", serde(skip))]
    83  - pub _padding1: u32,
     105 + _padding1: u32,
    84 106   pub binprm_inode: u64,
    85 107   pub port: u16,
    86 108   #[cfg_attr(feature = "user", serde(skip))]
    87  - pub _padding2: [u16; 3],
     109 + _padding2: [u16; 3],
    88 110  }
    89 111   
    90 112  impl AlertSocketBind {
    skipped 27 lines
    118 140  }
    119 141   
    120 142  #[repr(C)]
    121  -#[cfg_attr(feature = "user", derive(serde::Serialize, serde::Deserialize))]
     143 +#[cfg_attr(feature = "user", derive(Debug, serde::Serialize, serde::Deserialize))]
    122 144  #[derive(Copy, Clone)]
    123 145  pub struct AlertSocketConnect {
    124 146   pub pid: u32,
    125 147   #[cfg_attr(feature = "user", serde(skip))]
    126  - pub _padding1: u32,
     148 + _padding1: u32,
    127 149   pub binprm_inode: u64,
    128 150   #[cfg_attr(feature = "user", serde(serialize_with = "serialize_ipv4"))]
    129 151   pub addr_v4: u32,
    130 152   #[cfg_attr(feature = "user", serde(skip))]
    131  - pub _padding2: u32,
     153 + _padding2: u32,
    132 154   #[cfg_attr(feature = "user", serde(serialize_with = "serialize_ipv6"))]
    133 155   pub addr_v6: [u8; 16],
    134 156  }
    skipped 27 lines
    162 184  #[repr(C)]
    163 185  #[derive(Copy, Clone)]
    164 186  pub struct Paths {
    165  - // pub all: bool,
    166  - // pub _padding: [u8; 7],
    167  - // pub len: usize,
    168 187   pub paths: [u64; MAX_PATHS],
    169  - // pub all: u64,
    170 188  }
    171 189   
    172 190  #[repr(C)]
    173 191  #[derive(Copy, Clone)]
    174 192  pub struct Ports {
    175 193   pub all: bool,
    176  - pub _padding1: [u8; 7],
     194 + _padding1: [u8; 7],
    177 195   pub len: usize,
    178 196   pub ports: [u16; MAX_PORTS],
    179  - pub _padding2: [u16; 3 * MAX_PORTS],
     197 + _padding2: [u16; 3 * MAX_PORTS],
    180 198  }
    181 199   
    182 200  impl Ports {
    skipped 90 lines
  • ■ ■ ■ ■ ■
    guardity-ebpf/src/bprm_check_security.rs
    1  -use aya_bpf::{cty::c_long, programs::LsmContext};
     1 +use aya_bpf::{cty::c_long, programs::LsmContext, BpfContext};
     2 +use guardity_common::AlertBprmCheckSecurity;
    2 3   
    3  -use crate::vmlinux::linux_binprm;
     4 +use crate::{binprm::current_binprm_inode, maps::ALERT_BPRM_CHECK_SECURITY, vmlinux::linux_binprm};
    4 5   
    5 6  pub fn bprm_check_security(ctx: LsmContext) -> Result<i32, c_long> {
    6  - let binprm: *const linux_binprm = unsafe { ctx.arg(0) };
    7  - let argc = unsafe { (*binprm).argc };
     7 + let new_binprm: *const linux_binprm = unsafe { ctx.arg(0) };
     8 + let argc = unsafe { (*new_binprm).argc };
     9 + 
     10 + let old_binprm_inode = current_binprm_inode();
    8 11   
    9 12   if argc < 1 {
     13 + ALERT_BPRM_CHECK_SECURITY.output(
     14 + &ctx,
     15 + &AlertBprmCheckSecurity::new(ctx.pid() as u32, old_binprm_inode),
     16 + 0,
     17 + );
    10 18   return Ok(-1);
    11 19   }
    12 20   
    skipped 3 lines
  • ■ ■ ■ ■ ■ ■
    guardity-ebpf/src/maps.rs
    skipped 2 lines
    3 3   maps::{HashMap, PerfEventArray},
    4 4  };
    5 5  use guardity_common::{
    6  - AlertFileOpen, AlertSetuid, AlertSocketBind, AlertSocketConnect, Ipv4Addrs, Ipv6Addrs, Paths,
    7  - Ports,
     6 + AlertBprmCheckSecurity, AlertFileOpen, AlertSetuid, AlertSocketBind, AlertSocketConnect,
     7 + Ipv4Addrs, Ipv6Addrs, Paths, Ports,
    8 8  };
     9 + 
     10 +#[map]
     11 +pub static ALERT_BPRM_CHECK_SECURITY: PerfEventArray<AlertBprmCheckSecurity> =
     12 + PerfEventArray::pinned(1024, 0);
    9 13   
    10 14  /// Map of allowed file open paths for each binary.
    11 15  #[map]
    skipped 29 lines
    41 45   
    42 46  /// Map of alerts for `socket_bind` LSM hook inspection.
    43 47  #[map]
    44  -pub static ALERT_SOCKET_BIND: PerfEventArray<AlertSocketBind> =
    45  - PerfEventArray::pinned(1024, 0);
     48 +pub static ALERT_SOCKET_BIND: PerfEventArray<AlertSocketBind> = PerfEventArray::pinned(1024, 0);
    46 49   
    47 50  /// Map of allowed socket connect IPv4 addresses for each binary.
    48 51  #[map]
    skipped 19 lines
Please wait...
Page is in error, reload to recover