FLOSS supporter” • CoreOS / Red Hat • Principal Software Engineer • Working on Linux userland components • Previously: security researcher/engineer @lucabruno | [email protected] | github.com/lucab
called “hermetic”) Linux distribution. In practice, that means assembling softwares from many source projects into self-contained binary OS images. In our case: • Source tarballs RPM packages OSTree commits Image artifacts
format for software packages. RPM supports: • carrying binary data • declaring dependencies between packages • running pre- and post-install custom scripts It originated in 1995 at Red Hat, and it’s still used by Red Hat Enterprise Linux (RHEL). Over time it got adopted by many other distributions, like Fedora and OpenSUSE.
in multiple local branches, incrementally pulling from remote repositories. OSTree keeps a versioned history of changes, and on each boot it can checkout a specific commit. That allows transactional upgrades and rollbacks of the OS, without multiple A/B partitions. A “git-like” manager for bootable filesystem trees.
RPM and OStree, in order to build an hermetic OS. • Input: a set of binary packages (e.g. pre-built RPMs from RHEL or Fedora) • Output: an OSTree commit (e.g. a specific version of RHEL CoreOS or Fedora SilverBlue) It is made of several components: • a client/daemon part, running on the host • a standalone CLI part, running in the containerized build-pipeline
bufrd)?; let kf = glib::KeyFile::new(); kf.load_from_data( &cfg_data, glib::KeyFileFlags::NONE)?; let is_enabled = kf.boolean( “core”, “enabled”)?; Step 1: glib-rs glib is a multi-purpose C library offering a suite of data-structures (hashmaps, lists, etc.), OOP helpers and utilities. Both ostree and rpm-ostree heavily use glib for most of their C logic. Gnome developers have enthusiastically embraced Rust, and maintain glib-rs bindings (and many more) under the gtk-rs umbrella.
options for filter * @commit_filter: (allow-none): * Function to inspect individual files * @user_data: (allow-none): User data * @destroy_notify: A #GDestroyNotify * * Returns: (transfer full): * A new commit modifier. */ OstreeRepoCommitModifier * ostree_repo_commit_modifier_new (...) { ... } Step 2: gir-rs Binding C libraries from multiple language is a tedious job. In Gnome land, developers invented an Intermediate Representation (named GIR) to describe their public APIs. Bindings can be auto-generated from these GIR descriptions. For Rust, gir-rs takes care of that.
pub fn new<P: IsA<gio::File>>(path: &P) -> Repo { unsafe { from_glib_full(ffi::ostree_repo_new( path.as_ref().to_glib_none().0)) } } } Step 3: ostree-rs libostree is a C library built on top of glib, hosting the core ostree logic. It comes with a GIR description of the full API surface. Through gir-rs tooling, we generate two Rust crates: • ostree-sys (low-level unsafe bindings) • ostree (ergonomic interface to ostree logic) fn new_temp_with_mode( repo_mode: ostree::RepoMode) -> Result<ostree::Repo> { let dir = tempfile::tempdir()?; let repo = ostree::Repo::new_for_path( dir.path()); repo.create(repo_mode, NONE_CANCELLABLE) }
an IMA /// configuration, generate a new commit /// object with IMA signatures. pub fn ima_sign(repo: &ostree::Repo, ostree_ref: &str, opts: &ImaOpts) -> Result<String> { let writer = &mut CommitRewriter::new( repo, opts)?; writer.map_commit(ostree_ref) } Step 4: ostree-rs-ext Over time we started adding new optional/experimental features to ostree logic (e.g. IMA hashing). This is new pure-Rust code, without unsafe bits. ostree-ext largely relies on the vast ecosystem of crates (serde, tokio, etc.)
-> &mut FFIDnfPackage; fn get_nevra(self: &DnfPackage) -> String; fn get_name(self: &DnfPackage) -> String; fn get_evr(self: &DnfPackage) -> String; fn get_arch(self: &DnfPackage) -> String; Step 5: libdnf-sys libdnf is the external C library that provides all the logic for handling RPMs. Its API surface is quite large, but our Rust logic only uses a small subset of it. We (manually) bridged the few needed functions, and keep that internal to rpm-ostree.
bridging between C++ and Rust. It auto-generates glue code on both C++ and Rust sides. This automatic bridging spare us from writing a lot of (unsafe) glue. (Diagram from https://cxx.rs)
helper for stuffing RPM content in OSTree - written in Python. Ported to C due to increasing need of low-level primitives. Initial Rust logic added, as opt-in feature. Some core logic moved to Rust, no more opt-in. Growing amount of manual FFI glue. Ported to C++, reducing the amount of manual Rust integration through cxx-rs. • 2013 • 2014 • 2018 • 2019 • 2020
the last few years of development, about: • Development velocity • Complexity of reviews • Unit testing • Dependencies (Cargo) • Minimum Supported Rust Version (MSRV) and distro-packaging
C-only flow: • we increased the usage of unit-tests, thanks to cargo test. • rustfmt made code-formatting easy, and we started enforcing it in CI (later expanded to C code too). • reviewers are able to better focus on logic issues instead of C pitfalls (thanks to richer types and static checks).
to the previous C flow: • we had to figure how to build/ship our Rust code, in Fedora and RHEL. • we had to pay attention to newer Rust features we were using, and the minimum toolchain (MSRV) requirements. • we gained access to many high-quality crates for common tasks, decoupling from system libraries.
nix::Mode::empty())?; let target = "link-content"; let linkpath = "relpath"; symlinkat(target, Some(dirfd), // Option<RawFd> linkpath)?; Backup: nix-rs The nix crate provides a memory-safe alternative to the unsafe APIs exposed by the libc crate. This is done by wrapping low-level functionality with types/abstractions that enforce legal/safe usage.
another safe wrapper for the low-level primitives in the libc crate. It is still undergoing stabilization, and it is fully built on top of the new io-safety primitives. cap-std provides a capability-based version of libstd primitives. This is mostly useful as a building-block to implement filesystem sandboxing. Among other things, this is meant to avoid security bugs such as “path traversals” (e.g. unexpected data leak via ../../../etc/passwd)