denet 0.7.0

a simple process monitor
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
//! eBPF Diagnostic Tool
//!
//! This tool performs a comprehensive diagnostic of eBPF capabilities on the current system.
//! It checks for permissions, kernel support, filesystem access, and attempts to load a minimal
//! eBPF program to verify functionality.
//!
//! Usage:
//! ```
//! cargo run --bin ebpf_diag --features ebpf
//! cargo run --bin ebpf_diag --features ebpf -- --debug  # For verbose output
//! ```

use aya::EbpfLoader;
use std::env;

use std::process::{exit, Command};

// Include compiled eBPF bytecode with 8-byte alignment required by the `object`
// crate's ELF parser (it casts the slice directly to FileHeader64).
#[cfg(feature = "ebpf")]
#[repr(align(8))]
struct AlignedBytes<const N: usize>([u8; N]);

#[cfg(feature = "ebpf")]
static SYSCALL_TRACER_BYTECODE_ALIGNED: AlignedBytes<
    { include_bytes!(concat!(env!("OUT_DIR"), "/ebpf/syscall_tracer.o")).len() },
> = AlignedBytes(*include_bytes!(concat!(
    env!("OUT_DIR"),
    "/ebpf/syscall_tracer.o"
)));

#[cfg(feature = "ebpf")]
const SYSCALL_TRACER_BYTECODE: &[u8] = &SYSCALL_TRACER_BYTECODE_ALIGNED.0;

fn separator() {
    println!("\n{}", "=".repeat(80));
}

fn section_title(title: &str) {
    separator();
    println!("[ {} ]", title);
    separator();
}

// Global debug flag
static mut DEBUG_MODE: bool = false;

fn debug_println(msg: &str) {
    unsafe {
        if DEBUG_MODE {
            println!("{}", msg);
        }
    }
}

fn run_command(cmd: &str) -> (bool, String) {
    println!("$ {}", cmd);

    match Command::new("sh").arg("-c").arg(cmd).output() {
        Ok(output) => {
            let stdout = String::from_utf8_lossy(&output.stdout).to_string();
            let stderr = String::from_utf8_lossy(&output.stderr).to_string();
            let result = if stderr.is_empty() {
                stdout
            } else {
                format!("{}\nERROR: {}", stdout, stderr)
            };
            let success = output.status.success();

            // Only print the result if in debug mode
            unsafe {
                if DEBUG_MODE || result.lines().count() <= 3 {
                    println!("{}", result);
                } else {
                    println!("[Output hidden, use --debug for details]");
                }
            }
            (success, result)
        }
        Err(e) => {
            println!("ERROR: Failed to execute command: {}", e);
            (false, format!("Error: {}", e))
        }
    }
}

fn check_permissions() -> bool {
    section_title("USER PERMISSIONS");

    println!("Checking user permissions for eBPF...");
    debug_println("Detailed permission checks will be performed...");

    // Check if running as root
    let is_root = unsafe { libc::geteuid() == 0 };
    println!("Running as root: {}", is_root);

    // Check capabilities of current binary
    let exe_path = std::env::current_exe().unwrap_or_default();
    println!("Current executable: {:?}", exe_path);

    let (_, cap_output) = run_command(&format!("getcap {}", exe_path.display()));
    let has_bpf_cap = cap_output.contains("cap_bpf");
    println!("Has CAP_BPF capability: {}", has_bpf_cap);

    // Check if user is in tracing group
    let (_, groups_output) = run_command("groups");
    let in_tracing_group = groups_output.contains("tracing");
    println!("User in tracing group: {}", in_tracing_group);

    is_root || has_bpf_cap
}

fn check_kernel_support() -> bool {
    section_title("KERNEL SUPPORT");

    println!("Checking kernel support for eBPF...");
    debug_println("Examining kernel configuration in detail...");

    // Check kernel version
    let (kernel_success, kernel_version) = run_command("uname -r");
    if !kernel_success {
        println!("Failed to determine kernel version");
        return false;
    }

    // Parse kernel version
    let version_parts: Vec<&str> = kernel_version.trim().split('.').collect();
    if version_parts.len() >= 2 {
        if let (Ok(major), Ok(minor)) = (
            version_parts[0].parse::<u32>(),
            version_parts[1].parse::<u32>(),
        ) {
            println!("Kernel version {}.{} detected", major, minor);
            let version_ok = (major > 4) || (major == 4 && minor >= 18);
            println!("Kernel version sufficient for eBPF: {}", version_ok);
            if !version_ok {
                println!("WARNING: eBPF features require kernel 4.18 or newer");
            }
        }
    }

    // Check BPF config in kernel
    let (_config_success, config_output) = run_command("grep CONFIG_BPF /boot/config-$(uname -r)");
    let bpf_enabled = config_output.contains("CONFIG_BPF=y");
    println!("BPF enabled in kernel: {}", bpf_enabled);

    // Check JIT compiler
    let (_jit_success, jit_output) = run_command(
        "grep -i jit /proc/sys/net/core/bpf_jit_enable 2>/dev/null || echo 'Not available'",
    );
    let jit_enabled = jit_output.trim() == "1";
    println!("BPF JIT compiler enabled: {}", jit_enabled);

    // Check unprivileged BPF setting
    let (_unpriv_success, unpriv_output) = run_command(
        "cat /proc/sys/kernel/unprivileged_bpf_disabled 2>/dev/null || echo 'Not available'",
    );
    println!("Unprivileged BPF disabled: {}", unpriv_output.trim());

    bpf_enabled
}

fn check_filesystem_access() -> bool {
    section_title("FILESYSTEM ACCESS");

    println!("Checking filesystem access for eBPF...");
    debug_println("Testing various filesystem paths and permissions...");

    // Check debugfs mount
    let (debugfs_success, debugfs_output) = run_command("mount | grep debugfs");
    let debugfs_mounted = debugfs_success && debugfs_output.contains("debugfs");
    println!("debugfs mounted: {}", debugfs_mounted);

    // Check tracefs access
    let (_tracefs_success, tracefs_output) = run_command("ls -la /sys/kernel/debug/tracing 2>&1");
    let tracefs_accessible =
        !tracefs_output.contains("Permission denied") && !tracefs_output.contains("No such file");
    println!("tracefs accessible: {}", tracefs_accessible);

    // Check tracefs/events/syscalls access
    let (_syscalls_success, syscalls_output) =
        run_command("ls -la /sys/kernel/debug/tracing/events/syscalls 2>&1");
    let syscalls_accessible =
        !syscalls_output.contains("Permission denied") && !syscalls_output.contains("No such file");
    println!("syscalls tracepoints accessible: {}", syscalls_accessible);

    // Check BPF filesystem
    let (_bpf_fs_success, bpf_fs_output) = run_command("ls -la /sys/fs/bpf 2>&1");
    let bpf_fs_accessible = !bpf_fs_output.contains("No such file");
    println!("BPF filesystem accessible: {}", bpf_fs_accessible);

    // Check if we can write to tracefs
    let (write_success, write_output) = run_command(
        "touch /sys/kernel/debug/tracing/test_file 2>&1 && echo 'Write successful' && rm /sys/kernel/debug/tracing/test_file"
    );
    let can_write = write_success && write_output.contains("Write successful");
    println!("Can write to tracefs: {}", can_write);

    tracefs_accessible && syscalls_accessible
}

fn try_load_ebpf() -> bool {
    section_title("EBPF PROGRAM LOADING");

    debug_println("Attempting to load and attach an eBPF program to verify functionality...");

    #[cfg(not(feature = "ebpf"))]
    {
        println!("ERROR: eBPF feature not enabled. Recompile with --features ebpf");
        return false;
    }

    #[cfg(feature = "ebpf")]
    {
        println!("Attempting to load eBPF program...");

        // Check bytecode
        println!("Bytecode size: {} bytes", SYSCALL_TRACER_BYTECODE.len());

        // Check if bytecode looks valid (dump first few bytes)
        let preview_size = std::cmp::min(SYSCALL_TRACER_BYTECODE.len(), 32);
        let hex_bytes: Vec<String> = SYSCALL_TRACER_BYTECODE[..preview_size]
            .iter()
            .map(|b| format!("{:02x}", b))
            .collect();
        println!("Bytecode preview: {}", hex_bytes.join(" "));

        // Create BPF loader
        println!("Creating BPF loader...");
        let mut loader = EbpfLoader::new();

        // Try to load the bytecode
        match loader.load(SYSCALL_TRACER_BYTECODE) {
            Ok(mut bpf) => {
                println!("✓ eBPF bytecode loaded successfully!");

                // Check maps
                println!("Maps in loaded program:");
                let mut maps_found = false;
                for (name, _) in bpf.maps() {
                    println!("  - {}", name);
                    maps_found = true;
                }

                if !maps_found {
                    println!("WARNING: No maps found in the loaded program");
                }

                // Check for syscall_counts map
                let syscall_counts = bpf.take_map("syscall_counts");
                println!("syscall_counts map exists: {}", syscall_counts.is_some());

                // Check for pid_syscall_map
                let pid_syscall_map = bpf.take_map("pid_syscall_map");
                println!("pid_syscall_map exists: {}", pid_syscall_map.is_some());

                // Try to find a tracepoint program
                let mut has_tracepoint = false;
                let tracepoint_names = [
                    "trace_read_enter",
                    "trace_write_enter",
                    "trace_openat_enter",
                ];

                for name in tracepoint_names.iter() {
                    if let Some(prog) = bpf.program_mut(name) {
                        println!("Found program: {}", name);
                        has_tracepoint = true;

                        // Try to load it
                        match prog {
                            aya::programs::Program::TracePoint(tracepoint) => {
                                println!("Attempting to load {} program...", name);
                                match tracepoint.load() {
                                    Ok(_) => {
                                        println!("✓ Program loaded successfully");

                                        // Try to attach it
                                        let tracepoint_name = format!(
                                            "sys_enter_{}",
                                            name.trim_start_matches("trace_")
                                                .trim_end_matches("_enter")
                                        );
                                        println!(
                                            "Attempting to attach to syscalls/{}...",
                                            tracepoint_name
                                        );

                                        match tracepoint.attach("syscalls", &tracepoint_name) {
                                            Ok(_) => {
                                                println!("✓ Tracepoint attached successfully!");
                                                return true;
                                            }
                                            Err(e) => {
                                                println!("✗ Failed to attach tracepoint: {}", e);
                                                println!("Error details: {:?}", e);
                                            }
                                        }
                                    }
                                    Err(e) => {
                                        println!("✗ Failed to load program: {}", e);
                                        println!("Error details: {:?}", e);
                                    }
                                }
                            }
                            _ => {
                                println!("Program {} is not a tracepoint", name);
                            }
                        }
                        break;
                    }
                }

                if !has_tracepoint {
                    println!("✗ No tracepoint programs found!");
                    return false;
                }

                false
            }
            Err(e) => {
                println!("✗ Failed to load eBPF program: {}", e);
                println!("Error details: {:?}", e);
                false
            }
        }
    }
}

fn generate_report(perms_ok: bool, kernel_ok: bool, fs_ok: bool, load_ok: bool) -> bool {
    section_title("DIAGNOSTIC SUMMARY");

    println!(
        "Permissions check:   {}",
        if perms_ok { "✓ PASS" } else { "✗ FAIL" }
    );
    println!(
        "Kernel support:      {}",
        if kernel_ok { "✓ PASS" } else { "✗ FAIL" }
    );
    println!(
        "Filesystem access:   {}",
        if fs_ok { "✓ PASS" } else { "✗ FAIL" }
    );
    println!(
        "eBPF program loading: {}",
        if load_ok { "✓ PASS" } else { "✗ FAIL" }
    );

    let overall = perms_ok && kernel_ok && fs_ok && load_ok;
    println!(
        "\nOVERALL RESULT: {}",
        if overall {
            "✓ PASS - eBPF should work"
        } else {
            "✗ FAIL - eBPF will not work"
        }
    );

    if !overall {
        println!("\nRECOMMENDED ACTIONS:");

        if !perms_ok {
            println!("- Run with sudo privileges");
            println!("- OR add CAP_BPF capability: sudo setcap cap_bpf+ep /path/to/binary");
        }

        if !kernel_ok {
            println!("- Upgrade to kernel 4.18 or newer");
            println!("- Ensure CONFIG_BPF is enabled in kernel");
            println!("- Enable BPF JIT compilation: echo 1 > /proc/sys/net/core/bpf_jit_enable");
        }

        if !fs_ok {
            println!("- Ensure debugfs is mounted: mount -t debugfs none /sys/kernel/debug");
            println!("- Set correct permissions: chmod 755 /sys/kernel/debug");
            println!("- Set correct group permissions:");
            println!("  sudo groupadd -r tracing");
            println!("  sudo usermod -aG tracing $USER");
            println!("  sudo chgrp -R tracing /sys/kernel/debug/tracing");
            println!("  sudo chmod -R g+rwx /sys/kernel/debug/tracing");
        }
    }

    overall
}

fn main() {
    println!("eBPF Diagnostic Tool");
    println!("=====================");

    // Parse command line arguments
    let args: Vec<String> = env::args().collect();
    unsafe {
        DEBUG_MODE = args.iter().any(|arg| arg == "--debug");
        if DEBUG_MODE {
            println!("Debug mode enabled - verbose output will be shown");
        }
    }

    println!("Running comprehensive diagnostic checks for eBPF functionality...");
    debug_println("Detailed debugging information will be displayed");

    // Check if eBPF feature is enabled at compile time
    #[cfg(not(feature = "ebpf"))]
    {
        println!("\nERROR: This tool requires the 'ebpf' feature to be enabled.");
        println!("Recompile with: cargo build --features ebpf --bin ebpf_diag");
        exit(1);
    }

    #[cfg(feature = "ebpf")]
    // Run checks
    let perms_ok = check_permissions();
    let kernel_ok = check_kernel_support();
    let fs_ok = check_filesystem_access();

    // Only try loading if other checks pass
    let load_ok = if perms_ok && kernel_ok && fs_ok {
        try_load_ebpf()
    } else {
        println!("\nSkipping eBPF program loading due to failed prerequisites.");
        false
    };

    #[cfg(feature = "ebpf")]
    // Generate final report
    let success = generate_report(perms_ok, kernel_ok, fs_ok, load_ok);

    #[cfg(feature = "ebpf")]
    // Exit with appropriate code
    exit(if success { 0 } else { 1 });

    #[cfg(not(feature = "ebpf"))]
    unreachable!(); // This should never be reached as we exit(1) earlier
}