Skip to content

Commit 5bb503b

Browse files
committed
Enable precise report of instantiated controls with placeAt
1 parent dec0ab9 commit 5bb503b

File tree

4 files changed

+130
-36
lines changed

4 files changed

+130
-36
lines changed

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ private class InputControlInstantiation extends ElementInstantiation {
2424
InputControlInstantiation() { typeModel("UI5InputControl", this.getImportPath(), _) }
2525
}
2626

27-
private class DataFromInstantiatedAndPlacedAtControl extends RemoteFlowSource, XssThroughDom::Source
27+
private module TrackPlaceAtCallConfigFlow = TaintTracking::Global<TrackPlaceAtCallConfig>;
28+
29+
class DataFromInstantiatedAndPlacedAtControl extends RemoteFlowSource, XssThroughDom::Source
2830
{
31+
InputControlInstantiation controlInstantiation;
32+
ControlPlaceAtCall placeAtCall;
33+
2934
DataFromInstantiatedAndPlacedAtControl() {
30-
exists(
31-
InputControlInstantiation controlInstantiation, string typeAlias,
32-
ControlReference controlReference
33-
|
35+
exists(string typeAlias, ControlReference controlReference |
3436
/* Double check that the type derives a remote flow source. */
3537
typeModel(typeAlias, controlInstantiation.getImportPath(), _) and
3638
sourceModel(typeAlias, _, "remote", _) and
@@ -39,12 +41,18 @@ private class DataFromInstantiatedAndPlacedAtControl extends RemoteFlowSource, X
3941
this = controlReference.getAMemberCall("getValue") or
4042
this = controlReference.getAPropertyRead("value")
4143
)
42-
)
44+
) and
45+
TrackPlaceAtCallConfigFlow::flow(controlInstantiation, placeAtCall)
4346
}
4447

4548
override string getSourceType() {
4649
result = "Data from an instantiated control placed in a DOM tree"
4750
}
51+
52+
ControlPlaceAtCall getPlaceAtCall() {
53+
// result = "TODO"
54+
none() // TODO
55+
}
4856
}
4957

5058
class LocalModelContentBoundBidirectionallyToSourceControl extends RemoteFlowSource {

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

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -302,26 +302,27 @@ class CustomControl extends SapExtendCall {
302302
class ControlPlaceAtCall extends MethodCallNode {
303303
ControlPlaceAtCall() {
304304
exists(SapElement ui5Control |
305-
// /* 1. `this.placeAt(...)` in a custom control definition */
306-
// this = ui5Control.asDefinition().getAThisNode()
307-
// or
308-
// /*
309-
// * 2. `new SomeControl(...).placeAt(...)` where `SomeControl`
310-
// * may be UI5 library control or a custom control
311-
// */
312-
// ui5Control.asInstantiation().
313-
// or
314-
// /* 3. `.byId(...).placeAt(...)` */
315-
// ui5Control.asReference().
316-
none() // TODO
305+
/* 1. `this.placeAt(...)` in a custom control definition */
306+
this = ui5Control.asDefinition().getAThisNode().getAMemberCall("placeAt")
307+
or
308+
/*
309+
* 2. `new SomeControl(...).placeAt(...)` where
310+
* `SomeControl` may be UI5 library control or a custom control
311+
*/
312+
313+
this = ui5Control.asInstantiation().getAMemberCall("placeAt")
314+
or
315+
// this = ui5Control.getParentElement*().asInstantiation().getAMemberCall("placeAt") or
316+
/* 3. `.byId(...).placeAt(...)` */
317+
this = ui5Control.asReference().getAMemberCall("placeAt")
317318
)
318319
}
320+
321+
string getDomElementId() { result = this.getArgument(0).getStringValue() }
319322
}
320323

321324
abstract class Reference extends MethodCallNode { }
322325

323-
private predicate byId(MethodCallNode byId) { byId.getMethodName() = "byId" }
324-
325326
/**
326327
* A JS reference to a `UI5Control`, commonly obtained via its ID.
327328
*/
@@ -476,10 +477,10 @@ class CustomController extends SapExtendCall {
476477
}
477478

478479
override ThisNode getAThisNode() {
479-
/* 1. `this` referring to the binder */
480+
/* ========== 1. `this` referring to the binder ========== */
480481
result = super.getAThisNode()
481482
or
482-
/* 2. `this` bound to a callback's `this` */
483+
/* 2. ========== `this` bound to a callback's `this` ========== */
483484
/*
484485
* 2-1. The this node of `.attachDisplay` or `.detachDisplay` also represents this
485486
* controller.
@@ -1289,8 +1290,19 @@ class SapElement extends TSapElement {
12891290
result.asDefinition() = this.asReference().(ViewReference).getDefinition().getController() or
12901291
result.asDefinition() = this.asDefinition().(CustomController).getOwnerComponent() or
12911292
result.asDefinition() =
1292-
this.asReference().(ControllerReference).getDefinition().getOwnerComponent()
1293-
/* TODO: Implement branch for `asInstantiation` */
1293+
this.asReference().(ControllerReference).getDefinition().getOwnerComponent() or
1294+
/* ==================== exists(result.asInstantiation()) branches ==================== */
1295+
result.asInstantiation() =
1296+
this.asReference().(ControlReference).getAMemberCall(_).getAnArgument().getALocalSource() or
1297+
result.asInstantiation() =
1298+
this.asReference().(ControlReference).getAPropertyWrite().getRhs().getALocalSource()
1299+
// or
1300+
// result.asInstantiation() =
1301+
// this.asInstantiation().getAMemberCall(_).getAnArgument().getALocalSource() or
1302+
// result.asInstantiation() = this.asInstantiation().getAPropertyWrite().getRhs().getALocalSource() or
1303+
// result.asInstantiation() = this.asInstantiation().getAnArgument()
1304+
// TrackParentControlConfig::flow(this.asInstantiation())
1305+
/* =================================================================================== */
12941306
}
12951307

12961308
string getId() {
@@ -1313,15 +1325,14 @@ class SapElement extends TSapElement {
13131325

13141326
string toString() {
13151327
result = this.asDefinition().toString() or
1316-
result = this.asReference().toString()
1328+
result = this.asReference().toString() or
1329+
result = this.asInstantiation().toString()
13171330
}
13181331

1319-
predicate hasLocationInfo(
1320-
string filepath, int startline, int startcolumn, int endline, int endcolumn
1321-
) {
1322-
this.asDefinition().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
1323-
or
1324-
this.asReference().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
1332+
Location getLocation() {
1333+
result = this.asDefinition().getLocation() or
1334+
result = this.asReference().getLocation() or
1335+
result = this.asInstantiation().getLocation()
13251336
}
13261337
}
13271338

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,24 @@ private class HTMLControlInstantiation extends ElementInstantiation {
7979
HTMLControlInstantiation() { typeModel("UI5HTMLControl", this.getImportPath(), _) }
8080
}
8181

82+
private module TrackPlaceAtCallConfigFlow = TaintTracking::Global<TrackPlaceAtCallConfig>;
83+
8284
/**
8385
* The DOM value of a UI5 control that is dynamically generated then placed at
8486
* a certain position in a DOM.
8587
*/
86-
/* TODO: Needs testing of each branch */
87-
class DangerouslySetElementValueOfInstantiatedHTMLControlPlacedAtDom extends DataFlow::Node {
88+
/*
89+
* TODO 1: Needs testing of each branch
90+
* TODO 2: Model the `placeAt`:
91+
*/
92+
93+
private class DangerouslySetElementValueOfInstantiatedHTMLControlPlacedAtDom extends DataFlow::Node {
94+
HTMLControlInstantiation htmlControlInstantiation;
95+
ControlPlaceAtCall placeAtCall;
96+
8897
DangerouslySetElementValueOfInstantiatedHTMLControlPlacedAtDom() {
89-
/* 1. The content is set via the first argument of the constructor. */
90-
exists(HTMLControlInstantiation htmlControlInstantiation |
98+
(
99+
/* 1. The content is set via the first argument of the constructor. */
91100
exists(string typeAlias |
92101
typeModel(typeAlias, htmlControlInstantiation.getImportPath(), _) and
93102
/* Double check that the type derives a UI5-XSS sink. */
@@ -113,6 +122,7 @@ class DangerouslySetElementValueOfInstantiatedHTMLControlPlacedAtDom extends Dat
113122
/* 2-2. The content is written using the `setContent` setter. */
114123
this = controlReference.getAMemberCall("content").getArgument(0)
115124
)
116-
)
125+
) and
126+
TrackPlaceAtCallConfigFlow::flow(htmlControlInstantiation, placeAtCall)
117127
}
118128
}

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

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ module UI5PathGraph<PathNodeSig ConfigPathNode, PathGraphSig<ConfigPathNode> Con
7272

7373
UI5BindingPath asUI5BindingPathNode() { this = TUI5BindingPathNode(result) }
7474

75+
predicate isDataFlowNode() { exists(this.asDataFlowNode()) }
76+
77+
predicate isUI5BindingPathNode() { exists(this.asUI5BindingPathNode()) }
78+
7579
string toString() {
7680
result = this.asDataFlowNode().toString()
7781
or
@@ -218,3 +222,64 @@ module UI5PathGraph<PathNodeSig ConfigPathNode, PathGraphSig<ConfigPathNode> Con
218222
ui5PathNodeSucc.asUI5BindingPathNode() = any(UI5View view).getAnHtmlISink()
219223
}
220224
}
225+
226+
module TrackPlaceAtCallConfig implements DataFlow::ConfigSig {
227+
/**
228+
* A child element instantiation.
229+
*/
230+
predicate isSource(DataFlow::Node node) { node instanceof ElementInstantiation }
231+
232+
additional predicate isSinkWithPlaceAtCall(DataFlow::Node node, ControlPlaceAtCall placeAtCall) {
233+
node = placeAtCall
234+
}
235+
236+
/**
237+
* An "extension point" exposed from a parent element instantiation to
238+
* register a child to itself.
239+
*/
240+
predicate isSink(DataFlow::Node node) { isSinkWithPlaceAtCall(node, _) }
241+
242+
/**
243+
* Step from data being written and the property that is being written to.
244+
* Needed to express the fact that the child control is usually written to
245+
* a property of a parent control to establish the child-parent relationship.
246+
*
247+
* NOTE: The "extension point" exposed by the parent control can come in the
248+
* form of:
249+
*
250+
* 1. `new Parent({ children: [ new Child( ... ) ] })`
251+
* 2. `new Parent(...).children[0] = new Child( ... )`
252+
* 3. `new Parent(...).addChild(new Child( ... ))`
253+
*
254+
* The first and second cases are (nested) `PropWrite`s to `getArgument(0)`
255+
* and are easily covered. But the third case poses a new problem of identifying
256+
* methods that writes to the `children` property, which CodeQL can't verify
257+
* because its definition is in the library.
258+
*
259+
* - [X] Mark all method calls: overgeneralizes, can infer dumb things
260+
* (`sap.m.FlexBox.removeItem` comes to mind), but would still work for all
261+
* true cases
262+
* - [ ] Heuristic by prefix: not an attractive option since heuristics can fail
263+
*/
264+
predicate isAdditionalFlowStep(DataFlow::Node start, DataFlow::Node end) {
265+
exists(DataFlow::PropWrite propWrite |
266+
start = propWrite.getRhs() and
267+
end = propWrite.getBase()
268+
)
269+
or
270+
exists(DataFlow::MethodCallNode maybeAddingChildAPICall |
271+
start = maybeAddingChildAPICall.getAnArgument() and
272+
end = maybeAddingChildAPICall.getReceiver()
273+
)
274+
or
275+
exists(DataFlow::MethodCallNode call |
276+
start = call.getReceiver() and
277+
end = call
278+
)
279+
or
280+
exists(DataFlow::NewNode new |
281+
start = new.getAnArgument() and
282+
end = new
283+
)
284+
}
285+
}

0 commit comments

Comments
 (0)