※ 左右のカーソルキーでもページ繰りができます
実行時安全性を保証するために、 DTrace は自前の変数領域以外への書き出しを禁止されている
組み込み変数経由での参照先の改変は、 D スクリプトのコンパイル段階でガードが掛かる。
$ cat mod_va_mask.d /* * fop_getattr(vnode_t*, vattr_t*, int, cred_t*, caller_context_t*) */ fbt::fop_getattr:entry { ((vattr_t*)arg1)->va_mask &= ~(int)(0x00200); /* AT_MTIME */ } $ dtrace -s mod_va_mask.d -l dtrace: failed to compile script mod_va_mask.d: line 6: \ operator &= can only be applied to a writable variable $
組み込み変数には、 C/C++ で言うところの const 修飾相当が効いている模様
const
一時変数に一旦格納することで、 コンパイル時ガードは回避可能
$ cat mod_va_mask.d /* * fop_getattr(vnode_t*, vattr_t*, int, cred_t*, caller_context_t*) */ fbt::fop_getattr:entry { self->vap = (vattr_t*)arg1; self->vap->va_mask &= ~(int)(0x00200); /* AT_MTIME */ } $ dtrace -s mod_va_mask.d -l ID PROVIDER MODULE FUNCTION NAME 21412 fbt genunix fop_getattr entry $
個々の D スクリプト実行環境に割り当てられた変数領域以外への書き出しは、 基本的に全て抑止される。
$ cat mod_va_mask.d /* * fop_getattr(vnode_t*, vattr_t*, int, cred_t*, caller_context_t*) */ fbt::fop_getattr:entry { self->vap = (vattr_t*)arg1; self->vap->va_mask &= ~(int)(0x00200); /* AT_MTIME */ } $ dtrace -s mod_va_mask.d -c 'stat .' dtrace: error on enabled probe ID 1 \ (ID 21412: fbt:genunix:fop_getattr:entry): \ invalid address (0xd8a59e0c) in action #2 at DIF offset 20 .... $
基本的に、変数以外のメモリ領域を DTrace で改変することはできない
任意のメモリ領域に対する改変が実施できない、 という DTrace の制限を解除する。
「OpenSolaris のビルドとインストール (パート 1)」等を参照しつつ、 以下の手順でビルド
opensolaris.sh
bldenv opensolaris.sh 'dmake setup'
usr/src
bldenv opensolaris.sh 'dmake genassym'
usr/src/uts/arch
arch
i86pc
dtrace
usr/src/uts/arch/dtrace
intel
libdtrace.so
usr/src/lib/libdtrace/arch
i386
amd64
既にビルド環境がある場合でも、 パッチ適用を行なった場合は (5-1) "bldenv opensolaris.sh 'dmake setup" だけは実施すること
bldenv opensolaris.sh 'dmake setup
以下、具体的なパス名は、debug モジュールビルド時におけるもの:
usr/src/uts/intel/dtrace/debug32/dtrace
/kernel/drv/dtrace
usr/src/uts/intel/dtrace/debug64/dtrace
/kernel/drv/amd64/dtrace
usr/src/lib/libdtrace/i386/libdtrace.so.1
/usr/lib/libdtrace.so.1
usr/src/lib/libdtrace/amd64/libdtrace.so.1
/usr/lib/amd64/libdtrace.so.1
モジュール/ライブラリインストール後に再起動し、 新規モジュールのロード状況を確認する。
$ modinfo | grep dtrace 5 fffffffff7fce000 24900 155 1 dtrace (Dynamic Tracing/Injection)
うっかり想定外の改変を行ってしまわないように、 以下の2つの条件を満たした場合に限り、 任意の領域への書き出しを許可している。
第1の条件:
dtrace カーネルモジュールの、 dtrace_destructive_disallowed 変数および dtrace_injection_disallowed 変数が 0 の場合のみ改変を許可。
dtrace_destructive_disallowed
dtrace_injection_disallowed
dtrace_injection_disallowed のデフォルト値は 1
dtrace_injection_disallowed のデフォルト値は 1 → 利用者が明示的にこの変数を 1 に設定する=明確な意図を持って操作する必要がある
/etc/system
mdb
-kw
第2の条件:
D スクリプト実行時の destructive オプションに、 injectable が指定されている場合のみ改変を許可。
destructive
injectable
他の D スクリプト利用の際には従来通りの制限が掛かるので、 危険な D スクリプトを隔離できる。
制限解除 DTrace で以下の D スクリプト drop_va_mod.d を実施することで、 「stat(2) システムコールの結果から書き込みパーミッションを除く」 という改変を行なうことができる。
drop_va_mod.d
#pragma D option destructive=injectable /* * fop_getattr(vnode_t*, vattr_t*, int, cred_t*, caller_context_t*) */ fbt::fop_getattr:entry /(pid == $target)/ { self->vap = (vattr_t*)arg1; } fbt::fop_getattr:return /(NULL != self->vap) && (0 == arg1)/ { self->vap->va_mode &= ~0x92; /* == 0o222 */ } fbt::fop_getattr:return { self->vap = NULL; /* clear */ }
D スクリプト drop_va_mode.d と併用することで、 本来書き込みパーミッションを持つ筈のディレクトリの権限情報の取得結果を、 改変することに成功している。
drop_va_mode.d
$ $ stat --print="%a/%A\n" . 755/drwxr-xr-x $ pfexec dtrace -s drop_va_mode.d -c 'stat --print=%a/%A .' 555/dr-xr-xr-x $
※ 出力の比較が容易なように、 --print オプションにより stat(1) コマンドの出力をモード情報のみに制限している
--print
stat(1)
「それが僕には楽しかったから」でも動機としては十分ではあるが、 DTrace を制限解除することで考えられる応用について
以下の様な状況を再現するのは難しい:
T_WOULDBLOCK
EAGAIN
EIO
動作をエミュレーションさせるモジュールを実装する場合、 挙動を改変する都度、モジュールのビルド/再読み込みが必要
制限解除 DTrace で挙動を事後制御させれば良いではないか!
例えば、 制限解除 DTrace との連携を前提とした仲介ファイルシステムの VOP_GETATTR 実装イメージ:
VOP_GETATTR
int vop_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr, caller_context_t * cp) { int omit = 0; int result; INJECT_FS_VOP_GETATTR_PROLOGUE(&omit, &result, vp, vap, &flags, cr, cp); if(!omit){ vnode_t* targetvp; /* vp から仲介先 FS の vnode を引き当て */ result = VOP_GETATTR(targetvp, vap, flags, cr, cp); INJECT_FS_VOP_GETATTR_EPILOGUE(&result, vp, vap, flags, cr, cp); } return result; }
omit
DTrace 機能がどのように実現されているか
dtrace コマンド実行時に、 "-S" オプションを指定することで、 仮想マシンのアセンブラコードが出力される。
-S
$ dtrace -s drop_va_mode.d -l -S DIFO 0x8cdf30 returns D type (integer) (size 4) OFF OPCODE INSTRUCTION 00: 29011601 ldgs DT_VAR(278), %r1 ! DT_VAR(278) = "pid" 01: 25000002 setx DT_INTEGER[0], %r2 ! 0x0 02: 0f010200 cmp %r1, %r2 03: 12000006 be 6 04: 0e000001 mov %r0, %r1 05: 11000007 ba 7 06: 25000101 setx DT_INTEGER[1], %r1 ! 0x1 07: 23000001 ret %r1 ....
仮想マシンのオペコードは非常に SPARC 的な体系:
**** from usr/src/uts/common/sys/dtrace.h: #define DIF_OP_OR 1 /* or r1, r2, rd */ #define DIF_OP_XOR 2 /* xor r1, r2, rd */ #define DIF_OP_AND 3 /* and r1, r2, rd */ #define DIF_OP_SLL 4 /* sll r1, r2, rd */ #define DIF_OP_SRL 5 /* srl r1, r2, rd */ #define DIF_OP_SUB 6 /* sub r1, r2, rd */ #define DIF_OP_ADD 7 /* add r1, r2, rd */ #define DIF_OP_MUL 8 /* mul r1, r2, rd */ #define DIF_OP_SDIV 9 /* sdiv r1, r2, rd */ #define DIF_OP_UDIV 10 /* udiv r1, r2, rd */ ....
"DIF" は "DTrace Intermediate Format" の略
※ /usr/include/sys/dtrace.h からも参照可能
/usr/include/sys/dtrace.h
仮想マシンにおけるメインループ=オペコード実行処理部分は、 dtrace_dif_emulate() 関数
dtrace_dif_emulate()
**** from usr/src/uts/common/dtrace/dtrace.c: while (pc < textlen && !(*flags & CPU_DTRACE_FAULT)) { switch (DIF_INSTR_OP(instr)) { case DIF_OP_OR: regs[rd] = regs[r1] | regs[r2]; break; case DIF_OP_XOR: regs[rd] = regs[r1] ^ regs[r2]; break; case DIF_OP_AND: regs[rd] = regs[r1] & regs[r2]; break; : :
書き出し可否は dtrace_canstore() 関数が判定
dtrace_canstore()
**** from usr/src/uts/common/dtrace/dtrace.c: dtrace_dif_emulate() { : case DIF_OP_STX: if (!dtrace_canstore(regs[rd], 8, mstate, vstate)) { *flags |= CPU_DTRACE_BADADDR; *illval = regs[rd]; break; } :
基本的には、 当該 D スクリプト向けに確保された変数向けの領域以外には書き出し不可
カーネル内部における各 D スクリプト毎のオプション設定は、 dtrace_state 構造体の dts_options メンバに保持される
dtrace_state
dts_options
**** from usr/src/uts/common/sys/dtrace_impl.h: struct dtrace_state { dev_t dts_dev; /* device */ int dts_necbs; /* total number of ECBs */ dtrace_ecb_t **dts_ecbs; /* array of ECBs */ dtrace_epid_t dts_epid; /* next EPID to allocate */ : : dtrace_optval_t dts_options[DTRACEOPT_MAX]; /* options */ : :
ユーザ空間=dtrace プロセスにおける各 D スクリプト毎のオプション設定は、 dtrace_hdl 構造体の dt_options メンバに保持される。
dtrace_hdl
dt_options
**** from usr/src/lib/libdtrace/common/dt_impl.h: struct dtrace_hdl { const dtrace_vector_t *dt_vector; /* library vector, if vectored open */ void *dt_varg; /* vector argument, if vectored open */ dtrace_conf_t dt_conf; /* DTrace driver configuration profile */ char dt_errmsg[BUFSIZ]; /* buffer for formatted syntax error msgs */ : : uint64_t dt_options[DTRACEOPT_MAX]; /* dtrace run-time options */ : :
D スクリプト中のオプション指定の解析は、 dtrace_setopt() 関数で実施される。
dtrace_setopt() 関数で実施される。
関数で実施される。
**** from usr/src/lib/libdtrace/common/dt_options.c: dtrace_setopt(dtrace_hdl_t *dtp, const char *opt, const char *val) { : : for (op = _dtrace_ctoptions; op->o_name != NULL; op++) { if (strcmp(op->o_name, opt) == 0) return (op->o_func(dtp, val, op->o_option)); } : :
オプション名/解析方法/オプション識別子の組をテーブル化したものが、 以下の3種類用意されている:
_dtrace_ctoptions
_dtrace_drtoptions
_dtrace_rtoptions
今後の課題など
具体的に有用性がイメージできるようなモノを揃える必要がある
「それが僕には楽しかったから」でも良いのだけどね
いくら「破壊的操作は覚悟の上」とは言え、 安全性が高いに越した事は無い。
現状、改変対象アドレスの妥当性検証は、 dtrace_istoxic() を使用した判定のみ。
dtrace_istoxic()
ちなみに dtrace_istoxic() は、 各アーキテクチャ固有のアクセス禁止領域の判定を行う関数 (例: メモリマップド I/O 領域、ブートファームウェア領域)。 以下のアーキテクチャ依存ファイルで定義される dtrace_toxic_ranges() によって、 初期化時に登録される。
dtrace_toxic_ranges()
usr/src/uts/common/os/dtrace_subr.c
usr/src/uts/i86pc/os/dtrace_subr.c
usr/src/uts/sun4/os/dtrace_subr.c
as_segat() 等を使用した仮想空間の有無を元に、 アクセス妥当性を判定するのがベスト
as_segat()
(コメントや実装を見る範囲では) D スクリプト実行中の排他獲得は避けたい模様のため、 仮想空間管理のための排他獲得が必要な as_segat() 呼び出しは望ましくないと思われる。
助けて!Bryan !