// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
#include <bpf/bpf_helpers.h> char LICENSE[] SEC("license") = "GPL"; struct token_stats { __u64 packets; __u32 last_ifindex;
}; struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, 1); __type(key, __u32); __type(value, struct token_stats);
} stats_map SEC(".maps"); SEC("xdp")
int handle_packet(struct xdp_md *ctx)
{ struct token_stats *stats; __u32 key = 0; stats = bpf_map_lookup_elem(&stats_map, &key); if (!stats) return 0; stats->packets++; stats->last_ifindex = ctx->ingress_ifindex; return XDP_PASS;
}
// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
#include <bpf/bpf_helpers.h> char LICENSE[] SEC("license") = "GPL"; struct token_stats { __u64 packets; __u32 last_ifindex;
}; struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, 1); __type(key, __u32); __type(value, struct token_stats);
} stats_map SEC(".maps"); SEC("xdp")
int handle_packet(struct xdp_md *ctx)
{ struct token_stats *stats; __u32 key = 0; stats = bpf_map_lookup_elem(&stats_map, &key); if (!stats) return 0; stats->packets++; stats->last_ifindex = ctx->ingress_ifindex; return XDP_PASS;
}
// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
#include <bpf/bpf_helpers.h> char LICENSE[] SEC("license") = "GPL"; struct token_stats { __u64 packets; __u32 last_ifindex;
}; struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, 1); __type(key, __u32); __type(value, struct token_stats);
} stats_map SEC(".maps"); SEC("xdp")
int handle_packet(struct xdp_md *ctx)
{ struct token_stats *stats; __u32 key = 0; stats = bpf_map_lookup_elem(&stats_map, &key); if (!stats) return 0; stats->packets++; stats->last_ifindex = ctx->ingress_ifindex; return XDP_PASS;
}
struct bpf_object_open_opts open_opts = {}; open_opts.sz = sizeof(open_opts);
open_opts.bpf_token_path = env.token_path; skel = token_trace_bpf__open_opts(&open_opts);
struct bpf_object_open_opts open_opts = {}; open_opts.sz = sizeof(open_opts);
open_opts.bpf_token_path = env.token_path; skel = token_trace_bpf__open_opts(&open_opts);
struct bpf_object_open_opts open_opts = {}; open_opts.sz = sizeof(open_opts);
open_opts.bpf_token_path = env.token_path; skel = token_trace_bpf__open_opts(&open_opts);
err = token_trace_bpf__load(skel); // token used for map_create + prog_load
link = bpf_program__attach_xdp(skel->progs.handle_packet, ifindex); // token used for link_create
err = token_trace_bpf__load(skel); // token used for map_create + prog_load
link = bpf_program__attach_xdp(skel->progs.handle_packet, ifindex); // token used for link_create
err = token_trace_bpf__load(skel); // token used for map_create + prog_load
link = bpf_program__attach_xdp(skel->progs.handle_packet, ifindex); // token used for link_create
err = bpf_map_lookup_elem(map_fd, &key, &before);
// ... generate UDP packet to 127.0.0.1 ...
err = bpf_map_lookup_elem(map_fd, &key, &after);
printf("delta : %llu\n", after.packets - before.packets);
err = bpf_map_lookup_elem(map_fd, &key, &before);
// ... generate UDP packet to 127.0.0.1 ...
err = bpf_map_lookup_elem(map_fd, &key, &after);
printf("delta : %llu\n", after.packets - before.packets);
err = bpf_map_lookup_elem(map_fd, &key, &before);
// ... generate UDP packet to 127.0.0.1 ...
err = bpf_map_lookup_elem(map_fd, &key, &after);
printf("delta : %llu\n", after.packets - before.packets);
parent (root, init_user_ns) child (unprivileged, new userns) │ │ │ fork() │ ├────────────────────────────────────────>│ │ │ │ unshare(CLONE_NEWUSER) │ unshare(CLONE_NEWNS | CLONE_NEWNET)
parent (root, init_user_ns) child (unprivileged, new userns) │ │ │ fork() │ ├────────────────────────────────────────>│ │ │ │ unshare(CLONE_NEWUSER) │ unshare(CLONE_NEWNS | CLONE_NEWNET)
parent (root, init_user_ns) child (unprivileged, new userns) │ │ │ fork() │ ├────────────────────────────────────────>│ │ │ │ unshare(CLONE_NEWUSER) │ unshare(CLONE_NEWNS | CLONE_NEWNET)
parent (root, init_user_ns) child (new userns) │ │ │ fs_fd = fsopen("bpf", 0) │ <───── send fs_fd via SCM_RIGHTS ────│ │ │ fsconfig(fs_fd, "delegate_cmds", ...) │ (waiting for ack) fsconfig(fs_fd, "delegate_maps", "array") │ fsconfig(fs_fd, "delegate_progs", "xdp:...") │ fsconfig(fs_fd, "delegate_attachs", "any") │ fsconfig(fs_fd, FSCONFIG_CMD_CREATE) │ │ │ │ ───────── send ack ─────────────────>│
parent (root, init_user_ns) child (new userns) │ │ │ fs_fd = fsopen("bpf", 0) │ <───── send fs_fd via SCM_RIGHTS ────│ │ │ fsconfig(fs_fd, "delegate_cmds", ...) │ (waiting for ack) fsconfig(fs_fd, "delegate_maps", "array") │ fsconfig(fs_fd, "delegate_progs", "xdp:...") │ fsconfig(fs_fd, "delegate_attachs", "any") │ fsconfig(fs_fd, FSCONFIG_CMD_CREATE) │ │ │ │ ───────── send ack ─────────────────>│
parent (root, init_user_ns) child (new userns) │ │ │ fs_fd = fsopen("bpf", 0) │ <───── send fs_fd via SCM_RIGHTS ────│ │ │ fsconfig(fs_fd, "delegate_cmds", ...) │ (waiting for ack) fsconfig(fs_fd, "delegate_maps", "array") │ fsconfig(fs_fd, "delegate_progs", "xdp:...") │ fsconfig(fs_fd, "delegate_attachs", "any") │ fsconfig(fs_fd, FSCONFIG_CMD_CREATE) │ │ │ │ ───────── send ack ─────────────────>│
child (new userns) │ mnt_fd = fsmount(fs_fd, 0, 0) token_path = "/proc/self/fd/<mnt_fd>" set_loopback_up() exec("./token_trace", "-t", token_path, "-i", "lo")
child (new userns) │ mnt_fd = fsmount(fs_fd, 0, 0) token_path = "/proc/self/fd/<mnt_fd>" set_loopback_up() exec("./token_trace", "-t", token_path, "-i", "lo")
child (new userns) │ mnt_fd = fsmount(fs_fd, 0, 0) token_path = "/proc/self/fd/<mnt_fd>" set_loopback_up() exec("./token_trace", "-t", token_path, "-i", "lo")
cd bpf-developer-tutorial/src/features/bpf_token
bash setup_token_bpffs.sh /tmp/bpf-token
cd bpf-developer-tutorial/src/features/bpf_token
bash setup_token_bpffs.sh /tmp/bpf-token
cd bpf-developer-tutorial/src/features/bpf_token
bash setup_token_bpffs.sh /tmp/bpf-token
delegate_cmds=prog_load:map_create:btf_load:link_create
delegate_maps=array
delegate_progs=xdp:socket_filter
delegate_attachs=any
delegate_cmds=prog_load:map_create:btf_load:link_create
delegate_maps=array
delegate_progs=xdp:socket_filter
delegate_attachs=any
delegate_cmds=prog_load:map_create:btf_load:link_create
delegate_maps=array
delegate_progs=xdp:socket_filter
delegate_attachs=any
cd bpf-developer-tutorial/src/features/bpf_token
make
cd bpf-developer-tutorial/src/features/bpf_token
make
cd bpf-developer-tutorial/src/features/bpf_token
make
sudo ./token_userns_demo
sudo ./token_userns_demo
sudo ./token_userns_demo
token path : /proc/self/fd/5
interface : lo (ifindex=1)
packets before : 0
packets after : 1
delta : 1
last ifindex : 1
token path : /proc/self/fd/5
interface : lo (ifindex=1)
packets before : 0
packets after : 1
delta : 1
last ifindex : 1
token path : /proc/self/fd/5
interface : lo (ifindex=1)
packets before : 0
packets after : 1
delta : 1
last ifindex : 1
sudo ./token_userns_demo -v
sudo ./token_userns_demo -v
sudo ./token_userns_demo -v
./token_trace -t /proc/self/fd/<mnt-fd> -i lo
./token_trace -t /proc/self/fd/<mnt-fd> -i lo
./token_trace -t /proc/self/fd/<mnt-fd> -i lo - Container isolation: A Kubernetes pod that needs to run a simple XDP program must be given CAP_BPF + CAP_NET_ADMIN, which also grants it the ability to load any BPF program type and create any map type. There's no way to say "you can load XDP programs but not kprobes."
- CI/CD pipelines: A build job that tests an eBPF-based observability tool needs root-equivalent capabilities to load programs, even though the test only exercises a specific, well-known program type.
- Third-party integrations: A service mesh sidecar that attaches sockops programs needs capabilities that also grant it the ability to trace every process on the host. - A privileged process (container runtime, init system, platform daemon) creates a bpffs instance with specific delegation options that define exactly which BPF operations are allowed.
- The privileged process passes this bpffs mount to an unprivileged process (container, CI job, tenant workload).
- The unprivileged process derives a BPF token from the bpffs mount. The token is a file descriptor that carries the delegated permission set.
- When the unprivileged process makes bpf() syscalls (through libbpf or directly), it passes the token fd. The kernel checks permissions against the token instead of against the process's capabilities. - A host-namespace bpffs (the one at /sys/fs/bpf) does not produce usable tokens. Tokens only work when the bpffs is associated with a non-init user namespace.
- The privileged parent configures the bpffs before passing it to the child, but the child (in its own user namespace) is the one that creates and uses the token.
- This design prevents a process with an existing token from using it to escalate privileges outside its namespace boundary. - Explicit path: Set bpf_object_open_opts.bpf_token_path when opening the BPF object. libbpf will derive the token from the specified bpffs mount.
- Environment variable: Set LIBBPF_BPF_TOKEN_PATH to point to the bpffs mount. libbpf picks it up automatically.
- Default path: If the default /sys/fs/bpf is a delegated bpffs in the current user namespace, libbpf uses it implicitly. - Container runtimes (LXD, Docker, Kubernetes): Mount a delegated bpffs into a container with only the program and map types the workload needs. LXD already supports this through its security.delegate_bpf option.
- CI/CD testing: Give build jobs the ability to load and test specific eBPF programs without granting them host-level capabilities. The delegation policy acts as an allowlist for BPF operations.
- Multi-tenant BPF platforms: A platform daemon creates per-tenant bpffs mounts with different delegation policies. One tenant might be allowed XDP + array maps, while another might get tracepoint + ringbuf access.
- LSM integration: Because BPF tokens integrate with Linux Security Modules, you can combine token delegation with SELinux or AppArmor policies for defense-in-depth. Each token gets its own security context that LSM hooks can inspect. - BPF Token concept documentation
- BPF token kernel patch series (Andrii Nakryiko)
- BPF token LWN article
- Finer-grained BPF tokens LWN discussion
- Privilege delegation using BPF Token (LXD documentation)
- bpf_token_create() libbpf API
- https://docs.kernel.org/bpf/