Edited at

FreeBSDのCapsicumとは(その1)

More than 3 years have passed since last update.


大変お久しぶりです

自宅でソースをなかなか読めず、ストレス溜まりまくりのakachochinです。

とっくに正月過ぎましたが、今年もよろしくお願いします。

さて、私は「The Design and Implementation of the FreeBSD Operating System (2nd Edition)」を帰りの電車の中で読んでいます。

どの章も興味深いのですが、その中でも仕事でほとんど触れることのないセキュリティ分野はとりわけ新鮮です。

さて、そうした知識に触れると、ソースを読んでみたくなります。

※ソースコードリーディングの対象は、FreeBSDのCurrentです。


Capsicumとは

CapsicumはFreeBSDのセキュリティ機能の一つで、プロセスが自分とその子供達に自ら縛りをかける機能です。

厳密さに欠ける表現をあえてするなら、「自分とその子プロセスは、指定したファイルに対して何ができるか」という”縛り”を設定し、その後に「ケーパビリティモード」というモードに遷移することで該当ファイルに対するアクセスを制約する機能です。

これによって、脆弱性を突かれたりすることによる、意図しないファイルアクセスを防ぎます。

Capsicumについては、日本語で読める後藤大地氏の良記事がものすごく参考になります。

Capsicumはフレームワークです。よって、コマンドとかでなく個別具体的に実装を加える必要があります。どのようなものか見るため、早速、後藤氏の記事からサンプルソースを抜粋します。

cap_new()でケーパビリティの設定(取得)をして、cap_enter()で該当ケーパビリティを有効にする。ものすごくシンプルですね。


(後藤氏の記事より一部引用)

    fd = open("COPYRIGHT", O_RDWR);

if (-1 == fd)
err(EX_NOPERM, "open error: %d", errno);

/*
* このプロセスおよびその子プロセスは、openしたファイル
* (COPYRIGHT)に対して「READ」と「SEEK」しか許さない。
* そのようなケーパビリティを設定する。
*/

cap = cap_new(dup(fd), CAP_READ | CAP_SEEK);
if (-1 == cap)
err(EX_NOPERM, "cap_new(1) error: %d", errno);

close(fd);

/* cap_newで設定したケーパビリティを有効にする */
cap_enter();



cap_enter()を読む

ここまで見終わったところで、ソースを読んでみましょう。

manによると、cap_enter()はlibcの関数です。lib/の下で検索したが、直接の実装は見当たりません。おそらくビルド時に自動的にシステムコール呼び出しコードが生成される類と推定し、sys/の下を検索します。


sys/kern/sys_capability.c

int

sys_cap_enter(struct thread *td, struct cap_enter_args *uap)
{
struct ucred *newcred, *oldcred;
struct proc *p;

if (IN_CAPABILITY_MODE(td))
return (0);

newcred = crget();
p = td->td_proc;
PROC_LOCK(p);
oldcred = crcopysafe(p, newcred);
newcred->cr_flags |= CRED_FLAG_CAPMODE;
p->p_ucred = newcred;
PROC_UNLOCK(p);
crfree(oldcred);
return (0);
}


至ってシンプルな実装ですが、いくつかのことに気付かされます。

(1)書き換わるのはプロセス構造体のucred構造体の中身。このことから、スレッドでなく、プロセスに対するセキュリティ機能だと言えます。

ちなみに、ucredは名称から、user credentialを表現するものと思われます。

(2)ucred構造体は、上書きでなく、新規なものを新たに割り当てた上でパラメータを変更しています。普通に考えると面倒です。おそらくセキュリティ上の理由でしょうか。

次に、crget()を眺めます。

単純にucred構造体をmalloc()で割り当てているだけです。ちなみに、malloc()にM_CREDという割り当て領域のタイプを渡していますが、これによってmalloc()側で特殊な処理や保護をしてくれるわけではありません。


sys/kern/kern_prot.c

/*

* Allocate a zeroed cred structure.
*/

struct ucred *
crget(void)
{
register struct ucred *cr;

cr = malloc(sizeof(*cr), M_CRED, M_WAITOK | M_ZERO);
refcount_init(&cr->cr_ref, 1);
#ifdef AUDIT
audit_cred_init(cr);
#endif
#ifdef MAC
mac_cred_init(cr);
#endif
cr->cr_groups = cr->cr_smallgroups;
cr->cr_agroups =
sizeof(cr->cr_smallgroups) / sizeof(cr->cr_smallgroups[0]);
return (cr);
}


次に、crcopysafeを見ます。


sys/kern/kern_prot.c

struct ucred *

crcopysafe(struct proc *p, struct ucred *cr)
{
struct ucred *oldcred;
int groups;

PROC_LOCK_ASSERT(p, MA_OWNED);

oldcred = p->p_ucred;
while (cr->cr_agroups < oldcred->cr_agroups) {
groups = oldcred->cr_agroups;
PROC_UNLOCK(p);
crextend(cr, groups);
PROC_LOCK(p);
oldcred = p->p_ucred;
}
crcopy(cr, oldcred);

return (oldcred);
}

/* 略 */
static void
crextend(struct ucred *cr, int n)
{
int cnt;

/* Truncate? */
if (n <= cr->cr_agroups)
return;

/*
* We extend by 2 each time since we're using a power of two
* allocator until we need enough groups to fill a page.
* 略
*/

if ( n < PAGE_SIZE / sizeof(gid_t) ) {
if (cr->cr_agroups == 0)

/* 略 */
cr->cr_groups = malloc(cnt * sizeof(gid_t), M_CRED, M_WAITOK | M_ZERO);
cr->cr_agroups = cnt;
}


先のcrget()の末尾にある実装から、cr_agroupsはcr_groupsの要素数だと言えそうです。そして、crextend()を通して、cr_groupsを割り当て前の2倍のサイズで再割り当てをしています。

(領域が不足する都度、前回サイズの倍の領域割り当てを行っていく実装はカーネル実装ではたまにみかけます。)

ここも特筆すべきことはないようです。

つまり、cap_enter()はプロセス構造体内のパラメータに「ケーパビリティモードに入った」旨の印をつけるだけの処理だと言えそうです。

これにより、以後のシステムの挙動が変わると言えそうです。


今日はここまで

ちょっとソースを読むだけでもそれなりに見通しが立ってきました。

次にやることはCRED_FLAG_CAPMODEを参照しているコードを調べることで、これによりCapsicumの一端が見えそうですね。

それでは、今宵は失礼。