split fuzzy read/writes on collections into 2 pseudo-properties

This commit is contained in:
Erik Krogh Kristensen
2020-04-01 14:25:41 +02:00
parent b1bf7f9f3d
commit 957b60f84b
4 changed files with 35 additions and 20 deletions

View File

@@ -81,11 +81,6 @@ abstract private class CollectionFlowStep extends DataFlow::AdditionalFlowStep {
) {
this.loadStore(pred, succ, loadProp, storeProp)
}
/**
* Holds if this step on a collection can load a value with a known key.
*/
predicate canLoadValueWithKnownKey() { none() }
}
/**
@@ -100,10 +95,7 @@ module CollectionsTypeTracking {
exists(CollectionFlowStep step, PseudoProperty field |
summary = LoadStep(field) and
step.load(pred, result, field) and
not (
step.canLoadValueWithKnownKey() and // for a step that could load a known key,
field = mapValueUnknownKey() // don't load values with an unknown key.
)
not field = mapValueUnknownKey() // prune unknown reads in type-tracking
or
summary = StoreStep(field) and
step.store(pred, result, field)
@@ -191,7 +183,7 @@ private module CollectionDataFlow {
) {
pred = this and
succ = element and
fromProp = mapValueUnknownKey() and
fromProp = mapValueAll() and
toProp = "1"
}
}
@@ -205,7 +197,7 @@ private module CollectionDataFlow {
override predicate load(DataFlow::Node obj, DataFlow::Node element, PseudoProperty prop) {
obj = this.getReceiver() and
element = this.getCallback(0).getParameter(0) and
prop = [setElement(), mapValueUnknownKey()]
prop = [setElement(), mapValueAll()]
}
}
@@ -219,10 +211,9 @@ private module CollectionDataFlow {
override predicate load(DataFlow::Node obj, DataFlow::Node element, PseudoProperty prop) {
obj = this.getReceiver() and
element = this and
prop = mapValue(this.getArgument(0))
// reading the join of known and unknown values
(prop = mapValue(this.getArgument(0)) or prop = mapValueUnknownKey())
}
override predicate canLoadValueWithKnownKey() { any() }
}
/**
@@ -230,7 +221,8 @@ private module CollectionDataFlow {
*
* If the key of the call to `set` has a known string value,
* then the value will be saved into a pseudo-property corresponding to the known string value.
* The value will additionally be saved into a pseudo-property corresponding to values with unknown keys.
* Otherwise the value will be saved into a pseudo-property corresponding to values with unknown keys.
* The value will additionally be saved into a pseudo-property corresponding to all values.
*/
class MapSet extends CollectionFlowStep, DataFlow::MethodCallNode {
MapSet() { this.getMethodName() = "set" }
@@ -246,9 +238,11 @@ private module CollectionDataFlow {
* The pseudo-property represents both values where the key is a known string value (which is encoded in the pseudo-property),
* and values where the key is unknown.
*
* Additionally, all elements are stored into the pseudo-property `mapValueAll()`.
*
* The return-type is `string` as this predicate is used to define which pseudo-properties exist.
*/
string getAPseudoProperty() { result = [mapValue(this.getArgument(0)), mapValueUnknownKey()] }
string getAPseudoProperty() { result = [mapValue(this.getArgument(0)), mapValueAll()] }
}
/**
@@ -262,7 +256,7 @@ private module CollectionDataFlow {
) {
pred = this.getReceiver() and
succ = this and
fromProp = [mapValueUnknownKey(), setElement()] and
fromProp = [mapValueAll(), setElement()] and
toProp = iteratorElement()
}
}

View File

@@ -613,6 +613,11 @@ module PseudoProperties {
*/
string mapValueUnknownKey() { result = pseudoProperty("unknownMapValue") }
/**
* Gets a pseudo-property for the location of all the values in a map.
*/
string mapValueAll() { result = pseudoProperty("allMapValues") }
/**
* Gets a pseudo-property for the location of a map value where the key is `key`.
* The string value of the `key` is encoded in the result, and there is only a result if the string value of `key` is known.

View File

@@ -11,7 +11,9 @@ dataFlow
| tst.js:2:16:2:23 | source() | tst.js:46:7:46:7 | e |
| tst.js:2:16:2:23 | source() | tst.js:50:10:50:10 | e |
| tst.js:2:16:2:23 | source() | tst.js:53:8:53:21 | map.get("key") |
| tst.js:2:16:2:23 | source() | tst.js:55:8:55:28 | map.get ... nKey()) |
| tst.js:2:16:2:23 | source() | tst.js:59:8:59:22 | map2.get("foo") |
| tst.js:2:16:2:23 | source() | tst.js:64:8:64:26 | map3.get(unknown()) |
| tst.js:2:16:2:23 | source() | tst.js:69:8:69:26 | map3.get(unknown()) |
typeTracking
| tst.js:2:16:2:23 | source() | tst.js:2:16:2:23 | source() |
| tst.js:2:16:2:23 | source() | tst.js:6:14:6:14 | e |

View File

@@ -12,7 +12,7 @@
})
var map = new Map();
map.set("key", source); map.set(unknownKey(), source);
map.set("key", source);
map.forEach(v => {
sink(v);
});
@@ -52,5 +52,19 @@
sink(map.get("key")); // NOT OK.
sink(map.get("nonExistingKey")); // OK.
sink(map.get(unknownKey())); // NOT OK (for data-flow). OK for type-tracking.
// unknown write, known read
var map2 = new map();
map2.set(unknown(), source);
sink(map2.get("foo")); // NOT OK (for data-flow). OK for type-tracking.
// unknown write, unknown read
var map3 = new map();
map3.set(unknown(), source);
sink(map3.get(unknown())); // NOT OK (for data-flow). OK for type-tracking.
// known write, unknown read
var map4 = new map();
map4.set("foo", source);
sink(map3.get(unknown())); // NOT OK (for data-flow). OK for type-tracking.
})();