RustでLinuxのケーパビリティを操作する

この記事は[Rustその2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/rust2)の14日目の記事です。

本記事ではRustでLinuxのケーパビリティを操作する方法を紹介します。

# ケーパビリティとは
ケーパビリティはLinuxのスーパーユーザー（rootなど）がもっている権限をいくつかのグループに分割したものです。Linuxでは`setuid`などで特権が必要な操作に対してスーパーユーザーと同様の権限を与える事も可能ですが、それでは操作に必要な権限だけでなくスーパーユーザーがもつ権限を全て与える事になるので、必要以上の権限を与える事になってしまいます。
そういった時にケーパビリティを使えば全ての権限を与えるのではなく、操作に必要な権限のみを渡す事ができます。

詳細を知りたい方は[capabilities(7)](http://man7.org/linux/man-pages/man7/capabilities.7.html)などで確認してもらえればと思います。

# 環境
- OS: Ubuntu 16.04（kernel 4.4.0）
- Rust: 1.38.0

# Rustでケーパビリティの操作
今回、Rustでケーパビリティを操作するに当たって[caps-rs](https://github.com/lucab/caps-rs)という`crate`を使います。あとcapabilityの操作には関係しませんが、Linuxの操作をするにあたって便利な[nix-rust](https://github.com/nix-rust/nix)という`crate`も同じく使います。

```Cargo.toml
[dependencies]
caps = "0.3.3"
nix = "0.16.0"
```

前提として以下に記載する操作は基本的に一般ユーザーで行います。

## ケーパビリティの確認
まず、自分自身のケーパビリティを確認してみます。

```Rust:main.rs
extern crate caps;

use caps::CapSet;

fn main() {
    let cur = caps::read(None, CapSet::Permitted).unwrap();
    println!("-> Current permitted caps: {:?}.", cur);
}
```
これは自分自身の`Permitted`と呼ばれるケーパビリティセットの内容を表示します。
基本的にはケーパビリティは表示されないはずです（もちろん環境によります）`sudo`などを使用してスーパーユーザーの権限で実行すると全てのケーパビリティが表示されます。

```shell
// 一般ユーザーで実行
$ cargo run
-> Current permitted caps: {}.

// スーパーユーザーの権限で実行（ケーパビリティが多く表示されるので一部のみ記載）
$ sudo cargo run
-> Current permitted caps: {CAP_LINUX_IMMUTABLE, CAP_SETGID, CAP_NET_BROADCAST, CAP_SYS_NICE, ...}.
```
ケーパビリティセットは`Permitted`以外にもありますが同様の方法で確認する事ができます。

## ケーパビリティの削除
`caps-rs`を使えば簡単に特定のケーパビリティを削除する事ができます。
ここではファイルの所有者とグループを変更する`chown`を例に取っていきたいと思います。
`chown`では`CAP_CHOWN`というケーパビリティが必要になります。

まず最初はケーパビリティを削除せずにRustから`chown`を実行してみます。

```Rust:main.rs
extern crate caps;
extern crate nix;

use caps::{CapSet, Capability};
use nix::unistd::{chown, Gid, Uid};

fn main() {
    let cur = caps::read(None, CapSet::Effective).unwrap();
    println!("-> Current effective caps: {:?}.", cur);

    // スーパーユーザーに所有者とグループを変更
    chown("test.txt", Some(Uid::from_raw(0)), Some(Gid::from_raw(0))).unwrap();
}
```
```
// test.txt作成
$ touch test.txt
$ ls -al
drwxrwxr-x 5 vagrant vagrant 4096 Dec 14 06:48 ./
drwxrwxr-x 6 vagrant vagrant 4096 Dec 14 01:40 ../
-rw-rw-r-- 1 vagrant vagrant 5667 Dec 14 03:33 Cargo.lock
-rw-rw-r-- 1 vagrant vagrant  252 Dec 14 03:33 Cargo.toml
drwxrwxr-x 6 vagrant vagrant 4096 Dec 14 01:40 .git/
-rw-rw-r-- 1 vagrant vagrant   19 Dec 14 01:40 .gitignore
drwxrwxr-x 2 vagrant vagrant 4096 Dec 14 07:13 src/
drwxrwxr-x 3 vagrant vagrant 4096 Dec 14 01:46 target/
-rw-rw-r-- 1 vagrant vagrant    0 Dec 14 06:48 test.txt

// 一般ユーザーで実行（エラー）
$ cargo run
-> Current permitted caps: {}.
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Sys(EPERM)', src/libcore/result.rs:1084:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

// スーパーユーザーの権限で実行
$ sudo cargo run
-> Current permitted caps: {CAP_LINUX_IMMUTABLE, CAP_SETGID, CAP_NET_BROADCAST, CAP_SYS_NICE, ...}.

$ ls -al
drwxrwxr-x 5 vagrant vagrant 4096 Dec 14 06:48 ./
drwxrwxr-x 6 vagrant vagrant 4096 Dec 14 01:40 ../
-rw-rw-r-- 1 vagrant vagrant 5667 Dec 14 03:33 Cargo.lock
-rw-rw-r-- 1 vagrant vagrant  252 Dec 14 03:33 Cargo.toml
drwxrwxr-x 6 vagrant vagrant 4096 Dec 14 01:40 .git/
-rw-rw-r-- 1 vagrant vagrant   19 Dec 14 01:40 .gitignore
drwxrwxr-x 2 vagrant vagrant 4096 Dec 14 07:13 src/
drwxrwxr-x 3 vagrant vagrant 4096 Dec 14 01:46 target/
-rw-rw-r-- 1 root    root       0 Dec 14 06:48 test.txt
```
`test.txt`を配置して`sudo cargo run`で実行すると`text.txt`の所有者とグループがスーパーユーザー（root）に変わります。
（`cargo run`で一般ユーザーの権限で実行すると`CAP_CHOWN`のケーパービリティを持っていないので権限がなくエラーとなります）

次に`CAP_CHOWN`の権限を削除した上で`chown`を実行させてみます。

```Rust:main.rs
extern crate caps;
extern crate nix;

use caps::{CapSet, Capability};
use nix::unistd::{chown, Gid, Uid};

fn main() {
    let cur = caps::read(None, CapSet::Effective).unwrap();
    println!("-> Current effective caps: {:?}.", cur);

    // CAP_CHOWNをEffectiveケーパビリティセットから削除
    caps::drop(None, CapSet::Effective, Capability::CAP_CHOWN).unwrap();
    let cur = caps::read(None, CapSet::Effective).unwrap();
    println!("-> Current effective caps: {:?}.", cur);

    // スーパーユーザーに所有者とグループを変更
    chown("test.txt", Some(Uid::from_raw(0)), Some(Gid::from_raw(0))).unwrap();
}
```
```
// test.txtを作り直し
$ sudo rm test.txt
$ touch test.txt
$ ls -al
drwxrwxr-x 5 vagrant vagrant 4096 Dec 14 07:20 ./
drwxrwxr-x 6 vagrant vagrant 4096 Dec 14 01:40 ../
-rw-rw-r-- 1 vagrant vagrant 5667 Dec 14 03:33 Cargo.lock
-rw-rw-r-- 1 vagrant vagrant  252 Dec 14 03:33 Cargo.toml
drwxrwxr-x 6 vagrant vagrant 4096 Dec 14 01:40 .git/
-rw-rw-r-- 1 vagrant vagrant   19 Dec 14 01:40 .gitignore
drwxrwxr-x 2 vagrant vagrant 4096 Dec 14 07:19 src/
drwxrwxr-x 3 vagrant vagrant 4096 Dec 14 01:46 target/
-rw-rw-r-- 1 vagrant vagrant    0 Dec 14 07:20 test.txt

// 一般ユーザーで実行（エラー）
$ cargo run
-> Current effective caps: {}.
-> Current effective caps: {}.
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Sys(ENOENT)', src/libcore/result.rs:1084:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

// スーパーユーザーの権限で実行（エラー）
$ sudo cargo run
-> Current effective caps: {CAP_LINUX_IMMUTABLE, CAP_SETGID, CAP_NET_BROADCAST, CAP_SYS_NICE, ...}.
-> Current effective caps: {CAP_LINUX_IMMUTABLE, CAP_SETGID, CAP_NET_BROADCAST, CAP_SYS_NICE, ...}.
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Sys(ENOENT)', src/libcore/result.rs:1084:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
```
`chown`が実行できなくなった事により`CAP_CHOWN`が削除できていることが確認できました。スーパーユーザーでもケーパビリティが削除されればこのように権限がなく実行に失敗します。

# まとめ
`caps-rs`を使用すればRustでケーパビリティの確認・削除といった操作を簡単に実行する事ができます。上記以外にもケーパビリティをセットする・特定のケーパビリティが存在するか確認する、といった事も可能ですので詳しく知りたい方はドキュメントを確認してもらえればと思います。

# 参考
- [capabilities(7)](http://man7.org/linux/man-pages/man7/capabilities.7.html)
- [LXCで学ぶコンテナ入門 －軽量仮想化環境を実現する技術 第42回　Linuxカーネルのケーパビリティ［1］](https://gihyo.jp/admin/serial/01/linux_containers/0042)