diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2024-12-02 10:57:36 +0100 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2024-12-11 15:22:17 +0000 |
| commit | c756ba4e975097bf74b952367e2cd1a8db466c69 (patch) | |
| tree | 7cf501f6dec263b38066b485c118e4cb372d3625 /tools/syz-declextract/clangtool/declextract.cpp | |
| parent | 13dbd03bf7ca0a245cfdfd2d8c8b73e486bbaa7b (diff) | |
tools/syz-declextract: extract file_operations descriptions
Extend the clang tool to locate file_operations variables and arrays
and dump open/read/write/mmap/ioctl callbacks for each.
It also tries to extract set of ioctl commands and argument types
for them in a simple best-effort way (for now). It just locates switch
in the ioctl callback and extracts each case as a command.
Diffstat (limited to 'tools/syz-declextract/clangtool/declextract.cpp')
| -rw-r--r-- | tools/syz-declextract/clangtool/declextract.cpp | 134 |
1 files changed, 132 insertions, 2 deletions
diff --git a/tools/syz-declextract/clangtool/declextract.cpp b/tools/syz-declextract/clangtool/declextract.cpp index ca78a3b1d..68ca91d32 100644 --- a/tools/syz-declextract/clangtool/declextract.cpp +++ b/tools/syz-declextract/clangtool/declextract.cpp @@ -20,6 +20,7 @@ #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TypeTraits.h" +#include "clang/Frontend/CompilerInstance.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/StringRef.h" @@ -36,10 +37,19 @@ #include <unordered_map> #include <vector> +#include <sys/ioctl.h> + using namespace clang; using namespace clang::ast_matchers; -class Extractor : public MatchFinder { +// MacroDef/MacroMap hold information about macros defined in the file. +struct MacroDef { + std::string Value; // value as written in the source + SourceRange SourceRange; // soruce range of the value +}; +using MacroMap = std::unordered_map<std::string, MacroDef>; + +class Extractor : public MatchFinder, public tooling::SourceFileCallbacks { public: Extractor() { match(&Extractor::matchSyscall, @@ -59,6 +69,10 @@ public: match(&Extractor::matchNetlinkFamily, varDecl(hasType(recordDecl(hasName("genl_family")).bind("genl_family")), has(initListExpr().bind("genl_family_init")))); + + match(&Extractor::matchFileOps, + varDecl(forEachDescendant(initListExpr(hasType(recordDecl(hasName("file_operations")))).bind("init"))) + .bind("var")); } void print() const { Output.print(); } @@ -80,13 +94,17 @@ private: SourceManager* SourceManager = nullptr; Output Output; + MacroMap Macros; std::unordered_map<std::string, bool> EnumDedup; std::unordered_map<std::string, bool> StructDedup; + std::unordered_map<std::string, int> FileOpsDedup; void matchSyscall(); void matchIouring(); void matchNetlinkPolicy(); void matchNetlinkFamily(); + void matchFileOps(); + bool handleBeginSource(CompilerInstance& CI) override; template <typename M> void match(MatchFunc Action, const M& Matcher); void run(const MatchFinder::MatchResult& Result, MatchFunc Action); template <typename T> const T* getResult(StringRef ID) const; @@ -107,8 +125,45 @@ private: const T* findFirstMatch(const Node* Expr, const Condition& Cond); std::optional<QualType> getSizeofType(const Expr* E); int sizeofType(const Type* T); + std::vector<IoctlCmd> extractIoctlCommands(const std::string& Ioctl); }; +// PPCallbacksTracker records all macro definitions (name/value/source location). +class PPCallbacksTracker : public PPCallbacks { +public: + PPCallbacksTracker(Preprocessor& PP, MacroMap& Macros) : SM(PP.getSourceManager()), Macros(Macros) {} + +private: + SourceManager& SM; + MacroMap& Macros; + + void MacroDefined(const Token& MacroName, const MacroDirective* MD) override { + const char* NameBegin = SM.getCharacterData(MacroName.getLocation()); + const char* NameEnd = SM.getCharacterData(MacroName.getEndLoc()); + std::string Name(NameBegin, NameEnd - NameBegin); + const char* ValBegin = SM.getCharacterData(MD->getMacroInfo()->getDefinitionLoc()); + const char* ValEnd = SM.getCharacterData(MD->getMacroInfo()->getDefinitionEndLoc()) + 1; + // Definition includes the macro name, remove it. + ValBegin += std::min<size_t>(Name.size(), ValEnd - ValBegin); + // Trim whitespace from both ends. + while (ValBegin < ValEnd && isspace(*ValBegin)) + ValBegin++; + while (ValBegin < ValEnd && isspace(*(ValEnd - 1))) + ValEnd--; + std::string Value(ValBegin, ValEnd - ValBegin); + Macros[Name] = MacroDef{ + .Value = Value, + .SourceRange = SourceRange(MD->getMacroInfo()->getDefinitionLoc(), MD->getMacroInfo()->getDefinitionEndLoc()), + }; + } +}; + +bool Extractor::handleBeginSource(CompilerInstance& CI) { + Preprocessor& PP = CI.getPreprocessor(); + PP.addPPCallbacks(std::make_unique<PPCallbacksTracker>(PP, Macros)); + return true; +} + template <typename M> void Extractor::match(MatchFunc Action, const M& Matcher) { Matchers.emplace_back(new MatchCallbackThunk(*this, Action)); addMatcher(Matcher, Matchers.back().get()); @@ -526,6 +581,81 @@ void Extractor::matchIouring() { } } +void Extractor::matchFileOps() { + const auto* Fops = getResult<InitListExpr>("init"); + if (Fops->getNumInits() == 0 || isa<DesignatedInitExpr>(Fops->getInit(0))) { + // Some code constructs produce init list with DesignatedInitExpr. + // Unclear why, but it won't be handled by the following code, and is not necessary to handle. + return; + } + const auto* Var = getResult<VarDecl>("var"); + std::string VarName = Var->getNameAsString() + "_" + getDeclFileID(Var); + int NameSeq = FileOpsDedup[VarName]++; + if (NameSeq) + VarName += std::to_string(NameSeq); + auto Fields = structFieldIndexes(Fops->getType()->getAsRecordDecl()); + std::string Open = getDeclName(Fops->getInit(Fields["open"])); + std::string Ioctl = getDeclName(Fops->getInit(Fields["unlocked_ioctl"])); + std::string Read = getDeclName(Fops->getInit(Fields["read"])); + if (Read.empty()) + Read = getDeclName(Fops->getInit(Fields["read_iter"])); + std::string Write = getDeclName(Fops->getInit(Fields["write"])); + if (Write.empty()) + Write = getDeclName(Fops->getInit(Fields["write_iter"])); + std::string Mmap = getDeclName(Fops->getInit(Fields["mmap"])); + if (Mmap.empty()) + Mmap = getDeclName(Fops->getInit(Fields["get_unmapped_area"])); + auto Cmds = extractIoctlCommands(Ioctl); + Output.emit(FileOps{ + .Name = VarName, + .Open = std::move(Open), + .Read = std::move(Read), + .Write = std::move(Write), + .Mmap = std::move(Mmap), + .Ioctl = std::move(Ioctl), + .IoctlCmds = std::move(Cmds), + }); +} + +std::vector<IoctlCmd> Extractor::extractIoctlCommands(const std::string& Ioctl) { + if (Ioctl.empty()) + return {}; + // If we see the ioctl function definition, match cases of switches (very best-effort for now). + const auto& Cases = findAllMatches<CaseStmt>( + Context, functionDecl(hasName(Ioctl), forEachDescendant(switchStmt(forEachSwitchCase(caseStmt().bind("res")))))); + std::vector<IoctlCmd> Results; + for (auto* Case : Cases) { + const auto* Cmd = Case->getLHS(); + auto Range = Lexer::getAsCharRange(Cmd->getSourceRange(), *SourceManager, Context->getLangOpts()); + std::string CmdStr = Lexer::getSourceText(Range, *SourceManager, Context->getLangOpts()).str(); + auto MacroDef = Macros.find(CmdStr); + if (MacroDef == Macros.end()) + continue; + int64_t CmdVal = evaluate(Cmd); + noteConstUse(CmdStr, CmdVal, MacroDef->second.SourceRange); + FieldType CmdType; + const auto Dir = _IOC_DIR(CmdVal); + if (Dir == _IOC_NONE) { + CmdType = IntType{.ByteSize = 1, .IsConst = true}; + } else if (std::optional<QualType> Arg = getSizeofType(Cmd)) { + CmdType = PtrType{ + .Elem = genType(*Arg), + .IsConst = Dir == _IOC_READ, + }; + } else { + CmdType = PtrType{ + .Elem = BufferType{}, + .IsConst = Dir == _IOC_READ, + }; + } + Results.push_back(IoctlCmd{ + .Name = CmdStr, + .Type = std::move(CmdType), + }); + } + return Results; +} + int main(int argc, const char** argv) { llvm::cl::OptionCategory Options("syz-declextract options"); auto OptionsParser = tooling::CommonOptionsParser::create(argc, argv, Options); @@ -535,7 +665,7 @@ int main(int argc, const char** argv) { } Extractor Ex; tooling::ClangTool Tool(OptionsParser->getCompilations(), OptionsParser->getSourcePathList()); - if (Tool.run(tooling::newFrontendActionFactory(&Ex).get())) + if (Tool.run(tooling::newFrontendActionFactory(&Ex, &Ex).get())) return 1; Ex.print(); return 0; |
