Rustの依存関係の信頼性を検証する (crev)

## cargo-crevとは

最近、複数のRubyライブラリに[バックドアが発見されました](https://srad.jp/submission/83670/)。このような事件は[node/npm](https://opensource.srad.jp/story/18/11/29/0637229/)や[Python/PyPI](https://japan.zdnet.com/article/35140047/)などでも発生しています。これらは正当な作者のアカウントがハックされてアップロードされているケースや、はじめから悪意のある作者が公開しているケースなどがありますが、いずれのケースでも公開されているライブラリバージョンの安全性が検証されていないことが問題であるといえます。

これに対してRustのcrates.ioに関してもコミュニティーから懸念が表明されています ([URLO18860](https://users.rust-lang.org/t/are-rust-crates-secure/18860), [URLO29989](https://users.rust-lang.org/t/what-about-crate-injections/29989)) が、その中でdpc氏が提案しているのが分散コードレビューによる信頼性の担保です。これを実際に実装したのが[cargo-crev](https://github.com/crev-dev/cargo-crev)というツールです。

(なお、RustSecチームが[cargo-audit](https://github.com/RustSec/cargo-audit)というツールを公開していますが、これは既知の脆弱性データベースに基づいて脆弱性を調査するもので、悪意あるライブラリを事前に検出する能力はないと思います。もちろん、すでに発覚している悪意あるバックドアは脆弱性データベースに含まれるかもしれません。)

本ツールはコミュニティーに十分な数のレビュアーがいることが前提のエコシステムであり、うまくいくかは現時点では定かではありません。しかし試してみる価値はあると思います。

以下は[cargo-crevのgetting_started.md](https://github.com/crev-dev/cargo-crev/blob/cargo-crev-v0.8.0/cargo-crev/src/doc/getting_started.md) ([最新版はここ](https://github.com/dpc/crev/blob/master/cargo-crev/src/doc/getting_started.md))をもとに試してみたものです。

## cargo-crevのインストール

`cargo install cargo-crev` でも入りますが、[コンパイル済みバイナリ](https://github.com/crev-dev/cargo-crev/releases)を自前で入れるのが手っ取り早いです。

```console
# 記事執筆時点での最新版 (v0.8.0) のx86_64-linux-musl用バイナリ
wget https://github.com/dpc/crev/releases/download/cargo-crev-v0.8.0/cargo-crev-v0.8.0-x86_64-unknown-linux-musl.tar.gz
tar xvf cargo-crev-v0.8.0-x86_64-unknown-linux-musl.tar.gz
mv cargo-crev-v0.8.0-x86_64-unknown-linux-musl/cargo-crev ~/.cargo/bin/
rm -r cargo-crev-v0.8.0-x86_64-unknown-linux-musl*
```

## 既存レビューに対して検証する

cargo-crevは分散レビューシステムです。あなたの判断で、誰のレビューを信頼するか決める必要があります。[Google等でcrev-proofsと検索](https://www.google.co.jp/search?q=crev-proofs&ie=UTF-8)すると、公開されているレビューデータを探すことができます。また、crevのWikiに[誰でも追加できるレビュアー一覧ページ](https://github.com/crev-dev/cargo-crev/wiki/List-of-Proof-Repositories)があります。

ここではcargo-crev作者自身によるレビューを入れてみます。

```console
$ cargo crev fetch url https://github.com/dpc/crev-proofs
```

複数のレビューによって検証されることが前提のシステムですが、dpc氏は他の何人かのレビュアーを推移的に信頼しているので、これである程度のレビューが得られます。 (とはいえ、現実のコードの信頼性を担保するには数は全く足りません)

ここで、依存関係を検証したいプログラムのリポジトリに移動して以下のように実行します。

```console
$ cd path/to/your/repo
$ cargo crev verify
status reviews     downloads    own. issues lines  geiger flgs crate                version         latest_t       
none    0  0   409422   3256532 1/2    0/0  14384     329      rayon                1.1.0           
none    0  0  5548530  10466852 2/6    0/0    514       0      unicode-xid          0.1.0           
none    0  0   205077   3346217 1/2    0/0    341       0 CB   rand_chacha          0.2.0           
none    0  0  4531202   4836081 0/1    0/0   1040       0      semver-parser        0.7.0           
none    0  0  1960554   3463301 0/1    0/0   1481     187      lock_api             0.1.5           
none    0  0    73428    118720 0/2    0/0     70       0      actix-threadpool     0.1.1           
none    0  0   495748    495809 0/3    0/0     90       0      http-body            0.1.0           
none    0  2    87273    889625 0/1    0/0  14112       0      trust-dns-proto      0.7.4           
none    0  0  1439379   2840204 1/1    0/0   1464     154      miow                 0.2.1           
none    0  0  2518557   4449315 3/3    0/0   1570     359      net2                 0.2.33          
none    0  0   381246   4810599 0/1    0/0   2845     349 CB   parking_lot          0.8.0           
none    0  0    41920    430748 0/3    0/0    568       0      dotenv               0.14.1          
none    0  0   125348    573958 0/4    0/0   7196       0      aster                0.41.0          
none    0  0   413269   1003049 0/3    0/0    278       0      lru-cache            0.1.2           
none    0  0  3225725   3830376 0/1    0/0    275      75      iovec                0.1.2           
none    0  0   249256    637805 0/1    0/0    850      59      cexpr                0.2.3           
none    0  0  1232739   2134119 0/1    0/0    218       0      block-buffer         0.3.3           
(略)
none    0  0   486448   1748896 0/2    0/0    796       2 CB   core-foundation-sys  0.6.2           
none    0  0   203174   1875924 0/4    0/0  27793      37      syntex_syntax        0.58.1          
none    0  0  2318532   8372160 2/3    0/0    516      41      rand_core            0.3.1           
none    0  0    71090    751351 0/3    0/0   6721       4      deflate              0.7.20          
none    1  1  2485664   4939200 0/1    0/0    126      11      nodrop               0.1.13          
none    0  0  3205654  15956485 3/3    0/0    643       0      bitflags             0.9.1           
```

各列は以下のような意味のようです。

- digest: 検証対象クレートのハッシュ値。 (`-v` 指定時)
- status: 検証結果。none=十分なレビューがない・warn=注意あり・pass=十分なレビューがあった
- reviews:
  - 左: このバージョンに対するレビューの個数。
  - 右: このクレートの全てのバージョンに対するレビューの個数。
- downloads:
  - 左: このバージョンのダウンロード数
  - 右: このクレートの全てのバージョンのダウンロード数
- own.:
  - 左: 信頼できるオーナーの数
  - 右: オーナーの数
- issues:
  - 左: 信頼できるレビュアーからの注意情報の個数
  - 右: 注意情報の個数
- lines: クレートの行数 (CLOC)
- geiger: クレート中のunsafeコードの行数
- flags: 特殊なパッケージである場合
  - "CB": カスタムビルドスクリプト (`build.rs`) が使われている
- crate: クレートの名前
- version: クレートのバージョン
- latest_t (latest_trusted_version): 一番直近で、十分なレビューがあるバージョン

左端のステータスを全て "pass" にするのが我々の目標ということになります。これをpassにするための基準 (信頼度の算出方法) はある程度カスタマイズできるようなので、気になる人は `cargo crev verify --help` を見てみてください。)

## 自分でレビューする

信頼できるレビューがなければ、自分でレビューすればよいわけです。それを公開することで、コミュニティーへの貢献にもなります。

## リポジトリ作成

GitHubに `crev-proofs` リポジトリを作ります。masterブランチを作っておきます。

```console
$ mkdir crev-proofs && cd crev-proofs
$ git init
Initialized empty Git repository in .../crev-proofs/.git/
$ git commit --allow-empty -m "Initial commit"
[master (root-commit) 4f6e0bd] Initial commit
$ git push -u origin master
Counting objects: 2, done.
Writing objects: 100% (2/2), 169 bytes | 169.00 KiB/s, done.
Total 2 (delta 0), reused 0 (delta 0)
To github.com:qnighy/crev-proofs.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
$ hub create
Updating origin
created repository: qnighy/crev-proofs
```

以下、cargo-crevはこのリポジトリの複製を `$HOME/.config/crev/proofs` 以下に作って自動的にそこで作業します。今作った `crev-proofs` ディレクトリは消してしまって大丈夫です。

## CrevID作成

`cargo crev new id --url` コマンドでCrevIDを使えます。慣習にそったリポジトリ名の場合は `--github-username` オプションが使えます。

```console
$ cargo crev new id --github-username qnighy
https://github.com/qnighy/crev-proofs cloned to $HOME/.config/crev/proofs/******
CrevID will be protected by a passphrase.
There's no way to recover your CrevID if you forget your passphrase.
Enter new passphrase: ******
Enter new passphrase again: ******
Your CrevID was created and will be printed below in an encrypted form.
Make sure to back it up on another device, to prevent loosing it.

---
version: -1
url: "https://github.com/qnighy/crev-proofs"
public-key: ******
sealed-secret-key: ******
seal-nonce: ******
pass:
  version: 19
  variant: argon2id
  iterations: 192
  memory-size: 4096
  lanes: 4
  salt: ******
```

これで、自分のレビューを入れるためのバケットのようなものが作られました。このID (CrevID) は以下のようにして確認できます。

```console
$ cargo crev query id current
fkkJ3Z6xSDEZwFlyKgIDxYS2x6mS7b2cxbWBeEWgfXs https://github.com/qnighy/crev-proofs
```

## 推移的な信頼の表明

他のレビュアーに対する信頼を表明することができます。たとえば以下のようにするとdpc氏を信頼できます。

```console
$ cargo crev trust FYlr8YoYGVvDwHQxqEIs89reKKDy-oWisoO0qXXEfHE
```

「どれくらい信頼するか」「信頼する理由等のコメント」などの情報を記入して保存します。(信頼度は、たとえば推移的な信頼をどこまで辿るかの判定に使われます。)

`cargo crev query id trusted` で、この信頼をもとに推移的に信頼できるユーザーのリストを出すことができます。これらのユーザーのレビューを、 `cargo crev fetch trusted` で取得できます。

## レビュー作業の結果を公開する

まだ具体的なレビューはしていませんが、少し作業をしたので保存方法を確認しておきます。

自分のレビュー作業の結果は以下のようにしてpushできます。これは `cargo crev git push HEAD` の省略コマンドで、さらに言うとこれは `$HOME/.config/crev/proofs/${CREV_ID}` 以下に移動して `git` を実行するのと同じことです。

```console
$ cargo crev push
```

## 実際にレビューしてみる

もう一度、Rustコードのあるリポジトリで `cargo crev verify` をしてみます。「passになっていない」かつ「行数の少ないコード」を選んでレビューしてみましょう。今回ぼくは、以前パッチを投げたことがあるsignal-hook-registryを対象にレビューしてみたいと思います。

`cargo crev goto` または `cargo crev open` を使うことで、当該クレートのソースを閲覧することができます。前者はシェルが立ち上がり、後者は指定したプログラムが立ち上がります。

```console
# VSCodeでsignal-hook-registryのソースを見る
$ cargo crev open signal-hook-registry --cmd "code --wait -n" --cmd-save
```

(~~ただ、IDEで開いた場合、 `$HOME/.cargo/registry` 以下をrlsの出力で汚すことになります。これが果たしてOKなのかどうかはよくわからない……~~ <ins>←おそらく終了後に `.crate` ファイルから再展開するか何かしているので大丈夫そうです。</ins>)

クレートを一通りチェックしたら、以下のコマンドでレビューを作成します。

```console
# signal-hook-registryに対するレビューを作成する
$ cargo crev review signal-hook-registry
```

レビューファイルには以下のような属性が含められます。もちろん、すべて自己申告です。

- レビューの網羅性 (どれくらいしっかりソースを見たか)
- 理解度 (プログラムの動作をどれくらい理解してレビューしたか)
- レーティング
- アドバイザリ (当該バージョンには、今後修正される問題がある)
- イシュー (より一般的に何かの問題について記録する場所)
- コメント

レビューを書いたら、再び `cargo crev push` によって作業結果を公開できます。

## 何をレビューするべきか

cargo-crevは「何をレビューするべきか」については意図的に各レビュアーにある程度の判断を委ねているようです。crevの主な動機から考えると、第一には悪意あるコードが混入されていないかを判断した上で、悪意によらないセキュリティー上の問題などをついでに検証するのがよいのではないかと思います。

## まとめ

分散レビューのエコシステムがうまくいくかは現時点では定かではありませんが、少なくともそのベースとなるツール群は(計画の初期段階としては)思いのほか良くできていました。もちろん、まだ使い勝手に改善の余地はあるので、その部分は今後に期待します。

この試みによって、Rustがより広い意味での「安全な言語」に近づくことができるのではないかと思います。このエコシステムが成功するにはより多くの人の強力が必要なので、志あるRustプログラマーの皆さんにも、ぜひ一度は試してみてほしいと思いました。