Wiki - https://fedoraproject.org/wiki/Changes/UnprivilegedEBPFRBACDaemon
Discourse poll -
https://discussion.fedoraproject.org/t/f46-change-proposal-unprivileged-ebpf-rbac-daemon-with-lsm-enforcement-and-rootless-access-system-wide/204731/2
This is a proposed Change for Fedora Linux.
This document represents a proposed Change. As part of the Changes
process, proposals are publicly announced in order to receive
community feedback. This proposal will only be implemented if approved
by the Fedora Engineering Steering Committee.
== Summary ==
Ship <code>bpf-rbacd</code>, a system daemon that enables
unprivileged, policy-controlled eBPF access in Fedora. It delegates
BPF privileges to containers via BPF tokens with in-kernel LSM
enforcement, and provides rootless BPF access to desktop users via
proxy execution — both governed by a single YAML policy and gated by
an <code>ebpf</code> system group.
== Owner ==
* Name: [[Fedora eBPF SIG|https://fedoraproject.org/wiki/SIGs/eBPF]]
* FAS group: [https://accounts.fedoraproject.org/group/ebpf-sig/ @ebpf-sig]
* Email: dmellado@redhat.com
* Members:
** Daniel Mellado — dmellado@redhat.com
** Toke Høiland-Jørgensen — toke@redhat.com
** Viktor Malik — vmalik@redhat.com
== Detailed Description ==
=== Background ===
eBPF is a powerful kernel technology for networking, observability,
and security, but its use requires system-wide capabilities
(<code>CAP_BPF</code> combined with <code>CAP_PERFMON</code>,
<code>CAP_NET_ADMIN</code>, or <code>CAP_SYS_ADMIN</code>). This is an
all-or-nothing proposition: applications need privileges close to
root, which blocks adoption in containers and restricts desktop
developers.
The kernel's BPF token mechanism (Linux 6.9+) allows a privileged
issuer to delegate a constrained set of BPF operations to unprivileged
consumers inside user namespaces. However, tokens alone have two
significant limitations:
# '''No per-entity enforcement.''' Token constraints are set at the
bpffs mount level and apply uniformly to all consumers. There is no
mechanism to give different containers different allowed program
types, or to revoke access dynamically.
# '''No desktop path.''' BPF tokens are not supported in the initial
user namespace (<code>BPF_TOKEN_CREATE</code> returns
<code>EOPNOTSUPP</code>). Desktop developers running tracers or
profilers must still use <code>sudo</code>.
This change addresses both gaps through a single daemon that operates
in two complementary modes simultaneously.
=== Dual-mode architecture ===
<code>bpf-rbacd</code> serves two distinct classes of eBPF consumers
from a single process, using the same policy engine and the same YAML
configuration:
{| class="wikitable"
! Mode !! Target environment !! Mechanism !! LSM enforcement
|-
| '''Capability granting''' || Containers and services in user
namespaces || BPF token delegation via bpffs mount + "nsenter dance"
|| Yes — three LSM hooks enforce per-namespace policy bitmaps
in-kernel
|-
| '''Proxy execution''' || Desktop users and applications in the
initial user namespace (<code>init_user_ns</code>) || Daemon executes
<code>bpf()</code> on behalf of clients, passes FDs via
<code>SCM_RIGHTS</code> over a Unix socket || N/A — policy enforced
daemon-side before executing the syscall
|}
'''Why two modes?''' BPF tokens only work inside user namespaces — the
kernel explicitly rejects <code>BPF_TOKEN_CREATE</code> in
<code>init_user_ns</code> with <code>EOPNOTSUPP</code>. Containers
naturally have user namespaces, so tokens are the right mechanism
there. Desktop users do not, so they need a different path. The proxy
mode fills that gap without requiring application changes: standard
tools like bpftrace or bcc can be adapted to request FDs from the
daemon instead of calling <code>bpf()</code> directly.
Both modes share the same policy model (allow-list only, fails closed,
system-policy intersection) and the same group-based role resolution
(<code>ebpf</code>, <code>ebpf-net</code>, <code>ebpf-admin</code>).
An administrator writes one policy file and both modes obey it.
<pre>
bpf-rbacd (privileged daemon)
┌──────────────────────────────┐
│ Policy Engine │
│ (YAML → role → bitmaps) │
└──────┬───────────┬────────────┘
│ │
┌───────────▼──┐ ┌────▼──────────────┐
│ Capability │ │ Proxy execution │
│ granting │ │ (SCM_RIGHTS) │
│ mode │ │ mode │
└──────┬───────┘ └────┬───────────────┘
│ │
┌───────────▼──┐ ┌────────▼────────────┐
│ Container / │ │ Desktop user │
│ service in │ │ (init_user_ns) │
│ user ns │ │ │
│ │ │ $ bpf-rbac create-map│
│ Uses BPF │ │ → daemon executes │
│ token from │ │ → FD passed back │
│ bpffs │ │ via SCM_RIGHTS │
│ ↓ │ └──────────────────────┘
│ LSM enforces │
│ per-ns policy│
└──────────────┘
</pre>
=== What this change delivers ===
==== BPF token delegation and the ebpf system group ====
<code>bpf-rbacd</code> uses systemd-driven bpffs delegation to provide
BPF tokens to containers and services in user namespaces:
* A fixed <code>ebpf</code> system group gates who may consume the
delegated bpffs/token path. The group is for OS-level discovery and
packaging defaults; the kernel still enforces token constraints
independently.
* The daemon performs the '''"nsenter dance"''' — entering the target
user namespace via <code>setns()</code> before mounting bpffs, so the
<code>s_user_ns</code> is set correctly and
<code>BPF_TOKEN_CREATE</code> works inside the namespace.
* Integration with systemd <code>PrivateBPF=</code> (upstream
[https://github.com/systemd/systemd/pull/36134 PR #36134]) enables
automatic namespace delegation for systemd-managed services.
* Libraries such as libbpf, bpftool, and bcc automatically discover
and use delegated tokens without requiring <code>sudo</code>.
==== In-kernel eBPF LSM policy enforcement ====
The daemon loads eBPF LSM programs at startup that attach to the
kernel's security hooks for BPF operations. Each hook looks up the
calling process's user namespace inode ID in an eBPF hash map,
retrieves the policy bitmaps for that namespace's role, and returns
allow or deny. This gives administrators '''per-namespace, per-role
granularity''' enforced in-kernel, on top of the bpffs mount-level
token constraints.
'''Core hooks''' (implemented in both the Aya and C codebases):
{| class="wikitable"
! LSM Hook !! What it controls !! Policy dimension
|-
| <code>security_bpf</code> || Every <code>bpf()</code> syscall
invocation || <code>allowed_cmds</code> bitmap (PROG_LOAD, MAP_CREATE,
etc.)
|-
| <code>security_bpf_prog_load</code> || Program loading — inspects
program type || <code>allowed_prog_types</code> bitmap (kprobe, xdp,
etc.)
|-
| <code>security_bpf_map_create</code> || Map creation — inspects map
type || <code>allowed_map_types</code> bitmap (hash, array, etc.)
|}
'''Extended hooks''' (implemented in the C codebase —
[https://github.com/tohojo/bpf-rbac-lsm tohojo/bpf-rbac-lsm]):
{| class="wikitable"
! LSM Hook !! What it controls !! Why it matters
|-
| <code>security_bpf_prog</code> || FD-level access to existing
programs || Controls who can inspect or reuse loaded programs
|-
| <code>security_mmap</code> || Memory-mapping of BPF maps || Controls
map data access mode (read/write/exec)
|}
'''Bytecode walking for helper/kfunc policy:''' The C implementation
also uses open-coded iterators to walk BPF program bytecode at
<code>security_bpf_prog_load</code> time, extracting the helper
function IDs and kfunc BTF IDs that the program calls. This enables a
future policy dimension: restricting ''which kernel functions'' a
program may invoke, not just which program type it is. Events are
reported to userspace via a BPF ring buffer for audit logging and
policy decisions.
'''How LSM and tokens complement each other:'''
<pre>
Token says "you may load LSM checks: "your namespace
kprobe programs" role only permits tracepoint"
↓ → DENIED
</pre>
Even if a token is overly permissive (broad bpffs mount options), the
per-namespace LSM policy restricts actual operations to the role's
intersection with the system policy.
'''eBPF program loading at daemon startup:''' The daemon loads and
attaches the LSM programs before accepting any delegation requests.
This requires <code>CAP_BPF</code> + <code>CAP_SYS_ADMIN</code> on the
daemon itself (granted via the systemd unit's
<code>AmbientCapabilities</code>). Once loaded, the LSM hooks are
active system-wide; the eBPF policy map serves as the control plane
between the daemon and the LSM.
==== Rootless BPF for desktop users (proxy mode) ====
For users in the initial user namespace (desktops, developer
workstations), <code>bpf-rbacd</code> provides a proxy execution mode:
# User runs <code>bpf-rbac <command></code> (e.g., <code>bpf-rbac
create-map --type hash ...</code>)
# The CLI connects to the daemon's Unix socket at
<code>/run/bpf-rbac.sock</code>
# The daemon reads <code>SO_PEERCRED</code> — kernel-verified,
tamper-proof credentials
# Group membership is resolved to a policy role
# The daemon executes the allowed <code>bpf()</code> syscall with its
own privileges
# Resulting file descriptors are passed back via <code>SCM_RIGHTS</code>
# Denied operations return actionable error messages
=== Policy model ===
Policies are defined in <code>/etc/bpf-rbac/policy.yaml</code> using
an allow-list model that always fails closed:
<pre>
system_policy: # System-wide ceiling
commands: [PROG_LOAD, MAP_CREATE, MAP_LOOKUP_ELEM, MAP_UPDATE_ELEM,
LINK_CREATE, OBJ_PIN, OBJ_GET, BTF_LOAD]
prog_types:
kprobe: [load, attach, detach]
tracepoint: [load, attach, detach]
xdp: [load, attach, detach]
map_types:
hash: [create, read, write]
array: [create, read, write]
ringbuf: [create, read]
roles:
ebpf: # Tracing role
groups: [ebpf]
commands: [PROG_LOAD, MAP_CREATE, MAP_LOOKUP_ELEM, MAP_UPDATE_ELEM,
LINK_CREATE, BTF_LOAD]
prog_types:
kprobe: [load, attach]
tracepoint: [load, attach]
map_types:
hash: [create, read, write]
array: [create, read, write]
ringbuf: [create, read]
ebpf-net: # Networking role
groups: [ebpf-net]
prog_types:
xdp: [load, attach, detach]
sched_cls: [load, attach, detach]
map_types:
hash: [create, read, write]
devmap: [create, read, write]
ebpf-admin: # Unrestricted
groups: [ebpf-admin, wheel]
commands: [any]
prog_types: {any: [any]}
map_types: {any: [any]}
</pre>
The effective policy for any role is the '''set intersection''' of the
role policy and the system policy. A role can never exceed
administrator-defined system limits.
=== Two LSM implementations and the Rust BPF toolchain gap ===
The eBPF LSM programs exist in two parallel implementations. This is
not redundancy — it is a deliberate response to a Rust compiler
limitation that currently prevents the Aya-based implementation from
using advanced BPF features.
==== The Rust/DWARF/BTF problem ====
eBPF programs that call kernel functions (kfuncs) or reference kernel
symbols (ksyms) require BTF (BPF Type Format) metadata in the compiled
ELF. The toolchain flow is:
<pre>
Rust source → rustc (BPF target) → LLVM → ELF with DWARF
→ bpf-linker → ELF with BTF (including .ksyms DATASEC)
→ kernel verifier resolves kfuncs/ksyms at load time
</pre>
The problem: '''<code>rustc</code> does not emit DWARF debug info for
<code>extern</code> declarations on BPF targets.''' When a Rust eBPF
program declares:
<pre>
#[link_section = ".ksyms"]
extern "C" {
static LINUX_KERNEL_VERSION: u32; // ksym
fn bpf_iter_num_new(it: *mut bpf_iter_num, start: i32, end: i32); // kfunc
}
</pre>
…the compiler produces the LLVM global/function declaration but
attaches no <code>DIGlobalVariable</code> or <code>DISubprogram</code>
metadata. Without DWARF, <code>bpf-linker</code> cannot generate the
<code>.ksyms</code> BTF DATASEC entry, and the kernel verifier cannot
resolve these symbols at load time. The program fails to load.
This blocks:
* '''Open-coded iterators'''
(<code>bpf_iter_num_new/next/destroy</code>) — needed for bytecode
walking in the <code>security_bpf_prog_load</code> hook
* '''kfunc calls''' (<code>bpf_task_acquire</code>, etc.) — needed for
CO-RE task/namespace introspection
* '''The userns ID resolver''' —
<code>current->nsproxy->user_ns->ns.inum</code> access via BTF,
currently stubbed to return 0
The three core bitmap-based hooks (<code>security_bpf</code>,
<code>security_bpf_prog_load</code>,
<code>security_bpf_map_create</code>) work today with Aya because they
only use map lookups and context argument reads — no kfuncs or ksyms.
But the extended hooks (bytecode walking, FD access control, mmap
control) require kfuncs and are blocked.
==== The upstream Rust compiler fix ====
We are driving the fix in
[https://github.com/rust-lang/rust/pull/161390 rust-lang/rust#161390]
("Generate debug info for BPF extern declarations"). This patch:
* Allows <code>#[link_section]</code> on <code>ForeignStatic</code>
and <code>ForeignFn</code> (gated to BPF targets)
* Adds an <code>LLVMRustDIBuilderCreateGlobalVariableExpression</code>
wrapper exposing the <code>isDefinition</code> parameter (the LLVM-C
API hard-codes it to <code>true</code>)
* Emits <code>DIGlobalVariable</code> with <code>isDefinition:
false</code> for extern statics
* Emits <code>DISubprogram</code> without
<code>SPFlagDefinition</code> for extern functions
This is a reworked version of
[https://github.com/rust-lang/rust/pull/152899 #152899], rebased on
current <code>main</code> with review feedback addressed. It is
currently in review with the Rust compiler team (assigned to
@jdonszelmann, reviews from @bjorn3 and @tamird). The Aya maintainer
(@vadorovsky) is also involved.
Once this lands in nightly <code>rustc</code>, the full chain works:
Rust eBPF → DWARF with extern metadata → <code>bpf-linker</code> → BTF
with <code>.ksyms</code> → kernel loads kfuncs. This unblocks the
complete Aya-based LSM.
==== The C implementation: tohojo/bpf-rbac-lsm ====
[https://github.com/tohojo/bpf-rbac-lsm tohojo/bpf-rbac-lsm] is an
actively developed C-based LSM implementation (Toke Høiland-Jørgensen,
latest commits Sep 2026) that sidesteps the Rust toolchain limitation
entirely. It uses:
* '''C for the eBPF programs''' — compiled with clang/llvm, full
BTF/CO-RE support, no DWARF issues
* '''Rust + libbpf-rs for userspace''' — loads the C eBPF programs via
libbpf skeleton generation, processes ring buffer events, manages
policies
This implementation is more capable than the Aya version today:
{| class="wikitable"
! Feature !! Aya (Rust eBPF) !! C eBPF + libbpf-rs
|-
| <code>security_bpf</code> (command filtering) || ✅ || ✅
|-
| <code>security_bpf_prog_load</code> (type filtering) || ✅ || ✅
|-
| <code>security_bpf_map_create</code> (type filtering) || ✅ || ✅
|-
| <code>security_bpf_prog</code> (FD access) || ❌ || ✅
|-
| <code>security_mmap</code> (map mmap control) || ❌ || ✅
|-
| Userns ID resolution (CO-RE/BTF) || ❌ (stubbed) || ✅
|-
| Bytecode walking (open-coded iterators) || ❌ (blocked on rustc) || ✅
|-
| Helper/kfunc ID extraction || ❌ (blocked on rustc) || ✅
|-
| Policy enforcement per namespace || ❌ (stub returns 0) || ✅
|-
| Ring buffer event reporting || ❌ || ✅
|}
==== Integration strategy and the path to a pure Rust/Aya stack ====
The two implementations share the same userspace daemon
(<code>bpf-rbacd</code>) and the same policy engine. They are designed
to converge:
'''Phase 1 — Fedora 46 delivery (C eBPF + Rust userspace):'''
Use the C eBPF programs from <code>bpf-rbac-lsm</code> loaded via
<code>libbpf-rs</code> in the <code>bpf-rbacd</code> Rust userspace
daemon. This gives us the full feature set (all hooks, bytecode
walking, namespace resolution, policy enforcement) without waiting for
the Rust compiler fix. The daemon, policy engine, CLI, and all
userspace logic remain in Rust; only the eBPF kernel programs are in
C. This is a pragmatic split — the same pattern used by many
production eBPF projects (e.g., Cilium, bpfman).
'''Phase 2 — After <code>rustc</code> fix lands (Aya catches up):'''
Once [https://github.com/rust-lang/rust/pull/161390
rust-lang/rust#161390] is merged and reaches stable
<code>rustc</code>, we migrate the eBPF programs to Rust/Aya. The
groundwork is already done:
* The Aya-based LSM in [https://github.com/danielmellado/bpf-rbacd
danielmellado/bpf-rbacd] already compiles and produces correct ELF
relocations for the three core hooks.
* The [https://github.com/tohojo/bpf-rbacd/tree/load-lsm load-lsm
branch] already has kfunc declarations and open-coded iterator code
that compiles — it only needs the BTF DATASEC generation that the
<code>rustc</code> fix enables.
* Aya's kfunc relocation support
([https://github.com/aya-rs/aya/pull/1372 aya-rs/aya#1372]) is
progressing upstream.
The migration is a drop-in replacement: the userspace daemon, policy
engine, and CLI are unchanged. Only the eBPF binary source changes
from C to Rust.
'''Phase 3 — Target state (pure Rust):'''
A fully Rust stack — daemon, CLI, policy engine, ''and'' eBPF kernel
programs — all built with <code>cargo</code>. This is the desired end
state for <code>bpf-rbacd</code> because:
* '''Single toolchain:''' One <code>cargo build</code> compiles
everything. No separate clang/llvm dependency for the eBPF programs.
* '''Shared types:''' The <code>bpf-rbacd-common</code> crate already
defines <code>#[repr(C)]</code> policy types (<code>PolicyKey</code>,
<code>PolicyValue</code>) that compile for both <code>no_std</code>
(eBPF target) and <code>std</code> (userspace). With Aya, these types
are shared at compile time with full type safety — no manual struct
layout synchronization between C and Rust.
* '''Memory safety:''' Rust's borrow checker and type system apply to
the eBPF programs too. While eBPF's verifier provides runtime safety,
compile-time guarantees from Rust catch bugs earlier.
* '''Ecosystem alignment:''' Aya is the standard Rust eBPF framework.
Contributing improvements back (kfunc support, LSM patterns) benefits
the entire Rust eBPF community.
The <code>rustc</code> fix is not just a <code>bpf-rbacd</code>
concern — it unblocks kfunc/ksym usage for ''every'' Rust eBPF
program. We are driving this upstream because it is the right thing
for the ecosystem, and <code>bpf-rbacd</code> is a concrete,
high-profile consumer that validates the fix.
This phased approach means the Fedora 46 deliverable is '''not blocked
by the Rust compiler fix''', while the Rust/Aya migration remains a
first-class goal tracked by the eBPF SIG.
=== Kernel requirements ===
{| class="wikitable"
! Config !! Required for !! In Fedora?
|-
| <code>CONFIG_BPF_SYSCALL=y</code> || All BPF operations || Yes
|-
| <code>CONFIG_BPF_LSM=y</code> || LSM hook attachment || Yes
|-
| <code>bpf</code> in active LSM list || LSM program loading || Needs
verification
|-
| <code>CONFIG_DEBUG_INFO_BTF=y</code> || CO-RE/BTF for portable eBPF || Yes
|-
| <code>CONFIG_BPF_TOKEN</code> || BPF token delegation || Yes
|}
'''Action item:''' Verify that <code>bpf</code> is in the kernel's
compiled-in LSM list or <code>lsm=</code> boot parameter. If not, a
kernel config change is needed to add <code>bpf</code> to the LSM
order alongside <code>lockdown,yama,integrity,selinux</code>.
== Feedback ==
The design of <code>bpf-rbacd</code> is being presented at
[https://lpc.events/event/20/contributions/2441/ Linux Plumbers
Conference 2026] (Oct 6, eBPF Track) to solicit community feedback.
The talk covers the architecture, policy model, and LSM enforcement,
with the goal of making the design useful to other distributions and
operators.
Community consultation with kernel, systemd, and security stakeholders
is ongoing.
== Benefit to Fedora ==
* '''Security posture:''' Moves eBPF access from all-or-nothing
capabilities toward granular, role-based delegation. LSM enforcement
adds a mandatory kernel-level policy layer that token delegation alone
cannot provide.
* '''Per-entity granularity:''' Different containers, services, or
users get different eBPF permissions based on their role — not a
single system-wide delegation.
* '''Developer experience:''' Desktop developers and operators can run
eBPF tools without <code>sudo</code>, with clear errors when an
operation is outside their role's policy.
* '''Unified policy:''' One YAML file governs both container (LSM +
tokens) and desktop (proxy) access. Administrators learn one model.
* '''Dynamic policy and revocation:''' Policies can be updated at
runtime; removing a namespace's map entry causes the LSM to deny
subsequent operations without killing processes.
* '''Downstream alignment:''' Establishes the reference RBAC
implementation for RHEL, OpenShift (user namespaces GA in 4.21+), and
CoreOS. Kubernetes user namespaces are GA as of v1.36.
* '''Rust BPF ecosystem:''' The upstream <code>rustc</code> fix
([https://github.com/rust-lang/rust/pull/161390 #161390]) benefits the
entire Rust eBPF community. <code>bpf-rbacd</code> is designed to
become a fully pure-Rust eBPF project (daemon + kernel programs via
Aya) once the toolchain gap is closed, demonstrating that production
eBPF LSM programs can be written entirely in Rust.
* '''Community leadership:''' Fedora becomes the first distribution
with a complete, integrated eBPF RBAC stack spanning containers and
desktops.
== Scope ==
* Proposal owners:
** Package <code>bpf-rbacd</code> (daemon), <code>bpf-rbac</code> (CLI
client), and <code>bpf-rbacd-common</code> (shared types) as RPMs.
Source is hosted on [https://forge.fedoraproject.org/ebpf/bpf-rbacd
Fedora Forge].
** Ship the eBPF LSM programs from
[https://github.com/tohojo/bpf-rbac-lsm tohojo/bpf-rbac-lsm] (C eBPF +
libbpf-rs for loading). Migrate to Rust/Aya once
[https://github.com/rust-lang/rust/pull/161390 rust-lang/rust#161390]
lands.
** Deliver systemd unit (<code>bpf-rbacd.service</code>) with security
hardening (<code>ProtectSystem=strict</code>,
<code>ProtectHome=true</code>, capability bounding).
** Deliver default policy (<code>/etc/bpf-rbac/policy.yaml</code>)
reviewed with the Security Team.
** Create <code>ebpf</code>, <code>ebpf-net</code>, and
<code>ebpf-admin</code> system groups via
<code>systemd-sysusers</code>.
** Implement systemd-driven bpffs delegation (mount units,
<code>PrivateBPF=</code> coordination).
** Coordinate with kernel team to ensure <code>bpf</code> is in the
active LSM list.
** Drive the Rust compiler fix
([https://github.com/rust-lang/rust/pull/161390
rust-lang/rust#161390]) or use C fallback.
** Complete the CO-RE/BTF userns ID resolver in the eBPF LSM.
** Wire container/namespace discovery into the daemon (cgroup events
or CRI hooks).
** Coordinate library/tool updates for automatic token usage (libbpf,
bpftool, bcc).
** File Fedora package review, FESCo process, and tracker bugs.
** Write Fedora Quick Docs article on usage and security model.
* Other developers:
** Kernel maintainers: Confirm <code>CONFIG_BPF_LSM=y</code> and
<code>bpf</code> in LSM list; advise on LSM hook semantics and
regression triage.
** systemd maintainers: Coordinate on <code>PrivateBPF=</code>
integration so systemd-launched services automatically get delegated
namespaces.
** Rust compiler team: Review and land
[https://github.com/rust-lang/rust/pull/161390 rust-lang/rust#161390].
** Aya maintainers: kfunc relocation support
([https://github.com/aya-rs/aya/pull/1372 aya-rs/aya#1372]).
** Security Team: Review default policy, group membership model, and
LSM enforcement guarantees.
** eBPF tooling maintainers (bcc, bpftool, libbpf consumers): Ensure
tools work with proxy-mode FD passing and token-based flows.
** Container runtime maintainers (podman, CRI-O): Test user namespace
+ BPF token + LSM enforcement end-to-end.
** Edition WGs (Workstation, Server, CoreOS): Consider default group
membership and documentation per edition.
* Release engineering: No release engineering changes required.
<code>bpf-rbacd</code> is a new opt-in package with standard RPM
packaging — no mass rebuild, no installer image changes, and no
compose modifications.
* Policies and guidelines: The initial delivery uses C eBPF programs
compiled with clang/llvm and loaded via libbpf-rs — this follows
standard Fedora packaging (clang and libbpf are already packaged). The
future Aya-based path targets <code>bpfel-unknown-none</code> and
requires nightly Rust + <code>rust-src</code>, which may need a
packaging exemption or pre-built binary vendoring when that migration
occurs. Default policy requires Security Team review.
* Trademark approval: N/A (not needed for this Change)
* Alignment with the Fedora Strategy: Aligns with secure-by-default,
developer-friendly platform, and upstream-first goals. The change
drives fixes in <code>rustc</code>, Aya, and systemd rather than
carrying downstream patches.
== Upgrade/compatibility impact ==
This change introduces a new opt-in package. It does not modify
existing system behavior.
* '''Existing systems:''' No change unless <code>bpf-rbacd</code> is
installed. Workflows that use root or explicit capabilities continue
to work.
* '''New path:''' Users or administrators who want unprivileged eBPF
install <code>bpf-rbacd</code>, add users to the <code>ebpf</code>,
<code>ebpf-net</code>, or <code>ebpf-admin</code> groups, and enable
the service.
* '''Kernel config:''' If <code>bpf</code> needs to be added to the
LSM list, this could interact with other LSM consumers (notably
SELinux). Must be coordinated with the kernel team.
* '''ABI:''' No Fedora-specific ABI commitment beyond documented
<code>bpf()</code> semantics and the policy YAML format. Tools that
gain automatic token discovery may succeed in situations where they
previously failed without sudo.
== Early Testing (Optional) ==
Do you require 'QA Blueprint' support? N
== How To Test ==
# '''Hardware/data:''' Standard x86_64 or aarch64 Fedora install. VM
acceptable if BPF features are not stripped.
# '''Prepare:''' Install <code>bpf-rbacd</code>, start the service,
add test user to <code>ebpf</code> group.
# '''Proxy mode (desktop):'''
#: <pre>
# As a user in the ebpf group:
$ bpf-rbac status
Connected to: /run/bpf-rbac.sock
Your roles: ebpf
Allowed operations:
Maps: hash, array, percpu_hash, percpu_array, ringbuf
Programs: kprobe, uprobe, tracepoint, perf_event
# Create a map (should succeed)
$ bpf-rbac create-map --type hash --name test \
--key-size 4 --value-size 8 --max-entries 64
# Attempt a denied operation (should fail with clear error)
$ bpf-rbac create-map --type devmap --name test \
--key-size 4 --value-size 8 --max-entries 64
Error: map type 'devmap' is not allowed by your role 'ebpf'
</pre>
# '''LSM enforcement (requires root for setup):''' Launch a podman
container in a user namespace (<code>podman run --userns=auto
...</code>). Verify that the daemon delegates bpffs and populates the
policy map. Inside the container, attempt to load an allowed program
type (should succeed) and a denied type (should fail with
<code>EPERM</code>).
# '''Non-member:''' As a user NOT in any ebpf group, verify that all
operations are denied.
# '''Unit tests (no root required):'''
#: <pre>
cargo test --lib # Policy parsing, bitmaps, intersection
cargo test --test integration # Non-privileged integration tests
</pre>
# '''Expected results:''' Delegated operations succeed within the
role's allowlist; out-of-scope operations fail with clear errors; no
system instability or privilege escalation beyond the documented
policy.
== User Experience ==
* Most users see no change unless they use eBPF tooling.
* Desktop developers who run tracers or networking experiments may
stop needing <code>sudo</code> for supported scenarios, with clear
errors when an operation is denied.
* Container workloads are transparent — applications use standard
<code>bpf()</code> with automatic token acquisition; the LSM silently
enforces the namespace's policy.
* Administrators get a single documented policy file
(<code>/etc/bpf-rbac/policy.yaml</code>) and <code>journalctl -u
bpf-rbacd</code> for audit logging.
== Dependencies ==
* Linux kernel: 6.9+ with <code>CONFIG_BPF_TOKEN</code>,
<code>CONFIG_BPF_LSM=y</code>, <code>bpf</code> in active LSM list,
<code>CONFIG_DEBUG_INFO_BTF=y</code>
* Rust: Stable 1.85+ (daemon and CLI)
* [https://github.com/libbpf/libbpf libbpf] and
[https://github.com/libbpf/libbpf-rs libbpf-rs]: For loading C-based
eBPF LSM programs from the Rust daemon (initial delivery path)
* clang/llvm: For compiling the C eBPF programs with full BTF/CO-RE support
* [https://github.com/aya-rs/aya Aya]: eBPF library for Rust
(long-term target once [https://github.com/rust-lang/rust/pull/161390
rust-lang/rust#161390] lands; requires nightly Rust +
<code>bpfel-unknown-none</code> target)
* systemd: <code>PrivateBPF=</code> support
([https://github.com/systemd/systemd/pull/36134 PR #36134])
* Optional: podman with <code>--userns=auto</code>, CRI-O with user
namespace support
== Contingency Plan ==
* Contingency mechanism:
** The primary delivery path already uses the C eBPF programs (via
libbpf-rs), so the Rust compiler fix
([https://github.com/rust-lang/rust/pull/161390
rust-lang/rust#161390]) is not on the critical path for F46.
** If BPF LSM cannot be enabled in the Fedora kernel (e.g., SELinux
interaction with <code>bpf</code> in the LSM list): disable LSM
loading in <code>bpf-rbacd</code>; ship proxy mode only and rely on
token-level constraints for container workloads.
** If proxy mode has unresolved security concerns: ship LSM/container
path only; desktop users fall back to
<code>sudo</code>/<code>CAP_BPF</code>.
** If neither component is ready: do not ship <code>bpf-rbacd</code>.
eBPF access continues to work as it does today (privileged only). No
regression.
* Contingency deadline: Beta freeze
* Blocks release? No. <code>bpf-rbacd</code> is an opt-in package.
== Documentation ==
* Upstream code: [https://forge.fedoraproject.org/ebpf/bpf-rbacd
Fedora Forge ebpf/bpf-rbacd] (primary),
[https://github.com/danielmellado/bpf-rbacd GitHub mirror]
* C reference implementation: [https://github.com/tohojo/bpf-rbac-lsm
tohojo/bpf-rbac-lsm]
* Rust compiler fix: [https://github.com/rust-lang/rust/pull/161390
rust-lang/rust#161390]
* Kernel docs: [https://docs.kernel.org/bpf/bpf_token.html BPF Token
documentation]
* systemd: [https://github.com/systemd/systemd/pull/36134 PrivateBPF= PR]
* LPC 2026 talk: [https://lpc.events/event/20/contributions/2441/
BPF-RBACd: Delegating BPF permissions with high granularity]
* Fedora: Quick Docs article covering installation, group membership,
policy customization, security model, and container vs. desktop usage
paths.
* eBPF SIG: [https://fedoraproject.org/wiki/SIGs/eBPF SIGs/eBPF],
Matrix: [https://matrix.to/#/#ebpf:fedoraproject.org
#ebpf:fedoraproject.org]
== Release Notes ==
Fedora 46 introduces <code>bpf-rbacd</code>, a system daemon that
enables unprivileged, policy-controlled eBPF access through two
complementary modes:
* '''Capability granting''' for containers and services in user
namespaces — transparent eBPF access via BPF token delegation with
mandatory in-kernel LSM enforcement (three hooks:
<code>security_bpf</code>, <code>security_bpf_prog_load</code>,
<code>security_bpf_map_create</code>).
* '''Proxy execution''' for desktop users in the initial user
namespace — rootless BPF access via the <code>bpf-rbac</code> CLI,
with the daemon executing allowed operations on the user's behalf and
passing file descriptors back via <code>SCM_RIGHTS</code>.
Both modes are governed by a single YAML policy file
(<code>/etc/bpf-rbac/policy.yaml</code>) and gated by role-based
groups (<code>ebpf</code>, <code>ebpf-net</code>,
<code>ebpf-admin</code>). Install with <code>sudo dnf install
bpf-rbacd</code>.
--
Aoife Moloney
Fedora Operations Architect
Fedora Project
Matrix: @amoloney:fedora.im
IRC: amoloney
--
_______________________________________________
devel-announce mailing list -- devel-announce@lists.fedoraproject.org
To unsubscribe send an email to devel-announce-leave@lists.fedoraproject.org
Fedora Code of Conduct: https://docs.fedoraproject.org/en-US/project/code-of-conduct/
List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines
List Archives: https://lists.fedoraproject.org/archives/list/devel-announce@lists.fedoraproject.org
Do not reply to spam, report it: https://forge.fedoraproject.org/infra/tickets/issues/new