Skip to content

Commit 4a361e8

Browse files
authored
Merge pull request #7 from trailofbits/signal-handler-enhance
Enhanced version of signal query
2 parents ff37b35 + d7a5131 commit 4a361e8

File tree

9 files changed

+303
-36
lines changed

9 files changed

+303
-36
lines changed

cpp/src/security/AsyncUnsafeSignalHandler/AsyncUnsafeSignalHandler.qhelp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,48 @@
77
This is a CodeQL query constructed to find signal handlers that are performing async unsafe operations.
88
</p>
99

10+
<p>
11+
Because a signal may be delivered at any moment, e.g., in the middle of a malloc, the handler shouldn't touch
12+
the program's internal state.
13+
</p>
14+
1015
<p>
1116
The kernel defines a list of async-safe signal functions in its <a href="https://man7.org/linux/man-pages/man7/signal-safety.7.html">man page</a>.
12-
Any signal handler that performs operations that are not safe asynchronously may be vulnerable.
17+
Any signal handler that performs operations that are not safe for asynchronous execution may be vulnerable.
18+
</p>
19+
20+
<p>
21+
Moreover, signal handlers may be re-entered. Handlers' logic should take that possibility into account.
22+
</p>
23+
24+
<p>
25+
If the issue is exploitable depends on attacker's ability to deliver the signal.
26+
Remote attacks may be limitted to some signals (like SIGALRM and SIGURG), while local attacks could use all signals.
1327
</p>
1428
</overview>
1529

1630
<recommendation>
1731
<p>
1832
Attempt to keep signal handlers as simple as possible. Only call async-safe functions from signal handlers.
1933
</p>
34+
<p>
35+
Block delivery of new signals inside signal handlers to prevent handler re-entrancy issues.
36+
</p>
2037
</recommendation>
2138

2239
<example>
2340
<sample src="AsyncUnsafeSignalHandler.c" />
2441

2542
<p>
26-
In this example, while both syntatically valid, a correct handler is defined in the <code>correct_handler</code> function and sets a flag. The function calls <code>log_message</code>, a async unsafe function, within the main loop.
43+
In this example, while both syntatically valid, a correct handler is defined in the <code>correct_handler</code> function and sets a flag.
44+
The function calls <code>log_message</code>, a async unsafe function, within the main loop.
2745
</p>
2846
</example>
2947

3048
<references>
49+
<li>
50+
<a href="https://lcamtuf.coredump.cx/signals.txt">Michal Zalewski, "Delivering Signals for Fun and Profit"</a>
51+
</li>
3152
<li>
3253
<a href="https://wiki.sei.cmu.edu/confluence/display/c/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers">SEI CERT C Coding Standard "SIG30-C. Call only asynchronous-safe functions within signal handlers"</a>
3354
</li>

cpp/src/security/AsyncUnsafeSignalHandler/AsyncUnsafeSignalHandler.ql

Lines changed: 101 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
*/
1212

1313
import cpp
14+
import semmle.code.cpp.dataflow.new.DataFlow
15+
1416

1517
/* List from https://man7.org/linux/man-pages/man3/stdio.3.html */
1618
class StdioFunction extends Function {
@@ -46,22 +48,106 @@ predicate isAsyncUnsafe(Function signalHandler) {
4648
)
4749
}
4850

49-
predicate isSignalHandlerField(FieldAccess fa) {
50-
fa.getTarget().getName() in ["__sa_handler", "__sa_sigaction", "sa_handler", "sa_sigaction"]
51+
/**
52+
* Flows from any function ptr
53+
*/
54+
module HandlerToSignalConfiguration implements DataFlow::ConfigSig {
55+
predicate isSource(DataFlow::Node source) {
56+
source.asExpr() = any(Function f || f.getAnAccess())
57+
}
58+
59+
predicate isSink(DataFlow::Node sink) {
60+
sink = sink
61+
}
5162
}
63+
module HandlerToSignal = DataFlow::Global<HandlerToSignalConfiguration>;
5264

53-
from FunctionCall fc, Function signalHandler, FieldAccess fa
54-
where
55-
(
56-
fc.getTarget().getName().matches("%_signal") or
57-
fc.getTarget().getName() = "signal"
58-
) and
59-
signalHandler.getName() = fc.getArgument(1).toString() and
60-
isAsyncUnsafe(signalHandler)
65+
/**
66+
* signal(SIGX, signalHandler)
67+
*/
68+
predicate isSignal(FunctionCall signalCall, Function signalHandler) {
69+
signalCall.getTarget().getName() = "signal"
70+
and exists(DataFlow::Node source, DataFlow::Node sink |
71+
HandlerToSignal::flow(source, sink)
72+
and source.asExpr() = signalHandler.getAnAccess()
73+
and sink.asExpr() = signalCall.getArgument(1)
74+
)
75+
}
76+
77+
/**
78+
* struct sigaction sigactVar = ...
79+
* sigaction(SIGX, &sigactVar, ...)
80+
*/
81+
predicate isSigaction(FunctionCall sigactionCall, Function signalHandler, Variable sigactVar) {
82+
exists(Struct sigactStruct, Field handlerField, DataFlow::Node source, DataFlow::Node sink |
83+
sigactionCall.getTarget().getName() = "sigaction"
84+
and sigactionCall.getArgument(1).getAChild*() = sigactVar.getAnAccess()
85+
86+
// struct sigaction with the handler func
87+
and sigactStruct.getName() = "sigaction"
88+
and handlerField.getName() = ["sa_handler", "sa_sigaction", "__sa_handler", "__sa_sigaction", "__sigaction_u"]
89+
and (
90+
handlerField = sigactStruct.getAField()
91+
or
92+
exists(Union u | u.getAField() = handlerField and u = sigactStruct.getAField().getType())
93+
)
94+
95+
and (
96+
// sigactVar.sa_handler = &signalHandler
97+
exists(Assignment a, ValueFieldAccess vfa |
98+
vfa.getTarget() = handlerField
99+
and vfa.getQualifier+() = sigactVar.getAnAccess()
100+
and a.getLValue() = vfa.getAChild*()
101+
102+
and source.asExpr() = signalHandler.getAnAccess()
103+
and sink.asExpr() = a.getRValue()
104+
and HandlerToSignal::flow(source, sink)
105+
)
106+
or
107+
// struct sigaction sigactVar = {.sa_sigaction = signalHandler};
108+
exists(ClassAggregateLiteral initLit |
109+
sigactVar.getInitializer().getExpr() = initLit
110+
and source.asExpr() = signalHandler.getAnAccess()
111+
and sink.asExpr() = initLit.getAFieldExpr(handlerField).getAChild*()
112+
and HandlerToSignal::flow(source, sink)
113+
)
114+
)
115+
)
116+
}
117+
118+
/**
119+
* Determine if new signals are blocked
120+
* TODO: should only find writes and only for correct (or all) signals
121+
* TODO: should detect other block mechanisms
122+
*/
123+
predicate isSignalDeliveryBlocked(Variable sigactVar) {
124+
exists(ValueFieldAccess dfa |
125+
dfa.getQualifier+() = sigactVar.getAnAccess() and dfa.getTarget().getName() = "sa_mask"
126+
)
61127
or
62-
fc.getTarget().getName() = "sigaction" and
63-
isSignalHandlerField(fa) and
64-
signalHandler = fa.getTarget().getAnAssignedValue().(AddressOfExpr).getAddressable() and
128+
exists(Field mask |
129+
mask.getName() = "sa_mask"
130+
and exists(sigactVar.getInitializer().getExpr().(ClassAggregateLiteral).getAFieldExpr(mask))
131+
)
132+
}
133+
134+
string deliveryNotBlockedMsg() {
135+
result = "Moreover, delivery of new signals may be not blocked. "
136+
}
137+
138+
from FunctionCall fc, Function signalHandler, string msg
139+
where
65140
isAsyncUnsafe(signalHandler)
66-
select signalHandler,
67-
"is a non-trivial signal handler that may be using functions that are not async-safe."
141+
and (
142+
(isSignal(fc, signalHandler) and msg = deliveryNotBlockedMsg())
143+
or
144+
exists(Variable sigactVar |
145+
isSigaction(fc, signalHandler, sigactVar)
146+
and if isSignalDeliveryBlocked(sigactVar) then
147+
msg = ""
148+
else
149+
msg = deliveryNotBlockedMsg()
150+
)
151+
)
152+
select signalHandler, "$@ is a non-trivial signal handler that uses not async-safe functions. " + msg +
153+
"Handler is registered by $@.", signalHandler, signalHandler.toString(), fc, fc.toString()

cpp/test/include/libc/signal.h

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,27 @@
33
#ifndef HEADER_SIGNAL_STUB_H
44
#define HEADER_SIGNAL_STUB_H
55

6-
#ifdef __cplusplus
7-
extern "C" {
8-
#endif
9-
106
#define SIGALRM 14
117
#define SIGSEGV 11
8+
#define SIGTERM 15
129
#define SIG_ERR -1
1310
#define EXIT_FAILURE 2
14-
#define SA_SIGINFO 0x00000004
11+
#define SA_SIGINFO 4
1512

16-
typedef void (*sighandler_t)(int);
17-
extern int signal(int, sighandler_t);
13+
{} // to silent error from codeql's extractor
14+
typedef void (*sig_t)(int);
15+
extern int signal(int, sig_t);
1816

1917
typedef struct {
2018
unsigned long sig[64];
2119
} sigset_t;
20+
2221
typedef struct {
2322
int si_signo; /* Signal number */
2423
int si_errno; /* An errno value */
2524
int si_code; /* Signal code */
2625
} siginfo_t;
26+
2727
struct sigaction {
2828
void (*sa_handler)(int);
2929
void (*sa_sigaction)(int, siginfo_t *, void *);
@@ -35,9 +35,6 @@ struct sigaction {
3535
extern int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
3636
extern int kill(int pid, int sig);
3737

38-
#ifdef __cplusplus
39-
}
40-
#endif
4138

4239
#endif
4340

cpp/test/include/libc/stdarg.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#ifndef USE_HEADERS
2+
3+
#ifndef HEADER_STDARG_STUB_H
4+
#define HEADER_STDARG_STUB_H
5+
6+
#ifdef __cplusplus
7+
extern "C" {
8+
#endif
9+
10+
typedef void *va_list;
11+
#define va_start(ap, parmN)
12+
#define va_end(ap)
13+
#define va_arg(ap, type) ((type)0)
14+
15+
#ifdef __cplusplus
16+
}
17+
#endif
18+
19+
#endif
20+
21+
#else // --- else USE_HEADERS
22+
23+
#include <stdarg.h>
24+
25+
#endif // --- end USE_HEADERS

cpp/test/include/libc/stdlib.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ long lrand48(void) {
1616
}
1717

1818
void _Exit(int);
19+
void exit(int);
1920

2021
void free(void*);
2122
void *malloc(unsigned long);

cpp/test/include/libc/string_stubs.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ extern int wprintf(const wchar_t * format, ...);
2424
extern wchar_t* wcscpy(wchar_t * s1, const wchar_t * s2);
2525
extern void perror(const char *s);
2626

27+
extern void openlog(const char*, int, int);
28+
extern void syslog(int, const char*, ...);
29+
extern void closelog(void)
30+
2731
#ifdef __cplusplus
2832
}
2933
#endif
@@ -37,6 +41,7 @@ extern void perror(const char *s);
3741
#include <stdio.h>
3842
#include <stdlib.h>
3943
#include <wchar.h>
44+
#include <syslog.h>
4045

4146
#define strcpy_s strcpy
4247
#define _mbsncat strncat

cpp/test/include/libc/unistd.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#ifndef USE_HEADERS
2+
3+
#ifndef HEADER_UNISTD_STUB_H
4+
#define HEADER_UNISTD_STUB_H
5+
6+
#ifdef __cplusplus
7+
extern "C" {
8+
#endif
9+
10+
void _exit(int);
11+
12+
#ifdef __cplusplus
13+
}
14+
#endif
15+
16+
#endif
17+
18+
#else // --- else USE_HEADERS
19+
20+
#include <unistd.h>
21+
22+
#endif // --- end USE_HEADERS

0 commit comments

Comments
 (0)