Pinned eBPF Maps Need File Permissions and Owner Checks

BPF maps are shared storage. The Linux kernel documentation describes them as generic key-value structures used between kernel programs and user space, with operations such as create, lookup, update, and delete exposed through the bpf() system call. A map used for an allowlist, connection table, rate limit, or policy decision is therefore a control-plane object even when its values look like ordinary integers.

Objects are reference counted. A map that has no open file descriptors or other references can be freed, which is why operators pin selected maps and programs in the BPF filesystem. Pinning gives the object a stable path and lets a second process find it. It also creates a filesystem permission surface. A process that can update a security-relevant map may be able to change the behavior of a loaded program without loading new bytecode.

The pin is not automatically a policy

The bpffs path should be treated like a privileged configuration file. Check the mount, ownership, mode, and parent directory permissions. A world-writable map pin is a control-plane grant even if the program itself is immutable. The same applies to files that can be renamed, replaced, or opened through a less obvious path. Linux kernel changes have made ordinary filesystem permissions and LSM checks relevant to opening exported objects, which is another reason to inventory the entire path rather than just the map type.

Do not assume that every loader creates a new map. A loader configured to reuse a pinned map can attach a program to state produced by another deployment. The application should record whether a map is created, reused, or removed, and should verify the map type, key size, value size, and maximum entries before using it. A map with the right name but the wrong shape is not a safe dependency.

Inventory what the map controls

Classify maps by consequence. A statistics map may tolerate a broad writer group. A map that stores approved destinations, process identities, file hashes, or security policy decisions should have a much smaller writer set. Record the programs that use the map, the userspace services that update it, the service accounts that can reach the pin, and the recovery procedure if a write is rejected.

Use bpftool or the relevant loader tooling to list pinned objects and the processes that hold file descriptors against them. Compare that output with the deployment manifest. Look for orphaned pins, duplicate maps in private directories, and test scripts that still have write access. A pin that survives a service restart may be intentional, but it can also preserve stale policy after an upgrade.

Make updates observable

Log map updates at the control-plane layer: who changed the policy, which key or range changed, the expected version, and the result. Do not log every packet lookup. Separate the audit record from the high-volume data path. If the map supports atomic replacement, prefer a new map or a versioned key structure so readers do not observe a half-written policy.

Test the failure path. Remove write permission and confirm the service fails closed or enters a clearly reported degraded state. Change a map value and verify that the attached program sees the change. Restart the loader and check whether it reuses stale state or replaces the pin. Reboot and confirm the BPF filesystem mount and pin lifecycle match the documented design.

The kernel protects the object, but the deployment decides who may modify it. That makes pin ownership, file permissions, loader identity, and policy versioning part of the security boundary. A map that is invisible in the service inventory is still a place where runtime behavior can change.

References