Skip to content

Commit

Permalink
Merge branch 'main' into add-codeowners
Browse files Browse the repository at this point in the history
  • Loading branch information
GrosQuildu authored Aug 26, 2024
2 parents 013cbd5 + 4a361e8 commit eb86767
Show file tree
Hide file tree
Showing 18 changed files with 684 additions and 52 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/test.yml
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/
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ codeql database analyze database.db --format=sarif-latest --output=./tob.sarif -
|[Memory leak related to custom allocator](./cpp/src/docs/crypto/CustomAllocatorLeak.md)|Finds memory leaks from custom allocated memory|warning|medium|
|[Memory use after free related to custom allocator](./cpp/src/docs/crypto/CustomAllocatorUseAfterFree.md)|Finds use-after-frees related to custom allocators like `BN_new`|warning|medium|
|[Missing OpenSSL engine initialization](./cpp/src/docs/crypto/MissingEngineInit.md)|Finds created OpenSSL engines that may not be properly initialized|warning|medium|
|[Missing error handling](./cpp/src/docs/crypto/ErrorHandling.md)|Checks if returned error codes are properly checked|warning|high|
|[Missing error handling](./cpp/src/docs/crypto/MissingErrorHandling.md)|Checks if returned error codes are properly checked|warning|high|
|[Missing zeroization of potentially sensitive random BIGNUM](./cpp/src/docs/crypto/MissingZeroization.md)|Determines if random bignums are properly zeroized|warning|medium|
|[Random buffer too small](./cpp/src/docs/crypto/RandomBufferTooSmall.md)|Finds buffer overflows in calls to CSPRNGs|warning|high|
|[Use of legacy cryptographic algorithm](./cpp/src/docs/crypto/UseOfLegacyAlgorithm.md)|Detects potential instantiations of legacy cryptographic algorithms|warning|medium|
Expand All @@ -45,6 +45,7 @@ codeql database analyze database.db --format=sarif-latest --output=./tob.sarif -

| Name | Description | Severity | Precision |
| --- | ----------- | :----: | :--------: |
|[Async unsafe signal handler](./cpp/src/docs/security/AsyncUnsafeSignalHandler/AsyncUnsafeSignalHandler.md)|Async unsafe signal handler (like the one used in CVE-2024-6387)|warning|high|
|[Invalid string size passed to string manipulation function](./cpp/src/docs/security/CStrnFinder/CStrnFinder.md)|Finds calls to functions that take as input a string and its size as separate arguments (e.g., `strncmp`, `strncat`, ...) and the size argument is wrong|error|low|
|[Missing null terminator](./cpp/src/docs/security/NoNullTerminator/NoNullTerminator.md)|This query finds incorrectly initialized strings that are passed to functions expecting null-byte-terminated strings|error|high|
|[Unsafe implicit integer conversion](./cpp/src/docs/security/UnsafeImplicitConversions/UnsafeImplicitConversions.md)|Finds implicit integer casts that may overflow or be truncated, with false positive reduction via Value Range Analysis|warning|low|
Expand Down
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)
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;
}
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 cpp/src/security/AsyncUnsafeSignalHandler/AsyncUnsafeSignalHandler.ql
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()
Loading

0 comments on commit eb86767

Please sign in to comment.