はじめに
「このプログラム、信用していいの?」
外部データを処理するプログラム(画像処理、圧縮/解凍、パーサーなど)は脆弱性の温床。
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等)も使ってる。
この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!