ClangのFixedAddressCheckerを試してみる

ClangのFixedAddressCheckerを試してみます。

 

1. Checkerの実行方法

$ clang -cc1 -analyzer-checker=alpha.core.FixedAddr \
  -analyze <target>.c

2. 概要

FixedAddressChecker.cppのヘッダによれば、固定されたアドレスをポインタ に設定していないかどうかを確認する為のもので、CWE-587対策らしいです。

2.1. CWE

IPAのページによれば、CWEとはCommon Weakness Enumerationの略でソフトウェ アの脆弱性の一覧をカタログ化したものです。

2.2. CWE-587

CWEのページによれば、CWE-587とはNULLや0番地以外の固定アドレスを使用し たソフトウェアが全ての環境で動作するものではないというものだそうです。

例えばデフォルトだとLinuxカーネルは0xc0000000番地以降にマッピングされ ますが、これもカーネルの設定で任意に変えることができてしまいます。

3. FixedAddressCheckerの実装

PreStmt<BinaryOperator>を継承しています。

class FixedAddressChecker 
  : public Checker< check::PreStmt<BinaryOperator> > {
  mutable OwningPtr<BuiltinBug> BT;
 
public:
  void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const;
};

checkPreStmt(const BinaryOperator *, ...)で、0でない固定アドレスを代入 しているステートメントを補足します。

void FixedAddressChecker::checkPreStmt(const BinaryOperator *B,
                                       CheckerContext &C) const {
  // Using a fixed address is not portable because that address will probably
  // not be valid in all environments or platforms.
  # 演算子が=かどうか
  if (B->getOpcode() != BO_Assign)
    return;
 
  # タイプの判定?
  QualType T = B->getType();
  if (!T->isPointerType())
    return;
 
  ProgramStateRef state = C.getState();
  SVal RV = state->getSVal(B->getRHS(), C.getLocationContext());
  # 右辺がconstで、かつ0でないかどうか
  if (!RV.isConstant() || RV.isZeroConstant())
    return;
 
  if (ExplodedNode *N = C.addTransition()) {
    if (!BT)
      BT.reset(new BuiltinBug("Use fixed address", 
                          "Using a fixed address is not portable because that "
                          "address will probably not be valid in all "
                          "environments or platforms."));
    BugReport *R = new BugReport(*BT, BT->getDescription(), N);
    R->addRange(B->getRHS()->getSourceRange());
    C.emitReport(R);
  }
}

3.1. SValのisConstantメソッド

SValのiSConstantメソッドの実装は以下の通り。SValがnonloc::ConcreteInt かloc::ConcreteIntの場合にtrueを返します。

bool SVal::isConstant() const {
  return getAs<nonloc::ConcreteInt>() || getAs<loc::ConcreteInt>();
}

SValのisZeroConstantメソッドの実装は以下の通りです。getValueの値が0か どうかまで見ます。

bool SVal::isConstant(int I) const {
  if (Optional<loc::ConcreteInt> LV = getAs<loc::ConcreteInt>())
    return LV->getValue() == I;
  if (Optional<nonloc::ConcreteInt> NV = getAs<nonloc::ConcreteInt>())
    return NV->getValue() == I;
  return false;
}
 
bool SVal::isZeroConstant() const {
  return isConstant(0);
}

4. FixedAddressCheckerの実行結果

4.1. 対象コード

unsigned longで表現された値をchar *でキャストしてポインタに設定します。

$ cat cwe587.c
#include <stdio.h>
 
int main(int argc, char *argv[])
{
  unsigned long addr = 0xc0000000;
  unsigned long null = 0;
  char *p;
 
  p = (char *) null;
  p = (char *) addr;
  p = argv[0];
  addr = null;
 
  return 0;
}

4.2. 実行結果

p = (char *) addrでNULLでない固定アドレスが設定されていることが検出さ れます。

cwe587.c:10:5: warning: Using a fixed address is not portable because
that address will probably not be valid in all environments or
platforms
  p = (char *) addr;
    ^ ~~~~~~~~~~~~~
1 warning generated.

p = argv[0]が検出されないのは面白いですね。

5. BinaryOperatorの内容を表示するChecker

BinaryOperatorのgetType()->isPointerType()を表示し、右辺のSValの内容を 表示するCheckerを作成します。

5.1. Checkerの実装

checkPreStmt(const BinaryOperator *, ...)を用い、本サイトで以前作成し たdebus_stmtとdebug_svalという関数を用います。 debug_stmtでステートメントの内容を表示します。 debug_svalで右辺のSValのクラスを表示し、クラスがnonloc::ConcreteIntあ るいはloc::ConcreteIntの場合にgetValueの値を表示します。

void BinaryOperatorChecker::checkPreStmt(const BinaryOperator *BO,
                                         CheckerContext &C) const {
  debug_stmt(BO, C.getSourceManager());
  llvm::errs() << "BO->getType()->isPointerType() = "
               << BO->getType()->isPointerType() << "\n";
  ProgramStateRef state = C.getState();
  SVal sval = state->getSVal(BO->getRHS(), C.getLocationContext());
  debug_sval(&sval);
}

5.2. 対象コード

先ほどのものに若干の変更を加えたものを用います。

$ cat assign.c 
#include <stdio.h>
 
int main(int argc, char *argv[])
{
  unsigned long addr = 0xc0000000;
  unsigned long null = 0;
  char *p;
  unsigned long lhs, rhs = 1;
 
  p = (char *) null;
  p = (char *) addr;
  p = argv[0];
  addr = null;
  lhs = rhs;
  rhs = (unsigned long) argv[0];
  p = (char *) rhs;
 
  return 0;
}

5.3. Checkerの実行結果

p = (char *) nullはポインタで、右辺が0なconstと評価されてます。

BinaryOperator
  p = (char *) null;
  ~~~~~~~~~~~~~~
BO->getType()->isPointerType() = 1
Class = loc::ConcreteInt
Value = 0

p = (char *) addrはポインタで、右辺が3221225472(0xc0000000)なconstと 評価されてます。

BinaryOperator
  p = (char *) addr;
  ~~~~~~~~~~~~~~
BO->getType()->isPointerType() = 1
Class = loc::ConcreteInt
Value = 3221225472

p = argv[0]はポインタですが、右辺がMemRegionValとなっています。 配列で確保された領域を指しているからですね。

BinaryOperator
  p = argv[0];
  ~~~~~~~~~~~
BO->getType()->isPointerType() = 1
Class = loc::MemRegionVal

addr = nullはポインタではなく、右辺が0なconstと評価されてます。

BinaryOperator
  addr = null;
  ~~~~~~~~
BO->getType()->isPointerType() = 0
Class = nonloc::ConcreteInt
Offset = 0

lhs = rhsはポインタではなく、右辺が1なconstと評価されます。

BinaryOperator
  lhs = rhs;
  ~~~~~~~
BO->getType()->isPointerType() = 0
Class = nonloc::ConcreteInt
Offset = 1

rhs = (unsigned long) argv[0]はポインタではなく、右辺がLocAsIntegerで あると評価されています。Locationを持つ領域を整数で扱っているということ でしょうか?

BinaryOperator
  rhs = (unsigned long) argv[0];
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
BO->getType()->isPointerType() = 0
Class = nonloc::LocAsInteger

p = (char *) rhsはポインタですが、右辺がMemRegionValと評価されます。 きちんと配列の領域を指していると認識していますね。すばらしい。

BinaryOperator
  p = (char *) rhs;
  ~~~~~~~~~~~~~~
BO->getType()->isPointerType() = 1
Class = loc::MemRegionVal

6. まとめ

FixedAddressCheckerは、CWEという脆弱性カタログの一項目であるCWE-587を検 出するCheckerであることが分かりました。 BinaryOperatorのgetType()で代入がポインタの代入かどうかを確認できるこ とが分かりました。
ポインタに代入している値が固定値である場合は値を取り出せることが分かり ました。
配列の領域を一時変数で整数値として保存した後、保存した整数値をポインタ に代入した場合は、配列の領域を指していることが認識できることが分かりま した。

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