/*- * Copyright 2016 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include "printf_check.h" #include "clang/AST/AST.h" #include "clang/AST/Expr.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/RecursiveASTVisitor.h" #include #include #include #include #include #include #include #include using namespace clang; namespace rspamd { struct PrintfArgChecker; static bool cstring_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); static bool int_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); static bool long_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); static bool size_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); static bool char_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); static bool double_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); static bool long_double_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); static bool pointer_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); static bool pid_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); static bool time_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); static bool int64_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); static bool int32_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); static bool tok_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); static bool fstring_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); static bool gstring_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); static bool gerr_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx); using arg_parser_t = bool (*)(const Expr *, struct PrintfArgChecker *); static void print_error(const char *err, const Expr *e, const ASTContext *ast, CompilerInstance *ci) { auto loc = e->getExprLoc(); auto &diag = ci->getDiagnostics(); auto id = diag.getCustomDiagID(DiagnosticsEngine::Error, "format query error: %0"); diag.Report(loc, id) << err; } static void print_warning(const char *err, const Expr *e, const ASTContext *ast, CompilerInstance *ci) { auto loc = e->getExprLoc(); auto &diag = ci->getDiagnostics(); auto id = diag.getCustomDiagID(DiagnosticsEngine::Warning, "format query warning: %0"); diag.Report(loc, id) << err; } static void print_remark(const char *err, const Expr *e, const ASTContext *ast, CompilerInstance *ci) { auto loc = e->getExprLoc(); auto &diag = ci->getDiagnostics(); auto id = diag.getCustomDiagID(DiagnosticsEngine::Remark, "format query warning: %0"); diag.Report(loc, id) << err; } struct PrintfArgChecker { private: arg_parser_t parser; public: int width; int precision; bool is_unsigned; ASTContext *past; CompilerInstance *pci; PrintfArgChecker(arg_parser_t _p, ASTContext *_ast, CompilerInstance *_ci) : parser(_p), past(_ast), pci(_ci) { width = 0; precision = 0; is_unsigned = false; } virtual ~PrintfArgChecker() { } bool operator()(const Expr *e) { return parser(e, this); } }; class PrintfCheckVisitor::impl { std::unordered_map printf_functions; std::unordered_set format_specs; ASTContext *pcontext; CompilerInstance *ci; std::unique_ptr parseFlags(const std::string &flags, const Expr *e) { auto type = flags.back(); switch (type) { case 's': return std::make_unique(cstring_arg_handler, this->pcontext, this->ci); case 'd': return std::make_unique(int_arg_handler, this->pcontext, this->ci); case 'z': return std::make_unique(size_arg_handler, this->pcontext, this->ci); case 'l': return std::make_unique(long_arg_handler, this->pcontext, this->ci); case 'f': case 'g': return std::make_unique(double_arg_handler, this->pcontext, this->ci); case 'F': case 'G': return std::make_unique( long_double_arg_handler, this->pcontext, this->ci); case 'c': return std::make_unique(char_arg_handler, this->pcontext, this->ci); case 'p': return std::make_unique(pointer_arg_handler, this->pcontext, this->ci); case 'P': return std::make_unique(pid_arg_handler, this->pcontext, this->ci); case 't': return std::make_unique(time_arg_handler, this->pcontext, this->ci); case 'L': return std::make_unique(int64_arg_handler, this->pcontext, this->ci); case 'D': return std::make_unique(int32_arg_handler, this->pcontext, this->ci); case 'T': return std::make_unique(tok_arg_handler, this->pcontext, this->ci); case 'V': return std::make_unique(fstring_arg_handler, this->pcontext, this->ci); case 'v': return std::make_unique(gstring_arg_handler, this->pcontext, this->ci); case 'e': return std::make_unique(gerr_arg_handler, this->pcontext, this->ci); default: { auto err_msg = std::string("unknown parser flag: ") + type; print_warning(err_msg.c_str(), e, this->pcontext, this->ci); break; } } return nullptr; } std::shared_ptr> genParsers(const StringRef query, const Expr *e) { enum { ignore_chars = 0, read_percent, read_width, read_precision, read_arg } state = ignore_chars; int width, precision; std::string flags; auto res = std::make_shared>(); for (auto citer = query.begin(); citer != query.end(); ++citer) { auto c = *citer; switch (state) { case ignore_chars: if (c == '%') { state = read_percent; flags.clear(); width = precision = 0; } break; case read_percent: if (isdigit(c)) { state = read_width; width = c - '0'; } else if (c == '.') { state = read_precision; precision = c - '0'; } else if (c == '*') { /* %*s - need integer argument */ res->emplace_back(int_arg_handler, this->pcontext, this->ci); if (*std::next(citer) == '.') { ++citer; state = read_precision; } else { state = read_arg; } } else if (c == '%') { /* Percent character, ignore */ state = ignore_chars; } else { // Rewind iter --citer; state = read_arg; } break; case read_width: if (isdigit(c)) { width *= 10; width += c - '0'; } else if (c == '.') { state = read_precision; precision = c - '0'; } else { // Rewind iter --citer; state = read_arg; } break; case read_precision: if (isdigit(c)) { precision *= 10; precision += c - '0'; } else if (c == '*') { res->emplace_back(int_arg_handler, this->pcontext, this->ci); state = read_arg; } else { // Rewind iter --citer; state = read_arg; } break; case read_arg: auto found = format_specs.find(c); if (found != format_specs.end() || !isalpha(c)) { if (isalpha(c)) { flags.push_back(c); } auto handler = parseFlags(flags, e); if (handler) { auto handler_copy = *handler; handler_copy.precision = precision; handler_copy.width = width; res->emplace_back(std::move(handler_copy)); } else { return nullptr; } if (c == '%') { state = read_percent; } else { state = ignore_chars; } flags.clear(); width = precision = 0; } else { flags.push_back(c); } break; } } if (state == read_arg) { auto handler = parseFlags(flags, e); if (handler) { auto handler_copy = *handler; handler_copy.precision = precision; handler_copy.width = width; res->emplace_back(std::move(handler_copy)); } else { return nullptr; } } return res; } public: impl(ASTContext *_ctx, clang::CompilerInstance &_ci) : pcontext(_ctx), ci(&_ci) { /* name -> format string position */ printf_functions = { {"rspamd_printf", 0}, {"rspamd_default_log_function", 4}, {"rspamd_snprintf", 2}, {"rspamd_fprintf", 1}, {"rspamd_printf_gstring", 1}, {"rspamd_printf_fstring", 1}, {"rspamd_conditional_debug_fast", 6}, }; format_specs = { 's', 'd', 'l', 'L', 'v', 'V', 'f', 'F', 'g', 'G', 'T', 'z', 'D', 'c', 'p', 'P', 'e'}; }; bool VisitCallExpr(CallExpr *E) { if (E->getCalleeDecl() == nullptr) { print_remark("cannot get callee decl", E, this->pcontext, this->ci); return true; } auto callee = dyn_cast(E->getCalleeDecl()); if (callee == NULL) { print_remark("cannot get named callee decl", E, this->pcontext, this->ci); return true; } auto fname = callee->getNameAsString(); auto pos_it = printf_functions.find(fname); if (pos_it != printf_functions.end()) { const auto args = E->getArgs(); auto pos = pos_it->second; auto query = args[pos]; if (!query->isEvaluatable(*pcontext)) { print_remark("cannot evaluate query", E, this->pcontext, this->ci); /* It is not assumed to be an error */ return true; } clang::Expr::EvalResult r; if (!query->EvaluateAsRValue(r, *pcontext)) { print_warning("cannot evaluate rvalue of query", E, this->pcontext, this->ci); return false; } auto qval = dyn_cast( r.Val.getLValueBase().get()); if (!qval) { print_warning("bad or absent query string", E, this->pcontext, this->ci); return false; } auto parsers = genParsers(qval->getString(), E); if (parsers) { if (parsers->size() != E->getNumArgs() - (pos + 1)) { std::ostringstream err_buf; err_buf << "number of arguments for " << fname << " mismatches query string '" << qval->getString().str() << "', expected " << parsers->size() << " args" << ", got " << (E->getNumArgs() - (pos + 1)) << " args"; print_error(err_buf.str().c_str(), E, this->pcontext, this->ci); return false; } else { for (auto i = pos + 1; i < E->getNumArgs(); i++) { auto arg = args[i]; if (arg) { if (!parsers->at(i - (pos + 1))(arg)) { return false; } } } } } } return true; } }; PrintfCheckVisitor::PrintfCheckVisitor(ASTContext *ctx, clang::CompilerInstance &ci) : pimpl{new impl(ctx, ci)} { } PrintfCheckVisitor::~PrintfCheckVisitor() { } bool PrintfCheckVisitor::VisitCallExpr(clang::CallExpr *E) { return pimpl->VisitCallExpr(E); } /* Type handlers */ static bool cstring_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { auto type = arg->getType().split().Ty; if (!type->isPointerType()) { auto err_msg = std::string("bad string argument for %s: ") + arg->getType().getAsString(); print_error(err_msg.c_str(), arg, ctx->past, ctx->pci); return false; } auto ptr_type = type->getPointeeType().split().Ty; if (!ptr_type->isCharType()) { /* We might have char * here */ auto desugared_type = ptr_type->getUnqualifiedDesugaredType(); auto desugared_ptr_type = type->getUnqualifiedDesugaredType(); if (!desugared_type || (!desugared_type->isCharType() && !desugared_ptr_type->isVoidPointerType())) { if (desugared_type) { desugared_type->dump(); } auto err_msg = std::string("bad string argument for %s: ") + arg->getType().getAsString(); print_error(err_msg.c_str(), arg, ctx->past, ctx->pci); return false; } } return true; } static bool check_builtin_type(const Expr *arg, struct PrintfArgChecker *ctx, const std::vector &k, const std::string &fmt) { auto type = arg->getType().split().Ty; auto desugared_type = type->getUnqualifiedDesugaredType(); if (!desugared_type->isBuiltinType()) { auto err_msg = std::string("not a builtin type for ") + fmt + " arg: " + arg->getType().getAsString(); print_error(err_msg.c_str(), arg, ctx->past, ctx->pci); return false; } auto builtin_type = dyn_cast(desugared_type); auto kind = builtin_type->getKind(); auto found = false; for (auto kk: k) { if (kind == kk) { found = true; break; } } if (!found) { auto err_msg = std::string("bad argument for ") + fmt + " arg: " + arg->getType().getAsString() + ", resolved as: " + builtin_type->getNameAsCString(ctx->past->getPrintingPolicy()); print_error(err_msg.c_str(), arg, ctx->past, ctx->pci); return false; } return true; } static bool int_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { return check_builtin_type(arg, ctx, {BuiltinType::Kind::UInt, BuiltinType::Kind::Int}, "%d or *"); } static bool long_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { return check_builtin_type(arg, ctx, {BuiltinType::Kind::ULong, BuiltinType::Kind::Long}, "%l"); } static bool char_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { return check_builtin_type(arg, ctx, {BuiltinType::Kind::UChar, BuiltinType::Kind::SChar, BuiltinType::Kind::Int},// Because of char -> int propagation "%c"); } static bool size_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { if (sizeof(size_t) == sizeof(long)) { if (sizeof(long long) == sizeof(long)) { return check_builtin_type(arg, ctx, {BuiltinType::Kind::ULong, BuiltinType::Kind::Long, BuiltinType::Kind::LongLong, BuiltinType::Kind::ULongLong}, "%z"); } else { return check_builtin_type(arg, ctx, {BuiltinType::Kind::ULong, BuiltinType::Kind::Long}, "%z"); } } else if (sizeof(size_t) == sizeof(int)) { return check_builtin_type(arg, ctx, {BuiltinType::Kind::UInt, BuiltinType::Kind::Int}, "%z"); } else { assert(0); } return true; } static bool double_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { return check_builtin_type(arg, ctx, {BuiltinType::Kind::Double}, "%f or %g"); } static bool long_double_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { return check_builtin_type(arg, ctx, {BuiltinType::Kind::LongDouble}, "%F or %G"); } static bool pid_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { if (sizeof(pid_t) == sizeof(long)) { return check_builtin_type(arg, ctx, {BuiltinType::Kind::ULong, BuiltinType::Kind::Long}, "%P"); } else if (sizeof(pid_t) == sizeof(int)) { return check_builtin_type(arg, ctx, {BuiltinType::Kind::UInt, BuiltinType::Kind::Int}, "%P"); } else { assert(0); } } static bool time_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { if (sizeof(time_t) == sizeof(long)) { return check_builtin_type(arg, ctx, {BuiltinType::Kind::ULong, BuiltinType::Kind::Long}, "%t"); } else if (sizeof(time_t) == sizeof(int)) { return check_builtin_type(arg, ctx, {BuiltinType::Kind::UInt, BuiltinType::Kind::Int}, "%t"); } else { assert(0); } } static bool pointer_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { auto type = arg->getType().split().Ty; if (!type->isPointerType()) { auto err_msg = std::string("bad pointer argument for %p: ") + arg->getType().getAsString(); print_error(err_msg.c_str(), arg, ctx->past, ctx->pci); return false; } return true; } static bool int64_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { std::vector check; if (sizeof(int64_t) == sizeof(long long)) { check.push_back(BuiltinType::Kind::ULongLong); check.push_back(BuiltinType::Kind::LongLong); } if (sizeof(int64_t) == sizeof(long)) { check.push_back(BuiltinType::Kind::ULong); check.push_back(BuiltinType::Kind::Long); } return check_builtin_type(arg, ctx, check, "%L"); return true; } static bool int32_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { std::vector check; if (sizeof(int32_t) == sizeof(long)) { check.push_back(BuiltinType::Kind::ULong); check.push_back(BuiltinType::Kind::Long); } if (sizeof(int32_t) == sizeof(int)) { check.push_back(BuiltinType::Kind::UInt); check.push_back(BuiltinType::Kind::Int); } return check_builtin_type(arg, ctx, check, "%D"); return true; } static bool check_struct_type(const Expr *arg, struct PrintfArgChecker *ctx, const std::string &sname, const std::string &fmt) { auto type = arg->getType().split().Ty; if (!type->isPointerType()) { auto err_msg = std::string("non pointer argument for %s: ") + arg->getType().getAsString(); print_error(err_msg.c_str(), arg, ctx->past, ctx->pci); return false; } auto ptr_type = type->getPointeeType().split().Ty; auto desugared_type = ptr_type->getUnqualifiedDesugaredType(); if (!desugared_type->isRecordType()) { auto err_msg = std::string("not a record type for ") + fmt + " arg: " + arg->getType().getAsString(); print_error(err_msg.c_str(), arg, ctx->past, ctx->pci); return false; } auto struct_type = desugared_type->getAsStructureType(); auto struct_decl = struct_type->getDecl(); auto struct_def = struct_decl->getNameAsString(); if (struct_def != sname) { auto err_msg = std::string("bad argument '") + struct_def + "' for " + fmt + " arg: " + arg->getType().getAsString(); print_error(err_msg.c_str(), arg, ctx->past, ctx->pci); return false; } return true; } static bool tok_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { return check_struct_type(arg, ctx, "f_str_tok", "%T"); } static bool fstring_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { return check_struct_type(arg, ctx, "f_str_s", "%V"); } static bool gstring_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { return check_struct_type(arg, ctx, "_GString", "%v"); } static bool gerr_arg_handler(const Expr *arg, struct PrintfArgChecker *ctx) { return check_struct_type(arg, ctx, "_GError", "%e"); } }// namespace rspamd