diff --git a/crates/cardwire-daemon/src/core/inode.rs b/crates/cardwire-daemon/src/core/inode.rs new file mode 100644 index 0000000..0469777 --- /dev/null +++ b/crates/cardwire-daemon/src/core/inode.rs @@ -0,0 +1,156 @@ +//! Used to get inodes for specific files +use std::{ + collections::BTreeMap, fs::{self}, os::unix::fs::MetadataExt +}; + +use anyhow::Result; +use log::warn; + +use crate::core::pci::PciDevice; + +// shouldn't be necessary anymore +const _BLOCKED_PCI_FILES: &[&str] = &[ + "config", + "current_link_speed", + "max_link_speed", + "max_link_width", + "current_link_width", +]; + +pub fn render_to_inode(render: u32) -> Result { + let render_path = format!("/dev/dri/renderD{}", render); + let metadata = fs::metadata(&render_path).map_err(|e| { + warn!("failed to get inode for {}: {}", render_path, e); + e + })?; + let inode = metadata.ino(); + + Ok(inode) +} + +pub fn card_to_inode(card: u32) -> Result { + let card_path = format!("/dev/dri/card{}", card); + let metadata = fs::metadata(&card_path).map_err(|e| { + warn!("failed to get inode for {}: {}", card_path, e); + e + })?; + let inode = metadata.ino(); + + Ok(inode) +} + +// Here return a list of inode that contain the pci card, the audio card and their parents +pub fn pci_to_inode( + pci: String, + parent_pci: &Option, + pci_list: &BTreeMap, +) -> Result> { + let mut inodes: Vec = Vec::new(); + + // quick function that push the inodes into the vec + let push_pci_inode = |pci: &str, inodes: &mut Vec| { + // First get the link ino + let pci_path = format!("/sys/bus/pci/devices/{}", pci); + if let Ok(metadata) = fs::metadata(&pci_path) { + inodes.push(metadata.ino()); + } + + // Now without following link + let pci_path = format!("/sys/bus/pci/devices/{}", pci); + if let Ok(metadata) = fs::symlink_metadata(&pci_path) { + inodes.push(metadata.ino()); + } + }; + + // first we push the pci inode + push_pci_inode(&pci, &mut inodes); + // Also push the audio card inode + push_pci_inode(&pci.replace(".0", ".1"), &mut inodes); + + let mut current_parent: Option = parent_pci.clone(); + while let Some(parent_pci) = current_parent { + if let Some(pci_device) = pci_list.get(&parent_pci) { + current_parent = pci_device.parent_pci().clone(); + push_pci_inode(pci_device.pci_address(), &mut inodes); + } else { + warn!("expected parent pci {} not found in pci_list", parent_pci); + break; + } + } + + Ok(inodes) +} + +/// Used to verify the block status of a single pci +pub fn single_pci_to_inode(pci: &str) -> Result { + let pci_path = format!("/sys/bus/pci/devices/{}", pci); + let metadata = fs::metadata(&pci_path).map_err(|e| { + warn!("failed to get inode for {}: {}", pci_path, e); + e + })?; + let inode = metadata.ino(); + + Ok(inode) +} + +pub fn nvidia_to_inode(nvidia_minor: u32) -> Result { + let nvidia_path = format!("/dev/nvidia{}", nvidia_minor); + let metadata = fs::metadata(&nvidia_path).map_err(|e| { + warn!("failed to get inode for {}: {}", nvidia_path, e); + e + })?; + let inode = metadata.ino(); + + Ok(inode) +} + +/// The only gpu vendor that need it's backlight to be blocked is nvidia +pub fn backlight_to_inode(nvidia_minor: u32) -> Result { + let nvidia_path = format!("/sys/class/backlight/nvidia_{}", nvidia_minor); + let metadata = fs::metadata(&nvidia_path).map_err(|e| { + warn!("failed to get inode for {}: {}", nvidia_path, e); + e + })?; + let inode = metadata.ino(); + + Ok(inode) +} + +pub fn exp_nvidia_inodes() -> Result> { + let mut inodes: Vec = Vec::new(); + + // Get nvidiactl inode + let nvidiactl = "/dev/nvidiactl"; + if let Ok(metadata) = fs::metadata(nvidiactl) { + inodes.push(metadata.ino()); + } + + // Now try to find the vulkan file + // This is for normal distros + /// Files that get blocked when the NVIDIA block is on + const VULKAN_PATHS: &[&str] = &[ + // NixOS + "/run/opengl-driver/share/vulkan/icd.d/", + "/run/opengl-driver-32/share/vulkan/icd.d/", + // Standard Linux + "/etc/vulkan/icd.d/", + "/usr/share/vulkan/icd.d/", + ]; + + for path in VULKAN_PATHS { + let has_nvidia = fs::read_dir(path) + .map(|entries| { + entries.filter_map(|e| e.ok()).any(|entry| { + let name = entry.file_name(); + name == "nvidia_icd.json" || name == "nvidia_icd.x86_64.json" + }) + }) + .unwrap_or(false); + + if has_nvidia && let Ok(metadata) = fs::metadata(path) { + inodes.push(metadata.ino()); + } + } + + Ok(inodes) +} diff --git a/crates/cardwire-daemon/src/core/mod.rs b/crates/cardwire-daemon/src/core/mod.rs index b57e96c..2c90ee6 100644 --- a/crates/cardwire-daemon/src/core/mod.rs +++ b/crates/cardwire-daemon/src/core/mod.rs @@ -1,3 +1,5 @@ pub mod errors; pub mod gpu; pub mod pci; + +pub mod inode; diff --git a/crates/cardwire-daemon/src/interface/config.rs b/crates/cardwire-daemon/src/interface/config.rs index 79f2956..190b046 100644 --- a/crates/cardwire-daemon/src/interface/config.rs +++ b/crates/cardwire-daemon/src/interface/config.rs @@ -3,7 +3,7 @@ use std::sync::{ }; use crate::{file::CardwireConfig, interface::Modes}; -use cardwire_ebpf::EbpfBlocker; +use cardwire_ebpf::{EbpfBlocker, EbpfSettings}; use tokio::sync::RwLock; use zbus::{fdo, interface}; @@ -83,15 +83,9 @@ impl ConfigInterface { .store(state, Ordering::Relaxed); let mut blocker = self.blocker.write().await; // change the value in the ebpf map - if state { - blocker - .block_kind(&state.to_string(), cardwire_ebpf::BlockKind::NvidiaSetting) - .map_err(|e| fdo::Error::Failed(format!("failed to set nvidia block: {}", e))) - } else { - blocker - .unblock_kind(&state.to_string(), cardwire_ebpf::BlockKind::NvidiaSetting) - .map_err(|e| fdo::Error::Failed(format!("failed to set nvidia block: {}", e))) - } + blocker + .set_ebpf_setting(EbpfSettings::ExperimentalNvidia, state.into()) + .map_err(|e| fdo::Error::Failed(format!("failed to set nvidia block: {}", e))) } #[zbus(property)] pub async fn battery_auto_switch(&self) -> fdo::Result { diff --git a/crates/cardwire-daemon/src/interface/debug.rs b/crates/cardwire-daemon/src/interface/debug.rs index 88a0ad2..c9e105e 100644 --- a/crates/cardwire-daemon/src/interface/debug.rs +++ b/crates/cardwire-daemon/src/interface/debug.rs @@ -10,7 +10,7 @@ use tokio::{sync::RwLock, task}; use zbus::{fdo, interface}; use crate::{ - file::{CardwireGpuState, CardwireModeState}, interface::{ConfigMemory, GpuInterface} + file::{CardwireGpuState, CardwireModeState}, interface::{ConfigMemory, GpuInterface, Modes} }; #[derive(Clone)] @@ -90,6 +90,8 @@ impl DebugInterface { info!("pci list changed, refreshing the internal gpu list"); // Overwrite old list *pci_list = new_pci_list.clone(); + drop(pci_list); // drop lock to prevent deadlocks when blocking + let mut power_tasks = self.power_tasks.write().await; // get rid of the old gpu api and the old tasks @@ -106,12 +108,12 @@ impl DebugInterface { gpu_interfaces.clear(); // Read the new list let mut new_gpu_list = - gpu::read_gpu(&pci_list).map_err(|err| fdo::Error::Failed(err.to_string()))?; + gpu::read_gpu(&new_pci_list).map_err(|err| fdo::Error::Failed(err.to_string()))?; if let Err(err) = check_default_drm_class(&mut new_gpu_list) { warn!("Failed to determine default GPU: {}", err); } for (id, device) in new_gpu_list { - let gpu = GpuInterface::build( + let mut gpu = GpuInterface::build( device, Arc::clone(&self.blocker), Arc::clone(&self.pci_list), @@ -119,6 +121,36 @@ impl DebugInterface { Arc::clone(&self.mode_state), ) .map_err(|err| fdo::Error::Failed(err.to_string()))?; + + let mode = self.mode_state.read().await.mode(); + let config = self + .config + .auto_apply_gpu_state + .load(std::sync::atomic::Ordering::Relaxed); + + let should_block = match mode { + Modes::Integrated | Modes::Smart => !gpu.device.is_default(), + Modes::Hybrid => false, + Modes::Manual => { + let state = self.gpu_state.read().await; + state.gpu_block_state(gpu.device.pci.pci_address()) && config + } + }; + + if should_block { + info!( + "GPU {} should be blocked, re-applying block on hotplug", + gpu.device.name() + ); + if let Err(e) = gpu.block_gpu().await { + warn!( + "failed to automatically re-block {}: {}", + gpu.device.name(), + e + ); + } + } + gpu_interfaces.insert(id, gpu); } diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index 7b6750f..a4b86d5 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -6,11 +6,13 @@ use std::{ use crate::{ core::{ - gpu::{DbusGpuDevice, GpuDevice, GpuVendor}, pci::PciDevice + gpu::{DbusGpuDevice, GpuDevice, GpuVendor}, inode::{ + backlight_to_inode, card_to_inode, nvidia_to_inode, pci_to_inode, render_to_inode, single_pci_to_inode + }, pci::PciDevice }, file::{CardwireGpuState, CardwireModeState}, interface::Modes }; -use cardwire_ebpf::{BlockKind, EbpfBlocker}; -use log::{info, warn}; +use cardwire_ebpf::EbpfBlocker; +use log::{error, info, warn}; use tokio::sync::RwLock; use zbus::{fdo, interface, object_server::SignalEmitter}; @@ -58,46 +60,60 @@ impl GpuInterface { let mut blocker = self.blocker.write().await; let pci_list = self.pci_list.read().await; // First block the card id - blocker - .block_kind(&self.device.card().to_string(), BlockKind::Card) - .into_fdo()?; - // block the render id - blocker - .block_kind(&self.device.render().to_string(), BlockKind::Render) - .into_fdo()?; - // block the pci - blocker - .block_kind(self.device.pci.pci_address(), BlockKind::Pci) - .into_fdo()?; - // Also block the audio card - if self.device.pci.pci_address().ends_with(".0") { - let gpu_audio_adress = self.device.pci.pci_address().replace(".0", ".1"); - blocker - .block_kind(&gpu_audio_adress, BlockKind::Pci) - .into_fdo()?; - } - // Check if gpu has a parent pci - // first pci to block - let mut current_parent = self.device.pci.parent_pci().clone(); - - while let Some(parent_pci) = current_parent { - if let Some(pci_device) = pci_list.get(&parent_pci) { - blocker - .block_kind(pci_device.pci_address(), BlockKind::Pci) - .into_fdo()?; - current_parent = pci_device.parent_pci().clone(); - } else { - warn!("expected parent pci {} not found in pci_list", parent_pci); - break; + match card_to_inode(*self.device.card()) { + Ok(inode) => blocker.block_inode(inode).into_fdo()?, + Err(err) => { + error!("failed to block card{}: {}", *self.device.card(), err); + return Err(err).into_fdo(); } - } + }; + match render_to_inode(*self.device.render()) { + Ok(inode) => blocker.block_inode(inode).into_fdo()?, + Err(err) => { + error!("failed to block render{}: {}", *self.device.render(), err); + return Err(err).into_fdo(); + } + }; + match pci_to_inode( + self.device.pci.pci_address().to_string(), + self.device.pci.parent_pci(), + &pci_list, + ) { + Ok(inodes) => { + for inode in inodes { + if let Err(err) = blocker.block_inode(inode) { + error!("failed to block inode(pci) {}: {}", inode, err); + return Err(err).into_fdo(); + } + } + } + Err(err) => { + error!( + "failed to block pci {}: {}", + self.device.pci.pci_address(), + err + ); + return Err(err).into_fdo(); + } + }; // the last one, block nvidia if self.device.gpu_vendor() == GpuVendor::Nvidia && let Some(minor) = self.device.nvidia_minor() { - blocker - .block_kind(&minor.to_string(), BlockKind::Nvidia) - .into_fdo()?; + match nvidia_to_inode(*minor) { + Ok(inode) => blocker.block_inode(inode).into_fdo()?, + Err(err) => { + error!("failed to block nvidia{}: {}", *self.device.render(), err); + return Err(err).into_fdo(); + } + }; + match backlight_to_inode(*minor) { + Ok(inode) => blocker.block_inode(inode).into_fdo()?, + Err(err) => { + error!("failed to block backlight nvidia_{}: {}", minor, err); + return Err(err).into_fdo(); + } + }; } Ok(()) } @@ -106,46 +122,44 @@ impl GpuInterface { let mut blocker = self.blocker.write().await; let pci_list = self.pci_list.read().await; // First unblock the card id - blocker - .unblock_kind(&self.device.card().to_string(), BlockKind::Card) - .into_fdo()?; - // unblock the render id - blocker - .unblock_kind(&self.device.render().to_string(), BlockKind::Render) - .into_fdo()?; - // unblock the pci - blocker - .unblock_kind(self.device.pci.pci_address(), BlockKind::Pci) - .into_fdo()?; - // Also unblock the audio card - if self.device.pci.pci_address().ends_with(".0") { - let gpu_audio_adress = self.device.pci.pci_address().replace(".0", ".1"); - blocker - .unblock_kind(&gpu_audio_adress, BlockKind::Pci) - .into_fdo()?; - } - // Check if gpu has a parent pci - // first pci to unblock - let mut current_parent = self.device.pci.parent_pci().clone(); - - while let Some(parent_pci) = current_parent { - if let Some(pci_device) = pci_list.get(&parent_pci) { - blocker - .unblock_kind(pci_device.pci_address(), BlockKind::Pci) - .into_fdo()?; - current_parent = pci_device.parent_pci().clone(); - } else { - warn!("expected parent pci {} not found in pci_list", parent_pci); - break; + match card_to_inode(*self.device.card()) { + Ok(inode) => blocker.unblock_inode(inode).into_fdo()?, + Err(err) => return Err(err).into_fdo(), + }; + match render_to_inode(*self.device.render()) { + Ok(inode) => blocker.unblock_inode(inode).into_fdo()?, + Err(err) => return Err(err).into_fdo(), + }; + match pci_to_inode( + self.device.pci.pci_address().to_string(), + self.device.pci.parent_pci(), + &pci_list, + ) { + Ok(inodes) => { + for inode in inodes { + blocker.unblock_inode(inode).into_fdo()? + } } - } + Err(err) => return Err(err).into_fdo(), + }; // the last one, unblock nvidia if self.device.gpu_vendor() == GpuVendor::Nvidia && let Some(minor) = self.device.nvidia_minor() { - blocker - .unblock_kind(&minor.to_string(), BlockKind::Nvidia) - .into_fdo()?; + match nvidia_to_inode(*minor) { + Ok(inode) => blocker.unblock_inode(inode).into_fdo()?, + Err(err) => { + error!("failed to block nvidia{}: {}", *self.device.render(), err); + return Err(err).into_fdo(); + } + }; + match backlight_to_inode(*minor) { + Ok(inode) => blocker.unblock_inode(inode).into_fdo()?, + Err(err) => { + error!("failed to unblock backlight nvidia_{}: {}", minor, err); + return Err(err).into_fdo(); + } + }; } Ok(()) } @@ -153,22 +167,32 @@ impl GpuInterface { pub async fn gpu_blocked(&self) -> fdo::Result { let blocker = self.blocker.read().await; - Ok(blocker - .is_kind_blocked(self.device.pci.pci_address(), BlockKind::Pci) - .into_fdo()? - && blocker - .is_kind_blocked(&self.device.card().to_string(), BlockKind::Card) - .into_fdo()? - && blocker - .is_kind_blocked(&self.device.render().to_string(), BlockKind::Render) - .into_fdo()? - && if let Some(minor) = self.device.nvidia_minor() { - blocker - .is_kind_blocked(&minor.to_string(), BlockKind::Nvidia) - .into_fdo()? - } else { - true - }) + let card = match card_to_inode(*self.device.card()) { + Ok(inode) => blocker.is_inode_blocked(inode).into_fdo()?, + Err(err) => return Err(err).into_fdo(), + }; + let render = match render_to_inode(*self.device.render()) { + Ok(inode) => blocker.is_inode_blocked(inode).into_fdo()?, + Err(err) => return Err(err).into_fdo(), + }; + let pci = match single_pci_to_inode(self.device.pci.pci_address()) { + Ok(inode) => blocker.is_inode_blocked(inode).into_fdo()?, + Err(err) => return Err(err).into_fdo(), + }; + let nvidia = match self.device.nvidia_minor() { + // GPU is nvidia + Some(minor) => { + if let Ok(inode) = nvidia_to_inode(*minor) { + blocker.is_inode_blocked(inode).into_fdo()? + } else { + false + } + } + // GPU isnt nvidia, ignore but keep true + None => true, + }; + + Ok(card && render && pci && nvidia) } /// read fd link to find which apps opened the gpu async fn lsof_read(&self, s: &str) -> fdo::Result> { diff --git a/crates/cardwire-daemon/src/interface/mode.rs b/crates/cardwire-daemon/src/interface/mode.rs index 70aa7d1..0819e45 100644 --- a/crates/cardwire-daemon/src/interface/mode.rs +++ b/crates/cardwire-daemon/src/interface/mode.rs @@ -88,7 +88,7 @@ impl ModeInterface { }) } - /// set the mode in the CURRENT_MODE's bpf map + /// set the mode in the `cardwire_mode` bpf map async fn update_mode_bpf_map(&self, mode: Modes) -> fdo::Result<()> { let mut mode_map = self.mode_map.lock().await; let mode: u32 = Modes::into(mode); diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index 2e154de..d30bec0 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -1,35 +1,20 @@ //! where the struct and impl are declared use crate::{ analyzer::CardwireAnalyzer, core::{ - gpu::{self, GpuVendor, check_default_drm_class}, pci + gpu::{self, GpuVendor, check_default_drm_class}, inode::exp_nvidia_inodes, pci }, file::{CardwireConfig, CardwireGpuState, CardwireModeState}, interface::{ ConfigInterface, ConfigMemory, DebugInterface, GpuInterface, ModeInterface, Modes } }; use anyhow::{Context, Result}; -use cardwire_ebpf::{BlockKind, EbpfBlocker}; -use log::warn; +use cardwire_ebpf::{EbpfBlocker, EbpfSettings}; +use log::{error, warn}; use std::{collections::BTreeMap, sync::Arc}; use tokio::sync::RwLock; use zbus::{ fdo::{self}, interface }; -const BLOCKED_PCI_FILES: &[&str] = &[ - "config", - "current_link_speed", - "max_link_speed", - "max_link_width", - "current_link_width", -]; -/// Files that get blocked when the NVIDIA block is on -const BLOCKED_NVIDIA_FILES: &[&str] = &[ - "libGLX_nvidia.so.0", - "nvidia_icd.json", - "nvidia_icd.x86_64.json", - "nvidiactl", -]; - #[derive(Clone)] pub struct DaemonManager { pub mode_interface: ModeInterface, @@ -122,30 +107,41 @@ impl DaemonManager { .experimental_nvidia_block .load(std::sync::atomic::Ordering::Relaxed); let mode = self.debug_interface.mode_state.read().await; - let mut blocker = self.debug_interface.blocker.write().await; let mut state = self.debug_interface.gpu_state.write().await; - blocker.block_kind(&config.to_string(), cardwire_ebpf::BlockKind::NvidiaSetting)?; - - for file in BLOCKED_PCI_FILES { - blocker.block_kind(file, BlockKind::File)?; - } - let default: bool = state.is_default_state(); - // if there is an nvidia device, block nvidia file once + let mut blocker = self.debug_interface.blocker.write().await; + // Whitelist cardwire pid before starting + let pid = std::process::id(); + if let Err(err) = blocker.whitelist_cardwire_pid(pid) { + error!("failed to whitelist cardwire's pid: {}", err); + return Err(err.into()); + }; + + // Set nvidia setting + blocker.set_ebpf_setting(EbpfSettings::ExperimentalNvidia, config.into())?; + // Push nvidia inodes, if empty/error just ignore for (_, gpu) in gpus_list.iter() { - if gpu.device.gpu_vendor() == GpuVendor::Nvidia { - for file in BLOCKED_NVIDIA_FILES { - blocker.block_kind(file, BlockKind::NvidiaFile)?; + if gpu.device.gpu_vendor() == GpuVendor::Nvidia + && let Ok(inodes) = exp_nvidia_inodes() + && !inodes.is_empty() + { + for inode in inodes { + if let Err(err) = blocker.block_exp_inode(inode) { + error!("failed to block nvidia's file {}: {}", inode, err); + } } break; } } + + drop(blocker); + + let default: bool = state.is_default_state(); if default { for (_, gpu) in gpus_list.iter() { state.save_state(&gpu.device, false).await?; } } // Dropping the locks prevent set_mode being stuck - drop(blocker); drop(gpus_list); drop(state); let mode_to_apply = Modes::into(mode.mode()); diff --git a/crates/cardwire-ebpf/src/bpf.c b/crates/cardwire-ebpf/src/bpf.c index 88f336e..20ab3c1 100644 --- a/crates/cardwire-ebpf/src/bpf.c +++ b/crates/cardwire-ebpf/src/bpf.c @@ -1,374 +1,15 @@ +/// This file only contain the bpf programs, functions are defined in helpers.c #include #include #include #include #include #include +#include "bpf.h" +#include "helpers.h" char __license[] SEC("license") = "GPL"; -#define ENOENT 2 - -// kernel type definitions - -// For inode -struct hlist_node { - struct hlist_node *next, **pprev; -} __attribute__((preserve_access_index)); - -struct hlist_head { - struct hlist_node *first; -} __attribute__((preserve_access_index)); - -struct inode { - __u16 i_mode; - __u32 i_rdev; - struct hlist_head i_dentry; -} __attribute__((preserve_access_index)); - -struct qstr { - union { - struct { - __u32 hash; - __u32 len; - }; - __u64 hash_len; - }; - const unsigned char *name; -} __attribute__((preserve_access_index)); - -struct dentry___old { - struct qstr d_name; - struct dentry *d_parent; - struct inode *d_inode; - union { - struct hlist_node d_alias; - } d_u; -} __attribute__((preserve_access_index)); - -struct dentry { - struct qstr d_name; - struct dentry *d_parent; - struct inode *d_inode; - struct hlist_node d_alias; -} __attribute__((preserve_access_index)); - -struct path { - struct dentry *dentry; -} __attribute__((preserve_access_index)); - -struct file { - struct path f_path; -} __attribute__((preserve_access_index)); - -struct event_t { - __u32 pid; -}; - -struct close_t { - __u32 pid; -}; - -struct report_t { - __u32 pid; - char comm[32]; -}; - -struct trace_event_raw_sys_exit { - __u64 unused_common_fields; - long id; - long ret; -} __attribute__((preserve_access_index)); - -struct task_struct { - int tgid; - struct task_struct *real_parent; - int exit_code; -} __attribute__((preserve_access_index)); -// EBPF maps -// This one is to report the events to cardwire -struct { - __uint(type, BPF_MAP_TYPE_RINGBUF); - __uint(max_entries, 256 * 1024); -} EXEC_EVENTS SEC(".maps"); - -struct { - __uint(type, BPF_MAP_TYPE_RINGBUF); - __uint(max_entries, 256 * 1024); -} CLOSE_EVENTS SEC(".maps"); - -// This one is to report the app block to cardwire -struct { - __uint(type, BPF_MAP_TYPE_RINGBUF); - __uint(max_entries, 256 * 1024); -} REPORT SEC(".maps"); - -// List of blocked comm -// Used for smart mode -struct { - __uint(type, BPF_MAP_TYPE_HASH); - __uint(max_entries, 16384); - __type(key, __u32); - __type(value, __u8); -} ALLOWED_PID SEC(".maps"); - -/* - mode map, mode should be stored in key 0 - possible values: - integrated = 0 - hybrid = 1 - manual = 2 - enforce = 3 - smart = 4 -*/ -struct { - __uint(type, BPF_MAP_TYPE_HASH); - __uint(max_entries, 1); - __type(key, __u8); - __type(value, __u8); -} CURRENT_MODE SEC(".maps"); - -struct { - __uint(type, BPF_MAP_TYPE_HASH); - __uint(max_entries, 1024); - __type(key, __u32); - __type(value, __u8); -} BLOCKED_RENDERID SEC(".maps"); - -struct { - __uint(type, BPF_MAP_TYPE_HASH); - __uint(max_entries, 1024); - __type(key, __u32); - __type(value, __u8); -} BLOCKED_NVIDIAID SEC(".maps"); - -struct { - __uint(type, BPF_MAP_TYPE_HASH); - __uint(max_entries, 1024); - __type(key, __u32); - __type(value, __u8); -} BLOCKED_CARDID SEC(".maps"); - -struct { - __uint(type, BPF_MAP_TYPE_HASH); - __uint(max_entries, 1024); - __type(key, char[16]); - __type(value, __u8); -} BLOCKED_PCI SEC(".maps"); - -struct { - __uint(type, BPF_MAP_TYPE_HASH); - __uint(max_entries, 1024); - __type(key, char[30]); - __type(value, __u8); -} BLOCKED_PCI_FILES SEC(".maps"); - -struct { - __uint(type, BPF_MAP_TYPE_HASH); - __uint(max_entries, 64); - __type(key, __u32); - __type(value, __u8); -} SETTINGS SEC(".maps"); - -struct { - __uint(type, BPF_MAP_TYPE_HASH); - __uint(max_entries, 1024); - __type(key, char[30]); - __type(value, __u8); -} BLOCKED_NVIDIA_FILES SEC(".maps"); - -static __always_inline int get_pci_addr(struct dentry *dentry, char *pci_addr, - int size) -{ - struct dentry *parent; - const unsigned char *parent_name; - int ret; - - if (!dentry) - return 1; - - parent = BPF_CORE_READ(dentry, d_parent); - if (!parent) - return 1; - - parent_name = BPF_CORE_READ(parent, d_name.name); - ret = bpf_core_read_str(pci_addr, size, parent_name); - - // PCI address string is 12 chars + 1 null byte - if (ret < 13) - return 1; - - // Check for PCI address format (eg: 0000:00:00.0) - if (pci_addr[4] == ':' && pci_addr[7] == ':' && pci_addr[10] == '.') { - return 0; - } - - return 1; -} -static __always_inline int check_backlight_path(struct dentry *dentry) -{ - if (!dentry) - return 0; - - // current dir/file - struct qstr q = BPF_CORE_READ(dentry, d_name); - char name[16] = {}; - if (bpf_core_read_str(name, sizeof(name), q.name) < 0) { - return 0; - } - - // get parent folder - char p_name[16] = {}; - struct dentry *parent = BPF_CORE_READ(dentry, d_parent); - if (parent) { - const unsigned char *p_name_ptr = - BPF_CORE_READ(parent, d_name.name); - bpf_core_read_str(p_name, sizeof(p_name), p_name_ptr); - } - - // NVIDIA - char *t = (__builtin_memcmp(name, "nvidia_", 7) == 0) ? name : - (__builtin_memcmp(p_name, "nvidia_", 7) == 0) ? p_name : - NULL; - - if (t) { - if (t[7] >= '0' && t[7] <= '9') { - __u32 id = 0; - -#pragma unroll - for (int i = 7; i < 10 && t[i] >= '0' && t[i] <= '9'; - i++) { - id = id * 10 + (t[i] - '0'); - } - - if (bpf_map_lookup_elem(&BLOCKED_NVIDIAID, &id)) { - return 1; - } - } - } - - return 0; -} - -static __always_inline int is_blocked_device(struct dentry *d) -{ - if (!d) { - return 0; - } - // if it's cardwired, exit - char comm[16] = {}; - bpf_get_current_comm(comm, sizeof(comm)); - if (__builtin_memcmp(comm, "cardwired", 9) == 0) { - return 0; - } - // same for udev - if (__builtin_memcmp(comm, "(udev-worker)", 13) == 0) { - return 0; - } - bool blocked = false; - - struct inode *inode = BPF_CORE_READ(d, d_inode); - // Match card/render/nvidia minor - if (inode) { - __u16 i_mode = BPF_CORE_READ(inode, i_mode); - if ((i_mode & 00170000) == 00020000) { - __u32 i_rdev = BPF_CORE_READ(inode, i_rdev); - unsigned int major = i_rdev >> 20; - unsigned int minor = i_rdev & 0xFFFFF; - if (major == 226) { - __u32 id = minor; - if (bpf_map_lookup_elem(&BLOCKED_CARDID, &id)) { - blocked = true; - goto end; - } - if (bpf_map_lookup_elem(&BLOCKED_RENDERID, - &id)) { - blocked = true; - goto end; - } - } else if (major == 195) { - __u32 id = minor; - if (bpf_map_lookup_elem(&BLOCKED_NVIDIAID, - &id)) { - blocked = true; - goto end; - } - } - } - } - struct qstr q = BPF_CORE_READ(d, d_name); - // ignore long files - if (!q.name || q.len > 30) { - goto end; - } - char buf[32] = {}; - if (bpf_core_read_str(buf, sizeof(buf), q.name) < 0) { - goto end; - } - // Blocks specific NVIDIA files, it's dangerous and will only work if one nvidia gpu is blocked - __u32 block_nvidia_key = 0; - if (bpf_map_lookup_elem(&SETTINGS, &block_nvidia_key)) { - if (bpf_map_lookup_elem(&BLOCKED_NVIDIA_FILES, buf)) { - __u32 id0 = 0, id1 = 1; - if (bpf_map_lookup_elem(&BLOCKED_NVIDIAID, &id0) && - !bpf_map_lookup_elem(&BLOCKED_NVIDIAID, &id1)) { - blocked = true; - goto end; - } - } - } - // PCI Part - if (bpf_map_lookup_elem(&BLOCKED_PCI_FILES, buf)) { - char pci_addr[16] = {}; - if (get_pci_addr(d, pci_addr, sizeof(pci_addr)) != 0) { - goto end; - } - pci_addr[12] = '\0'; - - if (bpf_map_lookup_elem(&BLOCKED_PCI, pci_addr)) { - blocked = true; - goto end; - } - } - // backlight - if (check_backlight_path(d) == 1) { - blocked = true; - goto end; - } - -end: - if (!blocked) { - return 0; - } - // get mode - __u32 key = 0; - __u8 *mode = bpf_map_lookup_elem(&CURRENT_MODE, &key); - // get pid and ppid - __u32 pid = bpf_get_current_pid_tgid() >> 32; - struct task_struct *task = (struct task_struct *)bpf_get_current_task(); - __u32 ppid = BPF_CORE_READ(task, real_parent, tgid); - // if map lookup fails, or we are not blocking, or it's hybrid mode, allow - if (!mode || *mode == 1) { - return 0; - } - - // if is hybrid/manual mode, block - if (*mode == 0 || *mode == 2) { - return -ENOENT; - } - - // if smart, check the pid list - if (*mode == 3) { - if (!bpf_map_lookup_elem(&ALLOWED_PID, &pid) && - !bpf_map_lookup_elem(&ALLOWED_PID, &ppid)) { - // Neither pid nor ppid is allowed, block - return -ENOENT; - } - } - - return 0; -} - /* LSM to prevent open on DRM */ @@ -376,6 +17,10 @@ static __always_inline int is_blocked_device(struct dentry *d) SEC("lsm/file_open") int BPF_PROG(file_open, struct file *file) { + // If hybrid skip + if (is_hybrid()) + return 0; + struct dentry *d = BPF_CORE_READ(file, f_path.dentry); return is_blocked_device(d); } @@ -385,6 +30,10 @@ int BPF_PROG(file_open, struct file *file) SEC("lsm/inode_permission") int BPF_PROG(inode_permission, struct inode *inode, int mask) { + // If hybrid skip + if (is_hybrid()) + return 0; + char filename[16] = {}; const unsigned char *name_ptr = NULL; /* @@ -414,6 +63,8 @@ int BPF_PROG(inode_permission, struct inode *inode, int mask) SEC("lsm/inode_getattr") int BPF_PROG(inode_getattr, const struct path *path) { + if (is_hybrid()) + return 0; struct dentry *d = BPF_CORE_READ(path, dentry); return is_blocked_device(d); } @@ -424,19 +75,14 @@ int BPF_PROG(inode_getattr, const struct path *path) SEC("tracepoint/sched/sched_process_exec") int trace_exec(void *ctx) { - // get current cardwired mode, key should always be 0 - __u32 key = 0; - __u8 *mode = bpf_map_lookup_elem(&CURRENT_MODE, &key); - if (!mode) { - return 0; - } - //if mode is not smart or enforce, skip - if (*mode != 3) { + // if mode not smart, skip + if (!is_smart()) return 0; - } + // Init the struct struct event_t *rb_data = {}; - rb_data = bpf_ringbuf_reserve(&EXEC_EVENTS, sizeof(struct event_t), 0); + rb_data = + bpf_ringbuf_reserve(&cw_exec_events, sizeof(struct event_t), 0); // Check if present if (!rb_data) { return 0; @@ -452,23 +98,112 @@ int trace_exec(void *ctx) SEC("tracepoint/sched/sched_process_exit") int trace_process_exit(void *ctx) { - // get current cardwired mode, key should always be 0 - __u32 key = 0; - __u8 *mode = bpf_map_lookup_elem(&CURRENT_MODE, &key); - if (!mode) { - return 0; - } - //if mode is not smart, skip - if (*mode != 3) { + // if mode not smart, skip + if (!is_smart()) return 0; - } + + // Now create and send a close event containing the pid to the userspace struct close_t *rb_data = {}; - rb_data = bpf_ringbuf_reserve(&CLOSE_EVENTS, sizeof(struct close_t), 0); + rb_data = bpf_ringbuf_reserve(&cw_close_events, sizeof(struct close_t), + 0); if (!rb_data) { return 0; } rb_data->pid = bpf_get_current_pid_tgid() >> 32; bpf_ringbuf_submit(rb_data, 0); + return 0; +} + +SEC("tp/syscalls/sys_enter_getdents64") +int cardwire_sys_enter_getdents64(struct trace_event_raw_sys_enter *ctx) +{ + // If hybrid skip + if (is_hybrid()) + return 0; + + __u32 pid = bpf_get_current_pid_tgid() >> 32; + + struct task_struct *task = (struct task_struct *)bpf_get_current_task(); + __u32 ppid = BPF_CORE_READ(task, real_parent, tgid); + + // if it's cardwire skip it + if (is_cardwire_process(pid)) + return 0; + + // skip if whitelisted + if (is_process_whitelisted()) + return 0; + + // if we in smart mode and the pid is allowed, skip + if (is_smart()) { + if (is_pid_allowed(pid, ppid)) { + // if allowed, skip + return 0; + } + } + + // Get the memory address of the buffer where the list of entry will be stored + __u64 dirents_buf = ctx->args[1]; + if (!dirents_buf) { + return 0; + } + // Save addr into map + bpf_map_update_elem(&cw_dirent, &pid, &dirents_buf, BPF_ANY); + return 0; +} + +SEC("tp/syscalls/sys_exit_getdents64") +int cardwire_sys_exit_getdents64(struct trace_event_raw_sys_exit *ctx) +{ + // If hybrid skip + if (is_hybrid()) + return 0; + + __u32 pid = bpf_get_current_pid_tgid() >> 32; + + struct task_struct *task = (struct task_struct *)bpf_get_current_task(); + __u32 ppid = BPF_CORE_READ(task, real_parent, tgid); + + // if it's cardwire skip it + if (is_cardwire_process(pid)) + return 0; + // skip if whitelisted + if (is_process_whitelisted()) + return 0; + // if we in smart mode and the pid is allowed, skip + if (is_smart()) { + if (is_pid_allowed(pid, ppid)) { + // if allowed, skip + return 0; + } + } + + __u64 *dirents_buf_ptr = bpf_map_lookup_elem(&cw_dirent, &pid); + + if (!dirents_buf_ptr) + return 0; + + __u64 dirents_buf = *dirents_buf_ptr; + + // Clean up the map immediately so it doesn't fill up + bpf_map_delete_elem(&cw_dirent, &pid); + + // If getdents64 return 0 bytes + if (ctx->ret <= 0) { + return 0; + } + + struct dirents_data_t dirents_data = { + .bpos = 0, + .dirents_buf = dirents_buf, + .buff_size = ctx->ret, + .d_reclen = 0, + .last_visible_bpos = 0xFFFFFFFF, + }; + + // Run the loop + bpf_loop(10000, patch_dirent_if_found, &dirents_data, 0); + return 0; } \ No newline at end of file diff --git a/crates/cardwire-ebpf/src/bpf.h b/crates/cardwire-ebpf/src/bpf.h new file mode 100644 index 0000000..729767b --- /dev/null +++ b/crates/cardwire-ebpf/src/bpf.h @@ -0,0 +1,170 @@ + +// Return value used for file blocking +#define ENOENT 2 + +// kernel type definitions + +// For inode +struct hlist_node { + struct hlist_node *next, **pprev; +} __attribute__((preserve_access_index)); + +struct hlist_head { + struct hlist_node *first; +} __attribute__((preserve_access_index)); + +struct inode { + __u64 i_ino; + struct hlist_head i_dentry; +} __attribute__((preserve_access_index)); + +struct dentry___old { + struct inode *d_inode; + union { + struct hlist_node d_alias; + } d_u; +} __attribute__((preserve_access_index)); + +struct dentry { + struct inode *d_inode; + struct hlist_node d_alias; +} __attribute__((preserve_access_index)); + +struct path { + struct dentry *dentry; +} __attribute__((preserve_access_index)); + +struct file { + struct path f_path; +} __attribute__((preserve_access_index)); + +struct dirents_data_t { + __u32 bpos; + __u64 dirents_buf; + long buff_size; + __u16 d_reclen; + __u32 last_visible_bpos; +}; + +struct linux_dirent64 { + __u64 d_ino; + __s64 d_off; + short unsigned int d_reclen; + unsigned char d_type; + char d_name[0]; +} __attribute__((preserve_access_index)); + +struct trace_event_raw_sys_enter { + __u64 unused_common_fields; + long id; + unsigned long args[6]; + char __data[0]; +} __attribute__((preserve_access_index)); + +struct trace_event_raw_sys_exit { + __u64 unused_common_fields; + long id; + long ret; +} __attribute__((preserve_access_index)); + +struct task_struct { + int tgid; + struct task_struct *real_parent; + int exit_code; +} __attribute__((preserve_access_index)); + +// Ring related struct +struct event_t { + __u32 pid; +}; + +struct close_t { + __u32 pid; +}; + +struct report_t { + __u32 pid; + char comm[32]; +}; + +// EBPF maps +// This one is to report the events to cardwire +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 256 * 1024); +} cw_exec_events SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 256 * 1024); +} cw_close_events SEC(".maps"); + +// This one is to report the app block to cardwire +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 256 * 1024); +} cw_report SEC(".maps"); + +// List of blocked comm +// Used for smart mode +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 16384); + __type(key, __u32); + __type(value, __u8); +} cw_allowed_pid SEC(".maps"); + +/* + mode map, mode should be stored in key 0 + possible values: + integrated = 0 + hybrid = 1 + manual = 2 + smart = 3 +*/ +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 1); + __type(key, __u8); + __type(value, __u8); +} cw_mode SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 2048); + __type(key, __u64); + __type(value, __u8); +} cw_blocked_ino SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 2048); + __type(key, __u64); + __type(value, __u8); +} cw_exp_blk_ino SEC(".maps"); + +/* + setting map, setting are stored by key + possible values: + exp_nvidia = 0 +*/ +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 64); + __type(key, __u8); + __type(value, __u8); +} cw_settings SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 1); + __type(key, __u8); + __type(value, __u32); +} cw_daemon_pid SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 1024); + __type(key, __u32); + __type(value, __u64); +} cw_dirent SEC(".maps"); diff --git a/crates/cardwire-ebpf/src/helpers.h b/crates/cardwire-ebpf/src/helpers.h new file mode 100644 index 0000000..79974ee --- /dev/null +++ b/crates/cardwire-ebpf/src/helpers.h @@ -0,0 +1,197 @@ + +static __always_inline int is_hybrid() +{ + // get current cardwired mode, key should always be 0 + __u32 key = 0; + __u8 *mode = bpf_map_lookup_elem(&cw_mode, &key); + if (!mode) { + return false; + } + //if mode is hybrid, return true + if (*mode == 1) { + return true; + } + return false; +} + +static __always_inline int is_smart() +{ + // get current cardwired mode, key should always be 0 + __u32 key = 0; + __u8 *mode = bpf_map_lookup_elem(&cw_mode, &key); + if (!mode) { + return false; + } + //if mode is smart, true + if (*mode == 3) { + return true; + } + return false; +} + +static __always_inline int is_cardwire_process(__u32 pid) +{ + // key 0 contain cardwire pid, if pid/ppid = cardwire's pid then allow + __u8 cardwire_key = 0; + __u32 *cardwire_pid = + bpf_map_lookup_elem(&cw_daemon_pid, &cardwire_key); + if (cardwire_pid && *cardwire_pid == pid) { + return true; + } + return false; +} + +/// get if the process is whitelisted using comm name +static __always_inline int is_process_whitelisted() +{ + char comm[16] = {}; + bpf_get_current_comm(comm, sizeof(comm)); + // whitelist udev for pci hotplug + if (__builtin_memcmp(comm, "(udev-worker)", 13) == 0) { + return true; + } + return false; +} + +/// check if the pid is in the allow list, smart mode only +static __always_inline int is_pid_allowed(__u32 pid, __u32 ppid) +{ + return bpf_map_lookup_elem(&cw_allowed_pid, &pid) || + bpf_map_lookup_elem(&cw_allowed_pid, &ppid); +} + +/// check if experimental nvidia blocking is enabled +static __always_inline int is_nvidia_enabled() +{ + __u8 key = 0; + __u8 *value = bpf_map_lookup_elem(&cw_settings, &key); + if (!value) + return false; + return *value; +} + +static __always_inline int is_blocked_device(struct dentry *d) +{ + if (!d) { + return 0; + } + // get pid and ppid + __u32 pid = bpf_get_current_pid_tgid() >> 32; + struct task_struct *task = (struct task_struct *)bpf_get_current_task(); + __u32 ppid = BPF_CORE_READ(task, real_parent, tgid); + + // if it's cardwire skip it + if (is_cardwire_process(pid)) + return 0; + // skip if whitelisted + if (is_process_whitelisted()) + return 0; + + bool blocked = false; + + struct inode *inode = BPF_CORE_READ(d, d_inode); + // Match card/render/nvidia minor + if (inode) { + __u64 d_ino = BPF_CORE_READ(inode, i_ino); + if (d_ino) { + // if it's a blocked inode, go to end + if (bpf_map_lookup_elem(&cw_blocked_ino, &d_ino)) { + blocked = true; + goto end; + } + if (is_nvidia_enabled() && + bpf_map_lookup_elem(&cw_exp_blk_ino, &d_ino)) { + blocked = true; + goto end; + } + } + } +end: + if (!blocked) { + return 0; + } + // get mode + __u32 key = 0; + __u8 *mode = bpf_map_lookup_elem(&cw_mode, &key); + // if map lookup fails, or we are not blocking, or it's hybrid mode, allow + if (!mode || *mode == 1) { + return 0; + } + + // if is hybrid/manual mode, block + if (*mode == 0 || *mode == 2) { + return -ENOENT; + } + + // if smart, check the pid list + if (*mode == 3) { + if (!bpf_map_lookup_elem(&cw_allowed_pid, &pid) && + !bpf_map_lookup_elem(&cw_allowed_pid, &ppid)) { + // Neither pid nor ppid is allowed, block + return -ENOENT; + } + } + + return 0; +} + +static __always_inline int patch_dirent_if_found(__u32 _, + struct dirents_data_t *data) +{ + // Check if we reached the end of the buffer + if (data->bpos >= data->buff_size) { + return 1; // 1 = stop loop + } + + // Get the current directory entry + struct linux_dirent64 *dirent = + (struct linux_dirent64 *)(data->dirents_buf + data->bpos); + + if (bpf_probe_read(&data->d_reclen, sizeof(data->d_reclen), + &dirent->d_reclen) < 0) { + return 1; // Read error, break loop + } + + __u64 d_inode = 0; + if (bpf_probe_read(&d_inode, sizeof(d_inode), &dirent->d_ino) < 0) { + return 1; // Read error, break loop + } + + if (!d_inode) { + data->bpos += data->d_reclen; + return 0; // Skip and continue + } + + //Read the name of this entry + char dirname[64] = {}; + bpf_probe_read_user_str(dirname, sizeof(dirname), dirent->d_name); + + // Check if this is a file we want to hide + if (bpf_map_lookup_elem(&cw_blocked_ino, &d_inode) || + (is_nvidia_enabled() && + bpf_map_lookup_elem(&cw_exp_blk_ino, &d_inode))) { + if (data->last_visible_bpos != 0xFFFFFFFF) { + struct linux_dirent64 *visible_dirent = + (struct linux_dirent64 + *)(data->dirents_buf + + data->last_visible_bpos); + __u16 visible_reclen; + bpf_probe_read(&visible_reclen, sizeof(visible_reclen), + &visible_dirent->d_reclen); + + __u16 new_reclen = visible_reclen + data->d_reclen; + + // Overwrite the visible file's length so it skips over the hidden file + bpf_probe_write_user(&visible_dirent->d_reclen, + &new_reclen, sizeof(new_reclen)); + } + + data->bpos += data->d_reclen; + return 0; // Continue loop + } + + // Not a hidden file, update last_visible_bpos and advance + data->last_visible_bpos = data->bpos; + data->bpos += data->d_reclen; + return 0; // Continue loop +} diff --git a/crates/cardwire-ebpf/src/lib.rs b/crates/cardwire-ebpf/src/lib.rs index fca6a2e..714b95a 100644 --- a/crates/cardwire-ebpf/src/lib.rs +++ b/crates/cardwire-ebpf/src/lib.rs @@ -1,38 +1,17 @@ //! main lib code of cardwire-ebpf mod errors; -use std::fmt; - pub use crate::errors::{CardwireEbpfError, CardwireEbpfResult}; use aya::{ Btf, Ebpf, maps::{HashMap, MapError, RingBuf}, programs::{Lsm, TracePoint} }; -pub struct EbpfBlocker { - ebpf: Ebpf, -} -#[derive(PartialEq)] -pub enum BlockKind { - Card, - Render, - Pci, - Nvidia, - NvidiaSetting, - NvidiaFile, - File, +pub enum EbpfSettings { + ExperimentalNvidia, } -impl fmt::Display for BlockKind { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - BlockKind::Card => write!(f, "BLOCKED_CARDID"), - BlockKind::Render => write!(f, "BLOCKED_RENDERID"), - BlockKind::Pci => write!(f, "BLOCKED_PCI"), - BlockKind::Nvidia => write!(f, "BLOCKED_NVIDIAID"), - BlockKind::NvidiaSetting => write!(f, "SETTINGS"), - BlockKind::NvidiaFile => write!(f, "BLOCKED_NVIDIA_FILES"), - BlockKind::File => write!(f, "BLOCKED_PCI_FILES"), - } - } + +pub struct EbpfBlocker { + ebpf: Ebpf, } impl EbpfBlocker { @@ -82,29 +61,51 @@ impl EbpfBlocker { close_program .attach("sched", "sched_process_exit") .map_err(CardwireEbpfError::aya)?; + // to hide files + let cardwire_sys_enter_getdents64: &mut TracePoint = ebpf + .program_mut("cardwire_sys_enter_getdents64") + .ok_or_else(|| CardwireEbpfError::missing_lsm("cardwire_sys_enter_getdents64"))? + .try_into() + .map_err(CardwireEbpfError::aya)?; + + cardwire_sys_enter_getdents64 + .load() + .map_err(CardwireEbpfError::aya)?; + + cardwire_sys_enter_getdents64 + .attach("syscalls", "sys_enter_getdents64") + .map_err(CardwireEbpfError::aya)?; + // to hide files + let cardwire_sys_exit_getdents64: &mut TracePoint = ebpf + .program_mut("cardwire_sys_exit_getdents64") + .ok_or_else(|| CardwireEbpfError::missing_lsm("cardwire_sys_exit_getdents64"))? + .try_into() + .map_err(CardwireEbpfError::aya)?; + + cardwire_sys_exit_getdents64 + .load() + .map_err(CardwireEbpfError::aya)?; + + cardwire_sys_exit_getdents64 + .attach("syscalls", "sys_exit_getdents64") + .map_err(CardwireEbpfError::aya)?; + Ok(Self { ebpf }) } - /// turn a pci string into a u8 array with a fixed 16 size - fn pci_key(pci: &str) -> [u8; 16] { - let mut key = [0u8; 16]; - let bytes = pci.as_bytes(); - // leave one byte for terminator - let len = bytes.len().min(15); - key[..len].copy_from_slice(&bytes[..len]); - key[len] = 0; - key - } - /// turn a file string into a u8 array with a fixed 30 size - fn file_key(file: &str) -> [u8; 30] { - let mut key = [0u8; 30]; - let bytes = file.as_bytes(); - // leave one byte for terminator - let len = bytes.len().min(29); - key[..len].copy_from_slice(&bytes[..len]); - key[len] = 0; - key + /// whitelist cardwire's pid to prevent self-locking in ebpf + pub fn whitelist_cardwire_pid(&mut self, pid: u32) -> CardwireEbpfResult<()> { + let mut inode_map: HashMap<_, u8, u32> = HashMap::try_from( + self.ebpf + .map_mut("cw_daemon_pid") + .ok_or_else(|| CardwireEbpfError::missing_map("cw_daemon_pid"))?, + ) + .map_err(CardwireEbpfError::aya)?; + inode_map + .insert(0, pid, 0) + .map_err(|err| CardwireEbpfError::Aya(err.to_string())) } + /* Checks if bpf/lsm is enabled in the kernel */ @@ -115,208 +116,97 @@ impl EbpfBlocker { } } - fn is_format_valid(entity: &str, kind: &BlockKind) -> bool { - match kind { - BlockKind::Render => entity.parse::().is_ok(), - BlockKind::Card => entity.parse::().is_ok(), - BlockKind::Nvidia => entity.parse::().is_ok(), - // either 0 or 1 - BlockKind::NvidiaSetting => entity.parse::().is_ok(), - // just a string - BlockKind::NvidiaFile | BlockKind::File => true, - // Only the Pci need a real check - BlockKind::Pci => entity.starts_with("0000:") && !entity.contains("pcie"), - } - } - - /* - Block a kind - */ - pub fn block_kind(&mut self, entity: &str, kind: BlockKind) -> CardwireEbpfResult<()> { - // validate input format for the bpf map, else return Err - if !Self::is_format_valid(entity, &kind) { - return Err(CardwireEbpfError::WrongFormat { - kind: kind.to_string(), - input: entity.to_string(), - }); - } - - let kind_string = kind.to_string(); - - match kind { - BlockKind::Pci => { - let mut map: HashMap<_, [u8; 16], u8> = HashMap::try_from( - self.ebpf - .map_mut(&kind_string) - .ok_or_else(|| CardwireEbpfError::missing_map(&kind_string))?, - ) - .map_err(CardwireEbpfError::aya)?; - - let key = Self::pci_key(entity); - map.insert(key, 1, 0).map_err(CardwireEbpfError::aya)?; - } - // set file blocklist - BlockKind::NvidiaFile | BlockKind::File => { - let mut map: HashMap<_, [u8; 30], u8> = HashMap::try_from( - self.ebpf - .map_mut(&kind_string) - .ok_or_else(|| CardwireEbpfError::missing_map(&kind_string))?, - ) - .map_err(CardwireEbpfError::aya)?; - let key = Self::file_key(entity); - map.insert(key, 1, 0).map_err(CardwireEbpfError::aya)?; - } - BlockKind::NvidiaSetting => { - if entity.parse::().is_ok() { - let mut map: HashMap<_, u32, u8> = HashMap::try_from( - self.ebpf - .map_mut(&kind_string) - .ok_or_else(|| CardwireEbpfError::missing_map(&kind_string))?, - ) - .map_err(CardwireEbpfError::aya)?; - map.insert(0, 1, 0).map_err(CardwireEbpfError::aya)?; - } - } - BlockKind::Render | BlockKind::Card | BlockKind::Nvidia => { - let mut map: HashMap<_, u32, u8> = HashMap::try_from( - self.ebpf - .map_mut(&kind_string) - .ok_or_else(|| CardwireEbpfError::missing_map(&kind_string))?, - ) - .map_err(CardwireEbpfError::aya)?; - - if let Ok(value) = entity.parse::() { - map.insert(value, 1, 0).map_err(CardwireEbpfError::aya)?; - } - } - } - + pub fn block_inode(&mut self, inode: u64) -> CardwireEbpfResult<()> { + // Also insert hardcoded values for now + let mut inode_map: HashMap<_, u64, u8> = HashMap::try_from( + self.ebpf + .map_mut("cw_blocked_ino") + .ok_or_else(|| CardwireEbpfError::missing_map("cw_blocked_ino"))?, + ) + .map_err(CardwireEbpfError::aya)?; + inode_map + .insert(inode, 1, 0) + .map_err(CardwireEbpfError::aya)?; Ok(()) } - - /* - Unblock a kind - */ - pub fn unblock_kind(&mut self, entity: &str, kind: BlockKind) -> CardwireEbpfResult<()> { - // validate input format for the bpf map, else return Err - if !Self::is_format_valid(entity, &kind) { - return Err(CardwireEbpfError::WrongFormat { - kind: kind.to_string(), - input: entity.to_string(), - }); + pub fn unblock_inode(&mut self, inode: u64) -> CardwireEbpfResult<()> { + // Also insert hardcoded values for now + let mut inode_map: HashMap<_, u64, u8> = HashMap::try_from( + self.ebpf + .map_mut("cw_blocked_ino") + .ok_or_else(|| CardwireEbpfError::missing_map("cw_blocked_ino"))?, + ) + .map_err(CardwireEbpfError::aya)?; + match inode_map.get(&inode, 0) { + // Ok = key found, remove + Ok(_) => inode_map.remove(&inode).map_err(CardwireEbpfError::aya), + // key not found, skip + Err(MapError::KeyNotFound) => Ok(()), + Err(err) => Err(CardwireEbpfError::aya(err)), } + } - let kind_string = kind.to_string(); - - match kind { - BlockKind::Pci => { - let mut map: HashMap<_, [u8; 16], u8> = HashMap::try_from( - self.ebpf - .map_mut(&kind_string) - .ok_or_else(|| CardwireEbpfError::missing_map(&kind_string))?, - ) - .map_err(CardwireEbpfError::aya)?; - - let value = Self::pci_key(entity); - let _ = map.remove(&value); - } - // no file unblock - BlockKind::NvidiaFile | BlockKind::File => (), - BlockKind::Render | BlockKind::Card | BlockKind::Nvidia => { - let mut map: HashMap<_, u32, u8> = HashMap::try_from( - self.ebpf - .map_mut(&kind_string) - .ok_or_else(|| CardwireEbpfError::missing_map(&kind_string))?, - ) - .map_err(CardwireEbpfError::aya)?; - - if let Ok(value) = entity.parse::() { - let _ = map.remove(&value); - } - } - BlockKind::NvidiaSetting => { - let mut map: HashMap<_, u32, u8> = HashMap::try_from( - self.ebpf - .map_mut(&kind_string) - .ok_or_else(|| CardwireEbpfError::missing_map(&kind_string))?, - ) - .map_err(CardwireEbpfError::aya)?; - let _ = map.remove(&0); - } + pub fn is_inode_blocked(&self, inode: u64) -> CardwireEbpfResult { + // Also insert hardcoded values for now + let inode_map: HashMap<_, u64, u8> = HashMap::try_from( + self.ebpf + .map("cw_blocked_ino") + .ok_or_else(|| CardwireEbpfError::missing_map("cw_blocked_ino"))?, + ) + .map_err(CardwireEbpfError::aya)?; + match inode_map.get(&inode, 0) { + Ok(_) => Ok(true), + Err(MapError::KeyNotFound) => Ok(false), + Err(err) => Err(CardwireEbpfError::aya(err)), } + } + pub fn block_exp_inode(&mut self, inode: u64) -> CardwireEbpfResult<()> { + // Also insert hardcoded values for now + let mut inode_map: HashMap<_, u64, u8> = HashMap::try_from( + self.ebpf + .map_mut("cw_exp_blk_ino") + .ok_or_else(|| CardwireEbpfError::missing_map("cw_exp_blk_ino"))?, + ) + .map_err(CardwireEbpfError::aya)?; + inode_map + .insert(inode, 1, 0) + .map_err(CardwireEbpfError::aya)?; Ok(()) } - /* - Check a block - */ - pub fn is_kind_blocked(&self, entity: &str, kind: BlockKind) -> CardwireEbpfResult { - // validate input format for the bpf map, else return Err - if !Self::is_format_valid(entity, &kind) { - return Err(CardwireEbpfError::WrongFormat { - kind: kind.to_string(), - input: entity.to_string(), - }); - } - - let kind_string = kind.to_string(); - - match kind { - BlockKind::Pci => { - let map: HashMap<_, [u8; 16], u8> = HashMap::try_from( - self.ebpf - .map(&kind_string) - .ok_or_else(|| CardwireEbpfError::missing_map(&kind_string))?, - ) - .map_err(CardwireEbpfError::aya)?; - - let value = Self::pci_key(entity); - return match map.get(&value, 0) { - Ok(_) => Ok(true), - Err(MapError::KeyNotFound) => Ok(false), - Err(err) => Err(CardwireEbpfError::aya(err)), - }; - } - // no file unblock - BlockKind::NvidiaFile | BlockKind::File | BlockKind::NvidiaSetting => (), - BlockKind::Render | BlockKind::Card | BlockKind::Nvidia => { - let map: HashMap<_, u32, u8> = HashMap::try_from( - self.ebpf - .map(&kind_string) - .ok_or_else(|| CardwireEbpfError::missing_map(&kind_string))?, - ) - .map_err(CardwireEbpfError::aya)?; - - if let Ok(value) = entity.parse::() { - return match map.get(&value, 0) { - Ok(_) => Ok(true), - Err(MapError::KeyNotFound) => Ok(false), - Err(err) => Err(CardwireEbpfError::aya(err)), - }; - } - } - } - - Ok(false) + pub fn set_ebpf_setting(&mut self, setting: EbpfSettings, value: u8) -> CardwireEbpfResult<()> { + let key: u8 = match setting { + EbpfSettings::ExperimentalNvidia => 0, + }; + let mut setting_map: HashMap<_, u8, u8> = HashMap::try_from( + self.ebpf + .map_mut("cw_settings") + .ok_or_else(|| CardwireEbpfError::missing_map("cw_settings"))?, + ) + .map_err(CardwireEbpfError::aya)?; + setting_map + .insert(key, value, 0) + .map_err(CardwireEbpfError::aya) } + pub fn get_exec_ring(&mut self) -> CardwireEbpfResult> { - let map = self.ebpf.take_map("EXEC_EVENTS").unwrap(); + let map = self.ebpf.take_map("cw_exec_events").unwrap(); let ring_buf: RingBuf = RingBuf::try_from(map).unwrap(); Ok(ring_buf) } pub fn get_close_ring(&mut self) -> CardwireEbpfResult> { - let map = self.ebpf.take_map("CLOSE_EVENTS").unwrap(); + let map = self.ebpf.take_map("cw_close_events").unwrap(); let ring_buf: RingBuf = RingBuf::try_from(map).unwrap(); Ok(ring_buf) } pub fn get_pid_map(&mut self) -> CardwireEbpfResult> { - let map = self.ebpf.take_map("ALLOWED_PID").unwrap(); + let map = self.ebpf.take_map("cw_allowed_pid").unwrap(); let map: HashMap = HashMap::try_from(map).unwrap(); Ok(map) } pub fn get_mode_map(&mut self) -> CardwireEbpfResult> { - let map = self.ebpf.take_map("CURRENT_MODE").unwrap(); + let map = self.ebpf.take_map("cw_mode").unwrap(); let map: HashMap = HashMap::try_from(map).unwrap(); Ok(map) } diff --git a/flake.nix b/flake.nix index e91dcd1..0dce722 100644 --- a/flake.nix +++ b/flake.nix @@ -59,6 +59,7 @@ (toolchainFor system) (pkgs system).clang (pkgs system).libbpf + (pkgs system).bpftools (pkgs system).udev (pkgs system).pkg-config (pkgs system).mdbook diff --git a/nix/vm-configuration.nix b/nix/vm-configuration.nix index d6f0672..3d7efab 100644 --- a/nix/vm-configuration.nix +++ b/nix/vm-configuration.nix @@ -32,6 +32,8 @@ pciutils fish tmux + gnugrep + coreutils ]; services.getty.autologinUser = "john"; virtualisation.vmVariant = {