Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 10 additions & 8 deletions crates/cardwire-core/src/gpu/ebpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ pub fn is_gpu_blocked(blocker: &EbpfBlocker, gpu: &Gpu) -> Result<bool, Box<dyn
let (card_id, render_id) = gpu_node_ids(gpu)?;
Ok(
blocker.is_pci_blocked(gpu.pci_address())?
&& blocker.is_id_blocked(card_id)?
&& blocker.is_id_blocked(render_id)?,
&& blocker.is_card_blocked(card_id)?
&& blocker.is_render_blocked(render_id)?,
)
}

Expand All @@ -17,13 +17,15 @@ pub fn block_gpu(blocker: &mut EbpfBlocker, gpu: &Gpu, block: bool) -> Result<()
let (card_id, render_id) = gpu_node_ids(gpu)?;

if block {
blocker.block_id(card_id)?;
blocker.block_id(render_id)?;
blocker.block_pci(gpu.pci_address())
blocker.block_card(card_id)?;
blocker.block_render(render_id)?;
blocker.block_pci(gpu.pci_address())?;
Ok(())
} else {
blocker.unblock_id(card_id)?;
blocker.unblock_id(render_id)?;
blocker.unblock_pci(gpu.pci_address())
blocker.unblock_card(card_id)?;
blocker.unblock_render(render_id)?;
blocker.unblock_pci(gpu.pci_address())?;
Ok(())
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/cardwire-ebpf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ license.workspace = true

[dependencies]
aya = "0.13.1"
flate2 = "1.1.9"
thiserror = { workspace = true }
tokio.workspace = true
249 changes: 149 additions & 100 deletions crates/cardwire-ebpf/src/bpf.c
Original file line number Diff line number Diff line change
@@ -1,40 +1,25 @@
#include <linux/bpf.h>
#include "vmlinux.h"
#include <bpf/bpf_core_read.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>

#ifndef ENOENT
#define ENOENT 2
#endif

typedef unsigned int u32;
typedef unsigned char u8;

struct qstr {
const unsigned char *name;
} __attribute__((preserve_access_index));
char LICENSE[] SEC("license") = "Dual MIT/GPL";

struct dentry {
struct qstr d_name;
struct dentry *d_parent;
} __attribute__((preserve_access_index));

struct path {
struct dentry *dentry;
} __attribute__((preserve_access_index));

struct file {
struct path f_path;
} __attribute__((preserve_access_index));
#define ENOENT 2

char _license[] SEC("license") = "GPL";
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_IDS SEC(".maps");
} BLOCKED_CARDID SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_HASH);
Expand All @@ -43,93 +28,157 @@ struct {
__type(value, u8);
} BLOCKED_PCI SEC(".maps");

SEC("lsm/file_open")
int BPF_PROG(file_open, struct file *file) {
struct dentry *dentry = BPF_CORE_READ(file, f_path.dentry);
const unsigned char *name = BPF_CORE_READ(dentry, d_name.name);

if (!name) {
/* Safely read and compare kernel qstr, may be useless */
static __always_inline int qstr_eq(struct qstr q, const char *name, u32 len) {
if (!q.name || q.len != len) {
return 0;
}

char buf[8] = {}; // Big enough for "dri", "dev", "/"
if (len >= sizeof(buf)) {
return 0;
}

if (bpf_core_read_str(buf, sizeof(buf), q.name) < 0) {
return 0;
}

return __builtin_memcmp(buf, name, len) == 0;
}

/*
Check if file belongs to /dev/dri/, this prevent blocking normal files named renderD128 or card1
*/
static __always_inline int is_dev_dri(struct dentry *dentry){
struct dentry *parent;
struct qstr q;

if (!dentry) return 1;

// First parent must be "dri" (e.g., /dev/dri/card0)
parent = BPF_CORE_READ(dentry, d_parent);
if (!parent) return 1;
q = BPF_CORE_READ(parent, d_name);
if (!qstr_eq(q, "dri", 3)) return 1;

// Check for the next parent in the path hierarchy.
parent = BPF_CORE_READ(parent, d_parent);
if (!parent) return 1;
q = BPF_CORE_READ(parent, d_name);

if (qstr_eq(q, "dev", 3)) {
// If the parent was 'dev', the next parent must be the root '/'.
parent = BPF_CORE_READ(parent, d_parent);
if (!parent) return 1;
q = BPF_CORE_READ(parent, d_name);
if (!qstr_eq(q, "/", 1)) return 1;
} else if (qstr_eq(q, "/", 1)) {
// Parent is already '/', which means 'dri' is at the root of its fs
} else {
return 1;
}

// Read file name prefix.
char buf[8] = {0};
bpf_probe_read_kernel_str(&buf, sizeof(buf), name);

u32 id = 0;
int is_match = 0;
return 0;
}

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;
}

// Match renderD<id>.
if (buf[0] == 'r' && buf[1] == 'e' && buf[2] == 'n' && buf[3] == 'd' &&
buf[4] == 'e' && buf[5] == 'r' && buf[6] == 'D') {
SEC("lsm/file_open")
int BPF_PROG(file_open, struct file *file){
char filename[16] = {};
struct dentry *d = BPF_CORE_READ(file, f_path.dentry);
const unsigned char *name_ptr = NULL;

// Parse up to 3 digits.
char id_buf[4] = {0};
bpf_probe_read_kernel_str(&id_buf, sizeof(id_buf), name + 7);
if (d) {
name_ptr = BPF_CORE_READ(d, d_name.name);
}

#pragma unroll
for (int i = 0; i < 3; i++) {
if (id_buf[i] >= '0' && id_buf[i] <= '9') {
id = id * 10 + (id_buf[i] - '0');
is_match = 1;
} else {
break;
}
if (name_ptr) {
if (bpf_core_read_str(filename, sizeof(filename), name_ptr) < 0){
return 0;
}
}
// Match card<id>.
else if (buf[0] == 'c' && buf[1] == 'a' && buf[2] == 'r' && buf[3] == 'd') {

char id_buf[4] = {0};
bpf_probe_read_kernel_str(&id_buf, sizeof(id_buf), name + 4);

#pragma unroll
for (int i = 0; i < 3; i++) {
if (id_buf[i] >= '0' && id_buf[i] <= '9') {
id = id * 10 + (id_buf[i] - '0');
is_match = 1;
} else {
break;
// CardID Check
if (__builtin_memcmp(filename, "card", 4) == 0) {
if (is_dev_dri(d) != 0) return 0;

u32 id = 0;
int i = 4;
int is_match = 0;
#pragma unroll
for (int j = 0; j < 9; j++){
if (i >= sizeof(filename)) break;
char c = filename[i];
if (c >= '0' && c <= '9') {
id = id * 10 + (c - '0');
i++;
is_match = 1;
} else {
break;
}
}
}
}
// Match config under PCI device directory.
else if (buf[0] == 'c' && buf[1] == 'o' && buf[2] == 'n' && buf[3] == 'f' &&
buf[4] == 'i' && buf[5] == 'g' && buf[6] == '\0') {

struct dentry *parent = BPF_CORE_READ(dentry, d_parent);
const unsigned char *parent_name = BPF_CORE_READ(parent, d_name.name);

if (parent_name) {
char pci_addr[16] = {0};
bpf_probe_read_kernel_str(&pci_addr, sizeof(pci_addr), parent_name);

// Accept only PCI-like parent names: 0000:00:00.0.
if (pci_addr[4] == ':' && pci_addr[7] == ':' && pci_addr[10] == '.') {

bpf_printk("Checking config for PCI: %s", pci_addr);

// Force the function number to always be '0'
// This converts "0000:03:00.1" -> "0000:03:00.0"
pci_addr[11] = '0';
pci_addr[12] = '\0';

// Now look up the base address in the map
u8 *value = bpf_map_lookup_elem(&BLOCKED_PCI, &pci_addr);
if (value && *value == 1) {
bpf_printk("Blocked config for PCI: %s", pci_addr);
return -ENOENT;
if (is_match && bpf_map_lookup_elem(&BLOCKED_CARDID, &id)) {
return -ENOENT;
}
}
// RenderID Check
else if (__builtin_memcmp(filename, "renderD", 7) == 0) {
if (is_dev_dri(d) != 0) return 0;

u32 id = 0;
int i = 7;
int is_match = 0;
#pragma unroll
for (int j = 0; j < 9; j++){
if (i >= sizeof(filename)) break;
char c = filename[i];
if (c >= '0' && c <= '9'){
id = id * 10 + (c - '0');
i++;
is_match = 1;
} else {
break;
}
}
if (is_match && bpf_map_lookup_elem(&BLOCKED_RENDERID, &id)) {
return -ENOENT;
}
}
}
// Config Check via pci
else if (__builtin_memcmp(filename, "config", 6) == 0) {
char pci_addr[16] = {};
if (get_pci_addr(d, pci_addr, sizeof(pci_addr)) != 0){
return 0;
}

pci_addr[11] = '0';
pci_addr[12] = '\0';

if (is_match) {
u8 *value = bpf_map_lookup_elem(&BLOCKED_IDS, &id);
if (value && *value == 1) {
return -ENOENT;
if (bpf_map_lookup_elem(&BLOCKED_PCI, pci_addr)) {
return -ENOENT;
}
}
}

return 0;
}
Loading