taintedなコードを検出するGenericTaintCheckerを試してみます。
1. Checkerの実行方法
$ clang -cc1 -analyzer-checker=alpha.security.taint.TaintPropagation \ -analyze $@
2. GenericTaintCheckerの機能
ProgramStateRefを用いてtaintedな変数を記録します。getchar等のtaintedな 変数を取りうる関数呼び出しを補足しています。
class GenericTaintChecker : public Checker< check::PostStmt<CallExpr>, check::PreStmt<CallExpr> > {
2.1. addTaint
addTaintを用いて、変数がtaintedであることを設定します。getcharの戻り 値等をaddTaintedを用いてtaintedに設定します。
2.2. isTainted
isTaintedを用いて、変数がtaintedであることを確認します。malloc等の引数 にtaintedな変数を用いている場合はwarning出力します。
addTaintを使用しているのは本Checkerのみです。isTaintedを使用している Checkerは他にもあります。他のCheckerを動かす際に本Checkerが必要となり ます。
2.3. 対象コード
getcharで文字を取得し、その値を別の変数に設定した後、別の変数を用いて mallocを呼び出します。
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { int ch, size; char *buffer; ch = getchar(); size = ch; buffer = malloc(size); free(buffer); return 0; }
2.4. 実行結果
mallocの箇所でUntrusted dataが使われていると検出されます。
tainted.c:11:12: warning: Untrusted data is used to specify the buffer size (CERT/STR31-C. Guarantee that storage for strings has sufficient space for character data and the null terminator) buffer = malloc(size); ^~~~~~~~~~~~ 1 warning generated.
3. DivZeroChecker(GenericTaintCheckerを利用しているChecker)
DivZeroCheckerではisTaintedを使用しており、GenericTaintCheckerを動かし ていない場合と動かした場合とで実行結果が異なります。
3.1. 対象コード
getcharで取得した値で割り算を実行します。
#include <stdio.h> int main(void) { int ch; ch = getchar(); printf("%d\n", 10 / ch); return 0; }
3.2. GenericTaintCheckerを動かしていない場合
warningは出力されません。
$ clang -cc1 -analyzer-checker=core.DivideZero -analyze divzero.c
3.3. GenericTaintCheckerを動かした場合
warningが出力されます。
clang -cc1 -analyzer-checker=alpha.security.taint.TaintPropagation \ -analyzer-checker=core.DivideZero -analyze divzero.c divzero.c:7:21: warning: Division by a tainted value, possibly zero printf("%d\n", 10 / ch); ~~~^~~~ 1 warning generated.
4. シンプルなTaintなコードを検出するChecker
GenericTaintCheckerに習って、addTaintとisTaintedを両方使う、単純な taintedなコードを検出するCheckerを実装します。
4.1. addTaint
checkPostStmt(const CallExpr *CE, ...)でgetcharを見つけ、その戻り値が taintedであることをaddTaintで設定します。
void TaintChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { const FunctionDecl *FD = C.getCalleeDecl(CE); if (!FD) return; StringRef Name = C.getCalleeName(FD); if (Name.empty() || !Name.equals("getchar")) return; ProgramStateRef State = C.getState(); if (!State) return; State = State->addTaint(CE, C.getLocationContext()); C.addTransition(State); }
4.2. isTainted
checkPreStmt(const CallExpr *CE, ...)で関数引数を辿っていき、その引数 にtaintedな変数が使われている場合はステートメントを出力します。
void TaintChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { ProgramStateRef State = C.getState(); if (!State) return; for (CallExpr::const_arg_iterator I = CE->arg_begin(), E = CE->arg_end(); I != E; ++I) if (State->isTainted(*I, C.getLocationContext())) debug_stmt(dyn_cast<Stmt>(*I), C.getSourceManager()); }
4.3. 対象コード
2.3.のコードを使用します。
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { int ch, size; char *buffer; ch = getchar(); size = ch; buffer = malloc(size); free(buffer); return 0; }
4.4. 実行結果
GenericTaintCheckerを動かしていない状態で本Checkerを実行すると、malloc の引数sizeでステートメントが出力されます。
$ ./TaintChecker.sh tainted.c void <anonymous namespace>::TaintChecker::checkPreStmt(const clang::CallExpr *, clang::ento::CheckerContext &) const ImplicitCastExpr buffer = malloc(size); ~
5. まとめ
GenericTaintCheckerがaddTaintとisTaintedを利用していることを確認しまし た。GenericTaintCheckerを動作させることで、他のCheckerからでも isTaintedが利用できることを確認しました。