From a5dcef825ab41ebb40000d311b5c54ac485738b9 Mon Sep 17 00:00:00 2001 From: luytan Date: Tue, 7 Jul 2026 19:56:26 +0200 Subject: [PATCH 01/17] feat: hide a file using sys_enter_getdents64 and sys_exit_getdents64, code taken from a tutorial WIP --- crates/cardwire-ebpf/src/bpf.c | 409 ++++------------------------- crates/cardwire-ebpf/src/bpf.h | 204 ++++++++++++++ crates/cardwire-ebpf/src/helpers.h | 270 +++++++++++++++++++ crates/cardwire-ebpf/src/lib.rs | 28 ++ flake.nix | 1 + 5 files changed, 550 insertions(+), 362 deletions(-) create mode 100644 crates/cardwire-ebpf/src/bpf.h create mode 100644 crates/cardwire-ebpf/src/helpers.h diff --git a/crates/cardwire-ebpf/src/bpf.c b/crates/cardwire-ebpf/src/bpf.c index 88f336e..2fb1290 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 */ @@ -471,4 +112,48 @@ int trace_process_exit(void *ctx) 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) +{ + __u32 pid = bpf_get_current_pid_tgid() >> 32; + + __u64 dirents_buf = ctx->args[1]; + if (!dirents_buf) { + return 0; + } + bpf_map_update_elem(&map_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) +{ + __u32 pid = bpf_get_current_pid_tgid() >> 32; // Fixed: use __u32 + __u64 *dirents_buf = bpf_map_lookup_elem(&map_dirent, &pid); + if (!dirents_buf) + return 0; + + // If getdents64 returned an error or 0 bytes, clean up and exit + if (ctx->ret <= 0) { + bpf_map_delete_elem(&map_dirent, &pid); + return 0; + } + + struct dirents_data_t dirents_data = { + .bpos = 0, + .dirents_buf = dirents_buf, + .buff_size = ctx->ret, + .d_reclen = 0, + .d_reclen_prev = 0, + .patch_succeded = false, + }; + + bpf_loop(10000, patch_dirent_if_found, &dirents_data, 0); + + // CRITICAL FIX: Clean up the map so it doesn't fill up and block future hooks + bpf_map_delete_elem(&map_dirent, &pid); + + return 0; // Fix: return 0 from tracepoint } \ 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..428e6e6 --- /dev/null +++ b/crates/cardwire-ebpf/src/bpf.h @@ -0,0 +1,204 @@ + +// 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 { + __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 dirents_data_t { + __u32 bpos; + __u64 *dirents_buf; + long buff_size; + __u16 d_reclen; + __u16 d_reclen_prev; + bool patch_succeded; +}; + +struct linux_dirent64 { + __u64 d_ino; + __s64 d_off; + short unsigned int d_reclen; + unsigned char d_type; + char d_name[0]; +}; + +struct trace_event_raw_sys_enter { + __u64 unused_common_fields; + long id; + unsigned long args[6]; + char __data[0]; +}; + +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); +} 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"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 1024); + __type(key, __u32); + __type(value, __u64); +} map_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..7816010 --- /dev/null +++ b/crates/cardwire-ebpf/src/helpers.h @@ -0,0 +1,270 @@ +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; +} + +static __always_inline struct linux_dirent64 *get_dirent(__u64 dirents_buf, + int bpos) +{ + return (struct linux_dirent64 *)(dirents_buf + bpos); +} +static __always_inline bool is_end_of_buff(int bpos, long buff_size) +{ + return bpos >= buff_size; +} +static __always_inline int read_user__dirname(__u8 *dst, int size, + char *raw_data) +{ + return bpf_probe_read_user_str(dst, size, raw_data); +} + +static __always_inline int read_user__reclen(__u16 *dst, + unsigned short *raw_data) +{ + return bpf_probe_read(dst, sizeof(*dst), raw_data); +} + +static __always_inline bool +is_dirname_to_hide(__u8 *dirname, const char *dirname_to_hide, int target_len) +{ + int i = 0; + for (; i < target_len; i++) { + if (dirname[i] != dirname_to_hide[i]) + return false; + } + return dirname[i] == 0x00; +} + +static __always_inline bool remove_curr_dirent(struct dirents_data_t *data) +{ + struct linux_dirent64 *dirent_previous = get_dirent( + *data->dirents_buf, (data->bpos - data->d_reclen_prev)); + __u16 d_reclen_new = data->d_reclen + data->d_reclen_prev; + return bpf_probe_write_user(&dirent_previous->d_reclen, &d_reclen_new, + sizeof(d_reclen_new)) == 0; +} + +static __always_inline int get_str_max_len(__u8 *dir_to_hide, __u8 *dirname, + int expected_len) +{ + int max_len = sizeof(dir_to_hide) < sizeof(dirname) ? + sizeof(dir_to_hide) : + sizeof(dirname); + return expected_len < max_len ? expected_len : max_len; +} +static __always_inline int patch_dirent_if_found(__u32 _, + struct dirents_data_t *data) +{ + if (is_end_of_buff(data->bpos, data->buff_size)) + return 1; + + __u8 dirname[100]; + struct linux_dirent64 *dirent = + get_dirent(*data->dirents_buf, data->bpos); + + read_user__reclen(&data->d_reclen, &dirent->d_reclen); + read_user__dirname(dirname, sizeof(dirname), dirent->d_name); + + // "67956" is 5 characters long + if (is_dirname_to_hide(dirname, "renderD150", 10)) { + // Only attempt to patch if it's NOT the first dirent + if (data->bpos > 0) { + data->patch_succeded = remove_curr_dirent(data); + } else { + data->patch_succeded = + false; // Cannot hide the first dirent + } + return 1; // Stop looping + } + + data->d_reclen_prev = data->d_reclen; + data->bpos += data->d_reclen; + return 0; +} diff --git a/crates/cardwire-ebpf/src/lib.rs b/crates/cardwire-ebpf/src/lib.rs index fca6a2e..834833d 100644 --- a/crates/cardwire-ebpf/src/lib.rs +++ b/crates/cardwire-ebpf/src/lib.rs @@ -82,6 +82,34 @@ 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 }) } 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 From 33d30d843021790cb9138db3d1789022f7742e1f Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 8 Jul 2026 12:40:36 +0200 Subject: [PATCH 02/17] feat: clean up the code and use inode instead of dirname --- crates/cardwire-ebpf/src/bpf.c | 23 ++++--- crates/cardwire-ebpf/src/bpf.h | 6 +- crates/cardwire-ebpf/src/helpers.h | 105 +++++++++++++---------------- 3 files changed, 62 insertions(+), 72 deletions(-) diff --git a/crates/cardwire-ebpf/src/bpf.c b/crates/cardwire-ebpf/src/bpf.c index 2fb1290..e5f0062 100644 --- a/crates/cardwire-ebpf/src/bpf.c +++ b/crates/cardwire-ebpf/src/bpf.c @@ -118,11 +118,12 @@ SEC("tp/syscalls/sys_enter_getdents64") int cardwire_sys_enter_getdents64(struct trace_event_raw_sys_enter *ctx) { __u32 pid = bpf_get_current_pid_tgid() >> 32; - + // 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(&map_dirent, &pid, &dirents_buf, BPF_ANY); return 0; } @@ -130,30 +131,30 @@ int cardwire_sys_enter_getdents64(struct trace_event_raw_sys_enter *ctx) SEC("tp/syscalls/sys_exit_getdents64") int cardwire_sys_exit_getdents64(struct trace_event_raw_sys_exit *ctx) { - __u32 pid = bpf_get_current_pid_tgid() >> 32; // Fixed: use __u32 + __u32 pid = bpf_get_current_pid_tgid() >> 32; __u64 *dirents_buf = bpf_map_lookup_elem(&map_dirent, &pid); + if (!dirents_buf) return 0; - // If getdents64 returned an error or 0 bytes, clean up and exit + // Clean up the map immediately so it doesn't fill up + bpf_map_delete_elem(&map_dirent, &pid); + + // If getdents64 return 0 bytes if (ctx->ret <= 0) { - bpf_map_delete_elem(&map_dirent, &pid); return 0; } struct dirents_data_t dirents_data = { .bpos = 0, - .dirents_buf = dirents_buf, + .dirents_buf = *dirents_buf, .buff_size = ctx->ret, .d_reclen = 0, - .d_reclen_prev = 0, - .patch_succeded = false, + .last_visible_bpos = 0xFFFFFFFF, }; + // Run the loop bpf_loop(10000, patch_dirent_if_found, &dirents_data, 0); - // CRITICAL FIX: Clean up the map so it doesn't fill up and block future hooks - bpf_map_delete_elem(&map_dirent, &pid); - - return 0; // Fix: return 0 from tracepoint + return 0; } \ No newline at end of file diff --git a/crates/cardwire-ebpf/src/bpf.h b/crates/cardwire-ebpf/src/bpf.h index 428e6e6..5a8e430 100644 --- a/crates/cardwire-ebpf/src/bpf.h +++ b/crates/cardwire-ebpf/src/bpf.h @@ -16,6 +16,7 @@ struct hlist_head { struct inode { __u16 i_mode; __u32 i_rdev; + __u64 i_ino; struct hlist_head i_dentry; } __attribute__((preserve_access_index)); @@ -56,11 +57,10 @@ struct file { struct dirents_data_t { __u32 bpos; - __u64 *dirents_buf; + __u64 dirents_buf; long buff_size; __u16 d_reclen; - __u16 d_reclen_prev; - bool patch_succeded; + __u32 last_visible_bpos; }; struct linux_dirent64 { diff --git a/crates/cardwire-ebpf/src/helpers.h b/crates/cardwire-ebpf/src/helpers.h index 7816010..3031cc6 100644 --- a/crates/cardwire-ebpf/src/helpers.h +++ b/crates/cardwire-ebpf/src/helpers.h @@ -91,6 +91,10 @@ static __always_inline int is_blocked_device(struct dentry *d) 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 && d_ino == 431) { + bpf_printk("found this number: %d", d_ino); + } __u16 i_mode = BPF_CORE_READ(inode, i_mode); if ((i_mode & 00170000) == 00020000) { __u32 i_rdev = BPF_CORE_READ(inode, i_rdev); @@ -190,29 +194,9 @@ static __always_inline int is_blocked_device(struct dentry *d) return 0; } -static __always_inline struct linux_dirent64 *get_dirent(__u64 dirents_buf, - int bpos) -{ - return (struct linux_dirent64 *)(dirents_buf + bpos); -} -static __always_inline bool is_end_of_buff(int bpos, long buff_size) -{ - return bpos >= buff_size; -} -static __always_inline int read_user__dirname(__u8 *dst, int size, - char *raw_data) -{ - return bpf_probe_read_user_str(dst, size, raw_data); -} - -static __always_inline int read_user__reclen(__u16 *dst, - unsigned short *raw_data) -{ - return bpf_probe_read(dst, sizeof(*dst), raw_data); -} - -static __always_inline bool -is_dirname_to_hide(__u8 *dirname, const char *dirname_to_hide, int target_len) +static __always_inline bool is_dirname_to_hide(const char *dirname, + const char *dirname_to_hide, + int target_len) { int i = 0; for (; i < target_len; i++) { @@ -222,49 +206,54 @@ is_dirname_to_hide(__u8 *dirname, const char *dirname_to_hide, int target_len) return dirname[i] == 0x00; } -static __always_inline bool remove_curr_dirent(struct dirents_data_t *data) -{ - struct linux_dirent64 *dirent_previous = get_dirent( - *data->dirents_buf, (data->bpos - data->d_reclen_prev)); - __u16 d_reclen_new = data->d_reclen + data->d_reclen_prev; - return bpf_probe_write_user(&dirent_previous->d_reclen, &d_reclen_new, - sizeof(d_reclen_new)) == 0; -} - -static __always_inline int get_str_max_len(__u8 *dir_to_hide, __u8 *dirname, - int expected_len) -{ - int max_len = sizeof(dir_to_hide) < sizeof(dirname) ? - sizeof(dir_to_hide) : - sizeof(dirname); - return expected_len < max_len ? expected_len : max_len; -} static __always_inline int patch_dirent_if_found(__u32 _, struct dirents_data_t *data) { - if (is_end_of_buff(data->bpos, data->buff_size)) - return 1; + // Check if we reached the end of the buffer + if (data->bpos >= data->buff_size) { + return 1; // 1 = stop loop + } - __u8 dirname[100]; + // Get the current directory entry struct linux_dirent64 *dirent = - get_dirent(*data->dirents_buf, data->bpos); + (struct linux_dirent64 *)(data->dirents_buf + data->bpos); + __u64 d_inode = 0; + bpf_probe_read(&d_inode, sizeof(d_inode), &dirent->d_ino); + if (!d_inode) + return 0; + bpf_probe_read(&data->d_reclen, sizeof(data->d_reclen), + &dirent->d_reclen); + + //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 (d_inode == 431) { + 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_printk("blocking %s with inode %d", dirname, + d_inode); + bpf_probe_read(&visible_reclen, sizeof(visible_reclen), + &visible_dirent->d_reclen); - read_user__reclen(&data->d_reclen, &dirent->d_reclen); - read_user__dirname(dirname, sizeof(dirname), dirent->d_name); + __u16 new_reclen = visible_reclen + data->d_reclen; - // "67956" is 5 characters long - if (is_dirname_to_hide(dirname, "renderD150", 10)) { - // Only attempt to patch if it's NOT the first dirent - if (data->bpos > 0) { - data->patch_succeded = remove_curr_dirent(data); - } else { - data->patch_succeded = - false; // Cannot hide the first dirent + // 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)); } - return 1; // Stop looping + + data->bpos += data->d_reclen; + return 0; // Continue loop } - data->d_reclen_prev = data->d_reclen; + // Not a hidden file, update last_visible_bpos and advance + data->last_visible_bpos = data->bpos; data->bpos += data->d_reclen; - return 0; -} + return 0; // Continue loop +} \ No newline at end of file From 79a98861ca7cbfdcb3e7f297b681ddeb11cb154e Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 8 Jul 2026 16:08:23 +0200 Subject: [PATCH 03/17] feat: replace cardwire's bpf major:minor and name matching by inode matching --- crates/cardwire-daemon/src/core/gpu/models.rs | 2 +- crates/cardwire-daemon/src/core/inode.rs | 108 ++++++ crates/cardwire-daemon/src/core/mod.rs | 2 + .../cardwire-daemon/src/interface/config.rs | 19 +- crates/cardwire-daemon/src/interface/gpu.rs | 164 ++++---- crates/cardwire-daemon/src/interface/mode.rs | 1 + crates/cardwire-daemon/src/models.rs | 32 +- crates/cardwire-ebpf/src/bpf.c | 44 ++- crates/cardwire-ebpf/src/bpf.h | 46 +-- crates/cardwire-ebpf/src/helpers.h | 199 +++------- crates/cardwire-ebpf/src/lib.rs | 357 +++++++----------- 11 files changed, 414 insertions(+), 560 deletions(-) create mode 100644 crates/cardwire-daemon/src/core/inode.rs diff --git a/crates/cardwire-daemon/src/core/gpu/models.rs b/crates/cardwire-daemon/src/core/gpu/models.rs index b6ba047..4af75b0 100644 --- a/crates/cardwire-daemon/src/core/gpu/models.rs +++ b/crates/cardwire-daemon/src/core/gpu/models.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, str::FromStr}; +use std::{collections::HashMap, fmt::Display, str::FromStr}; use crate::core::pci::PciDevice; diff --git a/crates/cardwire-daemon/src/core/inode.rs b/crates/cardwire-daemon/src/core/inode.rs new file mode 100644 index 0000000..a0a89a0 --- /dev/null +++ b/crates/cardwire-daemon/src/core/inode.rs @@ -0,0 +1,108 @@ +//! Used to get inodes for specific files +use std::{ + collections::BTreeMap, fs::{self, File}, os::unix::fs::MetadataExt +}; + +use anyhow::Result; +use log::warn; + +use crate::core::pci::PciDevice; + +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", +]; + +pub fn render_to_inode(render: u32) -> Result { + let render_path = format!("/dev/dri/renderD{}", render); + let metadata = fs::metadata(render_path)?; + 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)?; + 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 return Some(inode) if successfull or return None is fail + let get_pci_inode = |pci: &str| { + let pci_path = format!("/sys/bus/pci/devices/{}", pci); + if let Ok(metadata) = fs::metadata(&pci_path) { + return Some(metadata.ino()); + } + return None; + }; + + // first we push the pci inode + match get_pci_inode(&pci) { + Some(inode) => inodes.push(inode), + None => { + warn!("failed to get inode for pci: {}", pci); + } + } + // Also push the audio card inode + match get_pci_inode(&pci.replace(".0", ".1")) { + Some(inode) => inodes.push(inode), + None => { + warn!("failed to get inode for pci: {}", &pci.replace(".0", ".1")); + } + } + + 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(); + match get_pci_inode(pci_device.pci_address()) { + Some(inode) => inodes.push(inode), + None => { + warn!("failed to get inode for pci: {}", pci_device.pci_address()); + continue; + } + } + } 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)?; + let inode = metadata.ino(); + + Ok(inode) +} + +fn nvidia_to_inode(nvidia_minor: u32) -> Result { + let nvidia_path = format!("/dev/nvidia{}", nvidia_minor); + let inode = File::open(nvidia_path)?.metadata()?.ino(); + + Ok(inode) +} 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..7cc01da 100644 --- a/crates/cardwire-daemon/src/interface/config.rs +++ b/crates/cardwire-daemon/src/interface/config.rs @@ -83,15 +83,16 @@ 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))) - } + //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))) + //} + Ok(()) } #[zbus(property)] pub async fn battery_auto_switch(&self) -> fdo::Result { diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index 7b6750f..dc4d9f4 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -6,10 +6,10 @@ use std::{ use crate::{ core::{ - gpu::{DbusGpuDevice, GpuDevice, GpuVendor}, pci::PciDevice + gpu::{DbusGpuDevice, GpuDevice, GpuVendor}, inode::{card_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 cardwire_ebpf::EbpfBlocker; use log::{info, warn}; use tokio::sync::RwLock; use zbus::{fdo, interface, object_server::SignalEmitter}; @@ -58,47 +58,34 @@ 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) => return Err(err).into_fdo(), + }; + match render_to_inode(*self.device.render()) { + Ok(inode) => blocker.block_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.block_inode(inode).into_fdo()? + } } - } + Err(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()?; - } + //if self.device.gpu_vendor() == GpuVendor::Nvidia + // && let Some(minor) = self.device.nvidia_minor() + //{ + // blocker + // .block_kind(&minor.to_string(), BlockKind::Nvidia) + // .into_fdo()?; + //} Ok(()) } /// unblock the gpu @@ -106,69 +93,54 @@ 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()?; - } + //if self.device.gpu_vendor() == GpuVendor::Nvidia + // && let Some(minor) = self.device.nvidia_minor() + //{ + // blocker + // .unblock_kind(&minor.to_string(), BlockKind::Nvidia) + // .into_fdo()?; + //} Ok(()) } /// check if the gpu is blocked 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.card()) { + 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(), + }; + + Ok(card && render && pci) } /// 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..fc9d6d2 100644 --- a/crates/cardwire-daemon/src/interface/mode.rs +++ b/crates/cardwire-daemon/src/interface/mode.rs @@ -92,6 +92,7 @@ impl ModeInterface { 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); + println!("updating current mode to: {}", mode); mode_map .insert(0, mode as u8, 0) .map_err(|err| fdo::Error::Failed(err.to_string())) diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index 2e154de..845270d 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -7,7 +7,7 @@ use crate::{ } }; use anyhow::{Context, Result}; -use cardwire_ebpf::{BlockKind, EbpfBlocker}; +use cardwire_ebpf::EbpfBlocker; use log::warn; use std::{collections::BTreeMap, sync::Arc}; use tokio::sync::RwLock; @@ -15,21 +15,6 @@ 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,15 @@ 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 - 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)?; - } - break; - } - } 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 e5f0062..66d7eed 100644 --- a/crates/cardwire-ebpf/src/bpf.c +++ b/crates/cardwire-ebpf/src/bpf.c @@ -17,6 +17,10 @@ char __license[] SEC("license") = "GPL"; 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); } @@ -26,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; /* @@ -55,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); } @@ -65,16 +75,10 @@ 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) { + // if mode not smart, skip + if (!is_smart()) return 0; - } - //if mode is not smart or enforce, skip - if (*mode != 3) { - return 0; - } + // Init the struct struct event_t *rb_data = {}; rb_data = bpf_ringbuf_reserve(&EXEC_EVENTS, sizeof(struct event_t), 0); @@ -93,16 +97,11 @@ 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) { + // if mode not smart, skip + if (!is_smart()) return 0; - } - //if mode is not smart, skip - if (*mode != 3) { - 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); if (!rb_data) { @@ -117,6 +116,11 @@ int trace_process_exit(void *ctx) 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; // Get the memory address of the buffer where the list of entry will be stored __u64 dirents_buf = ctx->args[1]; @@ -131,6 +135,10 @@ int cardwire_sys_enter_getdents64(struct trace_event_raw_sys_enter *ctx) 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; __u64 *dirents_buf = bpf_map_lookup_elem(&map_dirent, &pid); diff --git a/crates/cardwire-ebpf/src/bpf.h b/crates/cardwire-ebpf/src/bpf.h index 5a8e430..dc9e01e 100644 --- a/crates/cardwire-ebpf/src/bpf.h +++ b/crates/cardwire-ebpf/src/bpf.h @@ -69,14 +69,14 @@ struct linux_dirent64 { 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; @@ -149,38 +149,10 @@ struct { 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]); + __uint(max_entries, 2048); + __type(key, __u64); __type(value, __u8); -} BLOCKED_PCI_FILES SEC(".maps"); +} BLOCKED_INODES SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); @@ -191,10 +163,10 @@ struct { struct { __uint(type, BPF_MAP_TYPE_HASH); - __uint(max_entries, 1024); - __type(key, char[30]); - __type(value, __u8); -} BLOCKED_NVIDIA_FILES SEC(".maps"); + __uint(max_entries, 1); + __type(key, __u8); + __type(value, __u32); +} CARDWIRE_PID SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); diff --git a/crates/cardwire-ebpf/src/helpers.h b/crates/cardwire-ebpf/src/helpers.h index 3031cc6..621d142 100644 --- a/crates/cardwire-ebpf/src/helpers.h +++ b/crates/cardwire-ebpf/src/helpers.h @@ -1,76 +1,3 @@ -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) { @@ -82,6 +9,17 @@ static __always_inline int is_blocked_device(struct dentry *d) if (__builtin_memcmp(comm, "cardwired", 9) == 0) { 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); + // key 0 contain cardwire pid, if ppid = cardwire allow + __u8 cardwire_key = 0; + __u32 *cardwire_pid = bpf_map_lookup_elem(&CARDWIRE_PID, &cardwire_key); + if (cardwire_pid && *cardwire_pid == pid) { + return 0; + } + // same for udev if (__builtin_memcmp(comm, "(udev-worker)", 13) == 0) { return 0; @@ -92,74 +30,11 @@ static __always_inline int is_blocked_device(struct dentry *d) // Match card/render/nvidia minor if (inode) { __u64 d_ino = BPF_CORE_READ(inode, i_ino); - if (d_ino && d_ino == 431) { - bpf_printk("found this number: %d", d_ino); - } - __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)) { + if (d_ino && bpf_map_lookup_elem(&BLOCKED_INODES, &d_ino)) { blocked = true; goto end; } } - // backlight - if (check_backlight_path(d) == 1) { - blocked = true; - goto end; - } end: if (!blocked) { @@ -168,10 +43,6 @@ static __always_inline int is_blocked_device(struct dentry *d) // 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; @@ -194,18 +65,6 @@ static __always_inline int is_blocked_device(struct dentry *d) return 0; } -static __always_inline bool is_dirname_to_hide(const char *dirname, - const char *dirname_to_hide, - int target_len) -{ - int i = 0; - for (; i < target_len; i++) { - if (dirname[i] != dirname_to_hide[i]) - return false; - } - return dirname[i] == 0x00; -} - static __always_inline int patch_dirent_if_found(__u32 _, struct dirents_data_t *data) { @@ -229,15 +88,13 @@ static __always_inline int patch_dirent_if_found(__u32 _, bpf_probe_read_user_str(dirname, sizeof(dirname), dirent->d_name); // Check if this is a file we want to hide - if (d_inode == 431) { + if (bpf_map_lookup_elem(&BLOCKED_INODES, &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_printk("blocking %s with inode %d", dirname, - d_inode); bpf_probe_read(&visible_reclen, sizeof(visible_reclen), &visible_dirent->d_reclen); @@ -256,4 +113,34 @@ static __always_inline int patch_dirent_if_found(__u32 _, data->last_visible_bpos = data->bpos; data->bpos += data->d_reclen; return 0; // Continue loop +} + +static __always_inline int is_hybrid() +{ + // get current cardwired mode, key should always be 0 + __u32 key = 0; + __u8 *mode = bpf_map_lookup_elem(&CURRENT_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(&CURRENT_MODE, &key); + if (!mode) { + return false; + } + //if mode is smart, true + if (*mode == 3) { + return true; + } + return false; } \ No newline at end of file diff --git a/crates/cardwire-ebpf/src/lib.rs b/crates/cardwire-ebpf/src/lib.rs index 834833d..38fe44d 100644 --- a/crates/cardwire-ebpf/src/lib.rs +++ b/crates/cardwire-ebpf/src/lib.rs @@ -12,25 +12,21 @@ pub struct EbpfBlocker { } #[derive(PartialEq)] -pub enum BlockKind { - Card, - Render, - Pci, - Nvidia, - NvidiaSetting, - NvidiaFile, - File, +pub enum MapKind { + CardwirePid, + CurrentMode, + Settings, + BlockedInodes, + AllowedPid, } -impl fmt::Display for BlockKind { +impl fmt::Display for MapKind { 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"), + MapKind::CardwirePid => write!(f, "CARDWIRE_PID"), + MapKind::CurrentMode => write!(f, "CURRENT_MODE"), + MapKind::Settings => write!(f, "SETTINGS"), + MapKind::BlockedInodes => write!(f, "BLOCKED_INODES"), + MapKind::AllowedPid => write!(f, "ALLOWED_PID"), } } } @@ -110,29 +106,20 @@ impl EbpfBlocker { cardwire_sys_exit_getdents64 .attach("syscalls", "sys_exit_getdents64") .map_err(CardwireEbpfError::aya)?; + + // get pid of process and push to ebpf + let pid = std::process::id(); + println!("pushing this pid: {}", pid); + let mut inode_map: HashMap<_, u8, u32> = HashMap::try_from( + ebpf.map_mut("CARDWIRE_PID") + .ok_or_else(|| CardwireEbpfError::missing_map("CARDWIRE_PID"))?, + ) + .map_err(CardwireEbpfError::aya)?; + inode_map.insert(0, pid, 0); + 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 - } /* Checks if bpf/lsm is enabled in the kernel */ @@ -143,191 +130,137 @@ 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_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)?; + // //} + // + // // Also insert hardcoded values for now + // let mut inode_map: HashMap<_, u64, u8> = HashMap::try_from( + // self.ebpf + // .map_mut("BLOCKED_INODES") + // .ok_or_else(|| CardwireEbpfError::missing_map(&kind_string))?, + // ) + // .map_err(CardwireEbpfError::aya)?; + // // card1 = 431 + // inode_map + // .insert(431, 1, 0) + // .map_err(CardwireEbpfError::aya)?; + // // renderD128 = 430 + // inode_map + // .insert(430, 1, 0) + // .map_err(CardwireEbpfError::aya)?; + // // 0000:03:00.0 = 13757 + // inode_map + // .insert(13757, 1, 0) + // .map_err(CardwireEbpfError::aya)?; + // // 0000:03:00.1 = 13838 + // inode_map + // .insert(13838, 1, 0) + // .map_err(CardwireEbpfError::aya)?; + // } + // } + // + // Ok(()) + //} + // + + 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("BLOCKED_INODES") + .ok_or_else(|| CardwireEbpfError::missing_map("BLOCKED_INODES"))?, + ) + .map_err(CardwireEbpfError::aya)?; + println!("adding {} to map", inode); + 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(), - }); - } - - 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 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("BLOCKED_INODES") + .ok_or_else(|| CardwireEbpfError::missing_map("BLOCKED_INODES"))?, + ) + .map_err(CardwireEbpfError::aya)?; + inode_map.remove(&inode).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 is_inode_blocked(&self, inode: u64) -> CardwireEbpfResult<(bool)> { + // Also insert hardcoded values for now + let inode_map: HashMap<_, u64, u8> = HashMap::try_from( + self.ebpf + .map("BLOCKED_INODES") + .ok_or_else(|| CardwireEbpfError::missing_map("BLOCKED_INODES"))?, + ) + .map_err(CardwireEbpfError::aya)?; + return match inode_map.get(&inode, 0) { + Ok(_) => Ok(true), + Err(MapError::KeyNotFound) => Ok(false), + Err(err) => Err(CardwireEbpfError::aya(err)), + }; } + pub fn get_exec_ring(&mut self) -> CardwireEbpfResult> { let map = self.ebpf.take_map("EXEC_EVENTS").unwrap(); let ring_buf: RingBuf = RingBuf::try_from(map).unwrap(); From 886a225b9fa7d068cc8d5251d69f7b3884e72145 Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 8 Jul 2026 16:29:40 +0200 Subject: [PATCH 04/17] feat: whitelist pid at daemon build and remove unused code --- crates/cardwire-daemon/src/models.rs | 10 +- crates/cardwire-ebpf/src/lib.rs | 131 +++------------------------ 2 files changed, 20 insertions(+), 121 deletions(-) diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index 845270d..236c669 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -8,7 +8,7 @@ use crate::{ }; use anyhow::{Context, Result}; use cardwire_ebpf::EbpfBlocker; -use log::warn; +use log::{error, warn}; use std::{collections::BTreeMap, sync::Arc}; use tokio::sync::RwLock; use zbus::{ @@ -108,6 +108,14 @@ impl DaemonManager { .load(std::sync::atomic::Ordering::Relaxed); let mode = self.debug_interface.mode_state.read().await; let mut state = self.debug_interface.gpu_state.write().await; + 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()); + }; + drop(blocker); let default: bool = state.is_default_state(); if default { diff --git a/crates/cardwire-ebpf/src/lib.rs b/crates/cardwire-ebpf/src/lib.rs index 38fe44d..90fe94d 100644 --- a/crates/cardwire-ebpf/src/lib.rs +++ b/crates/cardwire-ebpf/src/lib.rs @@ -1,8 +1,6 @@ //! 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} @@ -11,26 +9,6 @@ pub struct EbpfBlocker { ebpf: Ebpf, } -#[derive(PartialEq)] -pub enum MapKind { - CardwirePid, - CurrentMode, - Settings, - BlockedInodes, - AllowedPid, -} -impl fmt::Display for MapKind { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - MapKind::CardwirePid => write!(f, "CARDWIRE_PID"), - MapKind::CurrentMode => write!(f, "CURRENT_MODE"), - MapKind::Settings => write!(f, "SETTINGS"), - MapKind::BlockedInodes => write!(f, "BLOCKED_INODES"), - MapKind::AllowedPid => write!(f, "ALLOWED_PID"), - } - } -} - impl EbpfBlocker { pub fn new() -> CardwireEbpfResult { // quit if bpf is not enabled @@ -107,17 +85,20 @@ impl EbpfBlocker { .attach("syscalls", "sys_exit_getdents64") .map_err(CardwireEbpfError::aya)?; - // get pid of process and push to ebpf - let pid = std::process::id(); - println!("pushing this pid: {}", pid); + Ok(Self { ebpf }) + } + + /// 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( - ebpf.map_mut("CARDWIRE_PID") + self.ebpf + .map_mut("CARDWIRE_PID") .ok_or_else(|| CardwireEbpfError::missing_map("CARDWIRE_PID"))?, ) .map_err(CardwireEbpfError::aya)?; - inode_map.insert(0, pid, 0); - - Ok(Self { ebpf }) + inode_map + .insert(0, pid, 0) + .map_err(|err| CardwireEbpfError::Aya(err.to_string())) } /* @@ -130,96 +111,6 @@ impl EbpfBlocker { } } - /* - 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)?; - // //} - // - // // Also insert hardcoded values for now - // let mut inode_map: HashMap<_, u64, u8> = HashMap::try_from( - // self.ebpf - // .map_mut("BLOCKED_INODES") - // .ok_or_else(|| CardwireEbpfError::missing_map(&kind_string))?, - // ) - // .map_err(CardwireEbpfError::aya)?; - // // card1 = 431 - // inode_map - // .insert(431, 1, 0) - // .map_err(CardwireEbpfError::aya)?; - // // renderD128 = 430 - // inode_map - // .insert(430, 1, 0) - // .map_err(CardwireEbpfError::aya)?; - // // 0000:03:00.0 = 13757 - // inode_map - // .insert(13757, 1, 0) - // .map_err(CardwireEbpfError::aya)?; - // // 0000:03:00.1 = 13838 - // inode_map - // .insert(13838, 1, 0) - // .map_err(CardwireEbpfError::aya)?; - // } - // } - // - // Ok(()) - //} - // - 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( @@ -246,7 +137,7 @@ impl EbpfBlocker { Ok(()) } - pub fn is_inode_blocked(&self, inode: u64) -> CardwireEbpfResult<(bool)> { + 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 From ae3ce04afdc8c134954b6940282e382faea91635 Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 8 Jul 2026 17:22:20 +0200 Subject: [PATCH 05/17] refactor: add more logging to inode, setting map and rename maps to lower case --- crates/cardwire-daemon/src/core/inode.rs | 23 +++++++-- crates/cardwire-daemon/src/interface/gpu.rs | 28 ++++++++--- crates/cardwire-daemon/src/interface/mode.rs | 2 +- crates/cardwire-ebpf/src/bpf.h | 10 ++-- crates/cardwire-ebpf/src/helpers.h | 14 +++--- crates/cardwire-ebpf/src/lib.rs | 50 +++++++++++++++----- 6 files changed, 92 insertions(+), 35 deletions(-) diff --git a/crates/cardwire-daemon/src/core/inode.rs b/crates/cardwire-daemon/src/core/inode.rs index a0a89a0..af9a555 100644 --- a/crates/cardwire-daemon/src/core/inode.rs +++ b/crates/cardwire-daemon/src/core/inode.rs @@ -25,7 +25,10 @@ const BLOCKED_NVIDIA_FILES: &[&str] = &[ pub fn render_to_inode(render: u32) -> Result { let render_path = format!("/dev/dri/renderD{}", render); - let metadata = fs::metadata(render_path)?; + 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) @@ -33,7 +36,10 @@ pub fn render_to_inode(render: u32) -> Result { pub fn card_to_inode(card: u32) -> Result { let card_path = format!("/dev/dri/card{}", card); - let metadata = fs::metadata(card_path)?; + 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) @@ -94,7 +100,10 @@ pub fn pci_to_inode( /// 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)?; + 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) @@ -102,7 +111,13 @@ pub fn single_pci_to_inode(pci: &str) -> Result { fn nvidia_to_inode(nvidia_minor: u32) -> Result { let nvidia_path = format!("/dev/nvidia{}", nvidia_minor); - let inode = File::open(nvidia_path)?.metadata()?.ino(); + let inode = File::open(&nvidia_path) + .map_err(|e| { + warn!("failed to get inode for {}: {}", nvidia_path, e); + e + })? + .metadata()? + .ino(); Ok(inode) } diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index dc4d9f4..af709db 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -10,7 +10,7 @@ use crate::{ }, file::{CardwireGpuState, CardwireModeState}, interface::Modes }; use cardwire_ebpf::EbpfBlocker; -use log::{info, warn}; +use log::{error, info, warn}; use tokio::sync::RwLock; use zbus::{fdo, interface, object_server::SignalEmitter}; @@ -60,11 +60,17 @@ impl GpuInterface { // First block the card id match card_to_inode(*self.device.card()) { Ok(inode) => blocker.block_inode(inode).into_fdo()?, - Err(err) => return Err(err).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) => return Err(err).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(), @@ -73,10 +79,20 @@ impl GpuInterface { ) { Ok(inodes) => { for inode in inodes { - blocker.block_inode(inode).into_fdo()? + if let Err(err) = blocker.block_inode(inode) { + error!("failed to block inode(pci) {}: {}", inode, err); + return Err(err).into_fdo(); + } } } - Err(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 @@ -131,7 +147,7 @@ impl GpuInterface { Ok(inode) => blocker.is_inode_blocked(inode).into_fdo()?, Err(err) => return Err(err).into_fdo(), }; - let render = match render_to_inode(*self.device.card()) { + 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(), }; diff --git a/crates/cardwire-daemon/src/interface/mode.rs b/crates/cardwire-daemon/src/interface/mode.rs index fc9d6d2..ee030c8 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-ebpf/src/bpf.h b/crates/cardwire-ebpf/src/bpf.h index dc9e01e..00c0488 100644 --- a/crates/cardwire-ebpf/src/bpf.h +++ b/crates/cardwire-ebpf/src/bpf.h @@ -145,28 +145,28 @@ struct { __uint(max_entries, 1); __type(key, __u8); __type(value, __u8); -} CURRENT_MODE SEC(".maps"); +} cardwire_mode SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 2048); __type(key, __u64); __type(value, __u8); -} BLOCKED_INODES SEC(".maps"); +} cardwire_blocked_inodes SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 64); - __type(key, __u32); + __type(key, __u8); __type(value, __u8); -} SETTINGS SEC(".maps"); +} cardwire_settings SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 1); __type(key, __u8); __type(value, __u32); -} CARDWIRE_PID SEC(".maps"); +} cardwire_daemon_pid SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); diff --git a/crates/cardwire-ebpf/src/helpers.h b/crates/cardwire-ebpf/src/helpers.h index 621d142..d9e9262 100644 --- a/crates/cardwire-ebpf/src/helpers.h +++ b/crates/cardwire-ebpf/src/helpers.h @@ -15,7 +15,8 @@ static __always_inline int is_blocked_device(struct dentry *d) __u32 ppid = BPF_CORE_READ(task, real_parent, tgid); // key 0 contain cardwire pid, if ppid = cardwire allow __u8 cardwire_key = 0; - __u32 *cardwire_pid = bpf_map_lookup_elem(&CARDWIRE_PID, &cardwire_key); + __u32 *cardwire_pid = + bpf_map_lookup_elem(&cardwire_daemon_pid, &cardwire_key); if (cardwire_pid && *cardwire_pid == pid) { return 0; } @@ -30,7 +31,8 @@ static __always_inline int is_blocked_device(struct dentry *d) // Match card/render/nvidia minor if (inode) { __u64 d_ino = BPF_CORE_READ(inode, i_ino); - if (d_ino && bpf_map_lookup_elem(&BLOCKED_INODES, &d_ino)) { + if (d_ino && + bpf_map_lookup_elem(&cardwire_blocked_inodes, &d_ino)) { blocked = true; goto end; } @@ -42,7 +44,7 @@ static __always_inline int is_blocked_device(struct dentry *d) } // get mode __u32 key = 0; - __u8 *mode = bpf_map_lookup_elem(&CURRENT_MODE, &key); + __u8 *mode = bpf_map_lookup_elem(&cardwire_mode, &key); // if map lookup fails, or we are not blocking, or it's hybrid mode, allow if (!mode || *mode == 1) { return 0; @@ -88,7 +90,7 @@ static __always_inline int patch_dirent_if_found(__u32 _, 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(&BLOCKED_INODES, &d_inode)) { + if (bpf_map_lookup_elem(&cardwire_blocked_inodes, &d_inode)) { if (data->last_visible_bpos != 0xFFFFFFFF) { struct linux_dirent64 *visible_dirent = (struct linux_dirent64 @@ -119,7 +121,7 @@ static __always_inline int is_hybrid() { // get current cardwired mode, key should always be 0 __u32 key = 0; - __u8 *mode = bpf_map_lookup_elem(&CURRENT_MODE, &key); + __u8 *mode = bpf_map_lookup_elem(&cardwire_mode, &key); if (!mode) { return false; } @@ -134,7 +136,7 @@ static __always_inline int is_smart() { // get current cardwired mode, key should always be 0 __u32 key = 0; - __u8 *mode = bpf_map_lookup_elem(&CURRENT_MODE, &key); + __u8 *mode = bpf_map_lookup_elem(&cardwire_mode, &key); if (!mode) { return false; } diff --git a/crates/cardwire-ebpf/src/lib.rs b/crates/cardwire-ebpf/src/lib.rs index 90fe94d..9bacf60 100644 --- a/crates/cardwire-ebpf/src/lib.rs +++ b/crates/cardwire-ebpf/src/lib.rs @@ -5,6 +5,11 @@ pub use crate::errors::{CardwireEbpfError, CardwireEbpfResult}; use aya::{ Btf, Ebpf, maps::{HashMap, MapError, RingBuf}, programs::{Lsm, TracePoint} }; + +pub enum EbpfSettings { + ExperimentalNvidia, +} + pub struct EbpfBlocker { ebpf: Ebpf, } @@ -92,8 +97,8 @@ impl EbpfBlocker { pub fn whitelist_cardwire_pid(&mut self, pid: u32) -> CardwireEbpfResult<()> { let mut inode_map: HashMap<_, u8, u32> = HashMap::try_from( self.ebpf - .map_mut("CARDWIRE_PID") - .ok_or_else(|| CardwireEbpfError::missing_map("CARDWIRE_PID"))?, + .map_mut("cardwire_daemon_pid") + .ok_or_else(|| CardwireEbpfError::missing_map("cardwire_daemon_pid"))?, ) .map_err(CardwireEbpfError::aya)?; inode_map @@ -115,11 +120,10 @@ impl EbpfBlocker { // Also insert hardcoded values for now let mut inode_map: HashMap<_, u64, u8> = HashMap::try_from( self.ebpf - .map_mut("BLOCKED_INODES") - .ok_or_else(|| CardwireEbpfError::missing_map("BLOCKED_INODES"))?, + .map_mut("cardwire_blocked_inodes") + .ok_or_else(|| CardwireEbpfError::missing_map("cardwire_blocked_inodes"))?, ) .map_err(CardwireEbpfError::aya)?; - println!("adding {} to map", inode); inode_map .insert(inode, 1, 0) .map_err(CardwireEbpfError::aya)?; @@ -129,27 +133,47 @@ impl EbpfBlocker { // Also insert hardcoded values for now let mut inode_map: HashMap<_, u64, u8> = HashMap::try_from( self.ebpf - .map_mut("BLOCKED_INODES") - .ok_or_else(|| CardwireEbpfError::missing_map("BLOCKED_INODES"))?, + .map_mut("cardwire_blocked_inodes") + .ok_or_else(|| CardwireEbpfError::missing_map("cardwire_blocked_inodes"))?, ) .map_err(CardwireEbpfError::aya)?; - inode_map.remove(&inode).map_err(CardwireEbpfError::aya)?; - Ok(()) + 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)), + } } 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("BLOCKED_INODES") - .ok_or_else(|| CardwireEbpfError::missing_map("BLOCKED_INODES"))?, + .map("cardwire_blocked_inodes") + .ok_or_else(|| CardwireEbpfError::missing_map("cardwire_blocked_inodes"))?, ) .map_err(CardwireEbpfError::aya)?; - return match inode_map.get(&inode, 0) { + match inode_map.get(&inode, 0) { Ok(_) => Ok(true), Err(MapError::KeyNotFound) => Ok(false), Err(err) => Err(CardwireEbpfError::aya(err)), + } + } + + 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("cardwire_settings") + .ok_or_else(|| CardwireEbpfError::missing_map("cardwire_settings"))?, + ) + .map_err(CardwireEbpfError::aya)?; + setting_map + .insert(&key, value, 0) + .map_err(CardwireEbpfError::aya) } pub fn get_exec_ring(&mut self) -> CardwireEbpfResult> { @@ -168,7 +192,7 @@ impl EbpfBlocker { 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("cardwire_mode").unwrap(); let map: HashMap = HashMap::try_from(map).unwrap(); Ok(map) } From c316b68b645f24c62c35c5f2d317b96a81679f2c Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 8 Jul 2026 18:05:23 +0200 Subject: [PATCH 06/17] feat: implement smart mode for getdents64 --- crates/cardwire-ebpf/src/bpf.c | 39 +++++++++- crates/cardwire-ebpf/src/helpers.h | 111 +++++++++++++++++------------ 2 files changed, 104 insertions(+), 46 deletions(-) diff --git a/crates/cardwire-ebpf/src/bpf.c b/crates/cardwire-ebpf/src/bpf.c index 66d7eed..94ac46e 100644 --- a/crates/cardwire-ebpf/src/bpf.c +++ b/crates/cardwire-ebpf/src/bpf.c @@ -117,11 +117,30 @@ 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) { @@ -140,6 +159,24 @@ int cardwire_sys_exit_getdents64(struct trace_event_raw_sys_exit *ctx) 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 = bpf_map_lookup_elem(&map_dirent, &pid); if (!dirents_buf) diff --git a/crates/cardwire-ebpf/src/helpers.h b/crates/cardwire-ebpf/src/helpers.h index d9e9262..883bff2 100644 --- a/crates/cardwire-ebpf/src/helpers.h +++ b/crates/cardwire-ebpf/src/helpers.h @@ -1,30 +1,81 @@ -static __always_inline int is_blocked_device(struct dentry *d) + +static __always_inline int is_hybrid() { - if (!d) { - return 0; + // get current cardwired mode, key should always be 0 + __u32 key = 0; + __u8 *mode = bpf_map_lookup_elem(&cardwire_mode, &key); + if (!mode) { + return false; } - // if it's cardwired, exit - char comm[16] = {}; - bpf_get_current_comm(comm, sizeof(comm)); - if (__builtin_memcmp(comm, "cardwired", 9) == 0) { - return 0; + //if mode is hybrid, return true + if (*mode == 1) { + return true; } - // 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); - // key 0 contain cardwire pid, if ppid = cardwire allow + 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(&cardwire_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(&cardwire_daemon_pid, &cardwire_key); if (cardwire_pid && *cardwire_pid == pid) { - return 0; + return true; } + return false; +} - // same for udev +/// 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 0; } +} + +/// 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(&ALLOWED_PID, &pid) || + bpf_map_lookup_elem(&ALLOWED_PID, &ppid); +} + +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); @@ -115,34 +166,4 @@ static __always_inline int patch_dirent_if_found(__u32 _, data->last_visible_bpos = data->bpos; data->bpos += data->d_reclen; return 0; // Continue loop -} - -static __always_inline int is_hybrid() -{ - // get current cardwired mode, key should always be 0 - __u32 key = 0; - __u8 *mode = bpf_map_lookup_elem(&cardwire_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(&cardwire_mode, &key); - if (!mode) { - return false; - } - //if mode is smart, true - if (*mode == 3) { - return true; - } - return false; } \ No newline at end of file From ad547e658f86a3f7182e05dada996b199aaeddba Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 8 Jul 2026 18:11:10 +0200 Subject: [PATCH 07/17] refactor: re-implement commented functions and fix clippy errors --- crates/cardwire-daemon/src/core/gpu/models.rs | 2 +- crates/cardwire-daemon/src/core/inode.rs | 2 +- crates/cardwire-daemon/src/interface/config.rs | 15 ++++----------- crates/cardwire-daemon/src/interface/gpu.rs | 2 +- crates/cardwire-daemon/src/models.rs | 6 ++++-- crates/cardwire-ebpf/src/lib.rs | 2 +- 6 files changed, 12 insertions(+), 17 deletions(-) diff --git a/crates/cardwire-daemon/src/core/gpu/models.rs b/crates/cardwire-daemon/src/core/gpu/models.rs index 4af75b0..b6ba047 100644 --- a/crates/cardwire-daemon/src/core/gpu/models.rs +++ b/crates/cardwire-daemon/src/core/gpu/models.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, fmt::Display, str::FromStr}; +use std::{fmt::Display, str::FromStr}; use crate::core::pci::PciDevice; diff --git a/crates/cardwire-daemon/src/core/inode.rs b/crates/cardwire-daemon/src/core/inode.rs index af9a555..dfd9894 100644 --- a/crates/cardwire-daemon/src/core/inode.rs +++ b/crates/cardwire-daemon/src/core/inode.rs @@ -59,7 +59,7 @@ pub fn pci_to_inode( if let Ok(metadata) = fs::metadata(&pci_path) { return Some(metadata.ino()); } - return None; + None }; // first we push the pci inode diff --git a/crates/cardwire-daemon/src/interface/config.rs b/crates/cardwire-daemon/src/interface/config.rs index 7cc01da..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,16 +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))) - //} - Ok(()) + 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/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index af709db..6d31890 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -151,7 +151,7 @@ impl GpuInterface { 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()) { + 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(), }; diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index 236c669..bd48ad7 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -1,13 +1,13 @@ //! where the struct and impl are declared use crate::{ analyzer::CardwireAnalyzer, core::{ - gpu::{self, GpuVendor, check_default_drm_class}, pci + gpu::{self, check_default_drm_class}, pci }, file::{CardwireConfig, CardwireGpuState, CardwireModeState}, interface::{ ConfigInterface, ConfigMemory, DebugInterface, GpuInterface, ModeInterface, Modes } }; use anyhow::{Context, Result}; -use cardwire_ebpf::EbpfBlocker; +use cardwire_ebpf::{EbpfBlocker, EbpfSettings}; use log::{error, warn}; use std::{collections::BTreeMap, sync::Arc}; use tokio::sync::RwLock; @@ -115,6 +115,8 @@ impl DaemonManager { error!("failed to whitelist cardwire's pid: {}", err); return Err(err.into()); }; + + blocker.set_ebpf_setting(EbpfSettings::ExperimentalNvidia, config.into())?; drop(blocker); let default: bool = state.is_default_state(); diff --git a/crates/cardwire-ebpf/src/lib.rs b/crates/cardwire-ebpf/src/lib.rs index 9bacf60..dd9c512 100644 --- a/crates/cardwire-ebpf/src/lib.rs +++ b/crates/cardwire-ebpf/src/lib.rs @@ -172,7 +172,7 @@ impl EbpfBlocker { ) .map_err(CardwireEbpfError::aya)?; setting_map - .insert(&key, value, 0) + .insert(key, value, 0) .map_err(CardwireEbpfError::aya) } From bc621e234a86381f800f57497458d63cfec04e29 Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 8 Jul 2026 18:18:15 +0200 Subject: [PATCH 08/17] feat: implement nvidia block --- crates/cardwire-daemon/src/core/inode.rs | 20 ++++---- crates/cardwire-daemon/src/interface/gpu.rs | 55 +++++++++++++++------ 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/crates/cardwire-daemon/src/core/inode.rs b/crates/cardwire-daemon/src/core/inode.rs index dfd9894..dfeacf7 100644 --- a/crates/cardwire-daemon/src/core/inode.rs +++ b/crates/cardwire-daemon/src/core/inode.rs @@ -1,6 +1,6 @@ //! Used to get inodes for specific files use std::{ - collections::BTreeMap, fs::{self, File}, os::unix::fs::MetadataExt + collections::BTreeMap, fs::{self}, os::unix::fs::MetadataExt }; use anyhow::Result; @@ -8,13 +8,15 @@ use log::warn; use crate::core::pci::PciDevice; -const BLOCKED_PCI_FILES: &[&str] = &[ +// shouldn't be necessary anymore +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", @@ -109,15 +111,13 @@ pub fn single_pci_to_inode(pci: &str) -> Result { Ok(inode) } -fn nvidia_to_inode(nvidia_minor: u32) -> Result { +pub fn nvidia_to_inode(nvidia_minor: u32) -> Result { let nvidia_path = format!("/dev/nvidia{}", nvidia_minor); - let inode = File::open(&nvidia_path) - .map_err(|e| { - warn!("failed to get inode for {}: {}", nvidia_path, e); - e - })? - .metadata()? - .ino(); + 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) } diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index 6d31890..8545f23 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -6,7 +6,9 @@ use std::{ use crate::{ core::{ - gpu::{DbusGpuDevice, GpuDevice, GpuVendor}, inode::{card_to_inode, pci_to_inode, render_to_inode, single_pci_to_inode}, pci::PciDevice + gpu::{DbusGpuDevice, GpuDevice, GpuVendor}, 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::EbpfBlocker; @@ -95,13 +97,17 @@ impl GpuInterface { } }; // 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()?; - //} + if self.device.gpu_vendor() == GpuVendor::Nvidia + && let Some(minor) = self.device.nvidia_minor() + { + 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(); + } + }; + } Ok(()) } /// unblock the gpu @@ -130,13 +136,17 @@ impl GpuInterface { 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()?; - //} + if self.device.gpu_vendor() == GpuVendor::Nvidia + && let Some(minor) = self.device.nvidia_minor() + { + 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(); + } + }; + } Ok(()) } /// check if the gpu is blocked @@ -156,7 +166,20 @@ impl GpuInterface { Err(err) => return Err(err).into_fdo(), }; - Ok(card && render && pci) + 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> { From 58b17296f4bc5c5f7617240888a0494d4ee6c93c Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 8 Jul 2026 18:18:57 +0200 Subject: [PATCH 09/17] fix: ignore blocked nvidia files for now --- crates/cardwire-daemon/src/core/inode.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cardwire-daemon/src/core/inode.rs b/crates/cardwire-daemon/src/core/inode.rs index dfeacf7..9401e8e 100644 --- a/crates/cardwire-daemon/src/core/inode.rs +++ b/crates/cardwire-daemon/src/core/inode.rs @@ -18,7 +18,7 @@ const _BLOCKED_PCI_FILES: &[&str] = &[ ]; /// Files that get blocked when the NVIDIA block is on -const BLOCKED_NVIDIA_FILES: &[&str] = &[ +const _BLOCKED_NVIDIA_FILES: &[&str] = &[ "libGLX_nvidia.so.0", "nvidia_icd.json", "nvidia_icd.x86_64.json", From 91728d37be3ba39bd8a62e13b45a495be95f47fe Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 8 Jul 2026 18:36:04 +0200 Subject: [PATCH 10/17] fix: also hide inode without following the link --- crates/cardwire-daemon/src/core/inode.rs | 41 ++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/crates/cardwire-daemon/src/core/inode.rs b/crates/cardwire-daemon/src/core/inode.rs index 9401e8e..c52e063 100644 --- a/crates/cardwire-daemon/src/core/inode.rs +++ b/crates/cardwire-daemon/src/core/inode.rs @@ -64,6 +64,14 @@ pub fn pci_to_inode( None }; + let get_pci_inode_no_link = |pci: &str| { + let pci_path = format!("/sys/bus/pci/devices/{}", pci); + if let Ok(metadata) = fs::symlink_metadata(&pci_path) { + return Some(metadata.ino()); + } + None + }; + // first we push the pci inode match get_pci_inode(&pci) { Some(inode) => inodes.push(inode), @@ -96,6 +104,39 @@ pub fn pci_to_inode( } } + // Now do the same but without the softlink + // first we push the pci inode + match get_pci_inode_no_link(&pci) { + Some(inode) => inodes.push(inode), + None => { + warn!("failed to get inode for pci: {}", pci); + } + } + // Also push the audio card inode + match get_pci_inode_no_link(&pci.replace(".0", ".1")) { + Some(inode) => inodes.push(inode), + None => { + warn!("failed to get inode for pci: {}", &pci.replace(".0", ".1")); + } + } + + 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(); + match get_pci_inode_no_link(pci_device.pci_address()) { + Some(inode) => inodes.push(inode), + None => { + warn!("failed to get inode for pci: {}", pci_device.pci_address()); + continue; + } + } + } else { + warn!("expected parent pci {} not found in pci_list", parent_pci); + break; + } + } + Ok(inodes) } From 643bb8e83bdcae70e6c0f70f31197a19f63f5f1f Mon Sep 17 00:00:00 2001 From: luytan Date: Wed, 8 Jul 2026 19:05:12 +0200 Subject: [PATCH 11/17] fix: is_process_whitelisted not returning bool and remove a forgotten println! --- crates/cardwire-daemon/src/interface/mode.rs | 1 - crates/cardwire-ebpf/src/bpf.h | 3 +-- crates/cardwire-ebpf/src/helpers.h | 3 ++- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/cardwire-daemon/src/interface/mode.rs b/crates/cardwire-daemon/src/interface/mode.rs index ee030c8..0819e45 100644 --- a/crates/cardwire-daemon/src/interface/mode.rs +++ b/crates/cardwire-daemon/src/interface/mode.rs @@ -92,7 +92,6 @@ impl ModeInterface { 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); - println!("updating current mode to: {}", mode); mode_map .insert(0, mode as u8, 0) .map_err(|err| fdo::Error::Failed(err.to_string())) diff --git a/crates/cardwire-ebpf/src/bpf.h b/crates/cardwire-ebpf/src/bpf.h index 00c0488..b27a219 100644 --- a/crates/cardwire-ebpf/src/bpf.h +++ b/crates/cardwire-ebpf/src/bpf.h @@ -137,8 +137,7 @@ struct { integrated = 0 hybrid = 1 manual = 2 - enforce = 3 - smart = 4 + smart = 3 */ struct { __uint(type, BPF_MAP_TYPE_HASH); diff --git a/crates/cardwire-ebpf/src/helpers.h b/crates/cardwire-ebpf/src/helpers.h index 883bff2..3da4d62 100644 --- a/crates/cardwire-ebpf/src/helpers.h +++ b/crates/cardwire-ebpf/src/helpers.h @@ -48,8 +48,9 @@ static __always_inline int is_process_whitelisted() bpf_get_current_comm(comm, sizeof(comm)); // whitelist udev for pci hotplug if (__builtin_memcmp(comm, "(udev-worker)", 13) == 0) { - return 0; + return true; } + return false; } /// check if the pid is in the allow list, smart mode only From 8775831abea14657c9ffa8534342cb44ee549163 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 9 Jul 2026 22:39:45 +0200 Subject: [PATCH 12/17] ci: this fix ci? --- nix/vm-configuration.nix | 2 ++ 1 file changed, 2 insertions(+) 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 = { From 180ecb5b3c502e2de50d264efbb3ec03a46cf2c0 Mon Sep 17 00:00:00 2001 From: luytan Date: Thu, 9 Jul 2026 22:39:55 +0200 Subject: [PATCH 13/17] refactor: simplify pci to inode function --- crates/cardwire-daemon/src/core/inode.rs | 69 ++++-------------------- 1 file changed, 9 insertions(+), 60 deletions(-) diff --git a/crates/cardwire-daemon/src/core/inode.rs b/crates/cardwire-daemon/src/core/inode.rs index c52e063..08fcc2d 100644 --- a/crates/cardwire-daemon/src/core/inode.rs +++ b/crates/cardwire-daemon/src/core/inode.rs @@ -55,82 +55,31 @@ pub fn pci_to_inode( ) -> Result> { let mut inodes: Vec = Vec::new(); - // quick function return Some(inode) if successfull or return None is fail - let get_pci_inode = |pci: &str| { + // 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) { - return Some(metadata.ino()); + inodes.push(metadata.ino()); } - None - }; - let get_pci_inode_no_link = |pci: &str| { + // Now without following link let pci_path = format!("/sys/bus/pci/devices/{}", pci); if let Ok(metadata) = fs::symlink_metadata(&pci_path) { - return Some(metadata.ino()); + inodes.push(metadata.ino()); } - None }; // first we push the pci inode - match get_pci_inode(&pci) { - Some(inode) => inodes.push(inode), - None => { - warn!("failed to get inode for pci: {}", pci); - } - } - // Also push the audio card inode - match get_pci_inode(&pci.replace(".0", ".1")) { - Some(inode) => inodes.push(inode), - None => { - warn!("failed to get inode for pci: {}", &pci.replace(".0", ".1")); - } - } - - 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(); - match get_pci_inode(pci_device.pci_address()) { - Some(inode) => inodes.push(inode), - None => { - warn!("failed to get inode for pci: {}", pci_device.pci_address()); - continue; - } - } - } else { - warn!("expected parent pci {} not found in pci_list", parent_pci); - break; - } - } - - // Now do the same but without the softlink - // first we push the pci inode - match get_pci_inode_no_link(&pci) { - Some(inode) => inodes.push(inode), - None => { - warn!("failed to get inode for pci: {}", pci); - } - } + push_pci_inode(&pci, &mut inodes); // Also push the audio card inode - match get_pci_inode_no_link(&pci.replace(".0", ".1")) { - Some(inode) => inodes.push(inode), - None => { - warn!("failed to get inode for pci: {}", &pci.replace(".0", ".1")); - } - } + 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(); - match get_pci_inode_no_link(pci_device.pci_address()) { - Some(inode) => inodes.push(inode), - None => { - warn!("failed to get inode for pci: {}", pci_device.pci_address()); - continue; - } - } + push_pci_inode(pci_device.pci_address(), &mut inodes); } else { warn!("expected parent pci {} not found in pci_list", parent_pci); break; From 68d016d3aceac73b2006f4069e4e47aeda03ae09 Mon Sep 17 00:00:00 2001 From: luytan Date: Fri, 10 Jul 2026 00:14:19 +0200 Subject: [PATCH 14/17] feat: implement backlight + nvidia exp blocking, and rename ebp maps to fit the convention --- crates/cardwire-daemon/src/core/inode.rs | 61 ++++++++++++++++++--- crates/cardwire-daemon/src/interface/gpu.rs | 17 +++++- crates/cardwire-daemon/src/models.rs | 18 +++++- crates/cardwire-ebpf/src/bpf.c | 12 ++-- crates/cardwire-ebpf/src/bpf.h | 30 +++++++--- crates/cardwire-ebpf/src/helpers.h | 48 +++++++++++----- crates/cardwire-ebpf/src/lib.rs | 42 +++++++++----- 7 files changed, 174 insertions(+), 54 deletions(-) diff --git a/crates/cardwire-daemon/src/core/inode.rs b/crates/cardwire-daemon/src/core/inode.rs index 08fcc2d..3b13541 100644 --- a/crates/cardwire-daemon/src/core/inode.rs +++ b/crates/cardwire-daemon/src/core/inode.rs @@ -17,14 +17,6 @@ const _BLOCKED_PCI_FILES: &[&str] = &[ "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", -]; - 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| { @@ -111,3 +103,56 @@ pub fn nvidia_to_inode(nvidia_minor: u32) -> Result { 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 { + if let Ok(metadata) = fs::metadata(path) { + inodes.push(metadata.ino()); + } + } + } + + Ok(inodes) +} diff --git a/crates/cardwire-daemon/src/interface/gpu.rs b/crates/cardwire-daemon/src/interface/gpu.rs index 8545f23..a4b86d5 100644 --- a/crates/cardwire-daemon/src/interface/gpu.rs +++ b/crates/cardwire-daemon/src/interface/gpu.rs @@ -7,7 +7,7 @@ use std::{ use crate::{ core::{ gpu::{DbusGpuDevice, GpuDevice, GpuVendor}, inode::{ - card_to_inode, nvidia_to_inode, pci_to_inode, render_to_inode, single_pci_to_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 }; @@ -107,6 +107,13 @@ impl GpuInterface { 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(()) } @@ -146,6 +153,13 @@ impl GpuInterface { 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(()) } @@ -165,7 +179,6 @@ impl GpuInterface { 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) => { diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index bd48ad7..d30bec0 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -1,7 +1,7 @@ //! where the struct and impl are declared use crate::{ analyzer::CardwireAnalyzer, core::{ - gpu::{self, 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 } @@ -116,7 +116,23 @@ impl DaemonManager { 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 + && 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(); diff --git a/crates/cardwire-ebpf/src/bpf.c b/crates/cardwire-ebpf/src/bpf.c index 94ac46e..c5f93a2 100644 --- a/crates/cardwire-ebpf/src/bpf.c +++ b/crates/cardwire-ebpf/src/bpf.c @@ -81,7 +81,8 @@ int trace_exec(void *ctx) // 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; @@ -103,7 +104,8 @@ int trace_process_exit(void *ctx) // 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; } @@ -147,7 +149,7 @@ int cardwire_sys_enter_getdents64(struct trace_event_raw_sys_enter *ctx) return 0; } // Save addr into map - bpf_map_update_elem(&map_dirent, &pid, &dirents_buf, BPF_ANY); + bpf_map_update_elem(&cw_dirent, &pid, &dirents_buf, BPF_ANY); return 0; } @@ -177,13 +179,13 @@ int cardwire_sys_exit_getdents64(struct trace_event_raw_sys_exit *ctx) } } - __u64 *dirents_buf = bpf_map_lookup_elem(&map_dirent, &pid); + __u64 *dirents_buf = bpf_map_lookup_elem(&cw_dirent, &pid); if (!dirents_buf) return 0; // Clean up the map immediately so it doesn't fill up - bpf_map_delete_elem(&map_dirent, &pid); + bpf_map_delete_elem(&cw_dirent, &pid); // If getdents64 return 0 bytes if (ctx->ret <= 0) { diff --git a/crates/cardwire-ebpf/src/bpf.h b/crates/cardwire-ebpf/src/bpf.h index b27a219..635052f 100644 --- a/crates/cardwire-ebpf/src/bpf.h +++ b/crates/cardwire-ebpf/src/bpf.h @@ -109,18 +109,18 @@ struct report_t { struct { __uint(type, BPF_MAP_TYPE_RINGBUF); __uint(max_entries, 256 * 1024); -} EXEC_EVENTS SEC(".maps"); +} cw_exec_events SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_RINGBUF); __uint(max_entries, 256 * 1024); -} CLOSE_EVENTS SEC(".maps"); +} 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); -} REPORT SEC(".maps"); +} cw_report SEC(".maps"); // List of blocked comm // Used for smart mode @@ -129,7 +129,7 @@ struct { __uint(max_entries, 16384); __type(key, __u32); __type(value, __u8); -} ALLOWED_PID SEC(".maps"); +} cw_allowed_pid SEC(".maps"); /* mode map, mode should be stored in key 0 @@ -144,32 +144,44 @@ struct { __uint(max_entries, 1); __type(key, __u8); __type(value, __u8); -} cardwire_mode SEC(".maps"); +} cw_mode SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 2048); __type(key, __u64); __type(value, __u8); -} cardwire_blocked_inodes SEC(".maps"); +} 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); -} cardwire_settings SEC(".maps"); +} cw_settings SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 1); __type(key, __u8); __type(value, __u32); -} cardwire_daemon_pid SEC(".maps"); +} cw_daemon_pid SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 1024); __type(key, __u32); __type(value, __u64); -} map_dirent SEC(".maps"); +} cw_dirent SEC(".maps"); diff --git a/crates/cardwire-ebpf/src/helpers.h b/crates/cardwire-ebpf/src/helpers.h index 3da4d62..8554169 100644 --- a/crates/cardwire-ebpf/src/helpers.h +++ b/crates/cardwire-ebpf/src/helpers.h @@ -3,7 +3,7 @@ static __always_inline int is_hybrid() { // get current cardwired mode, key should always be 0 __u32 key = 0; - __u8 *mode = bpf_map_lookup_elem(&cardwire_mode, &key); + __u8 *mode = bpf_map_lookup_elem(&cw_mode, &key); if (!mode) { return false; } @@ -18,7 +18,7 @@ static __always_inline int is_smart() { // get current cardwired mode, key should always be 0 __u32 key = 0; - __u8 *mode = bpf_map_lookup_elem(&cardwire_mode, &key); + __u8 *mode = bpf_map_lookup_elem(&cw_mode, &key); if (!mode) { return false; } @@ -34,7 +34,7 @@ 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(&cardwire_daemon_pid, &cardwire_key); + bpf_map_lookup_elem(&cw_daemon_pid, &cardwire_key); if (cardwire_pid && *cardwire_pid == pid) { return true; } @@ -56,8 +56,18 @@ static __always_inline int is_process_whitelisted() /// 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(&ALLOWED_PID, &pid) || - bpf_map_lookup_elem(&ALLOWED_PID, &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) @@ -83,20 +93,26 @@ static __always_inline int is_blocked_device(struct dentry *d) // Match card/render/nvidia minor if (inode) { __u64 d_ino = BPF_CORE_READ(inode, i_ino); - if (d_ino && - bpf_map_lookup_elem(&cardwire_blocked_inodes, &d_ino)) { - blocked = true; - goto end; + 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(&cardwire_mode, &key); + __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; @@ -109,8 +125,8 @@ static __always_inline int is_blocked_device(struct dentry *d) // if smart, check the pid list if (*mode == 3) { - if (!bpf_map_lookup_elem(&ALLOWED_PID, &pid) && - !bpf_map_lookup_elem(&ALLOWED_PID, &ppid)) { + 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; } @@ -142,7 +158,9 @@ static __always_inline int patch_dirent_if_found(__u32 _, 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(&cardwire_blocked_inodes, &d_inode)) { + 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 @@ -167,4 +185,4 @@ static __always_inline int patch_dirent_if_found(__u32 _, data->last_visible_bpos = data->bpos; data->bpos += data->d_reclen; return 0; // Continue loop -} \ No newline at end of file +} diff --git a/crates/cardwire-ebpf/src/lib.rs b/crates/cardwire-ebpf/src/lib.rs index dd9c512..714b95a 100644 --- a/crates/cardwire-ebpf/src/lib.rs +++ b/crates/cardwire-ebpf/src/lib.rs @@ -97,8 +97,8 @@ impl EbpfBlocker { pub fn whitelist_cardwire_pid(&mut self, pid: u32) -> CardwireEbpfResult<()> { let mut inode_map: HashMap<_, u8, u32> = HashMap::try_from( self.ebpf - .map_mut("cardwire_daemon_pid") - .ok_or_else(|| CardwireEbpfError::missing_map("cardwire_daemon_pid"))?, + .map_mut("cw_daemon_pid") + .ok_or_else(|| CardwireEbpfError::missing_map("cw_daemon_pid"))?, ) .map_err(CardwireEbpfError::aya)?; inode_map @@ -120,8 +120,8 @@ impl EbpfBlocker { // Also insert hardcoded values for now let mut inode_map: HashMap<_, u64, u8> = HashMap::try_from( self.ebpf - .map_mut("cardwire_blocked_inodes") - .ok_or_else(|| CardwireEbpfError::missing_map("cardwire_blocked_inodes"))?, + .map_mut("cw_blocked_ino") + .ok_or_else(|| CardwireEbpfError::missing_map("cw_blocked_ino"))?, ) .map_err(CardwireEbpfError::aya)?; inode_map @@ -133,8 +133,8 @@ impl EbpfBlocker { // Also insert hardcoded values for now let mut inode_map: HashMap<_, u64, u8> = HashMap::try_from( self.ebpf - .map_mut("cardwire_blocked_inodes") - .ok_or_else(|| CardwireEbpfError::missing_map("cardwire_blocked_inodes"))?, + .map_mut("cw_blocked_ino") + .ok_or_else(|| CardwireEbpfError::missing_map("cw_blocked_ino"))?, ) .map_err(CardwireEbpfError::aya)?; match inode_map.get(&inode, 0) { @@ -150,8 +150,8 @@ impl EbpfBlocker { // Also insert hardcoded values for now let inode_map: HashMap<_, u64, u8> = HashMap::try_from( self.ebpf - .map("cardwire_blocked_inodes") - .ok_or_else(|| CardwireEbpfError::missing_map("cardwire_blocked_inodes"))?, + .map("cw_blocked_ino") + .ok_or_else(|| CardwireEbpfError::missing_map("cw_blocked_ino"))?, ) .map_err(CardwireEbpfError::aya)?; match inode_map.get(&inode, 0) { @@ -161,14 +161,28 @@ impl EbpfBlocker { } } + 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(()) + } + 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("cardwire_settings") - .ok_or_else(|| CardwireEbpfError::missing_map("cardwire_settings"))?, + .map_mut("cw_settings") + .ok_or_else(|| CardwireEbpfError::missing_map("cw_settings"))?, ) .map_err(CardwireEbpfError::aya)?; setting_map @@ -177,22 +191,22 @@ impl EbpfBlocker { } 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("cardwire_mode").unwrap(); + let map = self.ebpf.take_map("cw_mode").unwrap(); let map: HashMap = HashMap::try_from(map).unwrap(); Ok(map) } From bd6e4903087cca4ec2d209a8d14170f97af3c6f6 Mon Sep 17 00:00:00 2001 From: luytan Date: Fri, 10 Jul 2026 00:23:03 +0200 Subject: [PATCH 15/17] chore: remove unused kernel definitions --- crates/cardwire-ebpf/src/bpf.h | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/crates/cardwire-ebpf/src/bpf.h b/crates/cardwire-ebpf/src/bpf.h index 635052f..729767b 100644 --- a/crates/cardwire-ebpf/src/bpf.h +++ b/crates/cardwire-ebpf/src/bpf.h @@ -14,26 +14,11 @@ struct hlist_head { } __attribute__((preserve_access_index)); struct inode { - __u16 i_mode; - __u32 i_rdev; __u64 i_ino; 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; @@ -41,8 +26,6 @@ struct dentry___old { } __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)); From 56f0495ff1e8a2ba3d525de88fc5d76b1a9ed8eb Mon Sep 17 00:00:00 2001 From: luytan Date: Fri, 10 Jul 2026 00:31:33 +0200 Subject: [PATCH 16/17] fix: implement udev pci event for inodes, fix bpf issues --- crates/cardwire-daemon/src/interface/debug.rs | 38 +++++++++++++++++-- crates/cardwire-ebpf/src/bpf.c | 8 ++-- crates/cardwire-ebpf/src/helpers.h | 19 +++++++--- 3 files changed, 54 insertions(+), 11 deletions(-) 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-ebpf/src/bpf.c b/crates/cardwire-ebpf/src/bpf.c index c5f93a2..20ab3c1 100644 --- a/crates/cardwire-ebpf/src/bpf.c +++ b/crates/cardwire-ebpf/src/bpf.c @@ -179,11 +179,13 @@ int cardwire_sys_exit_getdents64(struct trace_event_raw_sys_exit *ctx) } } - __u64 *dirents_buf = bpf_map_lookup_elem(&cw_dirent, &pid); + __u64 *dirents_buf_ptr = bpf_map_lookup_elem(&cw_dirent, &pid); - if (!dirents_buf) + 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); @@ -194,7 +196,7 @@ int cardwire_sys_exit_getdents64(struct trace_event_raw_sys_exit *ctx) struct dirents_data_t dirents_data = { .bpos = 0, - .dirents_buf = *dirents_buf, + .dirents_buf = dirents_buf, .buff_size = ctx->ret, .d_reclen = 0, .last_visible_bpos = 0xFFFFFFFF, diff --git a/crates/cardwire-ebpf/src/helpers.h b/crates/cardwire-ebpf/src/helpers.h index 8554169..79974ee 100644 --- a/crates/cardwire-ebpf/src/helpers.h +++ b/crates/cardwire-ebpf/src/helpers.h @@ -146,12 +146,21 @@ static __always_inline int patch_dirent_if_found(__u32 _, // 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; - bpf_probe_read(&d_inode, sizeof(d_inode), &dirent->d_ino); - if (!d_inode) - return 0; - bpf_probe_read(&data->d_reclen, sizeof(data->d_reclen), - &dirent->d_reclen); + 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] = {}; From 65bd1feefe3afb4a839c25ea1681d64e5268257f Mon Sep 17 00:00:00 2001 From: luytan Date: Fri, 10 Jul 2026 00:34:12 +0200 Subject: [PATCH 17/17] chore: fix clippy issue --- crates/cardwire-daemon/src/core/inode.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/cardwire-daemon/src/core/inode.rs b/crates/cardwire-daemon/src/core/inode.rs index 3b13541..0469777 100644 --- a/crates/cardwire-daemon/src/core/inode.rs +++ b/crates/cardwire-daemon/src/core/inode.rs @@ -147,10 +147,8 @@ pub fn exp_nvidia_inodes() -> Result> { }) .unwrap_or(false); - if has_nvidia { - if let Ok(metadata) = fs::metadata(path) { - inodes.push(metadata.ino()); - } + if has_nvidia && let Ok(metadata) = fs::metadata(path) { + inodes.push(metadata.ino()); } }