ClangのCheckSizeofPointerを試してみる

CheckSizeofPointerを試してみます。

 

1. 実行方法

clang -cc1 -analyzer-checker=alpha.core.SizeofPtr \
        -analyze $@

2. CWE-467

CheckSizeofPointer.cppのヘッダ部分によれば、sizeofにポインタを使用して いるコードを検出するCheckerだそうです。 コメントによれば、脆弱性カタログのCWE-467を見つけるものだそうです。 CWE-467はmalloc領域を指すポインタにsizeofを使った場合の問題を指摘する ものです。

3. CheckSizeofPointerの実装

StmtVisitorを継承し、VisitStmtとVisitUnaryExprOrTypeTraitExprを実装し ています。VisitStmtでVisitChildrenを呼び出し、VisitChildrenで child_iteratorを辿ります。よって、UnaryExprOrTypeTraitExprを見つけるま ではchild_iteratorで再帰的にASTを辿るようになっています。

class WalkAST : public StmtVisitor<WalkAST> {
<snip>
  void VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *E);
  void VisitStmt(Stmt *S) { VisitChildren(S); }
  void VisitChildren(Stmt *S);
};

3.1. UnaryExprOrTypeTraitExpr

sizeof(x)はUnaryExprOrTypeTraitExprで補足されます。sizeof以外にも alignofとvec_stepも補足されるようです。

3.2. VisitUnaryExprOrTypeTraitExpr

getKindで取得できる値はUETT_SizeOf, UETT_AlignOf, UETT_VecStepです。こ こではsizeofを補足するのでUETT_SizeOfでないものはフィルタリングします。

void WalkAST::VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *E) {
  if (E->getKind() != UETT_SizeOf)
    return;

このisArgumentTypeが分からない・・・。いつtrueになるのだろうか。 explicit typeもキャストに関する検索はひっかかるが、sizeofに使うという 話を見つけられてません。

  // If an explicit type is used in the code, usually the coder knows what he is
  // doing.
  if (E->isArgumentType())
    return;

sizeof(x)のxの型を取り出します。型がポインタかどうかを確認します。

  QualType T = E->getTypeOfArgument();
  if (T->isPointerType()) {

char **qをsizeof(*q)は!isa<DeclRefExpr>(ArgEx->IgnoreParens())がtrueに なります。char *pのsizeof(p)しか補足しないようになっていますね。

    // Many false positives have the form 'sizeof *p'. This is reasonable 
    // because people know what they are doing when they intentionally 
    // dereference the pointer.
    Expr *ArgEx = E->getArgumentExpr();
    if (!isa<DeclRefExpr>(ArgEx->IgnoreParens()))
      return;
<snip> # warning出力
  }
}

char **qのsizeof(*q)をダンプするとUnaryExprOrTypeTraitExprが入れ子になっ ていることを確認できます。

UnaryExprOrTypeTraitExpr 0x7f9d9b8b7710 'unsigned long' sizeof
`-ParenExpr 0x7f9d9b8b76f0 'char *' lvalue
  `-UnaryOperator 0x7f9d9b8b76d0 'char *' lvalue prefix '*'
    `-ImplicitCastExpr 0x7f9d9b8b76b8 'char **' <LValueToRValue>
      `-DeclRefExpr 0x7f9d9b8b7690 'char **' lvalue Var 0x7f9d9b8b6d80 'q' 'char **'

これはchar array[5]でsizeof(array)/sizeof(*array)で要素数を割り出す方 法で、sizeof(array)をwarning出力しないようにする為です。

4. CheckSizeofPointerの実行結果

4.1. 対象コード

#include <stdio.h>
 
int main(void)
{
  long l;
  int i;
  char *p, array[64];
 
  printf("sizeof(l) = %u\n", (unsigned) sizeof(l));
  printf("sizeof(i) = %u\n", (unsigned) sizeof(i));
  printf("sizeof(*p) = %u\n", (unsigned) sizeof(*p));
  printf("sizeof(p) = %u\n", (unsigned) sizeof(p));
  printf("sizeof(array) / sizeof(*array) = %u\n",
                 (unsigned) (sizeof(array) / sizeof(*array)));
 
  return 0;
}

私の環境で本プログラムを実行すると以下のようになりました。

sizeof(l) = 8
sizeof(i) = 4
sizeof(*p) = 1
sizeof(p) = 8
sizeof(array) / sizeof(*array) = 64

4.2. 実行結果

sizeof.c:12:41: warning: The code calls sizeof() on a pointer
type. This can produce an unexpected result
  printf("sizeof(p) = %u\n", (unsigned) sizeof(p));
                                        ^     ~~~
1 warning generated.

5. まとめ

UnaryExprOrTypeTraitExprでsizeofを補足できることが分かりました。 CheckSizeofPointerでsizeofにポインタを使用しているコードを検出できます。 配列の要素数を取得するsizeof(array)/sizeof(*array)などのコードは検出対 象から外れるようになっています。

ダウンロード
ソースコード
対象コード
src.tar.gz
GNU tar 404 Bytes