Skip to content

JavaScript: add support for NodeJS and Electron http client libraries #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 20, 2018

Conversation

rdmarsh2
Copy link
Contributor

@rdmarsh2 rdmarsh2 commented Aug 3, 2018

This PR adds RemoteFlowSources for both the NodeJS http/https client library and the Electron http/https client library and adds CredentialExprs for auth responses. The majority of the work is in NodeJSLib.qll; the Electron QL library simply extends the ClientRequest class from the QL NodeJS library.

@rdmarsh2 rdmarsh2 added the JS label Aug 3, 2018
@rdmarsh2 rdmarsh2 requested a review from a team August 3, 2018 18:20
nickrolfe pushed a commit to nickrolfe/codeql that referenced this pull request Aug 6, 2018
JavaScript: Further improve handling of destructuring patterns.
Copy link

@xiemaisi xiemaisi left a comment

Choose a reason for hiding this comment

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

A few suggestions and comments.

This adds a lot of new classes. Have you considered which of them could be made private?

I also dislike the amount of repetition. Is there no way of merging some of these definitions or pulling out shared bits of code?

Finally, as I commented on below, you seem to use TrackedNodes (inter-procedural flow) a lot. We have so far got along with SourceNodes (intra-procedural flow). I assume this is because you have seen examples where inter-procedural flow is needed?

/**
* A data flow node that is a Node.js HTTP or HTTPS client request.
*/
abstract class ClientRequest extends DataFlow::TrackedNode {
Copy link

Choose a reason for hiding this comment

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

This seems to overlap with the existing class RequestExpr. What's the rationale for introducing this class, and why is it a TrackedNode? Have you seen cases where we need to track requests inter-procedurally?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

RequestExpr is used in a NodeJS server handling an incoming request, while ClientRequest is when the NodeJS or Electron libraries are used to send a request, as in the call at line 18 of javascript/ql/test/library-tests/frameworks/NodeJSLib/src/http.js

I think I added that because I saw a case where we need to track them interprocedurally - I'll go back through the projects I was testing on and see if I actually needed it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It looks like I added it in an attempt to handle a use of bind to create the request callback. It didn't work, so I'll remove it and see if there's a better way to handle bind.

Copy link

Choose a reason for hiding this comment

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

OK, that's not clear from the description or the name. Please consider whether there is a better name, and extend the qldoc with an example.

bind is tricky; I'd say let's ignore it for now.

* A data flow node that is a Node.js HTTP or HTTPS client request.
*/
abstract class ClientRequest extends DataFlow::TrackedNode {
abstract DataFlow::Node getOptions();
Copy link

Choose a reason for hiding this comment

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

Public predicate, so needs qldoc.

/**
* A data flow node that is the parameter of a result callback for an HTTP or HTTPS request made through Node.js.
*/
abstract class RequestCallbackParam extends DataFlow::TrackedNode, RemoteFlowSource {
Copy link

Choose a reason for hiding this comment

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

This overlaps with RequestSource; same questions as for ClientRequest above.

RequestCallbackData() {
exists(RequestCallbackParam rcp, DataFlow::MethodCallNode mcn, DataFlow::FunctionNode fn |
rcp.flowsTo(mcn.getReceiver()) and
mcn.getArgument(0).mayHaveStringValue("data") and
Copy link

Choose a reason for hiding this comment

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

mcn.getMethodName() = "on"?

}

/**
* A data flow node that is a data callback for an HTTP or HTTPS request made through Node.js.
Copy link

Choose a reason for hiding this comment

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

Here and elsewhere, could you please add a brief example code snippet to the qldoc to clarify what is being modelled?

exists(DataFlow::MethodCallNode mcn |
mcn = this and
DataFlow::moduleMember("electron", "net").flowsTo(mcn.getReceiver()) and
mcn.getMethodName() = "request"
Copy link

Choose a reason for hiding this comment

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

Does this = DataFlow::moduleMember("electron", "net").getAMemberCall("request") not do what you want?

NewClientRequest() {
exists(DataFlow::NewNode nn |
this = nn and
DataFlow::moduleMember("electron", "ClientRequest").flowsTo(nn.getCallee())
Copy link

Choose a reason for hiding this comment

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

this = DataFlow::moduleMember("electron", "ClientRequest").getAnInstantiation()

Copy link

@xiemaisi xiemaisi left a comment

Choose a reason for hiding this comment

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

Thanks for addressing the review comments! I've got a few more comments, some of them stylistic, others about opportunities for using the SourceNode fluent API to simplify things. I didn't point out all opportunities for doing this.

Overall, I'd encourage you to make as many of these classes private as possible. If you have two very similar private classes that only exist to extend at abstract class, consider merging them for simplicity.



/**
* A Node.js-style HTTP or HTTPS request made using `Electron.client`, e.g. `new client(url)`.

Choose a reason for hiding this comment

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

You use electron.net above (lower-case electron), and Electron.client here (upper-case Electron). Is that intentional?

Choose a reason for hiding this comment

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

Here and below: we generally avoid "e.g." in favour of "for example" or similar.

@@ -506,4 +506,270 @@ module NodeJSLib {
}
}

/**
* A data flow node that is an HTTP or HTTPS client request made by a Node server, e.g. `http.request(url)`

Choose a reason for hiding this comment

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

We generally use "Node.js" instead of "Node" (although we're not as consistent about it as I'd like). Also, missing a full stop at the end.

/**
* A data flow node that is an HTTPS client request made by a Node process, e.g. `https.request(url)`.
*/
class HttpsRequest extends ClientRequest {

Choose a reason for hiding this comment

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

Do we have a good use case for separating this class from HttpsRequest and exposing both of them as public API? If not, I'd prefer merging them and making them private.

/**
* A data flow node that is the parameter of a result callback for an HTTPS request made by a Node process, e.g. `res` in `https.request(url, (res) => {})`.
*/
class HttpsRequestCallbackParam extends RequestCallbackParam {

Choose a reason for hiding this comment

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

Same question as for HttpsRequest above.

class RequestCallbackData extends RemoteFlowSource {
RequestCallbackData() {
exists(RequestCallbackParam rcp, DataFlow::MethodCallNode mcn, DataFlow::FunctionNode fn |
rcp.flowsTo(mcn.getReceiver()) and

Choose a reason for hiding this comment

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

mcn = rcp.getAMethodCall("on")

*/
class ClientRequestDataHandler extends DataFlow::FunctionNode {
ClientRequestDataHandler() {
exists(DataFlow::MethodCallNode mcn, ClientRequestResponseEvent cr |

Choose a reason for hiding this comment

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

Similar simplifications to above can be made here.

ClientRequestLoginPassword() {
exists(ClientRequestLoginCallback callback, DataFlow::CallNode call |
callback.flowsTo(call.getCallee()) and
this = call.getArgument(1).asExpr()

Choose a reason for hiding this comment

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

this = callback.getACall().getArgument(1).asExpr()

/**
* A data flow node that is an HTTP or HTTPS client request made by a Node server, e.g. `http.request(url)`
*/
abstract class ClientRequest extends DataFlow::SourceNode {

Choose a reason for hiding this comment

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

It's generally safer to extend DataFlow::DefaultSourceNode, which has (almost) the same extent as SourceNode, but isn't abstract.

/**
* A data flow node that is the parameter of a result callback for an HTTP or HTTPS request made by a Node process, e.g. `res` in `http.request(url, (res) => {})`.
*/
abstract class RequestCallbackParam extends DataFlow::SourceNode, RemoteFlowSource {

Choose a reason for hiding this comment

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

As above, extend DefaultSourceNode instead.

/**
* A data flow node that is the parameter of a response callback for an HTTP or HTTPS request made by a Node process, e.g. `res` in `http.request(url).on('response', (res) => {})`.
*/
class ClientRequestResponseEvent extends RemoteFlowSource, DataFlow::SourceNode {

Choose a reason for hiding this comment

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

DefaultSourceNode

@rdmarsh2 rdmarsh2 force-pushed the rdmarsh/js/electron-http-client branch from 19098f8 to d966e8d Compare August 10, 2018 21:02
Copy link

@xiemaisi xiemaisi left a comment

Choose a reason for hiding this comment

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

Looking good; a couple more suggestions.

@@ -33,4 +33,53 @@ module Electron {
this = DataFlow::moduleMember("electron", "BrowserView").getAnInstantiation()
}
}
/**

Choose a reason for hiding this comment

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

Indentation; also, please add blank line above.

Choose a reason for hiding this comment

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

This still needs addressing.



/**
* A data flow node that is the parameter of a redirect callback for an HTTP or HTTPS request made by a Node process, for example `res` in `net.request(url).on('redirect', (res) => {})`.

Choose a reason for hiding this comment

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

s/Node/Node.js/ for consistency.

}

/**
* A data flow node that is an HTTPS client request made by a Node.js process, for example `https.get(url)`.

Choose a reason for hiding this comment

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

This has exactly the same qldoc as the previous class, apart from the code example; either merge (my preferred option) or refine.

Choose a reason for hiding this comment

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

It's also wrong, of course, in that the qldoc talks about HTTPS, while the implementation is about HTTP.

Choose a reason for hiding this comment

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

s/HTTPS/HTTP or HTTPS/

exists(DataFlow::MethodCallNode mcn, DataFlow::FunctionNode fn |
mcn = req and
fn.flowsTo(mcn.getArgument(1)) and
this = fn.getParameter(0)

Choose a reason for hiding this comment

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

this = mcn.getCallback(1).getParameter(0)

rcp.getAMethodCall("on") = mcn and
mcn.getArgument(0).mayHaveStringValue("data") and
fn.flowsTo(mcn.getArgument(1)) and
fn.getParameter(0) = this

Choose a reason for hiding this comment

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

this = mcn.getCallback(1).getParameter(0)

/**
* A data flow node that is the parameter of a response callback for an HTTP or HTTPS request made by a Node.js process, for example `res` in `http.request(url).on('response', (res) => {})`.
*/
class ClientRequestResponseEvent extends RemoteFlowSource, DataFlow::ParameterNode {

Choose a reason for hiding this comment

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

Could this class be private?

Choose a reason for hiding this comment

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

Could it be?

Copy link

@xiemaisi xiemaisi left a comment

Choose a reason for hiding this comment

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

A few more comments about inconsistent qldoc. Also, could you add the eslint-scope attack as a test (with appropriate attribution)? With the latest extractor changes, this should now work out of the box.

}

/**
* A data flow node that is an HTTP client request made by a Node.js server, for example `http.request(url)`.

Choose a reason for hiding this comment

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

s/HTTP/HTTP or HTTPs/

}

/**
* A data flow node that is an HTTPS client request made by a Node.js process, for example `https.get(url)`.

Choose a reason for hiding this comment

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

s/HTTPS/HTTP or HTTPS/

*/
private class ClientRequestCallbackData extends RemoteFlowSource {
ClientRequestCallbackData() {
exists(ClientRequestCallbackParam rcp, DataFlow::MethodCallNode mcn|

Choose a reason for hiding this comment

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

Missing space after mcn.

Choose a reason for hiding this comment

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

Still missing.

}

/**
* A data flow node that is the parameter of a result callback for an HTTPS request made by a Node.js process, for example `res` in `https.request(url, (res) => {})`.

Choose a reason for hiding this comment

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

s/HTTPS/HTTP or HTTPS/

cr.getAMethodCall("on") = mcn and
mcn.getArgument(0).mayHaveStringValue("data") and
handler.flowsTo(mcn.getArgument(1)) and
this = handler.getParameter(0)

Choose a reason for hiding this comment

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

this = mcn.getCallback(1).getParameter(0)

@rdmarsh2
Copy link
Contributor Author

I'd prefer not to check in the unmodified attack to avoid antivirus trouble on Windows. Would a modified version with the URL changed be OK?

@xiemaisi
Copy link

Sure, that makes sense. Hadn't thought of the antivirus angle.

Copy link

@xiemaisi xiemaisi left a comment

Choose a reason for hiding this comment

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

Almost there! A few more cosmetic things, then we should be good to go.

Could you squash everything down to one (or a handful) of commits, please?

@@ -33,4 +33,53 @@ module Electron {
this = DataFlow::moduleMember("electron", "BrowserView").getAnInstantiation()
}
}
/**

Choose a reason for hiding this comment

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

This still needs addressing.

*/
private class ClientRequestCallbackData extends RemoteFlowSource {
ClientRequestCallbackData() {
exists(ClientRequestCallbackParam rcp, DataFlow::MethodCallNode mcn|

Choose a reason for hiding this comment

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

Still missing.

/**
* A data flow node that is the parameter of a response callback for an HTTP or HTTPS request made by a Node.js process, for example `res` in `http.request(url).on('response', (res) => {})`.
*/
class ClientRequestResponseEvent extends RemoteFlowSource, DataFlow::ParameterNode {

Choose a reason for hiding this comment

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

Could it be?

cr.getAMethodCall("on") = mcn and
mcn.getArgument(0).mayHaveStringValue("data") and
this = mcn.getCallback(1).getParameter(0)

Choose a reason for hiding this comment

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

Spurious blank line.

@xiemaisi
Copy link

Ping @rdmarsh2, could you address the few remaining superficial issues? This is basically good to go, I think.

@rdmarsh2 rdmarsh2 force-pushed the rdmarsh/js/electron-http-client branch from 7fe5620 to aaeda5d Compare August 17, 2018 17:17
@rdmarsh2
Copy link
Contributor Author

Addressed the last few comments and squashed down to a few commits

@semmle-qlci semmle-qlci merged commit 44e4b25 into github:master Aug 20, 2018
aibaars pushed a commit that referenced this pull request Oct 14, 2021
smowton pushed a commit to smowton/codeql that referenced this pull request Oct 28, 2021
Kotlin: Refactoring: Give diagnostic messages locations and severities
hohn added a commit to hohn/codeql that referenced this pull request Dec 13, 2021
erik-krogh referenced this pull request in erik-krogh/ql Dec 15, 2021
erik-krogh referenced this pull request in erik-krogh/ql Dec 15, 2021
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.

3 participants