ClangのGenericTaintCheckerを試してみる

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

ダウンロード
ソースコード
対象コードとChecker
src.tar.gz
GNU tar 1.4 KB