@@ -15,110 +15,92 @@ import cpp
1515private import semmle.code.cpp.ir.dataflow.TaintTracking
1616private import semmle.code.cpp.security.FlowSources
1717private import semmle.code.cpp.security.CommandExecution
18+ private import semmle.code.cpp.controlflow.IRGuards
1819
1920/**
20- * Generic case: invoke protocol handling through OS's protocol handling utilities. This aligns with CVE-2022-43550.
21+ * Holds when `call` invokes a system function (system, popen, exec*) with a command string
22+ * that contains a URL protocol handler, `sink` is the argument carrying the URL, and
23+ * `handlerType` describes which handler is invoked.
2124 */
22- class ShellProtocolHandler extends SystemFunction {
23- ShellProtocolHandler ( ) {
24- // Check if any calls to this function contain protocol handler invocations
25- exists ( FunctionCall call |
26- call .getTarget ( ) = this and
27- exists ( Expr arg |
28- arg = call .getArgument ( 0 ) .getAChild * ( ) and
29- exists ( StringLiteral sl | sl = arg or sl .getParent * ( ) = arg |
30- sl .getValue ( )
31- .regexpMatch ( "(?i).*(rundll32.*url\\.dll.*FileProtocolHandler|xdg-open|\\bopen\\b).*" )
32- )
33- )
25+ predicate isShellProtocolHandlerSink ( FunctionCall call , Expr sink , string handlerType ) {
26+ call .getTarget ( ) instanceof SystemFunction and
27+ exists ( StringLiteral sl |
28+ sl .getParent * ( ) = call .getArgument ( 0 ) and
29+ (
30+ sl .getValue ( ) .regexpMatch ( "(?i).*rundll32.*url\\.dll.*FileProtocolHandler.*" ) and
31+ handlerType = "rundll32 url.dll,FileProtocolHandler"
32+ or
33+ sl .getValue ( ) .regexpMatch ( "(?i).*xdg-open.*" ) and
34+ handlerType = "xdg-open"
35+ or
36+ sl .getValue ( ) .regexpMatch ( "(?i).*\\bopen\\b.*" ) and
37+ handlerType = "open"
3438 )
35- }
36-
37- string getHandlerType ( ) {
38- exists ( FunctionCall call , StringLiteral sl |
39- call .getTarget ( ) = this and
40- sl .getParent * ( ) = call .getArgument ( 0 ) and
41- (
42- sl .getValue ( ) .regexpMatch ( "(?i).*rundll32.*url\\.dll.*FileProtocolHandler.*" ) and
43- result = "rundll32 url.dll,FileProtocolHandler"
44- or
45- sl .getValue ( ) .regexpMatch ( "(?i).*xdg-open.*" ) and
46- result = "xdg-open"
47- or
48- sl .getValue ( ) .regexpMatch ( "(?i).*\\bopen\\b.*" ) and
49- result = "open"
50- )
51- )
52- }
39+ ) and
40+ sink = call .getArgument ( 0 )
5341}
5442
5543/**
56- * Qt's QDesktopServices::openUrl method
44+ * Holds when `call` invokes QDesktopServices::openUrl and `sink` is the URL argument.
5745 */
58- class QtProtocolHandler extends FunctionCall {
59- QtProtocolHandler ( ) { this .getTarget ( ) .hasQualifiedName ( "QDesktopServices" , "openUrl" ) }
46+ predicate isQtProtocolHandlerSink ( FunctionCall call , Expr sink ) {
47+ call .getTarget ( ) .hasQualifiedName ( "" , "QDesktopServices" , "openUrl" ) and
48+ sink = call .getArgument ( 0 )
49+ }
6050
61- Expr getUrlArgument ( ) { result = this .getArgument ( 0 ) }
51+ /**
52+ * Gets the sink expression for a given DataFlow::Node matching either Qt or shell handler sinks.
53+ */
54+ Expr getSinkExpr ( DataFlow:: Node sink ) {
55+ result = sink .asExpr ( ) or
56+ result = sink .asIndirectExpr ( )
6257}
6358
6459/**
65- * A sanitizer node that represents URL scheme validation
60+ * Holds when `g` is a guard condition that validates the URL scheme of expression `e`.
61+ * Flow is considered safe on the `branch` edge.
6662 */
67- class UrlSchemeValidationSanitizer extends DataFlow:: Node {
68- UrlSchemeValidationSanitizer ( ) {
69- exists ( FunctionCall fc |
70- fc = this .asExpr ( ) and
71- (
72- // String comparison on the untrusted URL
73- fc .getTarget ( ) .getName ( ) =
74- [
75- "strcmp" , "strncmp" , "strcasecmp" , "strncasecmp" , "strstr" , "strcasestr" , "_stricmp" ,
76- "_strnicmp"
77- ]
78- or
79- // Qt QUrl::scheme() comparison - QUrl::scheme() returns QString
80- // Pattern: url.scheme() == "http" or url.scheme() == "https"
81- exists ( FunctionCall schemeCall |
82- schemeCall .getTarget ( ) .hasQualifiedName ( "QUrl" , "scheme" ) and
83- (
84- // Direct comparison
85- fc .getTarget ( ) .hasName ( [ "operator==" , "operator!=" ] ) and
86- fc .getAnArgument ( ) = schemeCall
87- or
88- // QString comparison methods
89- fc = schemeCall and
90- exists ( FunctionCall qstringCmp |
91- qstringCmp .getQualifier ( ) = schemeCall and
92- qstringCmp .getTarget ( ) .hasQualifiedName ( "QString" , [ "compare" , "operator==" ] )
93- )
94- )
95- )
96- or
97- // Qt QString startsWith check for direct URL strings
98- fc .getTarget ( ) .hasQualifiedName ( "QString" , "startsWith" )
99- )
63+ predicate urlSchemeGuardChecks ( IRGuardCondition g , Expr e , boolean branch ) {
64+ branch = [ true , false ] and
65+ exists ( FunctionCall fc | g .getUnconvertedResultExpression ( ) .getAChild * ( ) = fc |
66+ // C string comparison on the URL (strcmp, strncmp, etc.)
67+ fc .getTarget ( ) .getName ( ) =
68+ [
69+ "strcmp" , "strncmp" , "strcasecmp" , "strncasecmp" , "strstr" , "strcasestr" , "_stricmp" ,
70+ "_strnicmp"
71+ ] and
72+ e = fc .getAnArgument ( )
73+ or
74+ // Qt QString::startsWith check
75+ fc .getTarget ( ) .hasQualifiedName ( "" , "QString" , "startsWith" ) and
76+ e = fc .getQualifier ( )
77+ or
78+ // Qt QUrl::scheme() comparison
79+ exists ( FunctionCall schemeCall |
80+ schemeCall .getTarget ( ) .hasQualifiedName ( "" , "QUrl" , "scheme" ) and
81+ fc .getAnArgument ( ) = schemeCall and
82+ e = schemeCall .getQualifier ( )
10083 )
101- }
84+ )
10285}
10386
10487/**
105- * Configuration for tracking untrusted data to protocol handler invocations
88+ * Configuration for tracking untrusted data to protocol handler invocations.
10689 */
10790module PotentiallyUnguardedProtocolHandlerConfig implements DataFlow:: ConfigSig {
10891 predicate isSource ( DataFlow:: Node source ) { source instanceof RemoteFlowSource }
10992
11093 predicate isSink ( DataFlow:: Node sink ) {
111- // QDesktopServices::openUrl()
112- exists ( QtProtocolHandler call | sink .asExpr ( ) = call .getUrlArgument ( ) )
94+ isQtProtocolHandlerSink ( _, getSinkExpr ( sink ) )
11395 or
114- // Shell protocol handlers (rundll32, xdg-open, open) via system()/popen()/exec*()
115- exists ( FunctionCall call |
116- call .getTarget ( ) instanceof ShellProtocolHandler and
117- sink .asExpr ( ) = call .getArgument ( 0 )
118- )
96+ isShellProtocolHandlerSink ( _, getSinkExpr ( sink ) , _)
11997 }
12098
121- predicate isBarrier ( DataFlow:: Node node ) { node instanceof UrlSchemeValidationSanitizer }
99+ predicate isBarrier ( DataFlow:: Node node ) {
100+ node = DataFlow:: BarrierGuard< urlSchemeGuardChecks / 3 > :: getABarrierNode ( )
101+ or
102+ node = DataFlow:: BarrierGuard< urlSchemeGuardChecks / 3 > :: getAnIndirectBarrierNode ( )
103+ }
122104}
123105
124106module PotentiallyUnguardedProtocolHandlerFlow =
@@ -132,17 +114,10 @@ from
132114where
133115 PotentiallyUnguardedProtocolHandlerFlow:: flowPath ( source , sink ) and
134116 (
135- exists ( QtProtocolHandler qtCall |
136- call = qtCall and
137- sink .getNode ( ) .asExpr ( ) = qtCall .getUrlArgument ( ) and
138- callType = "QDesktopServices::openUrl()"
139- )
117+ isQtProtocolHandlerSink ( call , getSinkExpr ( sink .getNode ( ) ) ) and
118+ callType = "QDesktopServices::openUrl()"
140119 or
141- exists ( ShellProtocolHandler shellFunc |
142- call .getTarget ( ) = shellFunc and
143- sink .getNode ( ) .asExpr ( ) = call .getArgument ( 0 ) and
144- callType = shellFunc .getHandlerType ( )
145- )
120+ isShellProtocolHandlerSink ( call , getSinkExpr ( sink .getNode ( ) ) , callType )
146121 )
147122select call , source , sink ,
148123 callType + " is called with untrusted input from $@ without proper URL scheme validation." ,
0 commit comments