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が利用できることを確認しました。