← Back to context

Comment by hubabuba44

7 days ago

I'm happy to see this on Linux and I really appreciate the open-sourcing of the eBPF component.

I maintain rustnet, a passive network monitor in the same eBPF + libpcap space, so I ran into a lot of the same issues. Wanted to share what has been working for me on the privilege side, in case any of it is useful for v2.

rustnet ships with setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' instead of setuid-root. During startup it loads the eBPF programs, opens the pcap handle, and then drops all three caps before touching any packet data. It clears the ambient set, sets PR_SET_NO_NEW_PRIVS, and applies a Landlock ruleset that restricts the filesystem to /proc plus configured log paths and blocks TCP bind/connect on 6.4+ kernels. Code is in src/network/platform/linux/sandbox/ if you want to have a look.

On the "needs to watch mounts" point, totally fair that Little Snitch needs live mount visibility, but I think it is achievable without staying UID 0:

- Watching for mount changes: poll() on /proc/self/mountinfo with POLLPRI wakes on every mount table change from a completely unprivileged process (this is what systemd and mount(8) use internally). Alternatively, an eBPF program on the mount/umount/move_mount tracepoints can be loaded at init and stream events via a ring buffer, with no continued cap cost after load. - Resolving an arbitrary PID to its binary across container mount namespaces: CAP_SYS_PTRACE is enough for that. The /proc/PID/root magic symlink does the namespace translation inline inside the kernel pathwalk, so open("/proc/12345/root/usr/bin/firefox", ...) opens the right file in the right container's view without ever calling setns(), which is what would otherwise need CAP_SYS_ADMIN (the new root).

Thanks for sharing! I took rustnet as proof that complex eBPF programs can be done in Rust. Otherwise I would not have dared to try this!

Reducing the set of privileges is on my todo list, but for the moment I just want to get things working without worrying about self-made limitations.

Regarding mount points: I needed the inode numbers of the mounted nodes. With my last commit this requirement has been dropped and it should be sufficient to read mountinfo (and access config files and sqlite3 databases, of course).

I don't need to get the executable from PID, that's already done in eBPF because I need to apply rules based on executable paths.

  • Ah nice, I'll take a look at the open source eBPF part for the process resolution which is an area where I still have some rough edges and there is probably something I can learn from your approach.