1 | //===--- SignalHandlerCheck.cpp - clang-tidy ------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "SignalHandlerCheck.h" |
10 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
11 | #include "llvm/ADT/DepthFirstIterator.h" |
12 | #include "llvm/ADT/STLExtras.h" |
13 | |
14 | // This is the minimal set of safe functions. |
15 | // https://wiki.sei.cmu.edu/confluence/display/c/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers |
16 | constexpr llvm::StringLiteral MinimalConformingFunctions[] = { |
17 | "signal" , "abort" , "_Exit" , "quick_exit" }; |
18 | |
19 | // The POSIX-defined set of safe functions. |
20 | // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03 |
21 | // 'quick_exit' is added to the set additionally because it looks like the |
22 | // mentioned POSIX specification was not updated after 'quick_exit' appeared |
23 | // in the C11 standard. |
24 | // Also, we want to keep the "minimal set" a subset of the "POSIX set". |
25 | // The list is repeated in bugprone-signal-handler.rst and should be kept up to date. |
26 | constexpr llvm::StringLiteral POSIXConformingFunctions[] = { |
27 | "_Exit" , |
28 | "_exit" , |
29 | "abort" , |
30 | "accept" , |
31 | "access" , |
32 | "aio_error" , |
33 | "aio_return" , |
34 | "aio_suspend" , |
35 | "alarm" , |
36 | "bind" , |
37 | "cfgetispeed" , |
38 | "cfgetospeed" , |
39 | "cfsetispeed" , |
40 | "cfsetospeed" , |
41 | "chdir" , |
42 | "chmod" , |
43 | "chown" , |
44 | "clock_gettime" , |
45 | "close" , |
46 | "connect" , |
47 | "creat" , |
48 | "dup" , |
49 | "dup2" , |
50 | "execl" , |
51 | "execle" , |
52 | "execv" , |
53 | "execve" , |
54 | "faccessat" , |
55 | "fchdir" , |
56 | "fchmod" , |
57 | "fchmodat" , |
58 | "fchown" , |
59 | "fchownat" , |
60 | "fcntl" , |
61 | "fdatasync" , |
62 | "fexecve" , |
63 | "ffs" , |
64 | "fork" , |
65 | "fstat" , |
66 | "fstatat" , |
67 | "fsync" , |
68 | "ftruncate" , |
69 | "futimens" , |
70 | "getegid" , |
71 | "geteuid" , |
72 | "getgid" , |
73 | "getgroups" , |
74 | "getpeername" , |
75 | "getpgrp" , |
76 | "getpid" , |
77 | "getppid" , |
78 | "getsockname" , |
79 | "getsockopt" , |
80 | "getuid" , |
81 | "htonl" , |
82 | "htons" , |
83 | "kill" , |
84 | "link" , |
85 | "linkat" , |
86 | "listen" , |
87 | "longjmp" , |
88 | "lseek" , |
89 | "lstat" , |
90 | "memccpy" , |
91 | "memchr" , |
92 | "memcmp" , |
93 | "memcpy" , |
94 | "memmove" , |
95 | "memset" , |
96 | "mkdir" , |
97 | "mkdirat" , |
98 | "mkfifo" , |
99 | "mkfifoat" , |
100 | "mknod" , |
101 | "mknodat" , |
102 | "ntohl" , |
103 | "ntohs" , |
104 | "open" , |
105 | "openat" , |
106 | "pause" , |
107 | "pipe" , |
108 | "poll" , |
109 | "posix_trace_event" , |
110 | "pselect" , |
111 | "pthread_kill" , |
112 | "pthread_self" , |
113 | "pthread_sigmask" , |
114 | "quick_exit" , |
115 | "raise" , |
116 | "read" , |
117 | "readlink" , |
118 | "readlinkat" , |
119 | "recv" , |
120 | "recvfrom" , |
121 | "recvmsg" , |
122 | "rename" , |
123 | "renameat" , |
124 | "rmdir" , |
125 | "select" , |
126 | "sem_post" , |
127 | "send" , |
128 | "sendmsg" , |
129 | "sendto" , |
130 | "setgid" , |
131 | "setpgid" , |
132 | "setsid" , |
133 | "setsockopt" , |
134 | "setuid" , |
135 | "shutdown" , |
136 | "sigaction" , |
137 | "sigaddset" , |
138 | "sigdelset" , |
139 | "sigemptyset" , |
140 | "sigfillset" , |
141 | "sigismember" , |
142 | "siglongjmp" , |
143 | "signal" , |
144 | "sigpause" , |
145 | "sigpending" , |
146 | "sigprocmask" , |
147 | "sigqueue" , |
148 | "sigset" , |
149 | "sigsuspend" , |
150 | "sleep" , |
151 | "sockatmark" , |
152 | "socket" , |
153 | "socketpair" , |
154 | "stat" , |
155 | "stpcpy" , |
156 | "stpncpy" , |
157 | "strcat" , |
158 | "strchr" , |
159 | "strcmp" , |
160 | "strcpy" , |
161 | "strcspn" , |
162 | "strlen" , |
163 | "strncat" , |
164 | "strncmp" , |
165 | "strncpy" , |
166 | "strnlen" , |
167 | "strpbrk" , |
168 | "strrchr" , |
169 | "strspn" , |
170 | "strstr" , |
171 | "strtok_r" , |
172 | "symlink" , |
173 | "symlinkat" , |
174 | "tcdrain" , |
175 | "tcflow" , |
176 | "tcflush" , |
177 | "tcgetattr" , |
178 | "tcgetpgrp" , |
179 | "tcsendbreak" , |
180 | "tcsetattr" , |
181 | "tcsetpgrp" , |
182 | "time" , |
183 | "timer_getoverrun" , |
184 | "timer_gettime" , |
185 | "timer_settime" , |
186 | "times" , |
187 | "umask" , |
188 | "uname" , |
189 | "unlink" , |
190 | "unlinkat" , |
191 | "utime" , |
192 | "utimensat" , |
193 | "utimes" , |
194 | "wait" , |
195 | "waitpid" , |
196 | "wcpcpy" , |
197 | "wcpncpy" , |
198 | "wcscat" , |
199 | "wcschr" , |
200 | "wcscmp" , |
201 | "wcscpy" , |
202 | "wcscspn" , |
203 | "wcslen" , |
204 | "wcsncat" , |
205 | "wcsncmp" , |
206 | "wcsncpy" , |
207 | "wcsnlen" , |
208 | "wcspbrk" , |
209 | "wcsrchr" , |
210 | "wcsspn" , |
211 | "wcsstr" , |
212 | "wcstok" , |
213 | "wmemchr" , |
214 | "wmemcmp" , |
215 | "wmemcpy" , |
216 | "wmemmove" , |
217 | "wmemset" , |
218 | "write" }; |
219 | |
220 | using namespace clang::ast_matchers; |
221 | |
222 | namespace clang::tidy { |
223 | |
224 | template <> |
225 | struct OptionEnumMapping< |
226 | bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind> { |
227 | static llvm::ArrayRef<std::pair< |
228 | bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind, StringRef>> |
229 | getEnumMapping() { |
230 | static constexpr std::pair< |
231 | bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind, StringRef> |
232 | Mapping[] = { |
233 | {bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind::Minimal, |
234 | "minimal" }, |
235 | {bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind::POSIX, |
236 | "POSIX" }, |
237 | }; |
238 | return {Mapping}; |
239 | } |
240 | }; |
241 | |
242 | namespace bugprone { |
243 | |
244 | namespace { |
245 | |
246 | /// Returns if a function is declared inside a system header. |
247 | /// These functions are considered to be "standard" (system-provided) library |
248 | /// functions. |
249 | bool isStandardFunction(const FunctionDecl *FD) { |
250 | // Find a possible redeclaration in system header. |
251 | // FIXME: Looking at the canonical declaration is not the most exact way |
252 | // to do this. |
253 | |
254 | // Most common case will be inclusion directly from a header. |
255 | // This works fine by using canonical declaration. |
256 | // a.c |
257 | // #include <sysheader.h> |
258 | |
259 | // Next most common case will be extern declaration. |
260 | // Can't catch this with either approach. |
261 | // b.c |
262 | // extern void sysfunc(void); |
263 | |
264 | // Canonical declaration is the first found declaration, so this works. |
265 | // c.c |
266 | // #include <sysheader.h> |
267 | // extern void sysfunc(void); // redecl won't matter |
268 | |
269 | // This does not work with canonical declaration. |
270 | // Probably this is not a frequently used case but may happen (the first |
271 | // declaration can be in a non-system header for example). |
272 | // d.c |
273 | // extern void sysfunc(void); // Canonical declaration, not in system header. |
274 | // #include <sysheader.h> |
275 | |
276 | return FD->getASTContext().getSourceManager().isInSystemHeader( |
277 | FD->getCanonicalDecl()->getLocation()); |
278 | } |
279 | |
280 | /// Check if a statement is "C++-only". |
281 | /// This includes all statements that have a class name with "CXX" prefix |
282 | /// and every other statement that is declared in file ExprCXX.h. |
283 | bool isCXXOnlyStmt(const Stmt *S) { |
284 | StringRef Name = S->getStmtClassName(); |
285 | if (Name.starts_with(Prefix: "CXX" )) |
286 | return true; |
287 | // Check for all other class names in ExprCXX.h that have no 'CXX' prefix. |
288 | return isa<ArrayTypeTraitExpr, BuiltinBitCastExpr, CUDAKernelCallExpr, |
289 | CoawaitExpr, CoreturnStmt, CoroutineBodyStmt, CoroutineSuspendExpr, |
290 | CoyieldExpr, DependentCoawaitExpr, DependentScopeDeclRefExpr, |
291 | ExprWithCleanups, ExpressionTraitExpr, FunctionParmPackExpr, |
292 | LambdaExpr, MSDependentExistsStmt, MSPropertyRefExpr, |
293 | MSPropertySubscriptExpr, MaterializeTemporaryExpr, OverloadExpr, |
294 | PackExpansionExpr, SizeOfPackExpr, SubstNonTypeTemplateParmExpr, |
295 | SubstNonTypeTemplateParmPackExpr, TypeTraitExpr, |
296 | UserDefinedLiteral>(Val: S); |
297 | } |
298 | |
299 | /// Given a call graph node of a \p Caller function and a \p Callee that is |
300 | /// called from \p Caller, get a \c CallExpr of the corresponding function call. |
301 | /// It is unspecified which call is found if multiple calls exist, but the order |
302 | /// should be deterministic (depend only on the AST). |
303 | Expr *findCallExpr(const CallGraphNode *Caller, const CallGraphNode *Callee) { |
304 | auto FoundCallee = llvm::find_if( |
305 | Range: Caller->callees(), P: [Callee](const CallGraphNode::CallRecord &Call) { |
306 | return Call.Callee == Callee; |
307 | }); |
308 | assert(FoundCallee != Caller->end() && |
309 | "Callee should be called from the caller function here." ); |
310 | return FoundCallee->CallExpr; |
311 | } |
312 | |
313 | SourceRange getSourceRangeOfStmt(const Stmt *S, ASTContext &Ctx) { |
314 | ParentMapContext &PM = Ctx.getParentMapContext(); |
315 | DynTypedNode P = DynTypedNode::create(Node: *S); |
316 | while (P.getSourceRange().isInvalid()) { |
317 | DynTypedNodeList PL = PM.getParents(Node: P); |
318 | if (PL.size() != 1) |
319 | return {}; |
320 | P = PL[0]; |
321 | } |
322 | return P.getSourceRange(); |
323 | } |
324 | |
325 | } // namespace |
326 | |
327 | AST_MATCHER(FunctionDecl, isStandardFunction) { |
328 | return isStandardFunction(FD: &Node); |
329 | } |
330 | |
331 | SignalHandlerCheck::SignalHandlerCheck(StringRef Name, |
332 | ClangTidyContext *Context) |
333 | : ClangTidyCheck(Name, Context), |
334 | AsyncSafeFunctionSet(Options.get(LocalName: "AsyncSafeFunctionSet" , |
335 | Default: AsyncSafeFunctionSetKind::POSIX)) { |
336 | if (AsyncSafeFunctionSet == AsyncSafeFunctionSetKind::Minimal) { |
337 | for (StringRef v : MinimalConformingFunctions) |
338 | ConformingFunctions.insert(key: v); |
339 | } else { |
340 | for (StringRef v : POSIXConformingFunctions) |
341 | ConformingFunctions.insert(key: v); |
342 | } |
343 | } |
344 | |
345 | void SignalHandlerCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
346 | Options.store(Options&: Opts, LocalName: "AsyncSafeFunctionSet" , Value: AsyncSafeFunctionSet); |
347 | } |
348 | |
349 | bool SignalHandlerCheck::isLanguageVersionSupported( |
350 | const LangOptions &LangOpts) const { |
351 | return !LangOpts.CPlusPlus17; |
352 | } |
353 | |
354 | void SignalHandlerCheck::registerMatchers(MatchFinder *Finder) { |
355 | auto SignalFunction = functionDecl(hasAnyName("::signal" , "::std::signal" ), |
356 | parameterCountIs(N: 2), isStandardFunction()); |
357 | auto HandlerExpr = |
358 | declRefExpr(hasDeclaration(InnerMatcher: functionDecl().bind(ID: "handler_decl" )), |
359 | unless(isExpandedFromMacro(MacroName: "SIG_IGN" )), |
360 | unless(isExpandedFromMacro(MacroName: "SIG_DFL" ))) |
361 | .bind(ID: "handler_expr" ); |
362 | auto HandlerLambda = cxxMemberCallExpr( |
363 | on(InnerMatcher: expr(ignoringParenImpCasts(InnerMatcher: lambdaExpr().bind(ID: "handler_lambda" ))))); |
364 | Finder->addMatcher(NodeMatch: callExpr(callee(InnerMatcher: SignalFunction), |
365 | hasArgument(N: 1, InnerMatcher: anyOf(HandlerExpr, HandlerLambda))) |
366 | .bind(ID: "register_call" ), |
367 | Action: this); |
368 | } |
369 | |
370 | void SignalHandlerCheck::check(const MatchFinder::MatchResult &Result) { |
371 | if (const auto *HandlerLambda = |
372 | Result.Nodes.getNodeAs<LambdaExpr>(ID: "handler_lambda" )) { |
373 | diag(Loc: HandlerLambda->getBeginLoc(), |
374 | Description: "lambda function is not allowed as signal handler (until C++17)" ) |
375 | << HandlerLambda->getSourceRange(); |
376 | return; |
377 | } |
378 | |
379 | const auto *HandlerDecl = |
380 | Result.Nodes.getNodeAs<FunctionDecl>(ID: "handler_decl" ); |
381 | const auto *HandlerExpr = Result.Nodes.getNodeAs<DeclRefExpr>(ID: "handler_expr" ); |
382 | assert(Result.Nodes.getNodeAs<CallExpr>("register_call" ) && HandlerDecl && |
383 | HandlerExpr && "All of these should exist in a match here." ); |
384 | |
385 | if (CG.size() <= 1) { |
386 | // Call graph must be populated with the entire TU at the beginning. |
387 | // (It is possible to add a single function but the functions called from it |
388 | // are not analysed in this case.) |
389 | CG.addToCallGraph(const_cast<TranslationUnitDecl *>( |
390 | HandlerDecl->getTranslationUnitDecl())); |
391 | assert(CG.size() > 1 && |
392 | "There should be at least one function added to call graph." ); |
393 | } |
394 | |
395 | if (!HandlerDecl->hasBody()) { |
396 | // Check the handler function. |
397 | // The warning is placed to the signal handler registration. |
398 | // No need to display a call chain and no need for more checks. |
399 | (void)checkFunction(HandlerDecl, HandlerExpr, {}); |
400 | return; |
401 | } |
402 | |
403 | // FIXME: Update CallGraph::getNode to use canonical decl? |
404 | CallGraphNode *HandlerNode = CG.getNode(HandlerDecl->getCanonicalDecl()); |
405 | assert(HandlerNode && |
406 | "Handler with body should be present in the call graph." ); |
407 | // Start from signal handler and visit every function call. |
408 | auto Itr = llvm::df_begin(G: HandlerNode), ItrE = llvm::df_end(G: HandlerNode); |
409 | while (Itr != ItrE) { |
410 | const auto *CallF = dyn_cast<FunctionDecl>((*Itr)->getDecl()); |
411 | unsigned int PathL = Itr.getPathLength(); |
412 | if (CallF) { |
413 | // A signal handler or a function transitively reachable from the signal |
414 | // handler was found to be unsafe. |
415 | // Generate notes for the whole call chain (including the signal handler |
416 | // registration). |
417 | const Expr *CallOrRef = (PathL > 1) |
418 | ? findCallExpr(Itr.getPath(PathL - 2), *Itr) |
419 | : HandlerExpr; |
420 | auto ChainReporter = [this, &Itr, HandlerExpr](bool SkipPathEnd) { |
421 | reportHandlerChain(Itr: Itr, HandlerRef: HandlerExpr, SkipPathEnd); |
422 | }; |
423 | // If problems were found in a function (`CallF`), skip the analysis of |
424 | // functions that are called from it. |
425 | if (checkFunction(FD: CallF, CallOrRef, ChainReporter)) |
426 | Itr.skipChildren(); |
427 | else |
428 | ++Itr; |
429 | } else { |
430 | ++Itr; |
431 | } |
432 | } |
433 | } |
434 | |
435 | bool SignalHandlerCheck::checkFunction( |
436 | const FunctionDecl *FD, const Expr *CallOrRef, |
437 | std::function<void(bool)> ChainReporter) { |
438 | bool FunctionIsCalled = isa<CallExpr>(Val: CallOrRef); |
439 | |
440 | if (isStandardFunction(FD)) { |
441 | if (!isStandardFunctionAsyncSafe(FD)) { |
442 | diag(CallOrRef->getBeginLoc(), "standard function %0 may not be " |
443 | "asynchronous-safe; " |
444 | "%select{using it as|calling it from}1 " |
445 | "a signal handler may be dangerous" ) |
446 | << FD << FunctionIsCalled << CallOrRef->getSourceRange(); |
447 | if (ChainReporter) |
448 | ChainReporter(/*SkipPathEnd=*/true); |
449 | return true; |
450 | } |
451 | return false; |
452 | } |
453 | |
454 | if (!FD->hasBody()) { |
455 | diag(CallOrRef->getBeginLoc(), "cannot verify that external function %0 is " |
456 | "asynchronous-safe; " |
457 | "%select{using it as|calling it from}1 " |
458 | "a signal handler may be dangerous" ) |
459 | << FD << FunctionIsCalled << CallOrRef->getSourceRange(); |
460 | if (ChainReporter) |
461 | ChainReporter(/*SkipPathEnd=*/true); |
462 | return true; |
463 | } |
464 | |
465 | if (getLangOpts().CPlusPlus) |
466 | return checkFunctionCPP14(FD, CallOrRef, ChainReporter); |
467 | |
468 | return false; |
469 | } |
470 | |
471 | bool SignalHandlerCheck::checkFunctionCPP14( |
472 | const FunctionDecl *FD, const Expr *CallOrRef, |
473 | std::function<void(bool)> ChainReporter) { |
474 | if (!FD->isExternC()) { |
475 | diag(CallOrRef->getBeginLoc(), |
476 | "functions without C linkage are not allowed as signal " |
477 | "handler (until C++17)" ); |
478 | if (ChainReporter) |
479 | ChainReporter(/*SkipPathEnd=*/true); |
480 | return true; |
481 | } |
482 | |
483 | const FunctionDecl *FBody = nullptr; |
484 | const Stmt *BodyS = FD->getBody(Definition&: FBody); |
485 | if (!BodyS) |
486 | return false; |
487 | |
488 | bool StmtProblemsFound = false; |
489 | ASTContext &Ctx = FBody->getASTContext(); |
490 | auto Matches = |
491 | match(Matcher: decl(forEachDescendant(stmt().bind(ID: "stmt" ))), Node: *FBody, Context&: Ctx); |
492 | for (const auto &Match : Matches) { |
493 | const auto *FoundS = Match.getNodeAs<Stmt>("stmt" ); |
494 | if (isCXXOnlyStmt(FoundS)) { |
495 | SourceRange R = getSourceRangeOfStmt(FoundS, Ctx); |
496 | if (R.isInvalid()) |
497 | continue; |
498 | diag(R.getBegin(), |
499 | "C++-only construct is not allowed in signal handler (until C++17)" ) |
500 | << R; |
501 | diag(R.getBegin(), "internally, the statement is parsed as a '%0'" , |
502 | DiagnosticIDs::Remark) |
503 | << FoundS->getStmtClassName(); |
504 | if (ChainReporter) |
505 | ChainReporter(/*SkipPathEnd=*/false); |
506 | StmtProblemsFound = true; |
507 | } |
508 | } |
509 | |
510 | return StmtProblemsFound; |
511 | } |
512 | |
513 | bool SignalHandlerCheck::isStandardFunctionAsyncSafe( |
514 | const FunctionDecl *FD) const { |
515 | assert(isStandardFunction(FD)); |
516 | |
517 | const IdentifierInfo *II = FD->getIdentifier(); |
518 | // Unnamed functions are not explicitly allowed. |
519 | // C++ std operators may be unsafe and not within the |
520 | // "common subset of C and C++". |
521 | if (!II) |
522 | return false; |
523 | |
524 | if (!FD->isInStdNamespace() && !FD->isGlobal()) |
525 | return false; |
526 | |
527 | if (ConformingFunctions.count(Key: II->getName())) |
528 | return true; |
529 | |
530 | return false; |
531 | } |
532 | |
533 | void SignalHandlerCheck::reportHandlerChain( |
534 | const llvm::df_iterator<clang::CallGraphNode *> &Itr, |
535 | const DeclRefExpr *HandlerRef, bool SkipPathEnd) { |
536 | int CallLevel = Itr.getPathLength() - 2; |
537 | assert(CallLevel >= -1 && "Empty iterator?" ); |
538 | |
539 | const CallGraphNode *Caller = Itr.getPath(n: CallLevel + 1), *Callee = nullptr; |
540 | while (CallLevel >= 0) { |
541 | Callee = Caller; |
542 | Caller = Itr.getPath(n: CallLevel); |
543 | const Expr *CE = findCallExpr(Caller, Callee); |
544 | if (SkipPathEnd) |
545 | SkipPathEnd = false; |
546 | else |
547 | diag(CE->getBeginLoc(), "function %0 called here from %1" , |
548 | DiagnosticIDs::Note) |
549 | << cast<FunctionDecl>(Val: Callee->getDecl()) |
550 | << cast<FunctionDecl>(Val: Caller->getDecl()); |
551 | --CallLevel; |
552 | } |
553 | |
554 | if (!SkipPathEnd) |
555 | diag(Loc: HandlerRef->getBeginLoc(), |
556 | Description: "function %0 registered here as signal handler" , Level: DiagnosticIDs::Note) |
557 | << cast<FunctionDecl>(Val: Caller->getDecl()) |
558 | << HandlerRef->getSourceRange(); |
559 | } |
560 | |
561 | } // namespace bugprone |
562 | } // namespace clang::tidy |
563 | |