-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into add-codeowners
- Loading branch information
Showing
18 changed files
with
684 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
name: CodeQL queries test | ||
on: | ||
pull_request: | ||
push: | ||
branches: | ||
- main | ||
jobs: | ||
test: | ||
name: Run CodeQL query tests | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- id: init | ||
uses: github/codeql-action/init@v3 | ||
- name: Run tests | ||
run: | | ||
${{ steps.init.outputs.codeql-path }} test run ./cpp/test/ | ||
${{ steps.init.outputs.codeql-path }} test run ./go/test/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
cpp/src/docs/security/AsyncUnsafeSignalHandler/AsyncUnsafeSignalHandler.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# Async unsafe signal handler | ||
This is a CodeQL query constructed to find signal handlers that are performing async unsafe operations. | ||
|
||
The kernel defines a list of async-safe signal functions in its [man page](https://man7.org/linux/man-pages/man7/signal-safety.7.html). Any signal handler that performs operations that are not safe asynchronously may be vulnerable. | ||
|
||
|
||
## Recommendation | ||
Attempt to keep signal handlers as simple as possible. Only call async-safe functions from signal handlers. | ||
|
||
|
||
## Example | ||
|
||
```c | ||
#include <signal.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
|
||
enum { MAXLINE = 1024 }; | ||
volatile sig_atomic_t eflag = 0; | ||
char *info = NULL; | ||
|
||
void log_message(void) { | ||
fputs(info, stderr); | ||
} | ||
|
||
void correct_handler(int signum) { | ||
eflag = 1; | ||
} | ||
|
||
int main(void) { | ||
if (signal(SIGINT, correct_handler) == SIG_ERR) { | ||
/* Handle error */ | ||
} | ||
info = (char *)malloc(MAXLINE); | ||
if (info == NULL) { | ||
/* Handle error */ | ||
} | ||
|
||
while (!eflag) { | ||
/* Main loop program code */ | ||
|
||
log_message(); | ||
|
||
/* More program code */ | ||
} | ||
|
||
log_message(); | ||
free(info); | ||
info = NULL; | ||
|
||
return 0; | ||
} | ||
``` | ||
In this example, while both syntatically valid, a correct handler is defined in the `correct_handler` function and sets a flag. The function calls `log_message`, a async unsafe function, within the main loop. | ||
## References | ||
* [SEI CERT C Coding Standard "SIG30-C. Call only asynchronous-safe functions within signal handlers"](https://wiki.sei.cmu.edu/confluence/display/c/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers) | ||
* [regreSSHion: RCE in OpenSSH's server, on glibc-based Linux systems](https://www.qualys.com/2024/07/01/cve-2024-6387/regresshion.txt) | ||
* [signal-safety - async-signal-safe functions](https://man7.org/linux/man-pages/man7/signal-safety.7.html) |
39 changes: 39 additions & 0 deletions
39
cpp/src/security/AsyncUnsafeSignalHandler/AsyncUnsafeSignalHandler.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
#include <signal.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
|
||
enum { MAXLINE = 1024 }; | ||
volatile sig_atomic_t eflag = 0; | ||
char *info = NULL; | ||
|
||
void log_message(void) { | ||
fputs(info, stderr); | ||
} | ||
|
||
void correct_handler(int signum) { | ||
eflag = 1; | ||
} | ||
|
||
int main(void) { | ||
if (signal(SIGINT, correct_handler) == SIG_ERR) { | ||
/* Handle error */ | ||
} | ||
info = (char *)malloc(MAXLINE); | ||
if (info == NULL) { | ||
/* Handle error */ | ||
} | ||
|
||
while (!eflag) { | ||
/* Main loop program code */ | ||
|
||
log_message(); | ||
|
||
/* More program code */ | ||
} | ||
|
||
log_message(); | ||
free(info); | ||
info = NULL; | ||
|
||
return 0; | ||
} |
64 changes: 64 additions & 0 deletions
64
cpp/src/security/AsyncUnsafeSignalHandler/AsyncUnsafeSignalHandler.qhelp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
<!DOCTYPE qhelp PUBLIC | ||
"-//Semmle//qhelp//EN" | ||
"qhelp.dtd"> | ||
<qhelp> | ||
<overview> | ||
<p> | ||
This is a CodeQL query constructed to find signal handlers that are performing async unsafe operations. | ||
</p> | ||
|
||
<p> | ||
Because a signal may be delivered at any moment, e.g., in the middle of a malloc, the handler shouldn't touch | ||
the program's internal state. | ||
</p> | ||
|
||
<p> | ||
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>. | ||
Any signal handler that performs operations that are not safe for asynchronous execution may be vulnerable. | ||
</p> | ||
|
||
<p> | ||
Moreover, signal handlers may be re-entered. Handlers' logic should take that possibility into account. | ||
</p> | ||
|
||
<p> | ||
If the issue is exploitable depends on attacker's ability to deliver the signal. | ||
Remote attacks may be limitted to some signals (like SIGALRM and SIGURG), while local attacks could use all signals. | ||
</p> | ||
</overview> | ||
|
||
<recommendation> | ||
<p> | ||
Attempt to keep signal handlers as simple as possible. Only call async-safe functions from signal handlers. | ||
</p> | ||
<p> | ||
Block delivery of new signals inside signal handlers to prevent handler re-entrancy issues. | ||
</p> | ||
</recommendation> | ||
|
||
<example> | ||
<sample src="AsyncUnsafeSignalHandler.c" /> | ||
|
||
<p> | ||
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. | ||
</p> | ||
</example> | ||
|
||
<references> | ||
<li> | ||
<a href="https://lcamtuf.coredump.cx/signals.txt">Michal Zalewski, "Delivering Signals for Fun and Profit"</a> | ||
</li> | ||
<li> | ||
<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> | ||
</li> | ||
<li> | ||
<a href="https://www.qualys.com/2024/07/01/cve-2024-6387/regresshion.txt">regreSSHion: RCE in OpenSSH's server, on glibc-based Linux systems</a> | ||
</li> | ||
<li> | ||
<a href="https://man7.org/linux/man-pages/man7/signal-safety.7.html">signal-safety - async-signal-safe functions</a> | ||
</li> | ||
</references> | ||
|
||
</qhelp> | ||
|
153 changes: 153 additions & 0 deletions
153
cpp/src/security/AsyncUnsafeSignalHandler/AsyncUnsafeSignalHandler.ql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/** | ||
* @name Async unsafe signal handler | ||
* @id tob/cpp/async-unsafe-signal-handler | ||
* @description Async unsafe signal handler (like the one used in CVE-2024-6387) | ||
* @kind problem | ||
* @tags security | ||
* @problem.severity warning | ||
* @precision high | ||
* @security-severity 7.0 | ||
* @group security | ||
*/ | ||
|
||
import cpp | ||
import semmle.code.cpp.dataflow.new.DataFlow | ||
|
||
|
||
/* List from https://man7.org/linux/man-pages/man3/stdio.3.html */ | ||
class StdioFunction extends Function { | ||
StdioFunction() { | ||
this.getName() in [ | ||
"fopen", "freopen", "fflush", "fclose", "fread", "fwrite", "fgetc", "fgets", "fputc", | ||
"fputs", "getc", "getchar", "gets", "putc", "putchar", "puts", "ungetc", "fprintf", | ||
"fscanf", "printf", "scanf", "sprintf", "sscanf", "vprintf", "vfprintf", "vsprintf", | ||
"fgetpos", "fsetpos", "ftell", "fseek", "rewind", "clearerr", "feof", "ferror", "perror", | ||
"setbuf", "setvbuf", "remove", "rename", "tmpfile", "tmpnam", | ||
] | ||
} | ||
} | ||
|
||
/* List from https://man7.org/linux/man-pages/man3/syslog.3.html */ | ||
class SyslogFunction extends Function { | ||
SyslogFunction() { this.getName() in ["syslog", "vsyslog",] } | ||
} | ||
|
||
/* List from https://man7.org/linux/man-pages/man0/stdlib.h.0p.html */ | ||
class StdlibFunction extends Function { | ||
StdlibFunction() { this.getName() in ["malloc", "calloc", "realloc", "free",] } | ||
} | ||
|
||
predicate isAsyncUnsafe(Function signalHandler) { | ||
exists(Function f | | ||
signalHandler.calls+(f) and | ||
( | ||
f instanceof StdioFunction or | ||
f instanceof SyslogFunction or | ||
f instanceof StdlibFunction | ||
) | ||
) | ||
} | ||
|
||
/** | ||
* Flows from any function ptr | ||
*/ | ||
module HandlerToSignalConfiguration implements DataFlow::ConfigSig { | ||
predicate isSource(DataFlow::Node source) { | ||
source.asExpr() = any(Function f || f.getAnAccess()) | ||
} | ||
|
||
predicate isSink(DataFlow::Node sink) { | ||
sink = sink | ||
} | ||
} | ||
module HandlerToSignal = DataFlow::Global<HandlerToSignalConfiguration>; | ||
|
||
/** | ||
* signal(SIGX, signalHandler) | ||
*/ | ||
predicate isSignal(FunctionCall signalCall, Function signalHandler) { | ||
signalCall.getTarget().getName() = "signal" | ||
and exists(DataFlow::Node source, DataFlow::Node sink | | ||
HandlerToSignal::flow(source, sink) | ||
and source.asExpr() = signalHandler.getAnAccess() | ||
and sink.asExpr() = signalCall.getArgument(1) | ||
) | ||
} | ||
|
||
/** | ||
* struct sigaction sigactVar = ... | ||
* sigaction(SIGX, &sigactVar, ...) | ||
*/ | ||
predicate isSigaction(FunctionCall sigactionCall, Function signalHandler, Variable sigactVar) { | ||
exists(Struct sigactStruct, Field handlerField, DataFlow::Node source, DataFlow::Node sink | | ||
sigactionCall.getTarget().getName() = "sigaction" | ||
and sigactionCall.getArgument(1).getAChild*() = sigactVar.getAnAccess() | ||
|
||
// struct sigaction with the handler func | ||
and sigactStruct.getName() = "sigaction" | ||
and handlerField.getName() = ["sa_handler", "sa_sigaction", "__sa_handler", "__sa_sigaction", "__sigaction_u"] | ||
and ( | ||
handlerField = sigactStruct.getAField() | ||
or | ||
exists(Union u | u.getAField() = handlerField and u = sigactStruct.getAField().getType()) | ||
) | ||
|
||
and ( | ||
// sigactVar.sa_handler = &signalHandler | ||
exists(Assignment a, ValueFieldAccess vfa | | ||
vfa.getTarget() = handlerField | ||
and vfa.getQualifier+() = sigactVar.getAnAccess() | ||
and a.getLValue() = vfa.getAChild*() | ||
|
||
and source.asExpr() = signalHandler.getAnAccess() | ||
and sink.asExpr() = a.getRValue() | ||
and HandlerToSignal::flow(source, sink) | ||
) | ||
or | ||
// struct sigaction sigactVar = {.sa_sigaction = signalHandler}; | ||
exists(ClassAggregateLiteral initLit | | ||
sigactVar.getInitializer().getExpr() = initLit | ||
and source.asExpr() = signalHandler.getAnAccess() | ||
and sink.asExpr() = initLit.getAFieldExpr(handlerField).getAChild*() | ||
and HandlerToSignal::flow(source, sink) | ||
) | ||
) | ||
) | ||
} | ||
|
||
/** | ||
* Determine if new signals are blocked | ||
* TODO: should only find writes and only for correct (or all) signals | ||
* TODO: should detect other block mechanisms | ||
*/ | ||
predicate isSignalDeliveryBlocked(Variable sigactVar) { | ||
exists(ValueFieldAccess dfa | | ||
dfa.getQualifier+() = sigactVar.getAnAccess() and dfa.getTarget().getName() = "sa_mask" | ||
) | ||
or | ||
exists(Field mask | | ||
mask.getName() = "sa_mask" | ||
and exists(sigactVar.getInitializer().getExpr().(ClassAggregateLiteral).getAFieldExpr(mask)) | ||
) | ||
} | ||
|
||
string deliveryNotBlockedMsg() { | ||
result = "Moreover, delivery of new signals may be not blocked. " | ||
} | ||
|
||
from FunctionCall fc, Function signalHandler, string msg | ||
where | ||
isAsyncUnsafe(signalHandler) | ||
and ( | ||
(isSignal(fc, signalHandler) and msg = deliveryNotBlockedMsg()) | ||
or | ||
exists(Variable sigactVar | | ||
isSigaction(fc, signalHandler, sigactVar) | ||
and if isSignalDeliveryBlocked(sigactVar) then | ||
msg = "" | ||
else | ||
msg = deliveryNotBlockedMsg() | ||
) | ||
) | ||
select signalHandler, "$@ is a non-trivial signal handler that uses not async-safe functions. " + msg + | ||
"Handler is registered by $@.", signalHandler, signalHandler.toString(), fc, fc.toString() |
Oops, something went wrong.