Skip to content

packaging(fedora): ship an SELinux policy that confines rustnet #271

@domcyrus

Description

@domcyrus

Background

rustnet runs with file capabilities (cap_net_raw,cap_bpf,cap_perfmon on kernels ≥5.8) and does passive packet capture only. At runtime it already drops capabilities after libpcap initializes and applies a Landlock sandbox (see SECURITY.md and src/network/platform/linux/sandbox/landlock.rs) for filesystem confinement.

Landlock has a critical gap on the network side: its network-restriction primitive only covers TCP bind/connect — UDP and raw sockets pass straight through. That means even with our existing sandbox in place, a compromised rustnet could exfiltrate over DNS, QUIC, or any other UDP-based channel. We need a MAC profile (SELinux on Fedora, AppArmor on Ubuntu in a sibling issue) to actually deny non-DNS UDP egress and exec of other binaries — things Landlock cannot enforce. This narrows the blast radius of any RCE/parser bug in a tool that handles untrusted packet data.

Scope

Add a selinux/ directory containing a .te/.fc/.if policy module (or a single .cil file — author's preference) for rustnet, plus packaging hooks in rpm/rustnet.spec to install and load it on Fedora 42+.

The policy should:

  • Define a rustnet_t domain entered when /usr/bin/rustnet is executed
  • Allow capabilities: cap_net_raw, cap_bpf, cap_perfmon (and cap_sys_admin fallback for older kernels); raw socket access for packet capture
  • Allow DNS reverse-lookup egress (rustnet does PTR queries on captured IPs via libc, so we need this — but only to the configured resolver, not arbitrary destinations):
    • Use the sysnet_dns_name_resolve() / corenet_udp_sendrecv_dns_port interfaces so UDP+TCP to port 53 reaches the system resolver
    • Reads of /etc/resolv.conf, /etc/nsswitch.conf, /etc/hosts
    • This intentionally permits local stub resolver (127.0.0.53:53 on systemd-resolved hosts) and external resolvers configured by the user — but nothing else on UDP/TCP
  • Allow reads (config & runtime):
    • /proc/* (process enumeration for socket→PID mapping)
    • /usr/share/rustnet/services (port-name database installed by the RPM)
    • User config: ~/.config/rustnet/config.yml (and $XDG_CONFIG_HOME variants) plus ./config.yml
  • Allow reads (GeoIP databases — multiple locations rustnet probes in order):
    • /usr/share/GeoIP/** (canonical, populated by geoipupdate)
    • /usr/local/share/GeoIP/**
    • /var/lib/GeoIP/**
    • ~/.local/share/rustnet/geoip/** (XDG_DATA_HOME fallback)
    • ./GeoLite2-*.mmdb (current directory — used during development; consider gating behind a boolean)
  • Allow writes (logging and capture export — paths chosen by the user):
    • Default log directory created by --log-level: ./logs/rustnet_*.log (rustnet creates logs/ in cwd today)
    • Arbitrary path passed to --json-log <FILE>
    • Arbitrary path passed to --pcap-export <FILE> plus the sidecar <FILE>.connections.jsonl
    • Strategy: label these as rustnet_log_t and allow rustnet_t to create/append to that type; for user-chosen paths, allow writes under user_home_t via a boolean (rustnet_can_write_user_home, default off) or require users to point output at /var/log/rustnet/ (preferred — add a tmpfiles.d snippet creating it with the right label)
  • Deny by default (this is the point of the policy — Landlock can't do this):
    • Outbound TCP connect other than to the DNS resolver port
    • Outbound UDP sendto other than to the DNS resolver port (Landlock cannot gate UDP at all)
    • Reading arbitrary $HOME/** outside the config/GeoIP/log paths above
    • Executing other binaries
    • Loading kernel modules
  • Be permissive on first ship so it logs AVCs without breaking users; flip to enforcing in a follow-up after audit2allow review

Acceptance criteria

The PR must include evidence of real-world end-to-end testing — not just "it builds". Include in the PR description: distro + kernel version tested on, the commands run, and the relevant output / screenshots / ausearch excerpts. Synthetic CI checks alone are not sufficient.

  • Policy module builds with make -f /usr/share/selinux/devel/Makefile
  • Installed and loaded by the RPM post-install on a real Fedora 42 (or 43) system — include sestatus + semodule -l | grep rustnet output
  • rustnet --help and a real capture session (5+ min, real traffic) run cleanly in permissive mode — include ausearch -m avc -ts recent output showing no AVCs
  • GeoIP works on real system: with a GeoLite2-City.mmdb placed in /usr/share/GeoIP/ (or installed via geoipupdate), captured connections show country info in the UI — include screenshot
  • DNS PTR lookups work on real system: with reverse-DNS enabled, captured IPs resolve to hostnames in the UI — tested both with systemd-resolved (127.0.0.53:53) and with an external resolver. Include screenshot
  • Logging works on real system: rustnet --log-level debug writes to ./logs/rustnet_*.log (or /var/log/rustnet/) without AVCs — include the log path + ausearch output
  • PCAP export works on real system: rustnet --pcap-export /tmp/cap.pcap writes both cap.pcap and cap.pcap.connections.jsonl — include ls -lZ output showing the SELinux labels
  • Non-DNS UDP egress blocked in enforcing mode on real system: flip to enforcing, run a synthetic nc -u <ip> 1234 from inside the rustnet_t domain (e.g. runcon -t rustnet_t -- nc -u ...), include the resulting AVC denial as proof
  • Documented in SECURITY.md with the AVC-review path for users
  • Maintainer can toggle enforcing with a single semanage permissive -d rustnet_t

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    help wantedExtra attention is neededpackagingDistribution packaging (deb, rpm, AUR, etc.)securitySecurity-related changes (sandboxing, MAC, privileges)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions