Skip to content

Conversation

erik-krogh
Copy link
Contributor

@erik-krogh erik-krogh commented Jun 9, 2020

To support the below case, where a rest-pattern is inside a property-pattern.

(function () {
	const {
		argv: {
			...args
		},
	} = require('yargs')
		.usage('Usage: foo bar')
		.command();

	cp.exec("cmd.sh " + args); // NOT OK
});

I feel that the change in DataFlow.qll shouldn't be needed.
I'm hoping I overlooked something, and that there's a better way of doing it.

Gets a TP/TN for CVE-2020-8149

@erik-krogh erik-krogh added the JS label Jun 9, 2020
@erik-krogh erik-krogh requested a review from a team June 9, 2020 11:29
Copy link
Contributor

@asgerf asgerf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't immediately see why the rest pattern isn't working, but this isn't quite right. The rest pattern should be found by the recursions in lvalAux and getABindingVarRef.

Isn't it just a case of us not having a taint step through rest patterns?

@erik-krogh
Copy link
Contributor Author

I can't immediately see why the rest pattern isn't working, but this isn't quite right. The rest pattern should be found by the recursions in lvalAux and getABindingVarRef.

Yes, it is recognized, but not with the correct def.

The rest-pattern in the example contains all the properties from the argv property, but according to lvalAux the def is the outer object, it should be the inner argv property-pattern.

@erik-krogh
Copy link
Contributor Author

And the expected output is breaking a lot because of the change in DataFlow.qll, so I'm hoping there is a better way.

@asgerf
Copy link
Contributor

asgerf commented Jun 9, 2020

def is the CFG node where the assignment to all variables on the LHS actually happen. It's modelled as one big bulk assignment, in order to model parallel assignments:

[x, y] = [y, x]

There's probably a simpler way to model that, but that's how DefUse.qll operates, and that's why def is inherited from the outer pattern.

@erik-krogh
Copy link
Contributor Author

Thanks for the feedback, I found a good solution.

I just needed to add a new lvalueFlowStep.

@erik-krogh erik-krogh requested a review from asgerf June 9, 2020 16:16
exists(PropertyPattern pattern |
pred = TPropNode(pattern) and
succ = lvalueNode(pattern.getValuePattern().(ObjectPattern).getRest())
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just review the runtime behavior of rest patterns:

  • { ...args } = x stores a shallow copy of x in args.
  • { p1, p2, ...args } = x stores a shallow copy of x without properties p1, p2, etc in args.

What this change is doing is essentially modelling this copying step via a pure data flow step x -> args.

There are two issues here:

  • How do we best express that step?
  • Is it safe to use a pure data flow step for this?

For the first issue, I think the step should go from the object pattern itself to its rest pattern:

exists(ObjectPattern pattern |
  pred = lvalueNode(pattern) and
  succ = lvalueNode(pattern.getRest())
)

For the second issue, I can completely see how this would work great most of the time, but not always. For example, this would cause getALocalSource to backtrack through a copy step, making some of our store-steps spuriously propagate back to the original object.

I think the best would be to add it as an AdditionalFlowStep. Those steps don't affect getALocalSource but do affect all data-flow configurations. And since the above formulation doesn't depend on any internal predicates it should be a straightforward addition.

Copy link
Contributor Author

@erik-krogh erik-krogh Jun 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

The only data-flow related use of rest-patterns I could fine was in DefUse::lvalAux, I think that threw me off.

I've added it as a default taint-tracking step for now.
Do you think it should be a default data-flow step?

@erik-krogh erik-krogh marked this pull request as ready for review June 10, 2020 18:20
Comment on lines 272 to 275
exists(PropertyPattern pattern |
pred.(DataFlow::PropNode).getAstNode() = pattern and
succ = DataFlow::lvalueNode(pattern.getValuePattern().(ObjectPattern).getRest())
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tried the version I proposed earlier?

Suggested change
exists(PropertyPattern pattern |
pred.(DataFlow::PropNode).getAstNode() = pattern and
succ = DataFlow::lvalueNode(pattern.getValuePattern().(ObjectPattern).getRest())
)
exists(ObjectPattern pattern |
pred = lvalueNode(pattern) and
succ = lvalueNode(pattern.getRest())
)

That should also work for rest patterns that aren't part of a property pattern, like:

let { ...args } = x
let [ { ...args} ] = x;

Copy link
Contributor Author

@erik-krogh erik-krogh Jun 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had not, I should have.

I've implemented your suggestion, but I used DestructuringPattern instead of ObjectPattern, that way we also support array-spreads.

Copy link
Contributor

@asgerf asgerf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent! 👍

@erik-krogh erik-krogh merged commit 86b23b2 into github:js-team-sprint Jun 12, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants