Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ codeql database analyze database.db --format=sarif-latest --output=./tob.sarif -
|[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|
|[Iterator invalidation](./cpp/src/docs/security/IteratorInvalidation/IteratorInvalidation.md)|Modifying a container while iterating over it can invalidate iterators, leading to undefined behavior.|warning|medium|
|[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|
|[Potentially unguarded protocol handler invocation](./cpp/src/docs/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.md)|Detects calls to URL protocol handlers with untrusted input that may not be properly validated for dangerous protocols|warning|medium|
|[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|

### Go

Expand All @@ -77,7 +79,8 @@ codeql database analyze database.db --format=sarif-latest --output=./tob.sarif -

| Name | Description | Severity | Precision |
| --- | ----------- | :----: | :--------: |
|[Recursive functions](./java-kotlin/src/docs/security/Recursion/Recursion.md)|Detects possibly unbounded recursive calls|warning|low|
|[Potentially unguarded protocol handler invocation](./java/src/docs/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.md)|Detects calls to URL protocol handlers with untrusted input that may not be properly validated for dangerous protocols|warning|medium|
|[Recursive functions](./java-kotlin/src/docs/security/Recursion/Recursion.md)|Detects recursive calls|warning|low|

## Query suites

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Potentially unguarded protocol handler invocation

This query detects calls to URL protocol handlers with untrusted input that may not be properly validated for dangerous protocols. This vulnerability is related to CWE-939 (Improper Authorization in Handler for Custom URL Scheme) and aligns with CVE-2022-43550.

When applications invoke protocol handlers (like `rundll32 url.dll,FileProtocolHandler` on Windows, `xdg-open` on Linux, `open` on macOS, or Qt's `QDesktopServices::openUrl()`), untrusted URLs can potentially trigger dangerous protocols such as `file://`, `smb://`, or other custom handlers that may lead to unauthorized file access, command execution, or other security issues.

## Detected patterns

The query identifies several common protocol handler invocation patterns:

- **Windows**: `rundll32 url.dll,FileProtocolHandler` via system calls
- **Linux**: `xdg-open` via system calls
- **macOS**: `open` command via system calls
- **Qt applications**: `QDesktopServices::openUrl()`

## Recommendation

Always validate URL schemes before passing them to protocol handlers. Only allow safe protocols like `http://` and `https://`. Reject or sanitize URLs containing potentially dangerous protocols.

## Example

The following vulnerable code passes untrusted input directly to a protocol handler:

```cpp
#include <QDesktopServices>
#include <QUrl>

void openUserUrl(const QString& userInput) {
// VULNERABLE: No validation of the URL scheme
QDesktopServices::openUrl(QUrl(userInput));
}
```

An attacker could provide a URL like `file:///etc/passwd` or `smb://attacker-server/share` to access unauthorized resources.

The corrected version validates the URL scheme before opening:

```cpp
#include <QDesktopServices>
#include <QUrl>

void openUserUrl(const QString& userInput) {
QUrl url(userInput);
QString scheme = url.scheme().toLower();

// Only allow safe protocols
if (scheme == "http" || scheme == "https") {
QDesktopServices::openUrl(url);
} else {
// Log error or show warning to user
qWarning() << "Rejected unsafe URL scheme:" << scheme;
}
}
```

For system command invocations:

```cpp
void openUserUrlViaShell(const char* userInput) {
// VULNERABLE: Untrusted input passed to xdg-open
char cmd[512];
snprintf(cmd, sizeof(cmd), "xdg-open '%s'", userInput);
system(cmd);
}
```

Should be corrected to validate the scheme:

```cpp
bool isValidScheme(const char* url) {
return (strncasecmp(url, "http://", 7) == 0 ||
strncasecmp(url, "https://", 8) == 0);
}

void openUserUrlViaShell(const char* userInput) {
if (isValidScheme(userInput)) {
char cmd[512];
snprintf(cmd, sizeof(cmd), "xdg-open '%s'", userInput);
system(cmd);
}
}
```

## References

- [CWE-939: Improper Authorization in Handler for Custom URL Scheme](https://cwe.mitre.org/data/definitions/939.html)
- [CVE-2022-43550: USB Creator has insufficiently protected credentials](https://ubuntu.com/security/CVE-2022-43550)
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* @name Potentially unguarded protocol handler invocation
* @id tob/cpp/unguarded-protocol-handler
* @description Detects calls to URL protocol handlers with untrusted input that may not be properly validated for dangerous protocols
* @kind path-problem
* @tags security
* external/cwe/cwe-939
* @precision medium
* @problem.severity warning
* @security-severity 6.5
* @group security
*/

import cpp
private import semmle.code.cpp.ir.dataflow.TaintTracking
private import semmle.code.cpp.security.FlowSources
private import semmle.code.cpp.security.CommandExecution
private import semmle.code.cpp.controlflow.IRGuards

/**
* Holds when `call` invokes a system function (system, popen, exec*) with a command string
* that contains a URL protocol handler, `sink` is the argument carrying the URL, and
* `handlerType` describes which handler is invoked.
*/
predicate isShellProtocolHandlerSink(FunctionCall call, Expr sink, string handlerType) {
call.getTarget() instanceof SystemFunction and
exists(StringLiteral sl |
sl.getParent*() = call.getArgument(0) and
(
sl.getValue().regexpMatch("(?i).*rundll32.*url\\.dll.*FileProtocolHandler.*") and
handlerType = "rundll32 url.dll,FileProtocolHandler"
or
sl.getValue().regexpMatch("(?i).*xdg-open.*") and
handlerType = "xdg-open"
or
sl.getValue().regexpMatch("(?i).*\\bopen\\b.*") and
handlerType = "open"
)
) and
sink = call.getArgument(0)
}

/**
* Holds when `call` invokes QDesktopServices::openUrl and `sink` is the URL argument.
*/
predicate isQtProtocolHandlerSink(FunctionCall call, Expr sink) {
call.getTarget().hasQualifiedName("", "QDesktopServices", "openUrl") and
sink = call.getArgument(0)
}

/**
* Gets the sink expression for a given DataFlow::Node matching either Qt or shell handler sinks.
*/
Expr getSinkExpr(DataFlow::Node sink) {
result = sink.asExpr() or
result = sink.asIndirectExpr()
}

/**
* Holds when `g` is a guard condition that validates the URL scheme of expression `e`.
* Flow is considered safe on the `branch` edge.
*/
predicate urlSchemeGuardChecks(IRGuardCondition g, Expr e, boolean branch) {
branch = [true, false] and
exists(FunctionCall fc | g.getUnconvertedResultExpression().getAChild*() = fc |
// C string comparison on the URL (strcmp, strncmp, etc.)
fc.getTarget().getName() =
[
"strcmp", "strncmp", "strcasecmp", "strncasecmp", "strstr", "strcasestr", "_stricmp",
"_strnicmp"
] and
e = fc.getAnArgument()
or
// Qt QString::startsWith check
fc.getTarget().hasQualifiedName("", "QString", "startsWith") and
e = fc.getQualifier()
or
// Qt QUrl::scheme() comparison
exists(FunctionCall schemeCall |
schemeCall.getTarget().hasQualifiedName("", "QUrl", "scheme") and
fc.getAnArgument() = schemeCall and
e = schemeCall.getQualifier()
)
)
}

/**
* Configuration for tracking untrusted data to protocol handler invocations.
*/
module PotentiallyUnguardedProtocolHandlerConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }

predicate isSink(DataFlow::Node sink) {
isQtProtocolHandlerSink(_, getSinkExpr(sink))
or
isShellProtocolHandlerSink(_, getSinkExpr(sink), _)
}

predicate isBarrier(DataFlow::Node node) {
node = DataFlow::BarrierGuard<urlSchemeGuardChecks/3>::getABarrierNode()
or
node = DataFlow::BarrierGuard<urlSchemeGuardChecks/3>::getAnIndirectBarrierNode()
}
}

module PotentiallyUnguardedProtocolHandlerFlow =
TaintTracking::Global<PotentiallyUnguardedProtocolHandlerConfig>;

import PotentiallyUnguardedProtocolHandlerFlow::PathGraph

from
PotentiallyUnguardedProtocolHandlerFlow::PathNode source,
PotentiallyUnguardedProtocolHandlerFlow::PathNode sink, FunctionCall call, string callType
where
PotentiallyUnguardedProtocolHandlerFlow::flowPath(source, sink) and
(
isQtProtocolHandlerSink(call, getSinkExpr(sink.getNode())) and
callType = "QDesktopServices::openUrl()"
or
isShellProtocolHandlerSink(call, getSinkExpr(sink.getNode()), callType)
)
select call, source, sink,
callType + " is called with untrusted input from $@ without proper URL scheme validation.",
source.getNode(), "this source"
9 changes: 9 additions & 0 deletions cpp/test/include/libc/stdint.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
#ifndef _SIZE_T_DEFINED
#define _SIZE_T_DEFINED
typedef unsigned long size_t;
#endif

#ifndef _SSIZE_T_DEFINED
#define _SSIZE_T_DEFINED
typedef long ssize_t;
#endif

#endif
#else // --- else USE_HEADERS
Expand Down
2 changes: 2 additions & 0 deletions cpp/test/include/libc/stdlib.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
extern "C" {
#endif

extern int system(const char *);

extern void *reallocf(void *, unsigned long);

int rand(void) {
Expand Down
7 changes: 7 additions & 0 deletions cpp/test/include/libc/string_stubs.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ extern "C" {
typedef int wchar_t;
#endif

#ifndef _SIZE_T_DEFINED
#define _SIZE_T_DEFINED
typedef unsigned long size_t;
#endif

extern void *memcpy(void *dst, const void *src, unsigned long n);
extern char* strcpy_s(char* dst, int max_amount, char* src);
extern int _mbsncat(char* dst, char* src, int count);
Expand All @@ -28,6 +33,8 @@ extern int wprintf(const wchar_t * format, ...);
extern wchar_t* wcscpy(wchar_t * s1, const wchar_t * s2);
extern void perror(const char *s);
extern int puts(const char *s);
extern int strcmp(const char *, const char *);
extern int strncmp(const char *, const char *, size_t);

extern void openlog(const char*, int, int);
extern void syslog(int, const char*, ...);
Expand Down
12 changes: 12 additions & 0 deletions cpp/test/include/libc/unistd.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@
#ifndef HEADER_UNISTD_STUB_H
#define HEADER_UNISTD_STUB_H

#ifndef _SIZE_T_DEFINED
#define _SIZE_T_DEFINED
typedef unsigned long size_t;
#endif

#ifndef _SSIZE_T_DEFINED
#define _SSIZE_T_DEFINED
typedef long ssize_t;
#endif

#ifdef __cplusplus
extern "C" {
#endif

ssize_t read(int, void *, size_t);

void _exit(int);

#ifdef __cplusplus
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Forward declarations - minimal stubs
extern "C" {
int read(int fd, void *buf, unsigned long count);
int system(const char *);
int sprintf(char *, const char *, ...);
int strncmp(const char *, const char *, unsigned long);
}

struct QString {
const char *data;
QString(const char *s) : data(s) {}
bool operator==(const char *other) const;
bool startsWith(const char *prefix) const;
};

struct QUrl {
const char *url;
QUrl(const char *s) : url(s) {}
QString scheme() const;
};

struct QDesktopServices {
static bool openUrl(const QUrl &url);
};

// BAD: read() into buffer, then pass to openUrl without scheme check
void bad1_qt_read() {
char buf[1024];
read(0, buf, sizeof(buf));
QDesktopServices::openUrl(QUrl(buf)); // BAD
}

// BAD: read() into buffer, intermediate usage, then openUrl without guard
void bad2_qt_read_indirect() {
char buf[1024];
char url[2048];
read(0, buf, sizeof(buf));
sprintf(url, "file://%s", buf);
// no scheme check before opening
QDesktopServices::openUrl(QUrl(url)); // BAD
}

// GOOD: hardcoded URL
void safe1_qt_hardcoded() {
QDesktopServices::openUrl(QUrl("https://example.com")); // GOOD
}

// GOOD: scheme check before openUrl
void safe2_qt_scheme_check() {
char buf[1024];
read(0, buf, sizeof(buf));
if (strncmp(buf, "https://", 8) == 0) {
QDesktopServices::openUrl(QUrl(buf)); // GOOD
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
edges
Comment thread
ex0dus-0x marked this conversation as resolved.
| PotentiallyUnguardedProtocolHandler.cpp:29:11:29:13 | read output argument | PotentiallyUnguardedProtocolHandler.cpp:30:29:30:37 | call to QUrl | provenance | TaintFunction |
| PotentiallyUnguardedProtocolHandler.cpp:29:11:29:13 | read output argument | PotentiallyUnguardedProtocolHandler.cpp:30:29:30:37 | call to QUrl | provenance | TaintFunction |
| PotentiallyUnguardedProtocolHandler.cpp:29:11:29:13 | read output argument | PotentiallyUnguardedProtocolHandler.cpp:30:29:30:37 | call to QUrl | provenance | TaintFunction |
| PotentiallyUnguardedProtocolHandler.cpp:30:29:30:37 | call to QUrl | PotentiallyUnguardedProtocolHandler.cpp:30:29:30:37 | call to QUrl | provenance | |
| PotentiallyUnguardedProtocolHandler.cpp:37:11:37:13 | read output argument | PotentiallyUnguardedProtocolHandler.cpp:40:29:40:37 | call to QUrl | provenance | TaintFunction |
| PotentiallyUnguardedProtocolHandler.cpp:37:11:37:13 | read output argument | PotentiallyUnguardedProtocolHandler.cpp:40:29:40:37 | call to QUrl | provenance | TaintFunction |
| PotentiallyUnguardedProtocolHandler.cpp:37:11:37:13 | read output argument | PotentiallyUnguardedProtocolHandler.cpp:40:29:40:37 | call to QUrl | provenance | TaintFunction |
| PotentiallyUnguardedProtocolHandler.cpp:40:29:40:37 | call to QUrl | PotentiallyUnguardedProtocolHandler.cpp:40:29:40:37 | call to QUrl | provenance | |
nodes
| PotentiallyUnguardedProtocolHandler.cpp:29:11:29:13 | read output argument | semmle.label | read output argument |
| PotentiallyUnguardedProtocolHandler.cpp:30:29:30:37 | call to QUrl | semmle.label | call to QUrl |
| PotentiallyUnguardedProtocolHandler.cpp:30:29:30:37 | call to QUrl | semmle.label | call to QUrl |
| PotentiallyUnguardedProtocolHandler.cpp:30:29:30:37 | call to QUrl | semmle.label | call to QUrl |
| PotentiallyUnguardedProtocolHandler.cpp:37:11:37:13 | read output argument | semmle.label | read output argument |
| PotentiallyUnguardedProtocolHandler.cpp:40:29:40:37 | call to QUrl | semmle.label | call to QUrl |
| PotentiallyUnguardedProtocolHandler.cpp:40:29:40:37 | call to QUrl | semmle.label | call to QUrl |
| PotentiallyUnguardedProtocolHandler.cpp:40:29:40:37 | call to QUrl | semmle.label | call to QUrl |
subpaths
#select
| PotentiallyUnguardedProtocolHandler.cpp:30:3:30:27 | call to openUrl | PotentiallyUnguardedProtocolHandler.cpp:29:11:29:13 | read output argument | PotentiallyUnguardedProtocolHandler.cpp:30:29:30:37 | call to QUrl | QDesktopServices::openUrl() is called with untrusted input from $@ without proper URL scheme validation. | PotentiallyUnguardedProtocolHandler.cpp:29:11:29:13 | read output argument | this source |
| PotentiallyUnguardedProtocolHandler.cpp:30:3:30:27 | call to openUrl | PotentiallyUnguardedProtocolHandler.cpp:29:11:29:13 | read output argument | PotentiallyUnguardedProtocolHandler.cpp:30:29:30:37 | call to QUrl | QDesktopServices::openUrl() is called with untrusted input from $@ without proper URL scheme validation. | PotentiallyUnguardedProtocolHandler.cpp:29:11:29:13 | read output argument | this source |
| PotentiallyUnguardedProtocolHandler.cpp:40:3:40:27 | call to openUrl | PotentiallyUnguardedProtocolHandler.cpp:37:11:37:13 | read output argument | PotentiallyUnguardedProtocolHandler.cpp:40:29:40:37 | call to QUrl | QDesktopServices::openUrl() is called with untrusted input from $@ without proper URL scheme validation. | PotentiallyUnguardedProtocolHandler.cpp:37:11:37:13 | read output argument | this source |
| PotentiallyUnguardedProtocolHandler.cpp:40:3:40:27 | call to openUrl | PotentiallyUnguardedProtocolHandler.cpp:37:11:37:13 | read output argument | PotentiallyUnguardedProtocolHandler.cpp:40:29:40:37 | call to QUrl | QDesktopServices::openUrl() is called with untrusted input from $@ without proper URL scheme validation. | PotentiallyUnguardedProtocolHandler.cpp:37:11:37:13 | read output argument | this source |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.ql
Loading
Loading