# Rust in Chromium [TOC] # Why? Handling untrustworthy data in non-trivial ways is a major source of security bugs, and it's therefore against Chromium's security policies [to do it in the Browser or Gpu process](../docs/security/rule-of-2.md) unless you are working in a memory-safe language. Rust provides a cross-platform memory-safe language so that all platforms can handle untrustworthy data directly from a privileged process, without the performance overheads and complexity of a utility process. # Status The Rust toolchain is enabled for and supports all platforms and development environments that are supported by the Chromium project. The first milestone to include full production-ready support was M119. Rust is approved by Chrome ATLs for production use in [certain third-party scenarios](../docs/adding_to_third_party.md#Rust). For questions or help, reach out to `rust-dev@chromium.org` or `#rust` on the [Chromium Slack](https://www.chromium.org/developers/slack/). If you use VSCode, we have [additional advice below](#using-vscode). # Adding a third-party Rust library Third-party libraries are pulled from [crates.io](https://crates.io), but Chromium does not use Cargo as a build system. ## Third-party review All third-party crates need to go through third-party review. See [//docs/adding_to_third_party.md](adding_to_third_party.md) for instructions on how to have a library reviewed. ## Importing a crate from crates.io The `//third_party/rust/chromium_crates_io/Cargo.toml` file defines the set of crates depended on from first-party code. Any transitive dependencies will be found from those listed there. The file is a [standard `Cargo.toml` file]( https://doc.rust-lang.org/cargo/reference/manifest.html), though the crate itself is never built, it is only used to collect dependencies through the `[dependencies]` section. To use a third-party crate "bar" version 3 from first party code: 1. Change directory to the root `src/` dir of Chromium. 1. Add the crate to `//third_party/rust/chromium_crates_io/Cargo.toml`: * `vpython3 ./tools/crates/run_gnrt.py add foo` to add the latest version of `foo`. * `vpython3 ./tools/crates/run_gnrt.py add foo@1.2.3` to add a specific version of `foo`. * Or, directly through (nightly) cargo: `cargo run --release --manifest-path tools/crates/gnrt/Cargo.toml --target-dir out/gnrt add foo` * Or, edit the Cargo.toml by hand, finding the version you want from [crates.io](https://crates.io). 1. Download the crate's files: * `./tools/crates/run_gnrt.py vendor` to download the new crate. * Or, directly through (nightly) cargo: `cargo run --release --manifest-path tools/crates/gnrt/Cargo.toml --target-dir out/gnrt vendor` * This will also apply any patches in `//third_party/rust/chromium_crates_io/patches` for the crates. If a patch can not apply, the crate's download will be cancelled and an error will be printed. See [patching errors](#patching-errors) below for how to resolve this. 1. Add the new files to git: * `git add -f third_party/rust/chromium_crates_io` * The `-f` is important, as files may be skipped otherwise from a `.gitignore` inside the crate. 1. (optional) If the crate is only to be used by tests and tooling, then specify the `"test"` group in `//third_party/rust/chromium_crates_io/gnrt_config.toml`: ``` [crate.foo] group = "test" ``` 1. Generate the `BUILD.gn` file for the new crate: * `vpython3 ./tools/crates/run_gnrt.py gen` * Or, directly through (nightly) cargo: `cargo run --release --manifest-path tools/crates/gnrt/Cargo.toml --target-dir out/gnrt gen` 1. Verify if all new dependencies are already audited by running `cargo vet`: * Install `cargo vet` if it's not yet installed: * `tools/crates/run_cargo.py install cargo-vet --locked --version=0.9.1` * TODO: Pre-package `cargo-vet` into `rust-toolchain`: https://crrev.com/c/5366668 * `./tools/crates/run_cargo_vet.py check` * If `check` fails, then there are missing audits, which need to be added to `//third_party/rust/chromium_crates_io/supply-chain/audits.toml`. * See [auditing_standards.md](https://github.com/google/rust-crate-audits/blob/main/auditing_standards.md) for the criteria for audits. * See [Cargo Vet documentation](https://mozilla.github.io/cargo-vet/recording-audits.html) for how to record the audit in `audits.toml`. * Some audits can be done by any engineer ("ub-risk-0" and "safe-to-run") while others will require specialists from the Security team. These are explained in the [auditing_standards.md](https://github.com/google/rust-crate-audits/blob/main/auditing_standards.md). * Audit updates in `audits.toml` should be part of the submitted CL so that `cargo vet` will continue to pass after the CL lands. 1. Upload the CL. If there is any `unsafe` usage then Security experts will need to audit the "ub-risk" level. Mark any `unsafe` usage with `TODO` code review comments, and include a link to it in the request for third-party and security review. ### Cargo features To enable a feature "spaceships" in the crate, change the entry in `//third_party/rust/chromium_crates_io/Cargo.toml` to include the feature: ```toml [dependencies] bar = { version = "3", features = [ "spaceships" ] } ``` ### Patching third-party crates You may patch a crate in tree, but save any changes made into a diff file in a `//third_party/rust/chromium_crates_io/patches/` directory for the crate. The diff file should be generated by `git-format-patch` each new patch numbered consecutively so that they can be applied in order. For example, these files might exist if the "foo" crate was patched with a couple of changes: ``` //third_party/rust/chromium_crates_io/patches/foo/patches/0001-Edit-the-Cargo-toml.diff //third_party/rust/chromium_crates_io/patches/foo/patches/0002-Other-changes.diff ``` The recommended procedure to create such patches is: 1. Commit the plain new version of the crate to your local git branch 2. Modify the crate as necessary 3. Commit that modified version 4. Use `git format-patch <unpatched version>` to generate the patch files 5. Add the patch files in a new, third, commit 6. Squash them, or rely on `git cl upload` doing so #### Patching errors If `gnrt vendor` fails to apply a patch for a crate, it will cancel the download of that crate rather than leave it in a broken state. To recreate patches, first get a pristine copy of the crate by using the `--no-patches` argument: 1. Download the crate without applying patches: * `vpython3 ./tools/crates/run_gnrt.py vendor --no-patches=<CRATE_NAME>` 2. Then recreate the patches as described in [Patching third-party crates]( #patching-third_party-crates). To verify the patches work, remove the vendored crate directory in `//third_party/rust/chromium_crates_io/vendor/`, named after the crate name and version. Then run the `vendor` action without `--no-patches` which will download the crate and apply the patches: * `vpython3 ./tools/crates/run_gnrt.py vendor` ## Security If a shipping library needs security review (has any `unsafe`), and the review finds it's not satisfying the [rule of 2](../docs/security/rule-of-2.md), then move it to the `"sandbox"` group in `//third_party/rust/chromium_crates_io/gnrt_config.toml` to make it clear it can't be used in a privileged process: ``` [crate.foo] group = "sandbox" ``` If a transitive dependency moves from `"safe"` to `"sandbox"` and causes a dependency chain across the groups, it will break the `gnrt vendor` step. You will need to fix the new crate so that it's deemed safe in unsafe review, or move the other dependent crates out of `"safe"` as well by setting their group in `gnrt_config.toml`. # Updating existing third-party crates To update crates to their latest minor versions: 1. Change directory to the root `src/` dir of Chromium. 1. Update the versions in `//third_party/rust/chromium_crates_io/Cargo.toml`. * `vpython3 ./tools/crates/run_gnrt.py update` * Or, directly through (nightly) cargo: `cargo run --release --manifest-path tools/crates/gnrt/Cargo.toml --target-dir out/gnrt update` 1. Download any updated crate's files: * `./tools/crates/run_gnrt.py vendor` * If you want to restrict the update to certain crates, add the crate names as arguments to `vendor`, like: `./tools/crates/run_gnrt.py vendor <crate-name>` * Or, directly through (nightly) cargo: `cargo run --release --manifest-path tools/crates/gnrt/Cargo.toml --target-dir out/gnrt vendor` 1. Add the downloaded files to git: * `git add -f third_party/rust/chromium_crates_io/vendor` * The `-f` is important, as files may be skipped otherwise from a `.gitignore` inside the crate. 1. If a crate in `//third_party/rust/chromium_crates_io/patches` was updated as part of vendoring, then reapply patches to it: * Go to the `//third_party/rust/chromium_crates_io` directory. * `./apply_patches.sh` (this currently requires linux). 1. Generate the `BUILD.gn` files * `vpython3 ./tools/crates/run_gnrt.py gen` * Or, directly through (nightly) cargo: `cargo run --release --manifest-path tools/crates/gnrt/Cargo.toml --target-dir out/gnrt gen` 1. Add the generated files to git: * `git add -f third_party/rust` ### Directory structure for third-party crates The directory structure for a crate "foo" version 3.4.2 is: ``` //third_party/ rust/ foo/ (for the "foo" crate) v3/ (version 3.4.2 maps to the v3 epoch) BUILD.gn (generated by gnrt gen) README.chromium (generated by gnrt vendor) chromium_crates_io/ vendor/ foo-3.4.2 (crate sources downloaded from crates.io) patches/ foo/ (patches for the "foo" crate) 0001-Edit-the-Cargo-toml.diff 0002-Other-changes.diff Cargo.toml Cargo.lock gnrt_config.toml ``` ## Writing a wrapper for binding generation Most Rust libraries will need a more C++-friendly API written on top of them in order to generate C++ bindings to them. The wrapper library can be placed in `//third_party/rust/<cratename>/<epoch>/wrapper` or at another single place that all C++ goes through to access the library. The [CXX](https://cxx.rs) is used to generate bindings between C++ and Rust. See [`//third_party/rust/serde_json_lenient/v0_1/wrapper/`]( https://source.chromium.org/chromium/chromium/src/+/main:third_party/rust/serde_json_lenient/v0_1/wrapper/) and [`//components/qr_code_generator`]( https://source.chromium.org/chromium/chromium/src/+/main:components/qr_code_generator/;l=1;drc=b185db5d502d4995627e09d62c6934590031a5f2) for examples. Rust libraries should use the [`rust_static_library`]( https://source.chromium.org/chromium/chromium/src/+/main:build/rust/rust_static_library.gni) GN template (not the built-in `rust_library`) to integrate properly into the mixed-language Chromium build and get the correct compiler options applied to them. The [CXX](https://cxx.rs) tool is used for generating C++ bindings to Rust code. Since it requires explicit declarations in Rust, an wrapper shim around a pure Rust library is needed. Add these Rust shims that contain the CXX `bridge` macro to the `cxx_bindings` GN variable in the `rust_static_library` to have CXX generate a C++ header for that file. To include the C++ header file, rooted in the `gen` output directory, use ``` #include "the/path/to/the/rust/file.rs.h" ``` # Debugging hints There are not yet Rust wrappers for Chromium's base logging APIs. We recommend use of Rust's standard [`eprintln`](https://doc.rust-lang.org/std/macro.eprintln.html) and [`dbg`](https://doc.rust-lang.org/std/macro.dbg.html) macros. # Using VSCode 1. Ensure you're using the `rust-analyzer` extension for VSCode, rather than earlier forms of Rust support. 2. Run `gn` with the `--export-rust-project` flag, such as: `gn gen out/Release --export-rust-project`. 3. `ln -s out/Release/rust-project.json rust-project.json` 4. When you run VSCode, or any other IDE that uses [rust-analyzer](https://rust-analyzer.github.io/) it should detect the `rust-project.json` and use this to give you rich browsing, autocompletion, type annotations etc. for all the Rust within the Chromium codebase. 5. Point rust-analyzer to the rust toolchain in Chromium. Otherwise you will need to install Rustc in your system, and Chromium uses the nightly compiler, so you would need that to match. Add the following to `.vscode/settings.json` in the Chromium checkout: ``` { // The rest of the settings... "rust-analyzer.cargo.extraEnv": { "PATH": "../../third_party/rust-toolchain/bin:$PATH", } } ``` This assumes you are working with an output directory like `out/Debug` which has two levels; adjust the number of `..` in the path according to your own setup. # Using cargo If you are building a throwaway or experimental tool, you might like to use pure `cargo` tooling rather than `gn` and `ninja`. Even then, you may choose to restrict yourself to the toolchain and crates that are already approved for use in Chromium. Here's how. ``` export PATH_TO_CHROMIUM_SRC=~/chromium/src mkdir my-rust-tool cd my-rust-tool mkdir .cargo cat <<END > .cargo/config.toml [source.crates-io] replace-with = "vendored-sources" [source.vendored-sources] directory = "$PATH_TO_CHROMIUM_SRC/third_party/rust/chromium_crates_io/vendor" END $PATH_TO_CHROMIUM_SRC/third_party/rust-toolchain/bin/cargo init --offline $PATH_TO_CHROMIUM_SRC/third_party/rust-toolchain/bin/cargo run --offline ``` Most `cargo` tooling works well with this setup; one exception is `cargo add`, but you can still add dependencies manually to your `Cargo.toml`: ``` [dependencies] log = "0.4" ```