Skip to content

Commit

Permalink
WIP: Let's introduce new capable features
Browse files Browse the repository at this point in the history
  • Loading branch information
LeChatP committed Jul 10, 2024
1 parent 4d0f50b commit 78fcd76
Show file tree
Hide file tree
Showing 12 changed files with 539 additions and 168 deletions.
3 changes: 3 additions & 0 deletions capable-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ edition = "2021"
[features]
default = []
user = ["aya"]
kernel = ["aya-ebpf"]

[dependencies]
aya = { git = "https://github.com/aya-rs/aya", optional = true }
aya-ebpf = { git = "https://github.com/aya-rs/aya", optional = true }
bitflags = "2.6.0"

[lib]
path = "src/lib.rs"
96 changes: 96 additions & 0 deletions capable-common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,97 @@
#![no_std]

#[cfg(feature = "aya")]
use aya::Pod;

use bitflags::bitflags;

pub enum OpenMode {
Read,
Write,
LSeek,
PRead,
PWrite,
Exec,
WriteRestricted,
Hash32,
Hash64,
}

pub enum FileOperation {
Open,
Create,
Delete,
LinkAt,
Lookup,
FollowLink,
}

bitflags! {
#[derive(PartialEq, Clone, Copy)]
pub struct Access: u32 {

// open modes
const READ = 0b1;
const WRITE = 0b10;
const LSEEK = 0b100;
const PREAD = 0b1000;
const PWRITE = 0b10000;
const EXEC = 0b100000;
const WRITE_RESTRICTED = 0b1000000;
const HASH32 = 0b10000000;
const HASH64 = 0b100000000;

// File operations
const OPEN = 0b1000000000;
const CREATE = 0b10000000000;
const DELETE = 0b100000000000;
const LINKAT = 0b1000000000000;
const LOOKUP = 0b10000000000000;
const FOLLOW_LINK = 0b100000000000000;
}
}

pub type NsId = u32;

impl Access {
pub fn open_mode(&self) -> Option<OpenMode> {
match self {
&Self::READ => Some(OpenMode::Read),
&Self::WRITE => Some(OpenMode::Write),
&Self::LSEEK => Some(OpenMode::LSeek),
&Self::PREAD => Some(OpenMode::PRead),
&Self::PWRITE => Some(OpenMode::PWrite),
&Self::EXEC => Some(OpenMode::Exec),
&Self::WRITE_RESTRICTED => Some(OpenMode::WriteRestricted),
&Self::HASH32 => Some(OpenMode::Hash32),
&Self::HASH64 => Some(OpenMode::Hash64),
//ignore others
_ => None,
}
}

pub fn file_operation(&self) -> Option<FileOperation> {
match self {
&Self::OPEN => Some(FileOperation::Open),
&Self::CREATE => Some(FileOperation::Create),
&Self::DELETE => Some(FileOperation::Delete),
&Self::LINKAT => Some(FileOperation::LinkAt),
&Self::LOOKUP => Some(FileOperation::Lookup),
&Self::FOLLOW_LINK => Some(FileOperation::FollowLink),
//ignore others
_ => None,
}
}
}



#[repr(C)]
#[derive(Clone, Copy)]
pub struct Request {
pub f_mode: Access,
pub f_path: [u8; 8188],
}

#[cfg(feature = "aya")]
unsafe impl Pod for Request {}
2 changes: 1 addition & 1 deletion capable-ebpf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ edition = "2021"
[dependencies]
aya-ebpf = { git = "https://github.com/aya-rs/aya" }
aya-log-ebpf = { git = "https://github.com/aya-rs/aya" }
capable-common = { path = "../capable-common" }
capable-common = { path = "../capable-common", features = ["kernel"] }

[[bin]]
name = "capable"
Expand Down
78 changes: 78 additions & 0 deletions capable-ebpf/src/capable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use aya_ebpf::{
helpers::{bpf_get_current_task, bpf_get_current_uid_gid, bpf_probe_read_kernel},
macros::map,
maps::HashMap,
programs::ProbeContext,
};

use crate::ebpf_util::{get_ns_inode, TaskStructPtr, MAX_PID};

use aya_log_ebpf::{debug, info};

type Key = i32;

#[map]
static mut CAPABILITIES_MAP: HashMap<Key, u64> = HashMap::with_max_entries(MAX_PID, 0);
#[map]
static mut UID_GID_MAP: HashMap<Key, u64> = HashMap::with_max_entries(MAX_PID, 0);
#[map]
static mut PPID_MAP: HashMap<Key, i32> = HashMap::with_max_entries(MAX_PID, 0);
#[map]
static mut PNSID_NSID_MAP: HashMap<Key, u64> = HashMap::with_max_entries(MAX_PID, 0);


pub fn try_capable(ctx: &ProbeContext) -> Result<u32, i64> {
info!(ctx, "capable");
unsafe {
let task: TaskStructPtr = bpf_get_current_task() as TaskStructPtr;
debug!(ctx, "debug1");
let task = bpf_probe_read_kernel(&task)?;
debug!(ctx, "debug2");
let ppid: i32 = get_ppid(task)?;
debug!(ctx, "debug3");
let pid: i32 = bpf_probe_read_kernel(&(*task).pid)? as i32;
debug!(ctx, "debug4");
let cap: u64 = (1 << ctx.arg::<u8>(2).unwrap()) as u64;
debug!(ctx, "debug5");
let uid: u64 = bpf_get_current_uid_gid();
debug!(ctx, "debug6");
let zero = 0;
let capval: u64 = *CAPABILITIES_MAP.get(&pid).unwrap_or(&zero);
debug!(ctx, "debug7");
let pinum_inum: u64 = Into::<u64>::into(get_parent_ns_inode(task)?) << 32
| Into::<u64>::into(get_ns_inode(task)?);
debug!(ctx, "debug8");
UID_GID_MAP
.insert(&pid, &uid, 0)
.expect("failed to insert uid");
debug!(ctx, "debug9");
PNSID_NSID_MAP
.insert(&pid, &pinum_inum, 0)
.expect("failed to insert pnsid");
debug!(ctx, "debug10");
PPID_MAP
.insert(&pid, &ppid, 0)
.expect("failed to insert ppid");
debug!(ctx, "debug11");
CAPABILITIES_MAP
.insert(&pid, &(capval | cap), 0)
.expect("failed to insert cap");
}
Ok(0)
}

unsafe fn get_ppid(task: TaskStructPtr) -> Result<i32, i64> {
let parent_task: TaskStructPtr = get_parent_task(task)?;
bpf_probe_read_kernel(&(*parent_task).pid)
}

unsafe fn get_parent_task(task: TaskStructPtr) -> Result<TaskStructPtr, i64> {
bpf_probe_read_kernel(&(*task).parent)
}

unsafe fn get_parent_ns_inode(task: TaskStructPtr) -> Result<u32, i64> {
let parent_task: TaskStructPtr = get_parent_task(task)?;
get_ns_inode(parent_task)
}


36 changes: 36 additions & 0 deletions capable-ebpf/src/ebpf_util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use aya_ebpf::{
helpers::{bpf_probe_read_kernel,bpf_get_current_task},
macros::map,
maps::array::Array,
};
use crate::open::PidPtr;
use crate::vmlinux::{ns_common, nsproxy, pid_namespace, task_struct};

#[map]
static mut NAMESPACE_ID: Array<u32> = Array::with_max_entries(1, 0);

pub unsafe fn is_namespace_ok() -> bool {
NAMESPACE_ID.get(0).map_or(false,|namespace| {
let task: TaskStructPtr = bpf_get_current_task() as TaskStructPtr;
let current_namespace = get_ns_inode(task);
current_namespace.ok().map_or(false, |ns| ns == *namespace)
})
}

pub type TaskStructPtr = *mut task_struct;
pub const MAX_PID: u32 = 4 * 1024 * 1024;
pub const EPERM : i32 = 1;

pub unsafe fn get_thread_pid(task: TaskStructPtr) -> Result<u64, i64> {
let pid: PidPtr = bpf_probe_read_kernel(&(*task).thread_pid)? as PidPtr;
let pid = bpf_probe_read_kernel(&(*pid).ino)? as u64;
Ok(pid)
}

pub unsafe fn get_ns_inode(task: TaskStructPtr) -> Result<u32, i64> {
let nsp: *mut nsproxy = bpf_probe_read_kernel(&(*task).nsproxy).map_err(|e| e as u32)?;
let pns: *mut pid_namespace =
bpf_probe_read_kernel(&(*nsp).pid_ns_for_children).map_err(|e| e as u32)?;
let nsc: ns_common = bpf_probe_read_kernel(&(*pns).ns).map_err(|e| e as u32)?;
bpf_probe_read_kernel(&nsc.inum)
}
112 changes: 35 additions & 77 deletions capable-ebpf/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,99 +7,57 @@
#[allow(dead_code)]
mod vmlinux;

mod capable;
mod open;
pub mod ebpf_util;
mod ret_open;

use aya_ebpf::{
helpers::{bpf_get_current_task, bpf_get_current_uid_gid, bpf_probe_read_kernel},
macros::{kprobe, map},
maps::HashMap,
programs::ProbeContext,
macros::{kretprobe, kprobe},
programs::{RetProbeContext, ProbeContext},
};
use aya_log_ebpf::{debug, info};
use vmlinux::{ns_common, nsproxy, pid_namespace, task_struct};

const MAX_PID: u32 = 4 * 1024 * 1024;
use crate::capable::try_capable;
use crate::open::{try_acl_may_create, try_acl_may_open, try_acl_may_delete, try_acl_may_linkat, try_acl_may_lookup, try_acl_may_follow_link};
use crate::ret_open::try_ret_acl_may_action;

type Key = i32;
type TaskStructPtr = *mut task_struct;
#[kprobe]
pub fn acl_may_open(ctx: ProbeContext) -> u32 {
try_acl_may_open(&ctx).unwrap_or_else(|ret| ret as u32)
}

#[map]
static mut KALLSYMS_MAP: HashMap<Key, u64> = HashMap::with_max_entries(MAX_PID, 0);
#[map]
static mut CAPABILITIES_MAP: HashMap<Key, u64> = HashMap::with_max_entries(MAX_PID, 0);
#[map]
static mut UID_GID_MAP: HashMap<Key, u64> = HashMap::with_max_entries(MAX_PID, 0);
#[map]
static mut PPID_MAP: HashMap<Key, i32> = HashMap::with_max_entries(MAX_PID, 0);
#[map]
static mut PNSID_NSID_MAP: HashMap<Key, u64> = HashMap::with_max_entries(MAX_PID, 0);
#[kprobe]
pub fn acl_may_create(ctx: ProbeContext) -> u32 {
try_acl_may_create(&ctx).unwrap_or_else(|ret| ret as u32)
}

#[kprobe]
pub fn capable(ctx: ProbeContext) -> u32 {
match try_capable(&ctx) {
Ok(ret) => ret,
Err(ret) => ret as u32,
}
pub fn acl_may_delete(ctx: ProbeContext) -> u32 {
try_acl_may_delete(&ctx).unwrap_or_else(|ret| ret as u32)
}

fn try_capable(ctx: &ProbeContext) -> Result<u32, i64> {
info!(ctx, "capable");
unsafe {
let task: TaskStructPtr = bpf_get_current_task() as TaskStructPtr;
debug!(ctx, "debug1");
let task = bpf_probe_read_kernel(&task)?;
debug!(ctx, "debug2");
let ppid: i32 = get_ppid(task)?;
debug!(ctx, "debug3");
let pid: i32 = bpf_probe_read_kernel(&(*task).pid)? as i32;
debug!(ctx, "debug4");
let cap: u64 = (1 << ctx.arg::<u8>(2).unwrap()) as u64;
debug!(ctx, "debug5");
let uid: u64 = bpf_get_current_uid_gid();
debug!(ctx, "debug6");
let zero = 0;
let capval: u64 = *CAPABILITIES_MAP.get(&pid).unwrap_or(&zero);
debug!(ctx, "debug7");
let pinum_inum: u64 = Into::<u64>::into(get_parent_ns_inode(task)?) << 32
| Into::<u64>::into(get_ns_inode(task)?);
debug!(ctx, "debug8");
UID_GID_MAP
.insert(&pid, &uid, 0)
.expect("failed to insert uid");
debug!(ctx, "debug9");
PNSID_NSID_MAP
.insert(&pid, &pinum_inum, 0)
.expect("failed to insert pnsid");
debug!(ctx, "debug10");
PPID_MAP
.insert(&pid, &ppid, 0)
.expect("failed to insert ppid");
debug!(ctx, "debug11");
CAPABILITIES_MAP
.insert(&pid, &(capval | cap), 0)
.expect("failed to insert cap");
}
Ok(0)
#[kprobe]
pub fn acl_may_linkat(ctx: ProbeContext) -> u32 {
try_acl_may_linkat(&ctx).unwrap_or_else(|ret| ret as u32)
}

unsafe fn get_ppid(task: TaskStructPtr) -> Result<i32, i64> {
let parent_task: TaskStructPtr = get_parent_task(task)?;
bpf_probe_read_kernel(&(*parent_task).pid)
#[kprobe]
pub fn acl_may_lookup(ctx: ProbeContext) -> u32 {
try_acl_may_lookup(&ctx).unwrap_or_else(|ret| ret as u32)
}

unsafe fn get_parent_task(task: TaskStructPtr) -> Result<TaskStructPtr, i64> {
bpf_probe_read_kernel(&(*task).parent)
#[kprobe]
pub fn acl_may_follow_link(ctx: ProbeContext) -> u32 {
try_acl_may_follow_link(&ctx).unwrap_or_else(|ret| ret as u32)
}

unsafe fn get_parent_ns_inode(task: TaskStructPtr) -> Result<u32, i64> {
let parent_task: TaskStructPtr = get_parent_task(task)?;
get_ns_inode(parent_task)
#[kretprobe]
pub fn acl_may_ret(ctx: RetProbeContext) -> u32 {
try_ret_acl_may_action(&ctx).unwrap_or_else(|ret| ret as u32)
}

unsafe fn get_ns_inode(task: TaskStructPtr) -> Result<u32, i64> {
let nsp: *mut nsproxy = bpf_probe_read_kernel(&(*task).nsproxy).map_err(|e| e as u32)?;
let pns: *mut pid_namespace =
bpf_probe_read_kernel(&(*nsp).pid_ns_for_children).map_err(|e| e as u32)?;
let nsc: ns_common = bpf_probe_read_kernel(&(*pns).ns).map_err(|e| e as u32)?;
bpf_probe_read_kernel(&nsc.inum)
#[kprobe]
pub fn capable(ctx: ProbeContext) -> u32 {
try_capable(&ctx).unwrap_or_else(|ret| ret as u32)
}

#[panic_handler]
Expand Down
Loading

0 comments on commit 78fcd76

Please sign in to comment.