├── .gitignore ├── AuditannotateCheck.cpp ├── AuditannotateCheck.h ├── LICENSE ├── README.md ├── build.sh ├── test.sh └── test ├── CMakeLists.txt ├── test.cpp └── test2.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | -------------------------------------------------------------------------------- /AuditannotateCheck.cpp: -------------------------------------------------------------------------------- 1 | #include "AuditannotateCheck.h" 2 | #include "clang/AST/ASTContext.h" 3 | #include "clang/ASTMatchers/ASTMatchFinder.h" 4 | 5 | using namespace clang; 6 | using namespace clang::ast_matchers; 7 | 8 | namespace clang { 9 | namespace tidy { 10 | namespace readability { 11 | 12 | const char VarDeclWithAutoId[] = "decl_auto"; 13 | const char AllImplicitCastExprsId[] = "implicit_casts"; 14 | const char LambdaExprCapturesId[] = "lambda_captures"; 15 | 16 | DeclarationMatcher makeAutoDeclMatcher() 17 | { 18 | return varDecl( 19 | anyOf( 20 | hasType(autoType()), 21 | hasType(pointerType(pointee(autoType()))) 22 | ) 23 | ).bind(VarDeclWithAutoId); 24 | } 25 | 26 | void AuditannotateCheck::replaceUsesOfAuto(const VarDecl *V) 27 | { 28 | // Implicit declarations are generated implicitly by the implementation and should be ignored. 29 | if (V->isImplicit()) 30 | return; 31 | 32 | 33 | auto VT = V->getType().getCanonicalType(); 34 | 35 | // Exclude lambdas because clang doesn't give good types for them 36 | // The output looks something like this: class (lambda at /Users/ryan/svn/llvm/test/test2.cpp:44:15) 37 | const Type *VTT = VT.getTypePtr(); 38 | if (const CXXRecordDecl *CXXRD = VTT->getAsCXXRecordDecl()) 39 | if (CXXRD->isLambda()) 40 | return; 41 | 42 | SourceRange Range(V->getTypeSourceInfo()->getTypeLoc().getSourceRange()); 43 | auto Diag = diag(Range.getBegin(), "auto makes it difficult to do security audits"); 44 | 45 | Diag << FixItHint::CreateReplacement(Range, VT.getAsString()); 46 | } 47 | 48 | void AuditannotateCheck::elaborateOnImplicitCasts(const ImplicitCastExpr *I) 49 | { 50 | switch (I->getCastKind()) 51 | { 52 | case CastKind::CK_Dependent: 53 | case CastKind::CK_IntegralCast: 54 | case CastKind::CK_IntegralToPointer: 55 | case CastKind::CK_PointerToIntegral: 56 | case CastKind::CK_BitCast: 57 | case CastKind::CK_UncheckedDerivedToBase: 58 | case CastKind::CK_ToUnion: 59 | case CastKind::CK_UserDefinedConversion: 60 | case CastKind::CK_AtomicToNonAtomic: 61 | case CastKind::CK_NonAtomicToAtomic: 62 | break; 63 | default: 64 | return; 65 | } 66 | 67 | 68 | const QualType dest_type = I->getType().getCanonicalType(); 69 | const Expr *E = I->getSubExpr(); 70 | 71 | if (isa(E)) 72 | return; 73 | 74 | const QualType source_type = E->getType().getCanonicalType(); 75 | 76 | SourceRange Range(I->getSourceRange()); 77 | auto Diag = diag(Range.getBegin(), "implicit cast"); 78 | 79 | std::string comment_string = "/* CAST:" + source_type.getAsString() + "->" + dest_type.getAsString() + "*/"; 80 | Diag << FixItHint::CreateInsertion(Range.getBegin(), comment_string); 81 | 82 | } 83 | 84 | bool AuditannotateCheck::typeHasPointerMembers(const Type *V) 85 | { 86 | if (V->isAnyPointerType()) 87 | return true; 88 | 89 | // Recursively unpack structures looking for any pointer members 90 | // XXX: Need to do classes and unions also 91 | if (V->isStructureType()) 92 | { 93 | const RecordType *ST = V->getAsStructureType(); 94 | const RecordDecl *STD = ST->getDecl()->getDefinition(); 95 | 96 | for (auto F : STD->fields()) 97 | { 98 | const QualType FT = F->getType().getCanonicalType(); 99 | const Type *FTT = FT.getTypePtr(); 100 | if (typeHasPointerMembers(FTT)) 101 | return true; 102 | } 103 | 104 | } 105 | 106 | 107 | return false; 108 | } 109 | 110 | void AuditannotateCheck::annotateLambaPointerCaptures(const LambdaExpr *L) 111 | { 112 | for (const auto C : L->captures()) 113 | { 114 | // XXX: check for VLA captures 115 | // 116 | // XXX: could probably add `|| C.capturesThis()` to this statement but I don't have a test 117 | if (C.capturesVariable()) 118 | { 119 | const VarDecl *CV = C.getCapturedVar(); 120 | const QualType VT = CV->getType().getCanonicalType(); 121 | 122 | const Type *VTT = VT.getTypePtr(); 123 | if (typeHasPointerMembers(VTT)) 124 | { 125 | auto Diag = diag(C.getLocation(), "captured pointer"); 126 | 127 | std::string comment_string = "/* captured pointer */"; 128 | Diag << FixItHint::CreateInsertion(C.getLocation(), comment_string); 129 | } 130 | } 131 | } 132 | 133 | } 134 | 135 | 136 | void AuditannotateCheck::registerMatchers(ast_matchers::MatchFinder *Finder) 137 | { 138 | Finder->addMatcher(makeAutoDeclMatcher(), this); 139 | Finder->addMatcher(lambdaExpr().bind(LambdaExprCapturesId), this); 140 | Finder->addMatcher(implicitCastExpr().bind(AllImplicitCastExprsId), this); 141 | } 142 | 143 | void AuditannotateCheck::check(const MatchFinder::MatchResult &Result) 144 | { 145 | if (const auto *V = Result.Nodes.getNodeAs(VarDeclWithAutoId)) 146 | { 147 | replaceUsesOfAuto(V); 148 | } 149 | else if (const auto *I = Result.Nodes.getNodeAs(AllImplicitCastExprsId)) 150 | { 151 | elaborateOnImplicitCasts(I); 152 | } 153 | else if (const auto *L = Result.Nodes.getNodeAs(LambdaExprCapturesId)) 154 | { 155 | annotateLambaPointerCaptures(L); 156 | } 157 | } 158 | 159 | 160 | 161 | } // namespace readability 162 | } // namespace tidy 163 | } // namespace clang 164 | 165 | -------------------------------------------------------------------------------- /AuditannotateCheck.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AUDITANNOTATE_H 2 | #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AUDITANNOTATE_H 3 | 4 | #include "../ClangTidy.h" 5 | 6 | namespace clang { 7 | namespace tidy { 8 | namespace readability { 9 | 10 | class AuditannotateCheck : public ClangTidyCheck { 11 | public: 12 | AuditannotateCheck(StringRef Name, ClangTidyContext *Context) 13 | : ClangTidyCheck(Name, Context) {} 14 | 15 | void registerMatchers(ast_matchers::MatchFinder *Finder) override; 16 | void check(const ast_matchers::MatchFinder::MatchResult &Result) override; 17 | 18 | private: 19 | void replaceUsesOfAuto(const VarDecl *V); 20 | void elaborateOnImplicitCasts(const ImplicitCastExpr *I); 21 | bool typeHasPointerMembers(const Type *V); 22 | void annotateLambaPointerCaptures(const LambdaExpr *L); 23 | 24 | }; 25 | 26 | } // namespace readability 27 | } // namespace tidy 28 | } // namespace clang 29 | 30 | #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AUDITANNOTATE_H 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Trail of Bits 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clang-tidy-audit 2 | 3 | A prototype tool that rewrites c/cpp/objc files to annotate security points of interest. 4 | 5 | * replaceUsesOfAuto - rewrite uses of auto to their deduced types 6 | * elaborateOnImplicitCasts - make implicit casts more explicit by injecting comments 7 | * annotateLambaPointerCaptures - inject comments anytime a pointer is captured by lambda 8 | 9 | ## Prerequisites 10 | 11 | This project builds clang and llvm and has the same prerequisites: 12 | 13 | ### Mac OS X 14 | ``` 15 | $ brew install cmake 16 | ``` 17 | 18 | ### Ubuntu / Debian Linux 19 | ``` 20 | $ sudo apt-get build-dep clang-3.7 21 | ``` 22 | 23 | ## Build 24 | 25 | ``` 26 | $ ./build.sh 27 | ``` 28 | 29 | ## Test 30 | 31 | ``` 32 | $ ./test.sh 33 | ``` 34 | 35 | ## Usage 36 | 37 | clang-tidy is built on top of clang's libtooling interface and requires your project to have a compilation database. The easiest way to get a compliation database is to let cmake generate one for you with: 38 | ``` 39 | $ cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON path_to_cmakelists 40 | ``` 41 | 42 | This creates a compile\_commands.json, which tells clang-tidy the specific compile commands used so it can properly parse the source file into an AST. 43 | 44 | ``` 45 | $ ./build/bin/clang-tidy -checks=readability-AuditAnnotate file_to_check.cpp 46 | ``` 47 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git clone git@github.com:llvm-mirror/llvm.git -b release_37 4 | git clone git@github.com:llvm-mirror/clang.git llvm/tools/clang -b release_37 5 | git clone git@github.com:llvm-mirror/clang-tools-extra.git llvm/tools/clang/tools/extra -b release_37 6 | git clone git@github.com:llvm-mirror/compiler-rt llvm/projects/compiler-rt -b release_37 7 | git clone git@github.com:llvm-mirror/libcxx.git llvm/projects/libcxx -b release_37 8 | git clone git@github.com:llvm-mirror/libcxxabi llvm/projects/libcxxabi -b release_37 9 | 10 | pushd llvm/tools/clang/tools/extra/clang-tidy 11 | python add_new_check.py readability Auditannotate 12 | popd 13 | 14 | # This overwrites the autogenerated ones 15 | cp AuditannotateCheck.cpp AuditannotateCheck.h llvm/tools/clang/tools/extra/clang-tidy/readability/ 16 | 17 | mkdir build 18 | pushd build 19 | cmake ../llvm 20 | make 21 | popd 22 | 23 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -v 4 | 5 | mkdir -p test/build 6 | pushd test/build 7 | cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .. 8 | make 9 | popd 10 | 11 | pushd test 12 | ln -s build/compile_commands.json compile_commands.json 13 | ../build/bin/clang-tidy -checks=readability-Auditannotate test.cpp 14 | ../build/bin/clang-tidy -checks=readability-AuditAnnotate test2.cpp 15 | popd 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4) 2 | project(cpptest) 3 | add_executable(cpptest test.cpp) 4 | set(CMAKE_CXX_FLAGS "--std=c++11") 5 | set(CMAKE_CXX_STANDARD 11) 6 | 7 | add_executable(cpptest2 test2.cpp) 8 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | std::list integer_list; 5 | 6 | void populate_list() 7 | { 8 | for (unsigned int i = 0; i < 0x100; i++) 9 | { 10 | unsigned short rr = arc4random() % 0x100; 11 | integer_list.push_back(rr); 12 | } 13 | } 14 | 15 | int main(int argc, char **argv) 16 | { 17 | 18 | if (argc != 2) 19 | { 20 | return 1; 21 | } 22 | 23 | populate_list(); 24 | 25 | unsigned int check = strtoul(argv[1], nullptr, 0); 26 | unsigned int count = 0; 27 | 28 | for (auto I = integer_list.begin(); I != integer_list.end(); ++I) 29 | { 30 | if (*I == check) 31 | { 32 | count++; 33 | } 34 | } 35 | 36 | std::cout << "Found " << count << " instances of " << check << "." << std::endl; 37 | 38 | count = 0; 39 | for (auto I : integer_list) 40 | { 41 | if (I == check) 42 | { 43 | count++; 44 | } 45 | } 46 | 47 | std::cout << "Found " << count << " instances of " << check << "." << std::endl; 48 | 49 | return count == 0; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /test/test2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | std::vector c = {1, 2, 3, 4, 5, 6, 7}; 9 | int *xx = new int; 10 | *xx = 7; 11 | 12 | struct { 13 | int *x; 14 | char b[0x20]; 15 | } vv; 16 | 17 | vv.x = xx; 18 | 19 | int x = 5; 20 | c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; }), c.end()); 21 | 22 | std::cout << "c: "; 23 | std::for_each(c.begin(), c.end(), [](int i){ std::cout << i << ' '; }); 24 | std::cout << '\n'; 25 | 26 | // the type of a closure cannot be named, but can be inferred with auto 27 | auto func1 = [](int i) { return i + 4; }; 28 | std::cout << "func1: " << func1(6) << '\n'; 29 | 30 | // like all callable objects, closures can be captured in std::function 31 | // (this may incur unnecessary overhead) 32 | std::function func2 = [](int i) { return i + 4; }; 33 | std::cout << "func2: " << func2(6) << '\n'; 34 | 35 | auto func3 = [&c](int *i) { return *i; }; 36 | std::cout << "func3: " << func3(&x) << '\n'; 37 | 38 | auto func4 = [&](int i) { return *vv.x + *xx; }; 39 | std::cout << "func4: " << func4(x) << '\n'; 40 | 41 | auto func5 = [xx](int i){ return i + *xx; }; 42 | std::cout << "func5: " << func5(x) << '\n'; 43 | 44 | auto func6 = [=](int i){ return *vv.x + *xx; }; 45 | 46 | delete xx; 47 | 48 | } 49 | --------------------------------------------------------------------------------