| # 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/vendor` |
| * 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`: |
| * To install and use `cargo vet`, you need nightly cargo in your `PATH`, which can be |
| found in `//third_party/rust-toolchain/bin` (except [on Mac Arm](https://crbug.com/1515913), |
| where it will need to be installed separately with |
| [`rustup install nightly`](https://rustup.rs/)). |
| * `cargo install --git https://github.com/mozilla/cargo-vet cargo-vet` |
| * We use `--git` to install cargo-vet from HEAD in order to use the `--cargo-arg` argument |
| which is not released yet. |
| * `cargo -Zunstable-options -C third_party/rust/chromium_crates_io/ vet check --cargo-arg=-Zbindeps --no-registry-suggestions` |
| * 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 `audit.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.lock`. |
| * `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` |
| * 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" |
| ``` |
| |
| # 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" |
| ``` |