diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml index 501e8721f..62f0da574 100644 --- a/.github/actions/setup-node/action.yml +++ b/.github/actions/setup-node/action.yml @@ -3,7 +3,7 @@ description: "Performs setup for caching and other common needs." runs: using: "composite" steps: - - run: sudo apt -y install build-essential openssl libssl-dev pkg-config liblz4-tool clang + - run: sudo apt -y install build-essential openssl libbpf-dev libelf-dev libssl-dev pkg-config liblz4-tool clang shell: bash - uses: actions/cache/restore@v4 # Restore most recent cache if available. diff --git a/Cargo.lock b/Cargo.lock index 8f81c62cf..0e4eb6824 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1082,6 +1082,7 @@ name = "rottweiler" version = "0.1.0" dependencies = [ "glibc", + "libbpf", ] [[package]] diff --git a/packages/rottweiler/Cargo.toml b/packages/rottweiler/Cargo.toml index 6177dc2bc..44783a7a5 100644 --- a/packages/rottweiler/Cargo.toml +++ b/packages/rottweiler/Cargo.toml @@ -16,3 +16,4 @@ source-groups = [ [build-dependencies] glibc = { path = "../glibc" } +libbpf = { path = "../libbpf" } diff --git a/packages/rottweiler/rottweiler.spec b/packages/rottweiler/rottweiler.spec index 0290ce327..f5089cf93 100644 --- a/packages/rottweiler/rottweiler.spec +++ b/packages/rottweiler/rottweiler.spec @@ -8,6 +8,10 @@ --build-dir %{_builddir}/sources \ --spdx --cyclonedx} +# Skip the FIPS check, which has a false positive because of a symbol that +# starts with the "ring" prefix. +%undefine cross_check_fips + Name: %{_cross_os}rottweiler Version: 0.1.0 Release: 1%{?dist} @@ -16,7 +20,9 @@ License: Apache-2.0 OR MIT URL: https://github.com/bottlerocket-os/bottlerocket BuildRequires: %{_cross_os}glibc-devel +BuildRequires: %{_cross_os}libbpf-devel Requires: %{_cross_os}cryptsetup +Requires: %{_cross_os}libbpf Requires: %{_cross_os}systemd-cryptsetup Requires: %{_cross_os}tpm2-tools diff --git a/packages/selinux-policy/fs.cil b/packages/selinux-policy/fs.cil index 89f78af0a..52d2065f6 100644 --- a/packages/selinux-policy/fs.cil +++ b/packages/selinux-policy/fs.cil @@ -22,6 +22,7 @@ (genfscon bdev / any) (genfscon binfmt_misc / binfmt_misc_fs) (genfscon bpf / any) +(genfscon bpf /rottweiler private) (genfscon cgroup / any) (genfscon cgroup2 / any) (genfscon debugfs / any) diff --git a/sources/Cargo.lock b/sources/Cargo.lock index fe2fce5cf..06486bcbd 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -108,7 +108,7 @@ dependencies = [ "bytestring", "derive_more", "encoding_rs", - "foldhash 0.1.5", + "foldhash", "futures-core", "http 0.2.12", "httparse", @@ -217,7 +217,7 @@ dependencies = [ "cfg-if", "derive_more", "encoding_rs", - "foldhash 0.1.5", + "foldhash", "futures-core", "futures-util", "impl-more", @@ -411,7 +411,7 @@ dependencies = [ "libc", "log", "models", - "nix", + "nix 0.26.4", "olpc-cjson", "rand 0.8.5", "reqwest", @@ -452,7 +452,7 @@ dependencies = [ "log", "maplit", "models", - "nix", + "nix 0.26.4", "num", "rand 0.8.5", "serde", @@ -654,11 +654,11 @@ dependencies = [ [[package]] name = "aws-lc-fips-sys" -version = "0.13.12" +version = "0.13.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ed8cd42adddefbdb8507fb7443fa9b666631078616b78f70ed22117b5c27d90" +checksum = "df6ea8e07e2df15b9f09f2ac5ee2977369b06d116f0c4eb5fa4ad443b73c7f53" dependencies = [ - "bindgen", + "bindgen 0.72.1", "cc", "cmake", "dunce", @@ -668,9 +668,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.16.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" dependencies = [ "aws-lc-fips-sys", "aws-lc-sys", @@ -680,11 +680,11 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.38.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +checksum = "bfa9b6986f250236c27e5a204062434a773a13243d2ffc2955f37bdba4c5c6a1" dependencies = [ - "bindgen", + "bindgen 0.69.5", "cc", "cmake", "dunce", @@ -789,9 +789,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.121.0" +version = "1.119.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61948728b681f88a1e49b9500469cf9e36575a424e745e2c5a651a42386e7d9c" +checksum = "1d65fddc3844f902dfe1864acb8494db5f9342015ee3ab7890270d36fbd2e01c" dependencies = [ "aws-credential-types", "aws-runtime", @@ -801,7 +801,6 @@ dependencies = [ "aws-smithy-eventstream", "aws-smithy-http", "aws-smithy-json", - "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -979,9 +978,9 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.63.13" +version = "0.63.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23374b9170cbbcc6f5df8dc5ebb9b6c5c28a3c8f599f0e8b8b10eb6f4a5c6e74" +checksum = "87294a084b43d649d967efe58aa1f9e0adc260e13a6938eb904c0ae9b45824ae" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -1280,6 +1279,29 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.11.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.116", + "which", +] + [[package]] name = "bindgen" version = "0.72.1" @@ -1289,13 +1311,13 @@ dependencies = [ "bitflags 2.11.0", "cexpr", "clang-sys", - "itertools", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 2.1.1", "shlex", "syn 2.0.116", ] @@ -1393,7 +1415,7 @@ dependencies = [ "bottlerocket-settings-models", "constants", "generate-readme", - "itertools", + "itertools 0.13.0", "log", "serde", "serde_json", @@ -1639,7 +1661,7 @@ name = "brush" version = "0.1.0" dependencies = [ "generate-readme", - "itertools", + "itertools 0.13.0", "pathdiff", "serde", "shlex", @@ -1786,6 +1808,24 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + [[package]] name = "cargo-readme" version = "3.3.1" @@ -1800,6 +1840,20 @@ dependencies = [ "toml", ] +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", +] + [[package]] name = "cbor-diag" version = "0.1.12" @@ -2111,9 +2165,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] @@ -2126,14 +2180,15 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc-fast" -version = "1.9.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd92aca2c6001b1bf5ba0ff84ee74ec8501b52bbef0cac80bf25a6c1d87a83d" +checksum = "6ddc2d09feefeee8bd78101665bd8645637828fa9317f9f292496dbbd8c65ff3" dependencies = [ "crc", "digest", + "rand 0.9.2", + "regex", "rustversion", - "spin", ] [[package]] @@ -2670,12 +2725,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -3007,7 +3056,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.1.5", + "foldhash", ] [[package]] @@ -3015,11 +3064,6 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", -] [[package]] name = "headers" @@ -3476,7 +3520,7 @@ dependencies = [ "base64", "generate-readme", "log", - "nix", + "nix 0.26.4", "serde", "serde_json", "snafu", @@ -3546,6 +3590,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -3617,6 +3670,54 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libbpf-cargo" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626c6fbcb5088716de86d0ccbdccedc17b13e59f41a605a3274029335e71fcbb" +dependencies = [ + "anyhow", + "cargo_metadata", + "clap", + "libbpf-rs", + "libbpf-sys", + "memmap2", + "serde", + "serde_json", + "tempfile", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "libbpf-rs" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e23d252d93e246c8787198369f06806c99c5077b5295be29505295f4e5426dc4" +dependencies = [ + "bitflags 2.11.0", + "libbpf-sys", + "libc", + "vsprintf", +] + +[[package]] +name = "libbpf-sys" +version = "1.5.1+v1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "912fae30b08bcbdb861d4b85bd09c05352c0ac9d7b93765ced5ca23709e7e590" +dependencies = [ + "cc", + "nix 0.30.1", + "pkg-config", +] + [[package]] name = "libc" version = "0.2.182" @@ -3750,11 +3851,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.16.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.16.1", + "hashbrown 0.15.5", ] [[package]] @@ -3813,6 +3914,15 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.7.1" @@ -3868,7 +3978,7 @@ dependencies = [ "lz4", "maplit", "models", - "nix", + "nix 0.26.4", "pentacle", "percent-encoding", "rand 0.8.5", @@ -3973,6 +4083,18 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -4357,6 +4479,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "pluto" version = "0.1.0" @@ -4435,7 +4563,7 @@ dependencies = [ "generate-readme", "log", "maplit", - "nix", + "nix 0.26.4", "schnauzer", "serde", "serde_json", @@ -4514,7 +4642,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.1.1", "rustls 0.23.31", "socket2 0.6.2", "thiserror 2.0.18", @@ -4525,16 +4653,16 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", "rand 0.9.2", "ring", - "rustc-hash", + "rustc-hash 2.1.1", "rustls 0.23.31", "rustls-pki-types", "slab", @@ -4848,7 +4976,9 @@ dependencies = [ "generate-readme", "hex", "hkdf", - "nix", + "libbpf-cargo", + "libbpf-rs", + "nix 0.26.4", "serde", "sha2", "snafu", @@ -4887,6 +5017,12 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -5841,12 +5977,6 @@ dependencies = [ "windows-sys 0.60.2", ] -[[package]] -name = "spin" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" - [[package]] name = "spki" version = "0.6.0" @@ -6058,7 +6188,7 @@ version = "0.1.0" dependencies = [ "generate-readme", "log", - "nix", + "nix 0.26.4", "serde", "simplelog", "snafu", @@ -6078,11 +6208,11 @@ dependencies = [ "generate-readme", "handlebars", "http 0.2.12", - "itertools", + "itertools 0.13.0", "log", "maplit", "models", - "nix", + "nix 0.26.4", "schnauzer", "serde_json", "simplelog", @@ -6101,7 +6231,7 @@ dependencies = [ "fs2", "generate-readme", "log", - "nix", + "nix 0.26.4", "num-derive", "num-traits", "semver", @@ -6555,6 +6685,7 @@ version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ + "chrono", "matchers", "nu-ansi-term", "once_cell", @@ -6862,6 +6993,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "vsprintf" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aec2f81b75ca063294776b4f7e8da71d1d5ae81c2b1b149c8d89969230265d63" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -7008,7 +7149,7 @@ dependencies = [ "generate-readme", "indexmap", "log", - "nix", + "nix 0.26.4", "serde", "serde_repr", "simplelog", diff --git a/sources/Cargo.toml b/sources/Cargo.toml index 202e49ac1..8e70e654b 100644 --- a/sources/Cargo.toml +++ b/sources/Cargo.toml @@ -170,6 +170,8 @@ indexmap = "2" ipnet = "2" itertools = "0.13" lazy_static = "1" +libbpf-cargo = { version = "0.25", default-features = false } +libbpf-rs = { version = "0.25", default-features = false } libc = "0.2" log = "0.4.28" lz4 = "1" diff --git a/sources/api/apiserver/src/server/mod.rs b/sources/api/apiserver/src/server/mod.rs index 65d238edd..79a935b2a 100644 --- a/sources/api/apiserver/src/server/mod.rs +++ b/sources/api/apiserver/src/server/mod.rs @@ -640,12 +640,21 @@ async fn deactivate_update() -> Result { /// Locks down the machine async fn lockdown() -> Result { debug!("Locking down now"); - let commands = vec![vec![ - "rottweiler", - "lock", - "directory", - "/.bottlerocket/datastore", - ]]; + let commands = vec![ + vec![ + "rottweiler", + "protect", + "directory", + "/.bottlerocket/datastore", + "/etc", + ], + vec![ + "rottweiler", + "lock", + "directory", + "/.bottlerocket/datastore", + ], + ]; for args in commands { let output = Command::new(args[0]) diff --git a/sources/clarify.toml b/sources/clarify.toml index 1e40c7804..ef98a7ac4 100644 --- a/sources/clarify.toml +++ b/sources/clarify.toml @@ -254,3 +254,37 @@ license-files = [ { path = "LICENSE-APACHE", hash = 0xbaee0da5 }, { path = "LICENSE-MIT", hash = 0x386ca1bc }, ] + +[clarify.libbpf-cargo] +expression = "LGPL-2.1-only OR BSD-2-Clause" +license-files = [ + { path = "LICENSE", hash = 0xa9d8035d }, + { path = "LICENSE.LGPL-2.1", hash = 0x9bb7c9c7 }, + { path = "LICENSE.BSD-2-Clause", hash = 0x783f4e3e }, +] + +[clarify.libbpf-rs] +expression = "LGPL-2.1-only OR BSD-2-Clause" +license-files = [ + { path = "LICENSE", hash = 0xa9d8035d }, + { path = "LICENSE.LGPL-2.1", hash = 0x9bb7c9c7 }, + { path = "LICENSE.BSD-2-Clause", hash = 0x783f4e3e }, +] + +[clarify.libbpf-sys] +expression = "BSD-2-Clause" +license-files = [ + { path = "LICENSE", hash = 0x35005f19 }, +] +skip-files = [ + "elfutils/COPYING", + "elfutils/COPYING-GPLV2", + "elfutils/COPYING-LGPLV3", + "elfutils/doc/COPYING-GFDL", + "elfutils/doc/man3/COPYING-GFDL", + "elfutils/doc/man7/COPYING-GFDL", + "libbpf/LICENSE", + "libbpf/LICENSE.BSD-2-Clause", + "libbpf/LICENSE.LGPL-2.1", + "zlib/LICENSE", +] diff --git a/sources/deny.toml b/sources/deny.toml index fbe4487cd..f1c83e343 100644 --- a/sources/deny.toml +++ b/sources/deny.toml @@ -7,7 +7,7 @@ confidence-threshold = 0.93 # Commented license types are allowed but not currently used allow = [ "Apache-2.0", - # "BSD-2-Clause", + "BSD-2-Clause", "BSD-3-Clause", "BSL-1.0", # "CC0-1.0", @@ -46,7 +46,7 @@ license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] multiple-versions = "deny" wildcards = "deny" -deny = [{ name = "structopt" }, { name = "clap", wrappers = ["cargo-readme"] }] +deny = [{ name = "structopt" }, { name = "clap", wrappers = ["cargo-readme", "libbpf-cargo"] }] skip = [ # `aws-sigv4 v1.2.6` depends on both `crypto-bigint v0.5.5` and `crypto-bigint v0.4.9` @@ -69,7 +69,9 @@ skip-tree = [ { name = "rand", version = "=0.8" }, # zbus introduces newer versions of crates that are either # incompatible or way ahead from the versions used in other crates - { name = "zbus", version = "=5.13.2" } + { name = "zbus", version = "=5.13.2" }, + # libbpf-sys wants a newer version of nix + { name = "nix", version = "=0.30.1" }, ] [bans.workspace-dependencies] diff --git a/sources/rottweiler/Cargo.toml b/sources/rottweiler/Cargo.toml index ac78d48d3..0592e8c2d 100644 --- a/sources/rottweiler/Cargo.toml +++ b/sources/rottweiler/Cargo.toml @@ -11,6 +11,7 @@ bottlerocket-image-features.workspace = true envy.workspace = true hex.workspace = true hkdf = { workspace = true, features = ["std"] } +libbpf-rs.workspace = true nix = { workspace = true, features = ["fs", "ioctl", "mount"] } serde = { workspace = true, features = ["derive"] } sha2.workspace = true @@ -20,3 +21,4 @@ zeroize = { workspace = true, features = ["alloc", "derive"] } [build-dependencies] generate-readme.workspace = true +libbpf-cargo.workspace = true diff --git a/sources/rottweiler/README.md b/sources/rottweiler/README.md index 596a1898b..0189951f0 100644 --- a/sources/rottweiler/README.md +++ b/sources/rottweiler/README.md @@ -27,7 +27,10 @@ interface for encrypting and managing encrypted storage resources including: - `encrypt directory ` - Encrypt a directory using fscrypt - `lock directory ` - Lock an encrypted directory (remove key) - `unlock directory ` - Unlock an encrypted directory (add key) +- `protect directory ` - Write-protect a directory using BPF LSM hooks (requires `bpf` feature) +- `unprotect directory ` - Remove write protection from a directory (requires `bpf` feature) - `check directory encrypted|unencrypted` - Check directory encryption state +- `check directory protected|unprotected` - Check directory protection state (requires `bpf` feature) #### TPM Measurement Operations - `measure settings` - Measure OS settings into PCR 8 diff --git a/sources/rottweiler/bpf/protect_dirs.bpf.c b/sources/rottweiler/bpf/protect_dirs.bpf.c new file mode 100644 index 000000000..e920e5429 --- /dev/null +++ b/sources/rottweiler/bpf/protect_dirs.bpf.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "vmlinux_minimal.h" +#include +#include +#include + +/* Constants not in vmlinux.h (preprocessor macros) */ +#define EPERM 1 + +#define MAX_PROTECTED_DEVICES 1024 + +/* Map of protected device IDs */ +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_PROTECTED_DEVICES); + __type(key, __u32); + __type(value, __s32); + __uint(pinning, LIBBPF_PIN_BY_NAME); +} protected_devices SEC(".maps"); + +/* Prevent remounting protected filesystems */ +SEC("lsm/sb_remount") +int BPF_PROG(block_remount, struct super_block *sb, void *mnt_opts) +{ + __u32 dev = 0; + __s32 *val = NULL; + + if (!sb) + return 0; + + /* Get device ID */ + if (bpf_core_read(&dev, sizeof(dev), &sb->s_dev)) + return 0; + + /* Check if this device is protected */ + val = bpf_map_lookup_elem(&protected_devices, &dev); + if (!val) + return 0; + + bpf_printk("Blocked remount on protected device %u", dev); + return -EPERM; +} + +/* Prevent mounting over protected directories */ +SEC("lsm/move_mount") +int BPF_PROG(block_move_mount, const struct path *from_path, const struct path *to_path) +{ + struct vfsmount *mnt = NULL; + struct super_block *sb = NULL; + __u32 dev = 0; + __s32 *val = NULL; + + if (!to_path) + return 0; + + /* Get the vfsmount of the target mount point */ + if (bpf_core_read(&mnt, sizeof(mnt), &to_path->mnt)) + return 0; + if (!mnt) + return 0; + + /* Get the superblock of the target mount point */ + if (bpf_core_read(&sb, sizeof(sb), &mnt->mnt_sb)) + return 0; + if (!sb) + return 0; + + /* Get device ID of the target mount point's filesystem */ + if (bpf_core_read(&dev, sizeof(dev), &sb->s_dev)) + return 0; + + /* Check if target is on a protected device */ + val = bpf_map_lookup_elem(&protected_devices, &dev); + if (!val) + return 0; + + bpf_printk("Blocked mount over protected device %u", dev); + return -EPERM; +} + +/* Prevent unmounting protected filesystems */ +SEC("lsm/sb_umount") +int BPF_PROG(block_umount, struct vfsmount *mnt, int flags) +{ + struct super_block *sb = NULL; + __u32 dev = 0; + __s32 *val = NULL; + + if (!mnt) + return 0; + + /* Get the superblock */ + if (bpf_core_read(&sb, sizeof(sb), &mnt->mnt_sb)) + return 0; + if (!sb) + return 0; + + /* Get device ID */ + if (bpf_core_read(&dev, sizeof(dev), &sb->s_dev)) + return 0; + + /* Check if this device is protected */ + val = bpf_map_lookup_elem(&protected_devices, &dev); + if (!val) + return 0; + + bpf_printk("Blocked umount on protected device %u", dev); + return -EPERM; +} + +char _license[] SEC("license") = "GPL"; diff --git a/sources/rottweiler/bpf/vmlinux_minimal.h b/sources/rottweiler/bpf/vmlinux_minimal.h new file mode 100644 index 000000000..a9a950c22 --- /dev/null +++ b/sources/rottweiler/bpf/vmlinux_minimal.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __VMLINUX_MINIMAL_H__ +#define __VMLINUX_MINIMAL_H__ + +typedef signed char __s8; +typedef unsigned char __u8; +typedef signed short __s16; +typedef unsigned short __u16; +typedef signed int __s32; +typedef unsigned int __u32; +typedef signed long long __s64; +typedef unsigned long long __u64; + +typedef __u16 __be16; +typedef __u32 __be32; +typedef __u32 __wsum; + +enum bpf_map_type { + BPF_MAP_TYPE_HASH = 1, +}; + +typedef __u32 dev_t; + +struct block_device { + dev_t bd_dev; +} __attribute__((preserve_access_index)); + +struct super_block { + dev_t s_dev; + struct block_device *s_bdev; +} __attribute__((preserve_access_index)); + +struct vfsmount { + struct super_block *mnt_sb; +} __attribute__((preserve_access_index)); + +struct path { + struct vfsmount *mnt; +} __attribute__((preserve_access_index)); + +#endif /* __VMLINUX_MINIMAL_H__ */ diff --git a/sources/rottweiler/build.rs b/sources/rottweiler/build.rs index 5b3a661c3..8d9571022 100644 --- a/sources/rottweiler/build.rs +++ b/sources/rottweiler/build.rs @@ -1,3 +1,17 @@ +use libbpf_cargo::SkeletonBuilder; +use std::env; +use std::path::PathBuf; + fn main() { generate_readme::from_main().unwrap(); + + println!("cargo:rerun-if-changed=bpf/protect_dirs.bpf.c"); + println!("cargo:rerun-if-changed=bpf/vmlinux_minimal.h"); + + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + + SkeletonBuilder::new() + .source("bpf/protect_dirs.bpf.c") + .build_and_generate(out_dir.join("bpf.skel.rs")) + .unwrap(); } diff --git a/sources/rottweiler/src/bpf.rs b/sources/rottweiler/src/bpf.rs new file mode 100644 index 000000000..955aeeb84 --- /dev/null +++ b/sources/rottweiler/src/bpf.rs @@ -0,0 +1,66 @@ +use libbpf_rs::skel::{OpenSkel, Skel, SkelBuilder}; +use snafu::prelude::*; +use std::path::Path; + +mod skel { + include!(concat!(env!("OUT_DIR"), "/bpf.skel.rs")); +} + +type Result = std::result::Result; + +const BPF_PIN_PATH: &str = "/sys/fs/bpf/rottweiler"; +const PROTECTED_MOUNTS_MAP: &str = "protected_devices"; + +pub fn open_protected_mounts_map() -> Result { + libbpf_rs::MapHandle::from_pinned_path(Path::new(BPF_PIN_PATH).join(PROTECTED_MOUNTS_MAP)) + .with_whatever_context(|_| "failed to open pinned protected_mounts map") +} + +pub fn load_bpf() -> Result { + if Path::new(BPF_PIN_PATH).join("block_remount").exists() + && Path::new(BPF_PIN_PATH).join("block_move_mount").exists() + && Path::new(BPF_PIN_PATH).join("block_umount").exists() + { + return open_protected_mounts_map(); + } + + let skel_builder = skel::ProtectDirsSkelBuilder::default(); + let mut open_object = std::mem::MaybeUninit::uninit(); + + let mut open_skel = skel_builder + .open(&mut open_object) + .with_whatever_context(|_| "failed to open BPF skeleton")?; + + open_skel + .maps + .protected_devices + .set_pin_path(Path::new(BPF_PIN_PATH).join(PROTECTED_MOUNTS_MAP)) + .with_whatever_context(|_| "failed to set map pin path")?; + + let mut skel = open_skel + .load() + .with_whatever_context(|_| "failed to load BPF program")?; + + skel.attach() + .with_whatever_context(|_| "failed to attach BPF program")?; + + std::fs::create_dir_all(BPF_PIN_PATH) + .with_whatever_context(|_| "failed to create link directory")?; + + if let Some(ref mut link) = skel.links.block_remount { + link.pin(Path::new(BPF_PIN_PATH).join("block_remount")) + .with_whatever_context(|_| "failed to pin block_remount link")?; + } + + if let Some(ref mut link) = skel.links.block_move_mount { + link.pin(Path::new(BPF_PIN_PATH).join("block_move_mount")) + .with_whatever_context(|_| "failed to pin block_move_mount link")?; + } + + if let Some(ref mut link) = skel.links.block_umount { + link.pin(Path::new(BPF_PIN_PATH).join("block_umount")) + .with_whatever_context(|_| "failed to pin block_umount link")?; + } + + open_protected_mounts_map() +} diff --git a/sources/rottweiler/src/directory.rs b/sources/rottweiler/src/directory.rs index 2f650c57e..5f0b1c4d6 100644 --- a/sources/rottweiler/src/directory.rs +++ b/sources/rottweiler/src/directory.rs @@ -1,8 +1,10 @@ +use nix::mount::MsFlags; use snafu::{Whatever, prelude::*}; use std::path::PathBuf; use crate::fscrypt::*; use crate::key; +use crate::{bpf, mount_point::MountPoint}; type Result = std::result::Result; @@ -60,3 +62,67 @@ pub fn is_encrypted(path: PathBuf) -> Result { Err(_) => Ok(false), } } + +/// Collect all unique mount points (parents and children) for the given paths. +fn collect_mounts(paths: &[PathBuf]) -> Result> { + let mut mounts = std::collections::HashMap::new(); + + for path in paths { + let parent = MountPoint::from_path(path)?; + let children = parent.find_children()?; + + mounts.entry(parent.device_id).or_insert(parent); + for child in children { + mounts.entry(child.device_id).or_insert(child); + } + } + + Ok(mounts) +} + +/// Write-protect a directory using BPF LSM hooks +pub fn protect(paths: Vec) -> Result<()> { + let map = bpf::load_bpf()?; + let mounts = collect_mounts(&paths)?; + + for mount in mounts.values() { + // Check for inconsistent state: in map but not read-only + if mount.is_in_map(&map)? && !mount.is_readonly()? { + whatever!( + "device {} is in protected map but not read-only - inconsistent state", + mount.device_id + ); + } + + if !mount.is_in_map(&map)? { + mount.remount(MsFlags::MS_RDONLY)?; + mount.add_to_map(&map)?; + } + } + + Ok(()) +} + +/// Remove write protection from a directory +pub fn unprotect(paths: Vec) -> Result<()> { + let map = bpf::load_bpf()?; + let mounts = collect_mounts(&paths)?; + + for mount in mounts.values() { + mount.remove_from_map(&map)?; + } + + for mount in mounts.values() { + mount.remount(MsFlags::empty())?; + } + + Ok(()) +} + +/// Check if a directory is write-protected (read-only and in BPF map) +pub fn is_protected(path: PathBuf) -> Result { + let map = bpf::load_bpf()?; + let mount = MountPoint::from_path(&path)?; + + Ok(mount.is_readonly()? && mount.is_in_map(&map)?) +} diff --git a/sources/rottweiler/src/main.rs b/sources/rottweiler/src/main.rs index 4cd893dcc..62f402e17 100644 --- a/sources/rottweiler/src/main.rs +++ b/sources/rottweiler/src/main.rs @@ -24,7 +24,10 @@ interface for encrypting and managing encrypted storage resources including: - `encrypt directory ` - Encrypt a directory using fscrypt - `lock directory ` - Lock an encrypted directory (remove key) - `unlock directory ` - Unlock an encrypted directory (add key) +- `protect directory ` - Write-protect a directory using BPF LSM hooks (requires `bpf` feature) +- `unprotect directory ` - Remove write protection from a directory (requires `bpf` feature) - `check directory encrypted|unencrypted` - Check directory encryption state +- `check directory protected|unprotected` - Check directory protection state (requires `bpf` feature) ### TPM Measurement Operations - `measure settings` - Measure OS settings into PCR 8 @@ -44,6 +47,7 @@ use snafu::Whatever; use std::path::{Path, PathBuf}; mod block_device; +mod bpf; mod directory; mod fscrypt; mod key; @@ -64,6 +68,7 @@ fn main() -> Result<()> { Some("encrypt" | "attach" | "detach" | "resize" | "lock" | "unlock" | "check") => { Some(2) } + Some("protect" | "unprotect") => Some(2), _ => None, }; if let Some(pos) = resource_pos @@ -111,6 +116,12 @@ fn main() -> Result<()> { Command::Unlock(cmd) => match cmd.resource { UnlockResource::Directory(cmd) => directory::unlock(cmd.path, cmd.key_id), }, + Command::Protect(cmd) => match cmd.resource { + ProtectResource::Directory(cmd) => directory::protect(cmd.paths), + }, + Command::Unprotect(cmd) => match cmd.resource { + UnprotectResource::Directory(cmd) => directory::unprotect(cmd.paths), + }, Command::Check(cmd) => match cmd.resource { CheckResource::BlockDevice(cmd) => { let path = cmd.path; @@ -148,6 +159,20 @@ fn main() -> Result<()> { false, "encrypted", ), + CheckDirectoryState::Protected(_) => handle_check( + directory::is_protected(path.clone())?, + "directory", + &path, + true, + "protected", + ), + CheckDirectoryState::Unprotected(_) => handle_check( + directory::is_protected(path.clone())?, + "directory", + &path, + false, + "protected", + ), } } }, @@ -199,6 +224,8 @@ enum Command { Resize(ResizeCmd), Lock(LockCmd), Unlock(UnlockCmd), + Protect(ProtectCmd), + Unprotect(UnprotectCmd), Check(CheckCmd), Measure(MeasureCmd), } @@ -367,6 +394,50 @@ struct UnlockDirectoryCmd { key_id: String, } +#[derive(FromArgs)] +#[argh(subcommand, name = "protect")] +/// Protect a resource +struct ProtectCmd { + #[argh(subcommand)] + resource: ProtectResource, +} + +#[derive(FromArgs)] +#[argh(subcommand)] +enum ProtectResource { + Directory(ProtectDirectoryCmd), +} + +#[derive(FromArgs)] +#[argh(subcommand, name = "directory")] +/// Write-protect a directory using BPF LSM hooks +struct ProtectDirectoryCmd { + #[argh(positional)] + paths: Vec, +} + +#[derive(FromArgs)] +#[argh(subcommand, name = "unprotect")] +/// Unprotect a resource +struct UnprotectCmd { + #[argh(subcommand)] + resource: UnprotectResource, +} + +#[derive(FromArgs)] +#[argh(subcommand)] +enum UnprotectResource { + Directory(UnprotectDirectoryCmd), +} + +#[derive(FromArgs)] +#[argh(subcommand, name = "directory")] +/// Remove write protection from a directory +struct UnprotectDirectoryCmd { + #[argh(positional)] + paths: Vec, +} + #[derive(FromArgs)] #[argh(subcommand, name = "check")] /// Check resource state @@ -426,6 +497,8 @@ struct CheckDirectoryCmd { enum CheckDirectoryState { Encrypted(CheckDirectoryEncryptedCmd), Unencrypted(CheckDirectoryUnencryptedCmd), + Protected(CheckDirectoryProtectedCmd), + Unprotected(CheckDirectoryUnprotectedCmd), } #[derive(FromArgs)] @@ -438,6 +511,16 @@ struct CheckDirectoryEncryptedCmd {} /// Check if unencrypted struct CheckDirectoryUnencryptedCmd {} +#[derive(FromArgs)] +#[argh(subcommand, name = "protected")] +/// Check if protected +struct CheckDirectoryProtectedCmd {} + +#[derive(FromArgs)] +#[argh(subcommand, name = "unprotected")] +/// Check if unprotected +struct CheckDirectoryUnprotectedCmd {} + #[derive(FromArgs)] #[argh(subcommand, name = "measure")] /// Measure data into TPM PCR diff --git a/sources/rottweiler/src/mount_point.rs b/sources/rottweiler/src/mount_point.rs index 3b8487a8b..083713f00 100644 --- a/sources/rottweiler/src/mount_point.rs +++ b/sources/rottweiler/src/mount_point.rs @@ -1,12 +1,31 @@ +use libbpf_rs::MapCore; +use nix::mount::{MsFlags, mount}; +use nix::sys::statvfs::{FsFlags, statvfs}; use snafu::prelude::*; +use std::collections::HashSet; use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; +use walkdir::WalkDir; type Result = std::result::Result; -/// Represents a filesystem mount point with its path +const NONE: Option<&'static [u8]> = None; +const PROTECTED: i32 = 1; + +/// Convert device number from stat encoding to kernel encoding +/// +/// stat() returns dev_t as u64 using old encoding: (major & 0xff) << 8 | (minor & 0xff) | ((minor & 0xfff00) << 12) +/// kernel uses u32: (major & 0xfff) << 20 | (minor & 0xfffff) +fn dev_to_kernel_encoding(stat_dev: u64) -> u32 { + let major = ((stat_dev >> 8) & 0xff) | ((stat_dev >> 32) & 0xfffff00); + let minor = (stat_dev & 0xff) | ((stat_dev >> 12) & 0xffffff00); + ((major << 20) | minor) as u32 +} + +/// Represents a filesystem mount point with its path and device ID pub struct MountPoint { pub path: PathBuf, + pub device_id: u32, } impl MountPoint { @@ -38,7 +57,10 @@ impl MountPoint { current = parent.to_path_buf(); } - Ok(Self { path: current }) + Ok(Self { + path: current.clone(), + device_id: dev_to_kernel_encoding(current_dev), + }) } /// Open the mount point path as a file descriptor @@ -47,4 +69,89 @@ impl MountPoint { format!("failed to open mount point '{}'", self.path.display()) }) } + + /// Find all child mount points within this mount point's directory tree + pub fn find_children(&self) -> Result> { + let mut child_mounts = Vec::new(); + let mut seen_devices = HashSet::new(); + seen_devices.insert(self.device_id); + + for entry in WalkDir::new(&self.path).into_iter().filter_map(|e| e.ok()) { + if !entry.file_type().is_dir() { + continue; + } + + let entry_dev = dev_to_kernel_encoding( + entry + .metadata() + .with_whatever_context(|_| { + format!("failed to get metadata for '{}'", entry.path().display()) + })? + .dev(), + ); + + if !seen_devices.contains(&entry_dev) { + seen_devices.insert(entry_dev); + child_mounts.push(Self { + path: entry.path().to_path_buf(), + device_id: entry_dev, + }); + } + } + + Ok(child_mounts) + } + + /// Remount the filesystem with the specified mount flags + pub fn remount(&self, flags: MsFlags) -> Result<()> { + mount(NONE, &self.path, NONE, MsFlags::MS_REMOUNT | flags, NONE) + .with_whatever_context(|_| format!("failed to remount '{}'", self.path.display())) + } + + /// Add this mount point's device ID to the BPF protected_mounts map + pub fn add_to_map(&self, map: &libbpf_rs::MapHandle) -> Result<()> { + map.update( + &self.device_id.to_ne_bytes(), + &PROTECTED.to_ne_bytes(), + libbpf_rs::MapFlags::ANY, + ) + .with_whatever_context(|_| { + format!( + "failed to add device {} to protected_mounts map", + self.device_id + ) + }) + } + + /// Remove this mount point's device ID from the BPF protected_mounts map + pub fn remove_from_map(&self, map: &libbpf_rs::MapHandle) -> Result<()> { + map.delete(&self.device_id.to_ne_bytes()) + .with_whatever_context(|_| { + format!( + "failed to remove device {} from protected_mounts map", + self.device_id + ) + }) + } + + /// Check if this mount point's device ID is in the BPF protected_mounts map + pub fn is_in_map(&self, map: &libbpf_rs::MapHandle) -> Result { + match map.lookup(&self.device_id.to_ne_bytes(), libbpf_rs::MapFlags::ANY) { + Ok(Some(_)) => Ok(true), + Ok(None) => Ok(false), + Err(e) => whatever!( + "failed to check if device {} is in map: {}", + self.device_id, + e + ), + } + } + + /// Check if this mount point is mounted read-only + pub fn is_readonly(&self) -> Result { + let stat = statvfs(&self.path) + .with_whatever_context(|_| format!("failed to statvfs '{}'", self.path.display()))?; + + Ok(stat.flags().contains(FsFlags::ST_RDONLY)) + } }