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