2014-02-12 (Wed) [長年日記]
_ 二つのフラグをロックせずに書き換える方法
先日の記事でPR_SET_DUMPABLE周りのLinuxカーネルのコードを読んでいた時に面白いコードがあったのでメモ。
以下のコードは、fs/exec.cのset_dumpable()という関数の定義で、引数のvalueは0(coreダンプしない)・1(coreダンプする)・2(suidセーフにcoreダンプする)という三つの値を取る。
/*
* set_dumpable converts traditional three-value dumpable to two flags and
* stores them into mm->flags. It modifies lower two bits of mm->flags, but
* these bits are not changed atomically. So get_dumpable can observe the
* intermediate state. To avoid doing unexpected behavior, get get_dumpable
* return either old dumpable or new one by paying attention to the order of
* modifying the bits.
*
* dumpable | mm->flags (binary)
* old new | initial interim final
* ---------+-----------------------
* 0 1 | 00 01 01
* 0 2 | 00 10(*) 11
* 1 0 | 01 00 00
* 1 2 | 01 11 11
* 2 0 | 11 10(*) 00
* 2 1 | 11 11 01
*
* (*) get_dumpable regards interim value of 10 as 11.
*/
void set_dumpable(struct mm_struct *mm, int value)
{
switch (value) {
case SUID_DUMPABLE_DISABLED:
clear_bit(MMF_DUMPABLE, &mm->flags);
smp_wmb();
clear_bit(MMF_DUMP_SECURELY, &mm->flags);
break;
case SUID_DUMPABLE_ENABLED:
set_bit(MMF_DUMPABLE, &mm->flags);
smp_wmb();
clear_bit(MMF_DUMP_SECURELY, &mm->flags);
break;
case SUID_DUMPABLE_SAFE:
set_bit(MMF_DUMP_SECURELY, &mm->flags);
smp_wmb();
set_bit(MMF_DUMPABLE, &mm->flags);
break;
}
}
コメントにもあるとおり、valueで指定した値は、カーネル内部では独立した二つのフラグで保持されるが、フラグの変更はアトミックに行われないので、中間状態の値を参照してしまう可能性がある。コメントの表でいうと、old→newが0→2、2→0のケースでは、二つのフラグが変更されるため、値の不整合が発生してしまう。
上記のコードでは、この問題を解決するために、フラグ操作の順番を注意深く制御することで、上記のケースでは中間状態が必ず決まった値を取るようにし、その値になっている場合は両方のフラグが立っている状態と見做すことにしている。 具体的には二つもとフラグをクリアする場合はMMF_DUMPABLEからクリアし、二つともフラグをセットする場合はMMF_DUMP_SECURELYからセットすることで、中間状態は必ずMMF_DUMP_SECURITYのみが立っている状態(2進表記で10)になる。
ちなみに上記のsmp_wmb()はメモリへの書き込みを待つためのマクロで、これによって実行順序を保証している(詳しくはがちゃぴん先生の記事参照)。
フラグを参照するget_dumpable()の方では、以下のように値がSUID_DUMPABLE_ENABLED(2進表記で01)より大きければ(つまり10か11なら)、SUID_DUMPABLE_SAFEを返すようになっている。
int __get_dumpable(unsigned long mm_flags)
{
int ret;
ret = mm_flags & MMF_DUMPABLE_MASK;
return (ret > SUID_DUMPABLE_ENABLED) ? SUID_DUMPABLE_SAFE : ret;
}
int get_dumpable(struct mm_struct *mm)
{
return __get_dumpable(mm->flags);
}