Skip to content

rottweiler: add directory protection#883

Draft
bcressey wants to merge 4 commits intobottlerocket-os:developfrom
bcressey:rottweiler-bpf-protect
Draft

rottweiler: add directory protection#883
bcressey wants to merge 4 commits intobottlerocket-os:developfrom
bcressey:rottweiler-bpf-protect

Conversation

@bcressey
Copy link
Copy Markdown
Contributor

Issue number:
Related: bottlerocket-os/bottlerocket#4680

Description of changes:
Extend rottweiler with the ability to lock down directories by remounting the underlying filesystem read-only, and installing a BPF LSM hook to prevent subsequent remounts, unmounts, or overmounts.

Label the directory on bpffs such that only API processes can modify the map used by the BPF LSM hook.

Update the lockdown API to also lock /etc and /.bottlerocket/datastore.

Testing done:

# fails for non-API user
bash-5.3# rw protect directory /.bottlerocket/datastore/
libbpf: failed to pin map: -EACCES
libbpf: map 'protected_devices': failed to auto-pin at '/sys/fs/bpf/rottweiler/protected_devices': -EACCES
libbpf: map 'protected_devices': failed to create: -EACCES
libbpf: failed to load BPF skeleton 'protect_dirs_bpf': -EACCES
Error: failed to load BPF program

Caused by this error:
  1: Permission denied (os error 13)

# succeeds via API
bash-5.3# apiclient lockdown
22:58:32 [INFO] Lockdown completed

# map can't be read by non-API user
bash-5.3# rw check directory /.bottlerocket/datastore protected
Error: failed to open pinned protected_mounts map

Caused by this error:
  1: Permission denied (os error 13)

# capture messages logged by BPF LSM hook
bash-5.3# cat /sys/kernel/debug/tracing/trace_pipe &
[1] 1920

bash-5.3# umount /.bottlerocket
          umount-1923    [000] ....1   599.620999: bpf_trace_printk: Blocked umount on protected device 265289767
umount: /.bottlerocket: must be superuser to unmount.

bash-5.3# mount -oremount,rw /.bottlerocket/
           mount-1924    [000] ....1   609.010153: bpf_trace_printk: Blocked remount on protected device 265289767
mount: /.bottlerocket: permission denied.
       dmesg(1) may have more information after failed mount system call.

bash-5.3# mount -t tmpfs none /.bottlerocket/
           mount-1925    [000] ....1   621.933652: bpf_trace_printk: Blocked mount over protected device 265289767
mount: /.bottlerocket: permission denied.
       dmesg(1) may have more information after failed mount system call.

Terms of contribution:

By submitting this pull request, I agree that this contribution is dual-licensed under the terms of both the Apache License, version 2.0, and the MIT license.

@bcressey bcressey requested a review from arnaldo2792 March 26, 2026 23:13
@bcressey
Copy link
Copy Markdown
Contributor Author

Keeping this in draft as there's a significant blocker: the system fails to boot if apiclient lockdown is applied via bootstrap command, since then /etc becomes read-only and systemd cannot update its default targets.

I'm also unsure whether the recursive remount under /etc is truly desirable; for example, it means that /etc/cni/net.d could not be populated by a CNI plugin later.

Ben Cressey added 3 commits March 26, 2026 23:29
After locking a directory, its key is no longer present in the kernel
and can no longer be unsealed from the TPM2 device. This is done to
prevent further modifications to persisted settings in the datastore.

A higher-level goal is to block changes to the effective settings on
the running system. To accomplish this, it's necessary to make `/etc`
and its child mounts read-only - so that configuration files cannot
be modified directly - and to make `/.bottlerocket` read-only, so the
datastore cannot be replaced by a directory with different contents.

To block the filesystems from being mounted read-write again, install
a BPF LSM hook that blocks remounting, unmounting, or mounting over
the protected directories.

Signed-off-by: Ben Cressey <bcressey@amazon.com>
Add rottweiler protect command to lockdown sequence to apply BPF LSM
write protection to /.bottlerocket/datastore and /etc before locking
the datastore encryption key.

Signed-off-by: Ben Cressey <bcressey@amazon.com>
Label rottweiler's BPF maps with "private_t" so that only API-related
processes can alter them. Otherwise, entries could be removed from
the "protected_devices" map to bypass the intended protection.

Signed-off-by: Ben Cressey <bcressey@amazon.com>
@bcressey bcressey force-pushed the rottweiler-bpf-protect branch from 8f3f3f5 to a19b7ab Compare March 26, 2026 23:29
When running `cargo` builds outside the SDK, e.g. to run unit tests,
rottweiler needs libbpf available.

Signed-off-by: Ben Cressey <bcressey@amazon.com>
@bcressey bcressey force-pushed the rottweiler-bpf-protect branch from 613d754 to 514c3d9 Compare March 27, 2026 15:25
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.

1 participant