mirror of
https://github.com/github/codeql.git
synced 2026-01-05 18:50:23 +01:00
271 lines
8.8 KiB
Plaintext
271 lines
8.8 KiB
Plaintext
/**
|
|
* EXPERIMENTAL. This API may change in the future.
|
|
*
|
|
* Provides predicates for working with values exported from a package.
|
|
*/
|
|
|
|
import javascript
|
|
private import semmle.javascript.internal.CachedStages
|
|
|
|
/**
|
|
* Gets a parameter that is a library input to a top-level package.
|
|
*/
|
|
cached
|
|
DataFlow::Node getALibraryInputParameter() {
|
|
Stages::Taint::ref() and
|
|
exists(int bound, DataFlow::FunctionNode func |
|
|
func = getAValueExportedByPackage().getABoundFunctionValue(bound)
|
|
|
|
|
result = func.getParameter(any(int arg | arg >= bound))
|
|
or
|
|
result = func.getFunction().getArgumentsVariable().getAnAccess().flow()
|
|
)
|
|
}
|
|
|
|
private import NodeModuleResolutionImpl as NodeModule
|
|
|
|
/**
|
|
* Gets a value exported by the main module from a named `package.json` file.
|
|
*/
|
|
private DataFlow::Node getAValueExportedByPackage() {
|
|
// The base case, an export from a named `package.json` file.
|
|
result =
|
|
getAnExportFromModule(any(PackageJson pack | exists(pack.getPackageName())).getExportedModule(_))
|
|
or
|
|
// module.exports.bar.baz = result;
|
|
exists(DataFlow::PropWrite write |
|
|
write = getAValueExportedByPackage() and
|
|
write.getPropertyName() = publicPropertyName() and
|
|
result = write.getRhs()
|
|
)
|
|
or
|
|
// class Foo {
|
|
// bar() {} // <- result
|
|
// };
|
|
// module.exports = new Foo();
|
|
exists(DataFlow::SourceNode callee |
|
|
callee = getAValueExportedByPackage().(DataFlow::NewNode).getCalleeNode().getALocalSource()
|
|
or
|
|
callee.(DataFlow::ClassNode).getConstructor() =
|
|
getAValueExportedByPackage().(DataFlow::NewNode).getCalleeNode().getAFunctionValue()
|
|
|
|
|
result = callee.getAPropertyRead("prototype").getAPropertyWrite(publicPropertyName()).getRhs()
|
|
or
|
|
result = callee.(DataFlow::ClassNode).getInstanceMethod(publicPropertyName()) and
|
|
not isPrivateMethodDeclaration(result)
|
|
)
|
|
or
|
|
// module.exports.foo = function () {
|
|
// return new Foo(); // <- result
|
|
// };
|
|
exists(DataFlow::FunctionNode func, DataFlow::NewNode inst, DataFlow::ClassNode clz |
|
|
func = getAValueExportedByPackage().getALocalSource() and inst = unique( | | func.getAReturn())
|
|
|
|
|
clz.getAnInstanceReference() = inst and
|
|
result = clz.getAnInstanceMember(_)
|
|
)
|
|
or
|
|
result = getAValueExportedByPackage().getALocalSource()
|
|
or
|
|
// Nested property reads.
|
|
result =
|
|
getAValueExportedByPackage().(DataFlow::SourceNode).getAPropertyReference(publicPropertyName())
|
|
or
|
|
// module.exports.foo = require("./other-module.js");
|
|
exists(Module mod |
|
|
mod = getAValueExportedByPackage().getEnclosingExpr().(Import).getImportedModule()
|
|
|
|
|
result = getAnExportFromModule(mod)
|
|
)
|
|
or
|
|
// re-export of a value from another module
|
|
// `module.exports.foo = require("./other").bar;`
|
|
// other.js:
|
|
// `module.exports.bar = function () { ... };`
|
|
exists(DataFlow::PropRead read, Import imp |
|
|
read = getAValueExportedByPackage() and
|
|
read.getBase().getALocalSource() = imp.getImportedModuleNode() and
|
|
result = imp.getImportedModule().getAnExportedValue(read.getPropertyName())
|
|
)
|
|
or
|
|
// require("./other-module.js"); inside an AMD module.
|
|
exists(Module mod, CallExpr call |
|
|
call = getAValueExportedByPackage().asExpr() and
|
|
call = any(AmdModuleDefinition e).getARequireCall() and
|
|
mod = call.getAnArgument().(Import).getImportedModule()
|
|
|
|
|
result = getAnExportFromModule(mod)
|
|
)
|
|
or
|
|
// module.exports = class Foo {
|
|
// bar() {} // <- result
|
|
// static baz() {} // <- result
|
|
// constructor() {} // <- result
|
|
// };
|
|
exists(DataFlow::ClassNode cla |
|
|
cla = getAValueExportedByPackage() and
|
|
not isPrivateMethodDeclaration(result)
|
|
|
|
|
result = cla.getInstanceMethod(publicPropertyName()) or
|
|
result = cla.getStaticMethod(publicPropertyName()) or
|
|
result = cla.getConstructor()
|
|
)
|
|
or
|
|
// One shot closures that define a "factory" function.
|
|
// Recognizes the following pattern:
|
|
// ```Javascript
|
|
// (function (root, factory) {
|
|
// if (typeof define === 'function' && define.amd) {
|
|
// define('library-name', factory);
|
|
// } else if (typeof exports === 'object') {
|
|
// module.exports = factory();
|
|
// } else {
|
|
// root.libraryName = factory();
|
|
// }
|
|
// }(this, function () {
|
|
// ....
|
|
// }));
|
|
// ```
|
|
// Such files are not recognized as modules, so we manually use `NodeModule::resolveMainModule` to resolve the file against a `package.json` file.
|
|
exists(ImmediatelyInvokedFunctionExpr func, DataFlow::ParameterNode factory, int i |
|
|
factory.getName() = "factory" and
|
|
func.getParameter(i) = factory.getParameter() and
|
|
DataFlow::globalVarRef("define").getACall().getAnArgument() = factory.getALocalUse() and
|
|
func.getFile() =
|
|
min(int j, File f |
|
|
f =
|
|
NodeModule::resolveMainModule(any(PackageJson pack | exists(pack.getPackageName())), j,
|
|
".")
|
|
|
|
|
f order by j
|
|
)
|
|
|
|
|
result = func.getInvocation().getArgument(i).flow().getAFunctionValue().getAReturn()
|
|
or
|
|
exists(DataFlow::ParameterNode exports | exports.getName() = "exports" |
|
|
exports = func.getInvocation().getAnArgument().flow().getAFunctionValue().getParameter(0) and
|
|
result = exports.getAPropertyWrite().getRhs()
|
|
)
|
|
)
|
|
or
|
|
// the exported value is a call to a unique callee
|
|
// ```JavaScript
|
|
// module.exports = foo();
|
|
// function foo() {
|
|
// return result;
|
|
// }
|
|
// ```
|
|
exists(DataFlow::CallNode call | call = getAValueExportedByPackage() |
|
|
result = unique( | | call.getCalleeNode().getAFunctionValue()).getAReturn()
|
|
)
|
|
or
|
|
exists(DataFlow::FunctionNode func |
|
|
func = getAValueExportedByPackage().getABoundFunctionValue(_)
|
|
|
|
|
// the exported value is a function that returns another import.
|
|
// ```JavaScript
|
|
// module.exports = function foo() {
|
|
// return require("./other-module.js");
|
|
// }
|
|
// ```
|
|
exists(Module mod |
|
|
mod = func.getAReturn().getALocalSource().getEnclosingExpr().(Import).getImportedModule() and
|
|
result = getAnExportFromModule(mod)
|
|
)
|
|
or
|
|
// a function that returns an object of methods. This acts a bit like a class.
|
|
result = func.getAReturn().getALocalSource().getAPropertySource().(DataFlow::FunctionNode)
|
|
)
|
|
or
|
|
// *****
|
|
// Common styles of transforming exported objects.
|
|
// *****
|
|
//
|
|
// Object.defineProperties
|
|
exists(DataFlow::MethodCallNode call |
|
|
call = DataFlow::globalVarRef("Object").getAMethodCall("defineProperties") and
|
|
[call, call.getArgument(0)] = getAValueExportedByPackage() and
|
|
result = call.getArgument(any(int i | i > 0))
|
|
)
|
|
or
|
|
// Object.defineProperty
|
|
exists(CallToObjectDefineProperty call |
|
|
[call, call.getBaseObject()] = getAValueExportedByPackage() and
|
|
call.getPropertyName() = publicPropertyName()
|
|
|
|
|
result = call.getPropertyDescriptor().getALocalSource().getAPropertyReference("value")
|
|
or
|
|
result =
|
|
call.getPropertyDescriptor()
|
|
.getALocalSource()
|
|
.getAPropertyReference("get")
|
|
.(DataFlow::FunctionNode)
|
|
.getAReturn()
|
|
)
|
|
or
|
|
// Object.assign and friends
|
|
exists(ExtendCall assign |
|
|
getAValueExportedByPackage() = [assign, assign.getDestinationOperand().getALocalSource()]
|
|
|
|
|
result = assign.getASourceOperand()
|
|
)
|
|
or
|
|
// Array.prototype.{map, reduce, entries, values}
|
|
exists(DataFlow::MethodCallNode map |
|
|
map.getMethodName() = ["map", "reduce", "entries", "values"] and
|
|
map = getAValueExportedByPackage()
|
|
|
|
|
result = map.getArgument(0).getABoundFunctionValue(_).getAReturn()
|
|
or
|
|
// assuming that the receiver of the call is somehow exported
|
|
result = map.getReceiver()
|
|
)
|
|
or
|
|
// Object.{fromEntries, freeze, seal, entries, values}
|
|
exists(DataFlow::MethodCallNode freeze |
|
|
freeze =
|
|
DataFlow::globalVarRef("Object")
|
|
.getAMethodCall(["fromEntries", "freeze", "seal", "entries", "values"])
|
|
|
|
|
freeze = getAValueExportedByPackage() and
|
|
result = freeze.getArgument(0)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets an exported node from the module `mod`.
|
|
*/
|
|
private DataFlow::Node getAnExportFromModule(Module mod) {
|
|
result = mod.getAnExportedValue(publicPropertyName())
|
|
or
|
|
result = mod.getABulkExportedNode()
|
|
or
|
|
// exports saved to the global object
|
|
result = DataFlow::globalObjectRef().getAPropertyWrite().getRhs() and
|
|
result.getTopLevel() = mod
|
|
or
|
|
result.analyze().getAValue() = TAbstractModuleObject(mod)
|
|
}
|
|
|
|
/**
|
|
* Gets a property name that we consider to be public.
|
|
*
|
|
* This only allows properties whose first character is a letter or number.
|
|
*/
|
|
bindingset[result]
|
|
private string publicPropertyName() { result.regexpMatch("[a-zA-Z0-9].*") }
|
|
|
|
/**
|
|
* Holds if the given function is part of a private (or protected) method declaration.
|
|
*/
|
|
private predicate isPrivateMethodDeclaration(DataFlow::FunctionNode func) {
|
|
exists(MethodDeclaration decl |
|
|
decl.getBody() = func.getFunction() and
|
|
(
|
|
decl.isPrivate()
|
|
or
|
|
decl.isProtected()
|
|
)
|
|
)
|
|
}
|