2014-02-11 (Tue) [長年日記]
_ ユーザIDを変更するプログラムのcoreダンプ
UnixライクなOSでは、ユーザIDを変更するプログラムでSEGVが発生した場合、ulimitなどでcoreを吐くように設定していてもcoreが吐かれないことがある。
これはセキュリティ上の理由で、例えばsetuidプログラムでは、一般ユーザに見られてはまずい情報がcoreファイルから漏洩したり、root権限のファイルを上書きしてしまうといった問題があるので、単純にroot権限でcoreファイルを作成してしまうことは好ましくない。 このため、setuidプログラムや、seteuid(2)などでユーザID/グループIDを変更するプログラムは、デフォルトではcoreを吐かないようになっている。
この動作は通常は問題ないが、デバッグの際に不便なので、Linuxなどでは設定で動作を変えられるようになっている。
グローバルな設定
Linuxの場合は、fs.suid_dumpableというカーネルパラメータでグローバルにこの挙動を変えることができ、sysctl -w fs.suid_dumpable=1
のように設定する。設定できる値は以下の三つ。
- 0
- ユーザID/グループIDを変更するプログラムはcoreを吐かない。
- 1
- すべてのプログラムがcoreを吐く。
- 2
- "0"のモードでcoreを吐かないケースでは、rootしか読めないパーミッションでcoreを吐き、既存のファイルがある場合は上書きしない。
proc(5)のマニュアルによると0がデフォルトのことだが、手元のUbuntuでは2がデフォルトになっているので、ディストリビューションによってデフォルトが異なるかもしれない。必要ならsysctl.confで設定しておくとよいだろう。
FreeBSDにもkern.sugid_coredumpというカーネルパラメータがあるが、上記の0と1のモードのみで、2のようなモードはない。
手元に環境がないが、Solarisの場合はcoreadmコマンドで設定できるようだ。
プロセス毎の設定
Linuxの場合、プロセス毎にフラグを持っていて、以下のようにprctl(2)で設定できる。*1
prctl(PR_SET_DUMPABLE, 0); /* coreダンプしないように設定 */ prctl(PR_SET_DUMPABLE, 1); /* coreダンプするように設定 */
fs.suid_dumpableが0の場合はseteuid(2)などを呼ぶとこのフラグが自動的に0にセットされるので、ユーザIDを変更した後でprctl(2)を使って値を再設定すればよい。*2
例えば、ApacheはCoreDumpDirectoryが設定されている場合はprctl(PR_SET_DUMPABLE, 1)
を実行するようになっている。
FreeBSDもP_SUGIDという同様のフラグ(ただしオンオフが逆)があるが、Linuxのようにシステムコールで設定することはできない。 ただし、実ユーザIDと実効ユーザID、実グループIDと実効グループIDがそれぞれ同じ状態でexecve(2)を呼ぶとクリアされる(これはLinuxも同様)。
FreeBSDのソースを読むと実は__setugid(2)というシステムコールが存在するが、リグシッションテスト用のシステムコールのようで、以下のように普通にビルドしたカーネルではENOSYSを返す。
int sys___setugid(struct thread *td, struct __setugid_args *uap) { #ifdef REGRESSION struct proc *p; p = td->td_proc; switch (uap->flag) { case 0: PROC_LOCK(p); p->p_flag &= ~P_SUGID; PROC_UNLOCK(p); return (0); case 1: PROC_LOCK(p); p->p_flag |= P_SUGID; PROC_UNLOCK(p); return (0); default: return (EINVAL); } #else /* !REGRESSION */ return (ENOSYS); #endif /* REGRESSION */ }
ただ、フラグの取得はissetugid(2)で行うことができ、RubyからもProcess::Sys.issetugidで取得できる。
p Process::Sys.issetugid
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); }
2014-02-19 (Wed) [長年日記]
_ キャストパズル
子供の知恵の輪を弄っていたらもうちょっと本格的なものをやりたくなったので、ハナヤマのキャストパズルというシリーズのキャストマーブルとキャストドーナツを購入した。
表示難易度がより高いキャストマーブル(写真左)の方はかなり感動的なアクションでさくっと外れたが、キャストドーナツ(写真右)の方はたぶんここから外すんだろうなという目処をつけてから外れるまで結構手間がかかった。元に戻すのも手間がかかったし。
構造を理解するとなるほどという造りなんだけど、外さないと内部構造がわからないのでもどかしい。
キャストマーブルの方が外す時と戻す時のアクションがシンプルかつアクロバティックなので、解けた後も楽しめる感じ。
2014-02-20 (Thu) [長年日記]
_ A memory of Jim Weirich
Today, I heard that Jim Weirich had passed away, but I can't believe it yet.
I met him at the first time at RubyConf 2005. It was also the first time I have attended RubyConf. He did a workshop titled "Continuations Demystified" with Chad Fowler there. In the workshop he explained continuations by comparing them to The Legend of Zelda, which is a Nintendo 64 video game. I had such a fun time.
You can download presentation materials from the following site:
<URL:http://www.zenspider.com/Languages/Ruby/RubyConf2005.html>
It's too late, but I've solved an exercise from the workshop, which exercise I didn't solve at that time.
The exercise is to implement throw/catch using continuations.
EXAMPLE:
result = cc_catch(:tag) { do_something() cc_throw(:tag, 1) fail "You never get here" } assert_equal 1, result
My answer is:
require "continuation" def cc_tag_stack Thread.current[:cc_tag_stack] ||= [] end def cc_catch(tag) callcc { |c| cc_tag_stack.push([tag, c]) begin yield ensure cc_tag_stack.pop end } end def cc_throw(tag, obj = nil) stack = cc_tag_stack.dup while x = stack.pop t, c = x if t == tag # The ensure clause in cc_catch is not called when c.call(obj) is # called, so cc_tag_stack should be rewound here. Thread.current[:cc_tag_stack] = stack c.call(obj) end end raise NameError, "uncaught throw #{tag}" end
I don't like this code very much because cc_throw is ugly. Jim's code is simpler:
$continuations = {} def cc_catch(sym) callcc { |cc| $continuations[sym] ||= [] $continuations[sym] << cc yield } ensure $continuations[sym].pop end def cc_throw(sym, value=nil) cc = $continuations[sym] fail NameError, "uncaught throw `#{sym}'" if cc.nil? || cc.empty? cc.last.call(value) end
I thank Jim for all his efforts for Ruby and the community. May his spirit rest in peace.