-
Notifications
You must be signed in to change notification settings - Fork 15.5k
[-Wunsafe-buffer-usage] Add check for custom printf/scanf functions #173096
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -825,9 +825,11 @@ struct LibcFunNamePrefixSuffixParser { | |||||
| // | ||||||
| // `UnsafeArg` is the output argument that will be set only if this function | ||||||
| // returns true. | ||||||
| static bool hasUnsafeFormatOrSArg(const CallExpr *Call, const Expr *&UnsafeArg, | ||||||
| const unsigned FmtArgIdx, ASTContext &Ctx, | ||||||
| bool isKprintf = false) { | ||||||
| // `FmtArgIdx` is insignificant if its value is negative, meaning that format | ||||||
| // arguments start at `FmtIdx` + 1. | ||||||
| static bool hasUnsafeFormatOrSArg(ASTContext &Ctx, const CallExpr *Call, | ||||||
| const Expr *&UnsafeArg, const unsigned FmtIdx, | ||||||
| int FmtArgIdx = -1, bool isKprintf = false) { | ||||||
| class StringFormatStringHandler | ||||||
| : public analyze_format_string::FormatStringHandler { | ||||||
| const CallExpr *Call; | ||||||
|
|
@@ -850,8 +852,8 @@ static bool hasUnsafeFormatOrSArg(const CallExpr *Call, const Expr *&UnsafeArg, | |||||
| unsigned PArgIdx = -1; | ||||||
|
|
||||||
| if (Precision.hasDataArgument()) | ||||||
| PArgIdx = Precision.getPositionalArgIndex() + FmtArgIdx; | ||||||
| if (0 < PArgIdx && PArgIdx < Call->getNumArgs()) { | ||||||
| PArgIdx = Precision.getArgIndex() + FmtArgIdx; | ||||||
| if (0 <= PArgIdx && PArgIdx < Call->getNumArgs()) { | ||||||
| const Expr *PArg = Call->getArg(PArgIdx); | ||||||
|
|
||||||
| // Strip the cast if `PArg` is a cast-to-int expression: | ||||||
|
|
@@ -886,9 +888,9 @@ static bool hasUnsafeFormatOrSArg(const CallExpr *Call, const Expr *&UnsafeArg, | |||||
| analyze_printf::PrintfConversionSpecifier::sArg) | ||||||
| return true; // continue parsing | ||||||
|
|
||||||
| unsigned ArgIdx = FS.getPositionalArgIndex() + FmtArgIdx; | ||||||
| unsigned ArgIdx = FS.getArgIndex() + FmtArgIdx; | ||||||
|
|
||||||
| if (!(0 < ArgIdx && ArgIdx < Call->getNumArgs())) | ||||||
| if (!(0 <= ArgIdx && ArgIdx < Call->getNumArgs())) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
| // If the `ArgIdx` is invalid, give up. | ||||||
| return true; // continue parsing | ||||||
|
|
||||||
|
|
@@ -921,12 +923,15 @@ static bool hasUnsafeFormatOrSArg(const CallExpr *Call, const Expr *&UnsafeArg, | |||||
| bool isUnsafeArgSet() { return UnsafeArgSet; } | ||||||
| }; | ||||||
|
|
||||||
| const Expr *Fmt = Call->getArg(FmtArgIdx); | ||||||
| const Expr *Fmt = Call->getArg(FmtIdx); | ||||||
| unsigned FmtArgStartingIdx = | ||||||
| FmtArgIdx < 0 ? FmtIdx + 1 : static_cast<unsigned>(FmtArgIdx); | ||||||
|
|
||||||
| if (auto *SL = dyn_cast<clang::StringLiteral>(Fmt->IgnoreParenImpCasts())) { | ||||||
| if (SL->getCharByteWidth() == 1) { | ||||||
| StringRef FmtStr = SL->getString(); | ||||||
| StringFormatStringHandler Handler(Call, FmtArgIdx, UnsafeArg, Ctx); | ||||||
| StringFormatStringHandler Handler(Call, FmtArgStartingIdx, UnsafeArg, | ||||||
| Ctx); | ||||||
|
|
||||||
| return analyze_format_string::ParsePrintfString( | ||||||
| Handler, FmtStr.begin(), FmtStr.end(), Ctx.getLangOpts(), | ||||||
|
|
@@ -935,7 +940,8 @@ static bool hasUnsafeFormatOrSArg(const CallExpr *Call, const Expr *&UnsafeArg, | |||||
| } | ||||||
|
|
||||||
| if (auto FmtStr = SL->tryEvaluateString(Ctx)) { | ||||||
| StringFormatStringHandler Handler(Call, FmtArgIdx, UnsafeArg, Ctx); | ||||||
| StringFormatStringHandler Handler(Call, FmtArgStartingIdx, UnsafeArg, | ||||||
| Ctx); | ||||||
| return analyze_format_string::ParsePrintfString( | ||||||
| Handler, FmtStr->data(), FmtStr->data() + FmtStr->size(), | ||||||
| Ctx.getLangOpts(), Ctx.getTargetInfo(), isKprintf) && | ||||||
|
|
@@ -946,7 +952,7 @@ static bool hasUnsafeFormatOrSArg(const CallExpr *Call, const Expr *&UnsafeArg, | |||||
| // In this case, this call is considered unsafe if at least one argument | ||||||
| // (including the format argument) is unsafe pointer. | ||||||
| return llvm::any_of( | ||||||
| llvm::make_range(Call->arg_begin() + FmtArgIdx, Call->arg_end()), | ||||||
| llvm::make_range(Call->arg_begin() + FmtArgStartingIdx, Call->arg_end()), | ||||||
| [&UnsafeArg, &Ctx](const Expr *Arg) -> bool { | ||||||
| if (Arg->getType()->isPointerType() && !isNullTermPointer(Arg, Ctx)) { | ||||||
| UnsafeArg = Arg; | ||||||
|
|
@@ -1161,7 +1167,7 @@ static bool hasUnsafePrintfStringArg(const CallExpr &Node, ASTContext &Ctx, | |||||
| // It is a fprintf: | ||||||
| const Expr *UnsafeArg; | ||||||
|
|
||||||
| if (hasUnsafeFormatOrSArg(&Node, UnsafeArg, 1, Ctx, false)) { | ||||||
| if (hasUnsafeFormatOrSArg(Ctx, &Node, UnsafeArg, 1)) { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given you're changing this line you should update the FmtIdx argument to include the parameter name:
Suggested change
|
||||||
| Result.addNode(Tag, DynTypedNode::create(*UnsafeArg)); | ||||||
| return true; | ||||||
| } | ||||||
|
|
@@ -1175,7 +1181,7 @@ static bool hasUnsafePrintfStringArg(const CallExpr &Node, ASTContext &Ctx, | |||||
|
|
||||||
| if (auto *II = FD->getIdentifier()) | ||||||
| isKprintf = II->getName() == "kprintf"; | ||||||
| if (hasUnsafeFormatOrSArg(&Node, UnsafeArg, 0, Ctx, isKprintf)) { | ||||||
| if (hasUnsafeFormatOrSArg(Ctx, &Node, UnsafeArg, 0, -1, isKprintf)) { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I'll stop commenting on including the parameter name, but every call that you're updating should correct that :D |
||||||
| Result.addNode(Tag, DynTypedNode::create(*UnsafeArg)); | ||||||
| return true; | ||||||
| } | ||||||
|
|
@@ -1190,7 +1196,7 @@ static bool hasUnsafePrintfStringArg(const CallExpr &Node, ASTContext &Ctx, | |||||
| // second is an integer, it is a snprintf: | ||||||
| const Expr *UnsafeArg; | ||||||
|
|
||||||
| if (hasUnsafeFormatOrSArg(&Node, UnsafeArg, 2, Ctx, false)) { | ||||||
| if (hasUnsafeFormatOrSArg(Ctx, &Node, UnsafeArg, 2)) { | ||||||
| Result.addNode(Tag, DynTypedNode::create(*UnsafeArg)); | ||||||
| return true; | ||||||
| } | ||||||
|
|
@@ -2068,6 +2074,7 @@ class UnsafeLibcFunctionCallGadget : public WarningGadget { | |||||
| constexpr static const char *const UnsafeVaListTag = | ||||||
| "UnsafeLibcFunctionCall_va_list"; | ||||||
|
|
||||||
| public: | ||||||
| enum UnsafeKind { | ||||||
| OTHERS = 0, // no specific information, the callee function is unsafe | ||||||
| SPRINTF = 1, // never call `-sprintf`s, call `-snprintf`s instead. | ||||||
|
|
@@ -2080,7 +2087,6 @@ class UnsafeLibcFunctionCallGadget : public WarningGadget { | |||||
| // considered unsafe as it is not compile-time check | ||||||
| } WarnedFunKind = OTHERS; | ||||||
|
|
||||||
| public: | ||||||
| UnsafeLibcFunctionCallGadget(const MatchResult &Result) | ||||||
| : WarningGadget(Kind::UnsafeLibcFunctionCall), | ||||||
| Call(Result.getNodeAs<CallExpr>(Tag)) { | ||||||
|
|
@@ -2171,6 +2177,85 @@ class UnsafeLibcFunctionCallGadget : public WarningGadget { | |||||
| SmallVector<const Expr *, 1> getUnsafePtrs() const override { return {}; } | ||||||
| }; | ||||||
|
|
||||||
| class UnsafeFormatAttributedFunctionCallGadget : public WarningGadget { | ||||||
| const CallExpr *const Call; | ||||||
| const Expr *UnsafeArg = nullptr; | ||||||
| constexpr static const char *const Tag = "UnsafeFormatAttributedFunctionCall"; | ||||||
| constexpr static const char *const UnsafeStringTag = | ||||||
| "UnsafeFormatAttributedFunctionCall_string"; | ||||||
|
|
||||||
| public: | ||||||
| UnsafeFormatAttributedFunctionCallGadget(const MatchResult &Result) | ||||||
| : WarningGadget(Kind::UnsafeLibcFunctionCall), | ||||||
| Call(Result.getNodeAs<CallExpr>(Tag)), | ||||||
| UnsafeArg(Result.getNodeAs<Expr>(UnsafeStringTag)) {} | ||||||
|
|
||||||
| static bool matches(const Stmt *S, ASTContext &Ctx, | ||||||
| const UnsafeBufferUsageHandler *Handler, | ||||||
| MatchResult &Result) { | ||||||
| if (ignoreUnsafeLibcCall(Ctx, *S, Handler)) | ||||||
| return false; | ||||||
| auto *CE = dyn_cast<CallExpr>(S); | ||||||
| if (!CE || !CE->getDirectCallee()) | ||||||
| return false; | ||||||
| const auto *FD = dyn_cast<FunctionDecl>(CE->getDirectCallee()); | ||||||
| if (!FD) | ||||||
| return false; | ||||||
|
|
||||||
| const FormatAttr *Attr = nullptr; | ||||||
| bool IsPrintf = false; | ||||||
| bool AnyAttr = llvm::any_of( | ||||||
| FD->specific_attrs<FormatAttr>(), | ||||||
| [&Attr, &IsPrintf](const FormatAttr *FA) -> bool { | ||||||
| if (const auto *II = FA->getType()) { | ||||||
| if (II->getName() == "printf" || II->getName() == "scanf") { | ||||||
| Attr = FA; | ||||||
| IsPrintf = II->getName() == "printf"; | ||||||
| return true; | ||||||
| } | ||||||
| } | ||||||
| return false; | ||||||
| }); | ||||||
| const Expr *UnsafeArg; | ||||||
|
|
||||||
| if (AnyAttr && !IsPrintf && | ||||||
| (CE->getNumArgs() >= static_cast<unsigned>(Attr->getFirstArg()))) { | ||||||
| // for scanf-like functions, any format argument is considered unsafe: | ||||||
| Result.addNode(Tag, DynTypedNode::create(*CE)); | ||||||
| return true; | ||||||
| } | ||||||
| if (AnyAttr && libc_func_matchers::hasUnsafeFormatOrSArg( | ||||||
| Ctx, CE, UnsafeArg, | ||||||
| // FormatAttribute indexes are 1-based: | ||||||
| Attr->getFormatIdx() - 1, Attr->getFirstArg() - 1)) { | ||||||
| Result.addNode(Tag, DynTypedNode::create(*CE)); | ||||||
| Result.addNode(UnsafeStringTag, DynTypedNode::create(*UnsafeArg)); | ||||||
| return true; | ||||||
| } | ||||||
| return false; | ||||||
| } | ||||||
|
|
||||||
| const Stmt *getBaseStmt() const { return Call; } | ||||||
|
|
||||||
| SourceLocation getSourceLoc() const override { return Call->getBeginLoc(); } | ||||||
|
|
||||||
| void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler, | ||||||
| bool IsRelatedToDecl, | ||||||
| ASTContext &Ctx) const override { | ||||||
| if (UnsafeArg) | ||||||
| Handler.handleUnsafeLibcCall( | ||||||
| Call, UnsafeLibcFunctionCallGadget::UnsafeKind::STRING, Ctx, | ||||||
| UnsafeArg); | ||||||
| else | ||||||
| Handler.handleUnsafeLibcCall( | ||||||
| Call, UnsafeLibcFunctionCallGadget::UnsafeKind::OTHERS, Ctx); | ||||||
| } | ||||||
|
|
||||||
| DeclUseList getClaimedVarUseSites() const override { return {}; } | ||||||
|
|
||||||
| SmallVector<const Expr *, 1> getUnsafePtrs() const override { return {}; } | ||||||
| }; | ||||||
|
|
||||||
| // Represents expressions of the form `DRE[*]` in the Unspecified Lvalue | ||||||
| // Context (see `findStmtsInUnspecifiedLvalueContext`). | ||||||
| // Note here `[]` is the built-in subscript operator. | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,10 @@ | ||
| // RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage \ | ||
| // RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage -Wno-gcc-compat\ | ||
| // RUN: -verify %s | ||
| // RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage \ | ||
| // RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage -Wno-gcc-compat\ | ||
| // RUN: -verify %s -x objective-c++ | ||
| // RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-libc-call \ | ||
| // RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-libc-call -Wno-gcc-compat\ | ||
| // RUN: -verify %s | ||
| // RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-libc-call \ | ||
| // RUN: %clang_cc1 -std=c++20 -Wno-all -Wunsafe-buffer-usage-in-libc-call -Wno-gcc-compat\ | ||
| // RUN: -verify %s -DTEST_STD_NS | ||
|
|
||
| typedef struct {} FILE; | ||
|
|
@@ -255,3 +255,27 @@ void dontCrashForInvalidFormatString() { | |
| snprintf((char*)0, 0, "%"); | ||
| snprintf((char*)0, 0, "\0"); | ||
| } | ||
|
|
||
|
|
||
| // Also warn about unsafe printf/scanf-like functions: | ||
| void myprintf(const char *F, ...) __attribute__((__format__ (__printf__, 1, 2))); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could we add a few tests where the format string isn't the first arg? and also something where the format string comes equal to and after the first arg index? (only as a "do something sane/don't crash" test, not because anyone should ever ever do this :D |
||
| void myprintf_2(const char *F, int irrelevant, const char *Str) __attribute__((__format__ (__printf__, 1, 3))); | ||
| void myscanf(const char *F, ...) __attribute__((__format__ (__scanf__, 1, 2))); | ||
|
|
||
| void test_myprintf(char * Str, std::string StdStr) { | ||
| myprintf("hello", Str); | ||
| myprintf("hello %s", StdStr.c_str()); | ||
| myprintf("hello %s", Str); // expected-warning{{function 'myprintf' is unsafe}} \ | ||
| expected-note{{string argument is not guaranteed to be null-terminated}} | ||
|
|
||
| myprintf_2("hello", 0, Str); | ||
| myprintf_2("hello %s", 0, StdStr.c_str()); | ||
| myprintf_2("hello %s", 0, Str); // expected-warning{{function 'myprintf_2' is unsafe}} \ | ||
| expected-note{{string argument is not guaranteed to be null-terminated}} | ||
| myscanf("hello %s"); | ||
| myscanf("hello %s", Str); // expected-warning{{function 'myscanf' is unsafe}} | ||
|
|
||
| int X; | ||
|
|
||
| myscanf("hello %d", &X); // expected-warning{{function 'myscanf' is unsafe}} | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FmtArgIdx should be std::optional rather than using a negative index