8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

「このプログラム、信用していいの?」

外部データを処理するプログラム(画像処理、圧縮/解凍、パーサーなど)は脆弱性の温床。

FreeBSDのCapsicumを使えば、プログラムの権限を最小限に制限できる。

Capsicumとは

┌─────────────────────────────────────────────────────────────┐
│                  Capability Mode                             │
│                                                              │
│  cap_enter() を呼ぶと...                                    │
│                                                              │
│  ✗ 新しいファイルを開けない                                 │
│  ✗ ネットワーク接続できない                                 │
│  ✗ 新しいプロセスを作れない                                 │
│  ✗ カーネルモジュールをロードできない                       │
│                                                              │
│  ✓ 既に開いているファイルは使える                          │
│  ✓ 許可された操作のみ可能(capability)                    │
└─────────────────────────────────────────────────────────────┘

Capability-based securityの実装。2010年にケンブリッジ大学とFreeBSD財団が開発。

基本的な使い方

最小のサンドボックス

#include <stdio.h>
#include <stdlib.h>
#include <sys/capsicum.h>
#include <errno.h>

int main(void)
{
    FILE *fp;

    /* ファイルを開く(capability modeに入る前に!) */
    fp = fopen("/tmp/test.txt", "r");
    if (fp == NULL) {
        perror("fopen");
        return 1;
    }

    /* Capability Modeに入る */
    if (cap_enter() < 0) {
        perror("cap_enter");
        return 1;
    }

    printf("Now in capability mode!\n");

    /* 既に開いているファイルは読める */
    char buf[256];
    if (fgets(buf, sizeof(buf), fp) != NULL) {
        printf("Read: %s", buf);
    }

    fclose(fp);

    /* 新しいファイルを開こうとすると失敗 */
    FILE *fp2 = fopen("/etc/passwd", "r");
    if (fp2 == NULL) {
        printf("Cannot open new file: %s\n", strerror(errno));
        /* ECAPMODE: Not permitted in capability mode */
    }

    return 0;
}
cc -o sandbox sandbox.c
./sandbox
# Now in capability mode!
# Read: Hello from test.txt
# Cannot open new file: Not permitted in capability mode

Capability Rights

ファイルディスクリプタごとに許可する操作を指定できる。

#include <sys/capsicum.h>

int main(void)
{
    int fd = open("/tmp/data.txt", O_RDWR);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    /* 読み取りのみ許可 */
    cap_rights_t rights;
    cap_rights_init(&rights, CAP_READ);

    if (cap_rights_limit(fd, &rights) < 0) {
        perror("cap_rights_limit");
        return 1;
    }

    /* Capability Modeに入る */
    cap_enter();

    /* 読み取りはOK */
    char buf[256];
    read(fd, buf, sizeof(buf));  /* 成功 */

    /* 書き込みは失敗 */
    write(fd, "test", 4);  /* ENOTCAPABLE */

    return 0;
}

主なCapability Rights

CAP_READ        // 読み取り
CAP_WRITE       // 書き込み
CAP_SEEK        // シーク
CAP_MMAP        // mmap
CAP_FSTAT       // fstat
CAP_FCHMOD      // fchmod
CAP_FCHOWN      // fchown
CAP_FCNTL       // fcntl
CAP_IOCTL       // ioctl
CAP_ACCEPT      // accept(ソケット)
CAP_CONNECT     // connect(ソケット)
CAP_BIND        // bind(ソケット)
CAP_LISTEN      // listen(ソケット)
CAP_SOCK_CLIENT // クライアントソケット操作一式
CAP_SOCK_SERVER // サーバーソケット操作一式

実用例:画像処理プログラム

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/capsicum.h>

void process_image(int in_fd, int out_fd)
{
    /* 画像処理のコード */
    char buf[4096];
    ssize_t n;

    while ((n = read(in_fd, buf, sizeof(buf))) > 0) {
        /* 何か処理 */
        write(out_fd, buf, n);
    }
}

int main(int argc, char *argv[])
{
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <input> <output>\n", argv[0]);
        return 1;
    }

    /* ファイルを開く(Capability Mode前) */
    int in_fd = open(argv[1], O_RDONLY);
    int out_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);

    if (in_fd < 0 || out_fd < 0) {
        perror("open");
        return 1;
    }

    /* Capabilityを制限 */
    cap_rights_t in_rights, out_rights;
    cap_rights_init(&in_rights, CAP_READ);
    cap_rights_init(&out_rights, CAP_WRITE);

    cap_rights_limit(in_fd, &in_rights);
    cap_rights_limit(out_fd, &out_rights);

    /* サンドボックスに入る */
    if (cap_enter() < 0) {
        perror("cap_enter");
        return 1;
    }

    /* ここからは安全!
       - 他のファイルにアクセスできない
       - ネットワークにアクセスできない
       - 新しいプロセスを作れない
       脆弱性があっても被害を最小化できる */

    process_image(in_fd, out_fd);

    close(in_fd);
    close(out_fd);

    return 0;
}

Casper - 特権分離デーモン

一部の操作(DNS解決など)はサンドボックス内でもしたい。Casperを使う。

#include <sys/capsicum.h>
#include <libcasper.h>
#include <casper/cap_dns.h>
#include <netdb.h>

int main(void)
{
    cap_channel_t *capcas, *capdns;
    struct hostent *hp;

    /* Casperに接続 */
    capcas = cap_init();
    if (capcas == NULL) {
        perror("cap_init");
        return 1;
    }

    /* DNS解決用のCapability */
    capdns = cap_service_open(capcas, "system.dns");
    if (capdns == NULL) {
        perror("cap_service_open");
        return 1;
    }
    cap_close(capcas);

    /* 許可するDNSクエリを制限 */
    const char *types[] = { "ADDR" };
    if (cap_dns_type_limit(capdns, types, 1) < 0) {
        perror("cap_dns_type_limit");
        return 1;
    }

    /* Capability Modeに入る */
    if (cap_enter() < 0) {
        perror("cap_enter");
        return 1;
    }

    /* サンドボックス内でもDNS解決可能! */
    hp = cap_gethostbyname(capdns, "www.freebsd.org");
    if (hp != NULL) {
        printf("Resolved: %s\n", hp->h_name);
    }

    cap_close(capdns);
    return 0;
}

コンパイル:

cc -o dns_example dns_example.c -lcasper -lcap_dns

既存プログラムのCapsum対応

多くのFreeBSD標準ツールはすでにCapsicum対応済み。

# Capsicumを使っているプログラムの例
# - cat
# - head
# - tail
# - gzip
# - bzip2
# - uniq
# - wc
# - tcpdump
# - ping

確認方法:

# procstatで確認
procstat -S `pgrep cat`
#   PID COMM             C:E
# 12345 cat              C:+
# C:+ = Capability Mode有効

ライブラリレベルの対応

libarchive

#include <archive.h>
#include <archive_entry.h>
#include <sys/capsicum.h>

int main(int argc, char *argv[])
{
    struct archive *a;
    int fd;

    /* アーカイブファイルを開く */
    fd = open(argv[1], O_RDONLY);

    /* Capability Modeに入る */
    cap_enter();

    /* libarchiveはCapsicum対応済み */
    a = archive_read_new();
    archive_read_support_format_all(a);
    archive_read_support_filter_all(a);
    archive_read_open_fd(a, fd, 10240);

    /* 安全にアーカイブを処理 */
    /* ... */

    archive_read_close(a);
    archive_read_free(a);

    return 0;
}

デバッグ

プロセスの状態確認

# Capability Modeかどうか確認
procstat -S $$
#   PID COMM             C:E
#  1234 bash             C:-
# C:- = 通常モード
# C:+ = Capability Mode

権限エラーの確認

# ktrace でシステムコールを追跡
ktrace -t c ./sandbox
kdump | grep -E 'ENOTCAPABLE|ECAPMODE'

CAP_SYSCTL_WRITEの例

/* sysctl読み取りを許可 */
cap_rights_t rights;
cap_rights_init(&rights, CAP_READ, CAP_SYSCTL_READ);

ベストプラクティス

1. 早めにサンドボックス化

int main(int argc, char *argv[])
{
    /* 1. 必要なリソースを全て取得 */
    int fd = open(argv[1], O_RDONLY);
    /* 設定ファイル読み込み、ネットワーク接続など */

    /* 2. Capabilityを制限 */
    cap_rights_t rights;
    cap_rights_init(&rights, CAP_READ);
    cap_rights_limit(fd, &rights);

    /* 3. できるだけ早くCapability Modeに入る */
    cap_enter();

    /* 4. 信頼できない入力を処理 */
    process_untrusted_input(fd);

    return 0;
}

2. 最小権限の原則

/* 悪い例:全部許可 */
cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_MMAP, ...);

/* 良い例:必要最小限 */
cap_rights_init(&rights, CAP_READ);  /* 読み取りだけ必要 */

3. Casperで特権操作を分離

┌─────────────────────────────────────────────────────────────┐
│                 Capsicumアーキテクチャ                       │
│                                                              │
│  ┌────────────────┐         ┌────────────────┐             │
│  │  メインプロセス  │ IPC ←→  │    Casper      │             │
│  │  (sandbox)     │         │  (特権操作)    │             │
│  │                │         │  DNS解決       │             │
│  │  - 入力処理    │         │  乱数生成      │             │
│  │  - 出力生成    │         │  パスワード取得 │             │
│  └────────────────┘         └────────────────┘             │
└─────────────────────────────────────────────────────────────┘

まとめ

Capsicumは:

  • cap_enter() でサンドボックスに入る
  • 事前に開いたファイルしか使えない
  • Capability Rightsで操作を制限
  • Casperで特権操作を分離
  • 脆弱性があっても被害を最小化

「信頼できない入力を処理するなら、Capsicumを使え」

FreeBSDの標準ツール(gzip, tar等)も使ってる。

この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!

8
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?