Skip to content

Commit 5aaadd6

Browse files
committed
Make SapDefineModule extend AmdModuleDefinition::Range and update the
expected results `SapDefineModule` in its previous form did not extend `AmdModuleDefinition::Range` which in turn extends standard library's `Module` that enables its subclasses to be identified with MaD definitions. The reason the queries have been working was thanks to `UI5AMDModule.qll` that provided a `SapAmdModule` class extending the `Module` class directly. The problem with `SapAmdModule` copy-pasted and only slightly modified the `AMD.qll` in the standard library, so it was out of sync with the standard library couterpart when the DataFlow library behind it was overhauled to have a new API. It was causing the majority of the problems when the `qlpack.yml`s were updated with the latest DataFlow API and the `SapAmdModule` failed to play nicely with the updated library modules, emitting new non-monotonic recursion errors. Therefore, this commit makes the `SapDefineModule` in `UI5.qll` extend `AmdModuleDefinition::Range` and removes the outdated `SapAmdModule`. `SapDefineModule`s are AMD-style modules defined with `sap.ui.define` or `sap.ui.require` function calls that augments the global AMD `define` functions with the capability of extending another such module.
1 parent 29a44fb commit 5aaadd6

File tree

21 files changed

+208
-196
lines changed

21 files changed

+208
-196
lines changed

javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/RemoteFlowSources.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ class ODataServiceModel extends UI5ExternalModel {
8383
this instanceof NewNode and
8484
(
8585
exists(RequiredObject oDataModel |
86-
oDataModel.flowsTo(this.getCalleeNode()) and
87-
oDataModel.getDependencyType() = "sap/ui/model/odata/v2/ODataModel"
86+
oDataModel.asSourceNode().flowsTo(this.getCalleeNode()) and
87+
oDataModel.getDependency() = "sap/ui/model/odata/v2/ODataModel"
8888
)
8989
or
9090
this.getCalleeName() = "ODataModel"

javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5.qll

Lines changed: 84 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ class Loader extends CallNode {
138138
/**
139139
* A user-defined module through `sap.ui.define` or `jQuery.sap.declare`.
140140
*/
141-
abstract class UserModule extends InvokeNode {
142-
abstract string getADependencyType();
141+
abstract class UserModule extends CallExpr {
142+
abstract string getADependency();
143143

144144
abstract string getModuleFileRelativePath();
145145

@@ -150,34 +150,44 @@ abstract class UserModule extends InvokeNode {
150150
* A user-defined module through `sap.ui.define`.
151151
* https://sapui5.hana.ondemand.com/sdk/#/api/sap.ui%23methods/sap.ui.define
152152
*/
153-
class SapDefineModule extends CallNode, UserModule {
154-
SapDefineModule() { this = globalVarRef("sap").getAPropertyRead("ui").getAMethodCall("define") }
153+
class SapDefineModule extends AmdModuleDefinition::Range, MethodCallExpr, UserModule {
154+
SapDefineModule() {
155+
/*
156+
* NOTE: This only matches a call to the dot expression `sap.ui.define`, and does not consider a flow among `sap`, `ui`, and `define`.
157+
*/
155158

156-
override string getADependencyType() { result = this.getDependencyType(_) }
159+
exists(GlobalVarAccess sap, DotExpr sapUi, DotExpr sapUiDefine |
160+
sap.getName() = "sap" and
161+
sapUi.getBase() = sap and
162+
sapUi.getPropertyName() = "ui" and
163+
this.getReceiver() = sapUiDefine
164+
// and this.getMethodName() = "define"
165+
)
166+
}
157167

158-
override string getModuleFileRelativePath() { result = this.getFile().getRelativePath() }
168+
string getDependency(int i) { result = this.(AmdModuleDefinition).getDependency(i).getValue() }
159169

160-
string getDependencyType(int i) {
161-
result = this.getArgument(0).getALocalSource().(ArrayLiteralNode).getElement(i).getStringValue()
162-
}
170+
override string getADependency() { result = this.getDependency(_) }
163171

164-
override RequiredObject getRequiredObject(string dependencyType) {
165-
exists(int i |
166-
this.getDependencyType(i) = dependencyType and
167-
result = this.getArgument(1).getALocalSource().(FunctionNode).getParameter(i)
168-
)
172+
override string getModuleFileRelativePath() { result = this.getFile().getRelativePath() }
173+
174+
override RequiredObject getRequiredObject(string name) {
175+
result = this.(AmdModuleDefinition).getDependencyParameter(name)
169176
}
170177

171178
WebApp getWebApp() { this.getFile() = result.getAResource() }
172179

173-
SapDefineModule getExtendingDefine() {
174-
exists(Extension baseExtension, Extension subclassExtension, SapDefineModule subclassDefine |
175-
baseExtension.getDefine() = this and
176-
subclassDefine = subclassExtension.getDefine() and
177-
any(RequiredObject module_ |
178-
module_ = subclassDefine.getRequiredObject(baseExtension.getName().replaceAll(".", "/"))
179-
).flowsTo(subclassExtension.getReceiver()) and
180-
result = subclassDefine
180+
/**
181+
* Gets the module defined with sap.ui.define that imports and extends this module.
182+
*/
183+
SapDefineModule getExtendingModule() {
184+
exists(SapExtendCall baseExtendCall, SapExtendCall subclassExtendCall |
185+
baseExtendCall.getDefine() = this and
186+
result = subclassExtendCall.getDefine() and
187+
result
188+
.getRequiredObject(baseExtendCall.getName().replaceAll(".", "/"))
189+
.asSourceNode()
190+
.flowsTo(subclassExtendCall.getReceiver())
181191
)
182192
}
183193
}
@@ -194,29 +204,27 @@ class JQuerySap extends DataFlow::SourceNode {
194204
/**
195205
* A user-defined module through `jQuery.sap.declare`.
196206
*/
197-
class JQueryDefineModule extends UserModule, DataFlow::MethodCallNode {
198-
JQueryDefineModule() { exists(JQuerySap jquerySap | jquerySap.flowsTo(this.getReceiver())) }
207+
class JQueryDefineModule extends UserModule, MethodCallExpr {
208+
JQueryDefineModule() { exists(JQuerySap jquerySap | jquerySap.asExpr() = this.getReceiver()) }
199209

200-
override string getADependencyType() {
201-
result = this.getArgument(0).asExpr().(StringLiteral).getValue()
202-
}
210+
override string getADependency() { result = this.getArgument(0).getStringValue() }
203211

204212
override string getModuleFileRelativePath() { result = this.getFile().getRelativePath() }
205213

206-
/** WARNING: toString() Hack! */
214+
/* WARNING: toString() Hack! */
207215
override RequiredObject getRequiredObject(string dependencyType) {
208216
result.toString() = dependencyType and
209-
this.getADependencyType() = dependencyType
217+
this.getADependency() = dependencyType
210218
}
211219
}
212220

213-
private RequiredObject sapControl(TypeTracker t) {
221+
private SourceNode sapControl(TypeTracker t) {
214222
t.start() and
215223
exists(UserModule d, string dependencyType |
216224
dependencyType = ["sap/ui/core/Control", "sap.ui.core.Control"]
217225
|
218-
d.getADependencyType() = dependencyType and
219-
result = d.getRequiredObject(dependencyType)
226+
d.getADependency() = dependencyType and
227+
result = d.getRequiredObject(dependencyType).asSourceNode()
220228
)
221229
or
222230
exists(TypeTracker t2 | result = sapControl(t2).track(t2, t))
@@ -229,8 +237,8 @@ private SourceNode sapController(TypeTracker t) {
229237
exists(UserModule d, string dependencyType |
230238
dependencyType = ["sap/ui/core/mvc/Controller", "sap.ui.core.mvc.Controller"]
231239
|
232-
d.getADependencyType() = dependencyType and
233-
result = d.getRequiredObject(dependencyType)
240+
d.getADependency() = dependencyType and
241+
result = d.getRequiredObject(dependencyType).asSourceNode()
234242
)
235243
or
236244
exists(TypeTracker t2 | result = sapController(t2).track(t2, t))
@@ -243,16 +251,16 @@ private SourceNode sapRenderer(TypeTracker t) {
243251
exists(UserModule d, string dependencyType |
244252
dependencyType = ["sap/ui/core/Renderer", "sap.ui.core.Renderer"]
245253
|
246-
d.getADependencyType() = dependencyType and
247-
result = d.getRequiredObject(dependencyType)
254+
d.getADependency() = dependencyType and
255+
result = d.getRequiredObject(dependencyType).asSourceNode()
248256
)
249257
or
250258
exists(TypeTracker t2 | result = sapController(t2).track(t2, t))
251259
}
252260

253261
private SourceNode sapRenderer() { result = sapRenderer(TypeTracker::end()) }
254262

255-
private class Renderer extends Extension {
263+
private class Renderer extends SapExtendCall {
256264
Renderer() { this.getReceiver().getALocalSource() = sapRenderer() }
257265

258266
FunctionNode getRenderer() {
@@ -264,10 +272,10 @@ private class Renderer extends Extension {
264272
}
265273
}
266274

267-
class CustomControl extends Extension {
275+
class CustomControl extends SapExtendCall {
268276
CustomControl() {
269277
this.getReceiver().getALocalSource() = sapControl() or
270-
exists(SapDefineModule sapModule | this.getDefine() = sapModule.getExtendingDefine())
278+
exists(SapDefineModule sapModule | this.getDefine() = sapModule.getExtendingModule())
271279
}
272280

273281
CustomController getController() { this = result.getAControlReference().getDefinition() }
@@ -436,7 +444,7 @@ class ControllerReference extends Reference {
436444
CustomController getDefinition() { result = viewReference.getDefinition().getController() }
437445
}
438446

439-
class CustomController extends Extension {
447+
class CustomController extends SapExtendCall {
440448
string name;
441449

442450
CustomController() {
@@ -775,8 +783,8 @@ private SourceNode sapComponent(TypeTracker t) {
775783
"sap.ui.core.UIComponent"
776784
]
777785
|
778-
d.getADependencyType() = dependencyType and
779-
result = d.getRequiredObject(dependencyType)
786+
d.getADependency() = dependencyType and
787+
result = d.getRequiredObject(dependencyType).asSourceNode()
780788
)
781789
or
782790
exists(TypeTracker t2 | result = sapComponent(t2).track(t2, t))
@@ -789,7 +797,7 @@ import ManifestJson
789797
/**
790798
* A UI5 Component that may contain other controllers or controls.
791799
*/
792-
class Component extends Extension {
800+
class Component extends SapExtendCall {
793801
Component() { this.getReceiver().getALocalSource() = sapComponent() }
794802

795803
string getId() { result = this.getName().regexpCapture("([a-zA-Z0-9.]+).Component", 1) }
@@ -1086,8 +1094,8 @@ class JsonModel extends UI5InternalModel {
10861094
this instanceof NewNode and
10871095
(
10881096
exists(RequiredObject jsonModel |
1089-
jsonModel.flowsTo(this.getCalleeNode()) and
1090-
jsonModel.getDependencyType() = "sap/ui/model/json/JSONModel"
1097+
jsonModel.asSourceNode().flowsTo(this.getCalleeNode()) and
1098+
jsonModel.getDependency() = "sap/ui/model/json/JSONModel"
10911099
)
10921100
or
10931101
/* Fallback */
@@ -1197,8 +1205,8 @@ class XmlModel extends UI5InternalModel {
11971205
XmlModel() {
11981206
this instanceof NewNode and
11991207
exists(RequiredObject xmlModel |
1200-
xmlModel.flowsTo(this.getCalleeNode()) and
1201-
xmlModel.getDependencyType() = "sap/ui/model/xml/XMLModel"
1208+
xmlModel.asSourceNode().flowsTo(this.getCalleeNode()) and
1209+
xmlModel.getDependency() = "sap/ui/model/xml/XMLModel"
12021210
)
12031211
}
12041212

@@ -1234,48 +1242,53 @@ class ResourceModel extends UI5Model, ModelReference {
12341242
}
12351243

12361244
class BindingMode extends RequiredObject {
1237-
BindingMode() { this.getDependencyType() = "sap/ui/model/BindingMode" }
1245+
BindingMode() { this.getDependency() = "sap/ui/model/BindingMode" }
12381246

1239-
PropRead getOneWay() { result = this.getAPropertyRead("OneWay") }
1247+
PropRead getOneWay() { result = this.asSourceNode().getAPropertyRead("OneWay") }
12401248

1241-
PropRead getTwoWay() { result = this.getAPropertyRead("TwoWay") }
1249+
PropRead getTwoWay() { result = this.asSourceNode().getAPropertyRead("TwoWay") }
12421250

1243-
PropRead getDefault() { result = this.getAPropertyRead("Default") }
1251+
PropRead getDefault_() { result = this.asSourceNode().getAPropertyRead("Default") }
12441252

1245-
PropRead getOneTime() { result = this.getAPropertyRead("OneTime") }
1253+
PropRead getOneTime() { result = this.asSourceNode().getAPropertyRead("OneTime") }
12461254
}
12471255

1248-
class RequiredObject extends SourceNode {
1256+
class RequiredObject extends Expr {
12491257
RequiredObject() {
12501258
exists(SapDefineModule sapDefineModule |
1251-
this = sapDefineModule.getArgument(1).getALocalSource().(FunctionNode).getParameter(_)
1259+
this = sapDefineModule.getArgument(1).(Function).getParameter(_)
12521260
) or
12531261
exists(JQueryDefineModule jQueryDefineModule |
1254-
this.toString() =
1255-
jQueryDefineModule.getArgument(0).getALocalSource().asExpr().(StringLiteral).getValue()
1262+
/* WARNING: toString() Hack! */
1263+
this.toString() = jQueryDefineModule.getArgument(0).(StringLiteral).getValue()
12561264
)
12571265
}
12581266

1259-
UserModule getDefiningModule() { result.getArgument(1).(FunctionNode).getParameter(_) = this }
1267+
pragma[inline]
1268+
SourceNode asSourceNode() { result = this.flow() }
1269+
1270+
UserModule getDefiningModule() { result.getArgument(1).(Function).getParameter(_) = this }
12601271

1261-
string getDependencyType() {
1272+
string getDependency() {
12621273
exists(SapDefineModule module_ | this = module_.getRequiredObject(result))
12631274
}
12641275
}
12651276

12661277
/**
12671278
* `SomeModule.extend(...)` where `SomeModule` stands for a module imported with `sap.ui.define`.
12681279
*/
1269-
class Extension extends InvokeNode, MethodCallNode {
1270-
Extension() {
1280+
class SapExtendCall extends InvokeNode, MethodCallNode {
1281+
SapExtendCall() {
12711282
/* 1. The receiver object is an imported one */
1272-
any(RequiredObject module_).flowsTo(this.getReceiver()) and
1283+
exists(RequiredObject requiredModule |
1284+
requiredModule.asSourceNode().flowsTo(this.getReceiver())
1285+
) and
12731286
/* 2. The method name is `extend` */
12741287
this.(MethodCallNode).getMethodName() = "extend"
12751288
}
12761289

12771290
FunctionNode getMethod(string methodName) {
1278-
result = this.getArgument(1).(ObjectLiteralNode).getAPropertySource(methodName).(FunctionNode)
1291+
result = this.getContent().(ObjectLiteralNode).getAPropertySource(methodName).(FunctionNode)
12791292
}
12801293

12811294
FunctionNode getAMethod() { result = this.getMethod(_) }
@@ -1287,22 +1300,22 @@ class Extension extends InvokeNode, MethodCallNode {
12871300
Metadata getMetadata() {
12881301
result = this.getContent().getAPropertySource("metadata")
12891302
or
1290-
exists(Extension baseExtension |
1291-
baseExtension.getDefine().getExtendingDefine() = this.getDefine() and
1292-
result = baseExtension.getMetadata()
1303+
exists(SapExtendCall baseExtendCall |
1304+
baseExtendCall.getDefine().getExtendingModule() = this.getDefine() and
1305+
result = baseExtendCall.getMetadata()
12931306
)
12941307
}
12951308

12961309
/** Gets the `sap.ui.define` call that wraps this extension. */
1297-
SapDefineModule getDefine() { this.getEnclosingFunction() = result.getArgument(1).asExpr() }
1310+
SapDefineModule getDefine() { this.getEnclosingFunction() = result.getArgument(1) }
12981311
}
12991312

13001313
private newtype TSapElement =
1301-
DefinitionOfElement(Extension extension) or
1314+
DefinitionOfElement(SapExtendCall extension) or
13021315
ReferenceOfElement(Reference reference)
13031316

13041317
class SapElement extends TSapElement {
1305-
Extension asDefinition() { this = DefinitionOfElement(result) }
1318+
SapExtendCall asDefinition() { this = DefinitionOfElement(result) }
13061319

13071320
Reference asReference() { this = ReferenceOfElement(result) }
13081321

@@ -1331,12 +1344,12 @@ class SapElement extends TSapElement {
13311344
}
13321345

13331346
/**
1334-
* The property metadata found in an Extension.
1347+
* The property metadata found in an SapExtendCall.
13351348
*/
13361349
class Metadata extends ObjectLiteralNode {
1337-
Extension extension;
1350+
SapExtendCall extension;
13381351

1339-
Extension getExtension() { result = extension }
1352+
SapExtendCall getExtension() { result = extension }
13401353

13411354
Metadata() { this = extension.getContent().getAPropertySource("metadata") }
13421355

javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5FormulaInjectionQuery.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ private class StoragePutCall extends CallNode {
1010
/* 1. This is a call to `sap.ui.util.Storage.put` */
1111
// 1-1. Required from `sap/ui/util/Storage`
1212
exists(RequiredObject storageClass |
13-
this.getReceiver().getALocalSource() = storageClass and
13+
this.getReceiver().getALocalSource() = storageClass.asSourceNode() and
1414
this.getCalleeName() = "put"
1515
)
1616
or
@@ -46,7 +46,7 @@ private class FileSaveCall extends CallNode {
4646
FileSaveCall() {
4747
/* 1. Required from `sap/ui/core/util/File` */
4848
exists(RequiredObject fileClass |
49-
this.getReceiver().getALocalSource() = fileClass and
49+
this.getReceiver().getALocalSource() = fileClass.asSourceNode() and
5050
this.getCalleeName() = "save"
5151
)
5252
or

javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5XssQuery.qll

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,20 @@ class Configuration extends DomBasedXss::Configuration {
3232
)
3333
}
3434

35-
override predicate isSanitizer(DataFlow::Node node) {
35+
override predicate isBarrier(DataFlow::Node node) {
3636
/* 1. Already a sanitizer defined in `DomBasedXssQuery::Configuration` */
3737
super.isSanitizer(node)
3838
or
3939
/* 2. Value read from a non-string control property */
40-
node = any(PropertyMetadata m | not m.isUnrestrictedStringType())
40+
exists(PropertyMetadata m | not m.isUnrestrictedStringType() | node = m)
4141
or
4242
/* 3-1. Sanitizers provided by `sap.base.security` */
4343
exists(SapDefineModule d, DataFlow::ParameterNode par |
4444
node = par.getACall() and
4545
par =
4646
d.getRequiredObject("sap/base/security/" +
4747
["encodeCSS", "encodeJS", "encodeURL", "encodeURLParameters", "encodeXML"])
48+
.asSourceNode()
4849
)
4950
or
5051
/* 3-2. Sanitizers provided by `jQuery.sap` */

javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/dataflow/FlowSteps.qll

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import javascript
22
import advanced_security.javascript.frameworks.ui5.UI5
3-
private import semmle.javascript.frameworks.data.internal.ApiGraphModelsExtensions as ApiGraphModelsExtensions
43

54
/**
65
* Step from a part of internal model to a relevant control property.

javascript/frameworks/ui5/test/models/sink/pathSinkTest.ql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
*/
77

88
import javascript
9-
import semmle.javascript.security.dataflow.TaintedPathQuery as TaintedPathQuery
9+
import semmle.javascript.security.dataflow.TaintedPathQuery
1010
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow as UI5DataFlow
1111

12-
class UI5ExtPathISink extends TaintedPathQuery::Sink {
12+
class UI5ExtPathISink extends DataFlow::Node {
1313
UI5ExtPathISink() { this = ModelOutput::getASinkNode("ui5-path-injection").asSink() }
1414
}
1515

16-
from TaintedPathQuery::Sink sink
16+
from UI5ExtPathISink sink
1717
select sink, sink.toString()

0 commit comments

Comments
 (0)