Skip to content

Drop unnecessary capabilities after BPF initialization#3450

Open
robbycochran wants to merge 6 commits into
masterfrom
rc-harden-caps
Open

Drop unnecessary capabilities after BPF initialization#3450
robbycochran wants to merge 6 commits into
masterfrom
rc-harden-caps

Conversation

@robbycochran

@robbycochran robbycochran commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Self-harden collector by dropping unnecessary Linux capabilities after BPF initialization, regardless of how it is deployed. Even with privileged: true, the process capability set is minimized per-thread before entering the event loop.

Based on prior work by @erthalion in #1908.

Per-thread capability model

Thread Entry point Capabilities kept Why
Main (event loop) RunService() BPF, PERFMON, SYS_PTRACE BPF map ops, capture restart, /proc reads
Stats exporter CollectorStatsExporter::run() BPF bpf_map_lookup_elem() for metrics
Network notifier NetworkStatusNotifier::Run() SYS_PTRACE /proc reads for network state
Signal client SignalServiceClient::EstablishGRPCStream() none gRPC streaming only
Config loader ConfigLoader::WatchFile() none inotify + YAML parsing only

On kernels < 5.8 (RHEL 8), the main thread keeps SYS_ADMIN + SYS_PTRACE instead of BPF + PERFMON + SYS_PTRACE.

Implementation

  • DropCapabilities.h — single utility: clears all caps + bounding set via CAPNG_SELECT_ALL, adds back only what is specified
  • Called at each thread entry point with that thread's minimal set
  • Uses libcap-ng which was already linked but never called
  • Kernel version detection via HostInfo::GetKernelVersion() for RHEL 8 fallback
  • Warns but does not abort if the drop fails (collector still works, just without hardening)

What gets dropped (~38 of 41 capabilities)

NET_RAW, SYS_MODULE, DAC_OVERRIDE, CHOWN, FOWNER, KILL, SETUID, SETGID, NET_BIND_SERVICE, MKNOD, SYS_RAWIO, AUDIT_WRITE, SYS_RESOURCE, DAC_READ_SEARCH, NET_ADMIN, and all others not in the keep list above.

Differences from #1908

  • Drops after InitKernel() instead of at main() start — allows dropping SYS_RESOURCE
  • Kernel-version-aware: discrete CAP_BPF on 5.8+, CAP_SYS_ADMIN fallback on older
  • Does not keep CAP_DAC_READ_SEARCH (validated unnecessary via CI in Replace privileged:true with minimal capabilities in integration tests #3447)
  • Does not keep CAP_SYS_ADMIN on modern kernels (validated BPF + PERFMON suffice)

Companion to #3447 which reduces the deployment-side privileges.

Test plan

  • C++ compilation succeeds (builder container, all 4 architectures)
  • Unit tests pass
  • Integration tests pass — collector logs show capability drop messages
  • Verify collector remains functional after drops (process events, network flows, /proc scraping, metrics)

After BPF programs are loaded and attached, collector no longer needs
CAP_BPF, CAP_PERFMON, or CAP_SYS_RESOURCE. Drop all capabilities
except CAP_SYS_PTRACE (needed for ongoing /proc scraping) immediately
after InitKernel() succeeds.

On kernels < 5.8 (RHEL 8), also keep CAP_SYS_ADMIN since the BPF
subsystem may check it on runtime operations.

This makes collector self-hardening regardless of how it is deployed —
even with privileged:true, the process capability set is minimized
before entering the event loop.

libcap-ng was already linked but unused; this is the first actual use.
@robbycochran robbycochran requested a review from a team as a code owner June 11, 2026 20:51
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: ccef6c04-c84b-4886-b54f-0890ffa76ad9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rc-harden-caps

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter

codecov-commenter commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 41.66667% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 27.37%. Comparing base (339edbf) to head (47fe2e9).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
collector/lib/DropCapabilities.h 62.50% 0 Missing and 3 partials ⚠️
collector/lib/CollectorStatsExporter.cpp 0.00% 1 Missing ⚠️
collector/lib/ConfigLoader.cpp 0.00% 1 Missing ⚠️
collector/lib/NetworkStatusNotifier.cpp 0.00% 0 Missing and 1 partial ⚠️
collector/lib/SignalServiceClient.cpp 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3450      +/-   ##
==========================================
+ Coverage   27.34%   27.37%   +0.03%     
==========================================
  Files          95       96       +1     
  Lines        5420     5432      +12     
  Branches     2545     2551       +6     
==========================================
+ Hits         1482     1487       +5     
- Misses       3211     3214       +3     
- Partials      727      731       +4     
Flag Coverage Δ
collector-unit-tests 27.37% <41.66%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

Code audit of falcosecurity-libs found two runtime paths that may need
BPF capabilities:

1. bpf_map_lookup_elem() in pman_get_scap_stats() — called periodically
   for metrics collection via CollectorStatsExporter
2. sinsp::restart_capture() — can re-attach BPF programs via
   bpf_program__attach() on SCAP_UNEXPECTED_BLOCK events

Drop CAP_SYS_RESOURCE (only needed for RLIMIT_MEMLOCK at init) and all
other capabilities not in the keep list. On kernel >= 5.8 keep
SYS_PTRACE + BPF + PERFMON; on older kernels keep SYS_PTRACE +
SYS_ADMIN.

This still drops ~38 of 41 capabilities on modern kernels, eliminating
NET_RAW, SYS_MODULE, DAC_OVERRIDE, CHOWN, FOWNER, MKNOD, SYS_RAWIO,
and all other unneeded privileges.
Add DropCapabilities() utility in DropCapabilities.h and apply it at
each thread entry point with only the capabilities that thread needs:

- Main thread (event loop): BPF, PERFMON, SYS_PTRACE
- CollectorStatsExporter: BPF (map lookups for metrics)
- NetworkStatusNotifier: SYS_PTRACE (/proc reads)
- SignalServiceClient: none (gRPC only)
- ConfigLoader: none (inotify + yaml only)

Uses CAPNG_SELECT_ALL to also clear the bounding set, preventing
capability regain via execve.

Based on prior work by Dmitrii Dolgov in PR #1908.
The main thread drops the bounding set (CAPNG_SELECT_ALL) which
requires CAP_SETPCAP. After that, worker threads can no longer modify
the bounding set. Fix by having only the main thread clear the bounding
set (clear_bounding=true) while worker threads only modify effective +
permitted (CAPNG_SELECT_CAPS).
@github-actions

Copy link
Copy Markdown

/retest collector-on-push

2 similar comments
@github-actions

Copy link
Copy Markdown

/retest collector-on-push

@github-actions

Copy link
Copy Markdown

/retest collector-on-push

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants