diff --git a/CodeQL_Queries/cpp/Chrome/README.md b/CodeQL_Queries/cpp/Chrome/README.md new file mode 100644 index 0000000..c136b4c --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/README.md @@ -0,0 +1,63 @@ +# Code QL library for Chrome + +This repository contains various [CodeQL](https://codeql.com) libraries for [Chromium](https://chromium.googlesource.com/chromium/src.git). In order to use it, follow the instructions to install the [CodeQL plugin in eclipse](https://help.semmle.com/ql-for-eclipse/Content/WebHelp/installation.html) and then import this repository as an eclipse project. + +## Building snapshot + +As the Chromium source code is huge. In order to use CodeQL with it, you are likely to need to build your own Chromium snapshot and restrict it to a specific target, instead of building and running CodeQL on the whole of Chromium. This can be achieved by a two stage build. For example, if the directory of interest is `content/browser` (e.g. when researching Chromium IPC), then first build the entire Chromium. Next, remove all the build artifacts (*.o, *.so, *.a) in the `src/out//obj/content/browser/` directory (including subdirectories) and rebuild with CodeQL, following the instructions [here](https://help.semmle.com/codeql/codeql-cli.html). This should result in a usable size snapshot. Note, however, that building with CodeQL CLI is a memory intensive process and even a restricted snapshot would take up a huge amount of RAM (e.g. `content/browser` requires at least 50-60GB of RAM to build) and I'd recommended building it in a VM in the cloud. + +## Overview of various libraries + +The libraries in this repository are organized as follows: + +### `commons.qll`: + +Mostly contain general enchancement to the standard QL library and some Chromium specific, but still general material. For example, because the operators `->` and `=` are often overloaded in Chrome, this somehow upsets the usual `getQualifier` and `Assignment` in QL, so two general methods, `getQualifier` and `GeneralAssignment` are implemented to take these into account. Another useful predicates are `constructionCall` and `polyConstructionCall`, which identify all the constructor calls, as well as constructions via `make_unique` and `MakeRefCounted` (the former works fine in code search, but not the later, but neither will work on vanilla QL (you just ended inside the standard library)) + +### `collections.qll`: + +Generally deals with `std::map`, `std::vector` etc. I use some heuristics there because there are just too many different types of containers and it is likely to miss out some if I add them manually. The library provides methods that get the component types of a container and also function calls that set/reset components in a container. A set/reset method call (e.g. push_back/erase/clear etc.) are useful as they are needed to identify when raw pointers might get remove and when managed pointers may get reset (which will cause the underlying object to be deleted and may cause a UaF somewhere else) Many bugs are actually related to pointers/managed pointers stored in containers. + +### `callbacks.qll`: + +The is a very useful library. It provides utilities to track the pointer types that are stored inside a callback, both retained and unretained. It uses dataflow in `callback_tracking.qll` to do this and can track through callback fields and multiple callbacks, e.g. + +```c +A* a; +... +assignCallback(base::BindOnce(&foo, Unretained(a))); + + +void assignCallback(Callback callback) { + callback_ = std::move(callback); +} + +... +void bar() { + x = base::BindOnce(&foo2, std::move(callback_)); //<-- x unretains type A via the assignCallback call. +} +``` + +I normally use this as a look up during investigation to see whether a particular callback is dangerous or not. + +### `callback_tracking.qll` + +Only used by `callbacks.qll` to track the types stored in callbacks. + +### `bindings.qll` + +Use for modelling mojom interface. + +## `pointers`: + +QL libraries for managed and raw pointers. + +## `object_lifetime`: + +### `lifetime_management.qll`: + +QL library that models various clean up logic in Chrome. I haven't got round to implement too much code there yet. + +`obj_lifetime.qll`: + +Mostly contain classes that are long living, e.g. BrowserContext, Singleton and the objects that they manage. Mostly use to exclude raw pointer results as these are not likely to be destroyed. diff --git a/CodeQL_Queries/cpp/Chrome/bindings.qll b/CodeQL_Queries/cpp/Chrome/bindings.qll new file mode 100644 index 0000000..695ab27 --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/bindings.qll @@ -0,0 +1,99 @@ +import cpp +import common + +/** + * Library for mojo bindings. + */ + +class StrongBinding extends ClassTemplateInstantiation { + StrongBinding() { + getName().matches("StrongBinding%") + } + + Type getBindingType() { + result = this.getTemplateArgument(0).stripType() + } +} + +class Binding extends ClassTemplateInstantiation { + Binding() { + getName().matches("Binding<%") + } + + Type getBindingType() { + result = this.getTemplateArgument(0).stripType() + } +} + + +class MojoReceiver extends ClassTemplateInstantiation { + MojoReceiver() { + getQualifiedName().matches("%mojo::Receiver<%") + } + + Type getBindingType() { + result = this.getTemplateArgument(0).stripType() + } +} + +class InterfaceBinding extends Class { + FunctionCall addBinding; + + InterfaceBinding() { + addBinding.getTarget().hasName("AddBinding") and + this = generalStripType(addBinding.getArgument(0).getAChild*().getType()) + } + + FunctionCall getABinding() { + result = addBinding + } +} + +class InterfacePtr extends ClassTemplateInstantiation { + InterfacePtr() { + stripType().getName().matches("InterfacePtr<%") or + stripType().getName().matches("InterfacePtrInfo<%") + } + + Type getInterfaceType() { + result = getTemplateArgument(0) + } + + Type getInterfacePtrType() { + exists(string s | s = getInterfaceType().getName() + "Ptr" and + result.getName() = s + ) + } +} + +class SetConnectionErrorHandler extends Function { + SetConnectionErrorHandler() { + getName() = "set_connection_error_handler" or + getName() = "set_connection_error_with_reason_handler" + } +} + +/** + * `StructPtr` usually use for transporting data in IPC. + */ +class StructPtr extends Class { + StructPtr() { + getName().matches("StructPtr<%") or + getName().matches("InlinedStructPtr<%") + } + + Type getStructType() { + result = getTemplateArgument(0) + } +} + +Type stripStructPtrType(Type c) { + ( + c.getName().matches("vector<%") and + result = stripStructPtrType(c.(Class).getTemplateArgument(0)) + ) or + exists(StructPtr t | t = c.stripType() and + result = t.getStructType().stripType() + ) or + result = c.stripType() +} diff --git a/CodeQL_Queries/cpp/Chrome/callback_tracking.qll b/CodeQL_Queries/cpp/Chrome/callback_tracking.qll new file mode 100644 index 0000000..b1c58d1 --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/callback_tracking.qll @@ -0,0 +1,137 @@ +import cpp +import common +import bindings +import field +import callbacks + +/** + * Dataflow library for tracking unretained or retained types in a callback. + */ + +/** + * An assignment to a callback field. + */ +predicate isCallbackFieldSink(DataFlow::Node sink) { + exists(Field f | sink.asExpr() = generalAssignValue(f)) +} + +/** + * Running of a callback. + */ +predicate runCallbackSink(DataFlow::Node sink) { + exists(RunCallback cb | cb.getCallback() = sink.asExpr()) +} + +/** + * An expression that posts a callback to a task runner. + */ +predicate postTaskSink(DataFlow::Node sink) { + exists(FunctionCall postTask | postTask.getTarget().getName().matches("PostTask%") or + postTask.getTarget().getName() = "PostDelayedTask" | + postTask.getAnArgument() = sink.asExpr() + ) +} + +/** + * Callback gets passed inside an interface pointer function. The idea is that such a + * callback may then be called from the renderer. (A bit like + * https://bugs.chromium.org/p/project-zero/issues/detail?id=1755 + * ) + */ +predicate interfacePtrCallSink(DataFlow::Node sink) { + exists(FunctionCall mojom, InterfacePtr iPtr, Function interfaceFunc | + overrides*(mojom.getTarget(), interfaceFunc) and + interfaceFunc.getDeclaringType() = iPtr.getInterfaceType() and + sink.asExpr() = mojom.getAnArgument() + ) or + exists(BindCall bc, InterfacePtr iPtr, Function interfaceFunc | + overrides*(bc.getFunction(), interfaceFunc) and interfaceFunc.getDeclaringType() = iPtr.getInterfaceType() and + sink.asExpr() = bc.getAnArgument() and + sink.asExpr() != bc.getArgument(0) + ) +} + +/** + * Callback that then binds to another callback as an argument. + */ +predicate callbackArgSink(DataFlow::Node sink) { + exists(GeneralCallback cb | cb.getACallbackArg() = sink.asExpr()) +} + +class CallbackConfig extends DataFlow::Configuration { + CallbackConfig() { + this = "callbackconfig" + } + + override predicate isSource(DataFlow::Node source) { + ( + exists(GeneralCallback fc | + source.asExpr() = fc + ) + or + exists(CallbackField f | source.asExpr() = f.getAnAccess()) + ) + and + not source.asExpr().getFile().getBaseName() = "bind.h" and + not source.asExpr().getFile().getBaseName() = "callback_helpers.h"// and + } + + override predicate isSink(DataFlow::Node sink) { + ( + isCallbackFieldSink(sink) + or + runCallbackSink(sink) + or + postTaskSink(sink) + or + interfacePtrCallSink(sink) + or + callbackArgSink(sink) + ) and + ( + //Exclude sinks that are in uninteresting files. + not sink.asExpr().getFile().getBaseName() = "bind_internal.h" and + not sink.asExpr().getFile().getBaseName() = "tuple" and + not sink.asExpr().getFile().getBaseName() = "memory" and + not sink.asExpr().getFile().getAbsolutePath().matches("%/libc++/%") and + not sink.asExpr().getFile().getBaseName() = "bind.h" and + not sink.asExpr().getFile().getBaseName() = "binding_state.h" and + not sink.asExpr().getFile().getBaseName() = "interface_endpoint_client.h" and + not sink.asExpr().getFile().getBaseName() = "associated_interface_registry.h" and + not sink.asExpr().getFile().getBaseName() = "callback_helpers.h" and + not sink.asExpr().getFile().getBaseName() = "callback_list.h" and + sink.asExpr().fromSource() + ) + } + + override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + callbackStep(node1, node2) or + collectionsEdge(node1, node2) or + getEdge(node1, node2) or + generalAssignEdge(node1, node2) or + exists(Parameter p | p = node1.asParameter() and + node2.asExpr() = p.getAnAccess() + ) or + copyConstructorEdge(node1, node2) + or + pointerTransferEdge(node1, node2) + or + adaptCallbackEdge(node1, node2) + or + callbackWrapperEdge(node1, node2) + or + callbackToBindEdge(node1, node2) + or + polymorphicCallEdge(node1, node2) + or + passEdge(node1, node2) + or + ownedEdge(node1, node2) + or + retainedRefEdge(node1, node2) + or + unretainedEdge(node1, node2) + or + forRangeEdge(node1, node2) + } +} \ No newline at end of file diff --git a/CodeQL_Queries/cpp/Chrome/callbacks.qll b/CodeQL_Queries/cpp/Chrome/callbacks.qll new file mode 100644 index 0000000..6602fb7 --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/callbacks.qll @@ -0,0 +1,349 @@ +import cpp +import semmle.code.cpp.dataflow.TaintTracking +import callback_tracking +import bindings +import collections +import pointers.managed_ptr +import pointers.raw_ptr +import common + +//-------- dataflow edges + +predicate callbackStep(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc instanceof BindCall | + exists(FunctionAccess fa, Function f, int i, int j | + fa.getTarget() = f and fa = fc.getArgument(0).getAChild*() and + ( + if (f.isStatic() or not exists(f.getDeclaringType()))then + j = i + 1 + else + j = i + 2 + ) + and + node1.asExpr() = fc.getArgument(j) and + node2.asParameter() = f.getParameter(i) + ) + or + exists(LambdaExpression lambda, int i, int j | + j = i + 1 and + lambda = fc.getArgument(0) and + node1.asExpr() = fc.getArgument(j) and + node2.asParameter() = lambda.getLambdaFunction().getParameter(i) + ) + ) +} + +predicate unretainedEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget().getName() = "Unretained" or + fc.getTarget().getName() = "UnretainedWrapper" | + fc.getAnArgument() = node1.asExpr() and + fc = node2.asExpr() + ) +} + +predicate retainedRefEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget().getName() = "RetainedRef" or + fc.getTarget().getName() = "RetainedRefWrapper" | + fc.getAnArgument() = node1.asExpr() and + fc = node2.asExpr() + ) +} + +predicate passEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget().getQualifiedName() = "base::Passed" or + fc.getTarget().getName() = "PassedRefWrapper" | + fc.getAnArgument() = node1.asExpr() and + fc = node2.asExpr() + ) +} + +predicate ownedEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget().getQualifiedName() = "base::Owned" or + fc.getTarget().getName() = "OwnedWrapper" | + fc.getAnArgument() = node1.asExpr() and + fc = node2.asExpr() + ) +} + +predicate adaptCallbackEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget().hasName("AdaptCallbackForRepeating") and + node1.asExpr() = fc.getAnArgument() and node2.asExpr() = fc + ) +} + +predicate callbackWrapperEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget().hasName("OnceCallback") or + fc.getTarget().hasName("RepeatingCallback") | + node1.asExpr() = fc.getAnArgument() and + node2.asExpr() = fc + ) or + exists(FunctionCall fc | fc.getTarget().hasName("BarrierClosure") and + node1.asExpr() = fc.getArgument(1) and + node2.asExpr() = fc + ) +} + +predicate callbackToBindEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall bind | bind.getTarget().getName().matches("Bind%") and + node2.asExpr() = bind and + node1.asExpr() = bind.getAnArgument() and + node1.asExpr().getType().stripType().(Class).getABaseClass*().hasName("CallbackBase") + ) +} + +//-------- dataflow edges end + +/** + * A useful predicate to construct call graphs which takes `Bind` callbacks into account. + */ +predicate reach(Function f, Function g) { + ( + exists(FunctionCall gc | + if g instanceof Destructor then + g = gc.getTarget() + else + overrides*(g, gc.getTarget()) + | + g = gc.getTarget() and + gc.getEnclosingFunction() = f + ) or + exists(CallbackSinks sink | sink.getEnclosingCallable() = f and + sink.getCalledFunction() = g and + (sink instanceof CallbackPostTaskSink or sink instanceof CallbackRunSink) + ) + ) +} + +class UnretainedWrapper extends ClassTemplateInstantiation { + UnretainedWrapper() { + stripType().getName().matches("UnretainedWrapper<%") + } + + Type getUnretainedType() { + result = getTemplateArgument(0) + } +} + +class RetainedRefWrapper extends ClassTemplateInstantiation { + RetainedRefWrapper() { + stripType().getName().matches("RetainedRefWrapper<%") + } + + Type getRetainedType() { + result = getTemplateArgument(0) + } +} + +class RunCallback extends FunctionCall { + Expr callback; + + RunCallback() { + exists(FunctionCall fc | fc.getTarget() instanceof StdMove and + this.getTarget().getName() = "Run" and + this.getQualifier() = fc and + fc.getAnArgument() = callback + ) + } + + Expr getCallback() {result = callback} +} + +/** + * A general callback, either created from `BindOnce`, `BindRepeating` or + * from a wrapper like `AdaptCallbackForRepeating`. + */ +abstract class GeneralCallback extends FunctionCall { + + /** Gets a retained type in the binding set of a callback.*/ + Type getARetainedType() { + exists(ClassTemplateInstantiation t | + t.getName().matches("RetainedRefWrapper<%") | + generalStripType(getAnArgument().getType()) = t and + result = t.getTemplateArgument(0) + ) or + exists(ManagedPtr t | getAnArgument().getType().stripType() = t and + result = t.getManagedType() + ) or + exists(PointerType t, Expr arg | arg = getAnArgument() and + t = arg.getType() and + t.stripType().(Class).getABaseClass*().getName().matches("RefCounted%") and + result = t.stripType() + ) + } + + /** + * Gets an unretained type in the binding set of a callback. + */ + Type getAnUnretainedType() { + exists(ClassTemplateInstantiation t | generalStripType(getAnArgument().getType()) = t and + t.getName().matches("UnretainedWrapper<%") and + result = t.getTemplateArgument(0) + ) or + exists(PointerType t, Expr arg | arg = getAnArgument() and + t = arg.getType() and + not exists(Class c | c.getName().matches("RefCounted%") and c = t.stripType().(Class).getABaseClass*()) and + result = t.stripType() + ) + } + + /** + * Gets an argument of the callback that is another callback. Use for propagating the dataflow when + * tracking unretained types etc. + */ + Expr getACallbackArg() { + result = getAnArgument() and + generalStripType(result.getType()).(Class).getABaseClass+().hasName("CallbackBase") + } +} + +/** + * Callbacks that are created using methods like `BindOnce`, `BindRepeating`. + */ +class BindCall extends GeneralCallback { + BindCall() { + this.getTarget().getName().matches("Bind%") and + not this.getTarget().getName() = "Binding" and + not this.getTarget().getName() = "BindState" and + not this.getTarget().getName() = "BindImpl" + } + + //TODO: handle case where first argument is a callback + Function getFunction() { + exists(FunctionAccess fa | fa = getArgument(0) and + result = fa.getTarget() + ) + } +} + +/** + * A callback that is converted from another callback by wrapping it in various functions. + */ +class CallbackWrapper extends GeneralCallback { + CallbackWrapper() { + getTarget().hasName("WrapCallbackWithDefaultInvokeIfNotRun") or + getTarget().hasName("WrapCallbackWithDropHandler") or + getTarget().hasName("AdaptCallbackForRepeating") + } + + Expr getWrappedCallbackArg() { + result = getArgument(0) + } +} + +class Callback extends Class { + Callback() { + stripType().(Class).getABaseClass+().getName() = "CallbackBase" + } +} + +/** + * A `Field` that stores callbacks, either normal callback or a collection. + */ +class CallbackField extends Field { + CallbackField() { + generalStripType(getType()) instanceof Callback or + ( + this instanceof GeneralPointerField and + this.(GeneralPointerField).getPointerType() instanceof Callback + ) + or + ( + this instanceof GeneralManagedField and + this.(GeneralManagedField).getManagedType() instanceof Callback + ) + or + ( + this instanceof CollectionField and + ( + this.getType().(MapType).getComponentType() instanceof Callback or + this.getType().(ListType).getComponentType() instanceof Callback + ) + ) + } +} + +class CallbackSinks extends DataFlow::Node { + DataFlow::Node source; + + CallbackSinks() { + exists(CallbackConfig cfg | + cfg.hasFlow(source, this) + ) + } + + DataFlow::Node getASource() { + result = source + } + + /** + * Gets the actuall function that is called in this callback. + */ + Function getCalledFunction() { + exists(GeneralCallback bc | bc = source.asExpr() | + result = bc.getArgument(0).getAChild*().(FunctionAccess).getTarget() or + result = bc.getArgument(0).getAChild*().(LambdaExpression).getLambdaFunction() or + exists(CallbackSinks sink | sink.asExpr() = bc.getArgument(0) and + result = sink.getCalledFunction() + ) + ) or + exists(CallbackField f, CallbackSinks sink | f.getAnAccess() = source.asExpr() and + sink.asExpr() = generalAssignValue(f) and + result = sink.getCalledFunction() + ) + } + + Class getARetainedType() { + exists(GeneralCallback bc | bc = source.asExpr() | + result = bc.getARetainedType() or + //propagates through callback args + exists(CallbackSinks sink | sink.asExpr() = bc.getACallbackArg() and + result = sink.getARetainedType() + ) + ) or + //Propagates through fields + exists(CallbackField f, CallbackSinks sink | f.getAnAccess() = source.asExpr() and + sink.asExpr() = generalAssignValue(f) and + result = sink.getARetainedType() + ) + } + + Class getAnUnretainedType() { + exists(GeneralCallback bc | bc = source.asExpr() | + result = bc.getAnUnretainedType() or + //propagates through callback args + exists(CallbackSinks sink | sink.asExpr() = bc.getACallbackArg() and + result = sink.getAnUnretainedType() + ) + ) or + //Propagates through fields + exists(CallbackField f, CallbackSinks sink | f.getAnAccess() = source.asExpr() and + sink.asExpr() = generalAssignValue(f) and + result = sink.getAnUnretainedType() + ) + + } +} + +class CallbackFieldSink extends CallbackSinks { + CallbackFieldSink() { + isCallbackFieldSink(this) + } +} + +class CallbackPostTaskSink extends CallbackSinks { + CallbackPostTaskSink() { + postTaskSink(this) + } +} + +class CallbackRunSink extends CallbackSinks { + CallbackRunSink() { + runCallbackSink(this) + } +} + +class CallbackInterfacePtrSink extends CallbackSinks { + CallbackInterfacePtrSink() { + interfacePtrCallSink(this) + } +} \ No newline at end of file diff --git a/CodeQL_Queries/cpp/Chrome/collections.qll b/CodeQL_Queries/cpp/Chrome/collections.qll new file mode 100644 index 0000000..138c2a1 --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/collections.qll @@ -0,0 +1,292 @@ +import cpp +import common +import semmle.code.cpp.dataflow.DataFlow + +//-------- Dataflow edges ------------ + +/** + * Edges that goes from some simple standard containers into their elements. + */ +predicate pairEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | + fc.getTarget().getName().matches("tuple%") or + fc.getTarget().getName().matches("pair<%") or + fc.getTarget().getName().matches("value_type%") + | + node1.asExpr() = fc.getAnArgument() and node2.asExpr() = fc + ) +} + +/** Goes from an element into a collection via an overloaded index operator.*/ +predicate indexSetEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget().hasName("operator[]") | + node2.asExpr() = getQualifier(fc) and node1.asExpr() = fc + ) +} + +/** Goes from a collection to its element via an overloaded index operator. */ +predicate indexGetEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget().hasName("operator[]") | + node1.asExpr() = getQualifier(fc) and node2.asExpr() = fc + ) +} + +/** + * Edge that goes from an a method that adds to a collection into a collection. + */ +predicate addToCollectionEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget() instanceof AddToCollection and + getQualifier(fc) = node2.asExpr() and node1.asExpr() = fc + ) +} + +/** + * Edge that goes from a collection to a the result of a method that takes elements from it. + */ +predicate takeFromCollectionEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget() instanceof TakeFromCollection and + getQualifier(fc) = node1.asExpr() and node2.asExpr() = fc + ) +} + +/** + * To go from an iterator to iterator->second. + */ +predicate mapIteratorEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FieldAccess fa | fa.getTarget().hasName("second") and + node1.asExpr() = getQualifier(fa) and + node2.asExpr() = fa + ) +} + +/** + * Combine edge that goes from a collection to an element. + */ +predicate collectionsGetEdge(DataFlow::Node node1, DataFlow::Node node2) { + indexGetEdge(node1, node2) or + takeFromCollectionEdge(node1, node2) or + mapIteratorEdge(node1, node2) or + pairEdge(node1, node2) +} + +/** + * Combine edge that goes from an element into a collection. + */ +predicate collectionsSetEdge(DataFlow::Node node1, DataFlow::Node node2) { + indexSetEdge(node1, node2) or + addToCollectionEdge(node1, node2) or + pairEdge(node1, node2) +} + +/** + * Combine edge that goes either from element to collection or vice versa. + */ +predicate collectionsEdge(DataFlow::Node node1, DataFlow::Node node2) { + indexGetEdge(node1, node2) or + indexSetEdge(node1, node2) or + addToCollectionEdge(node1, node2) or + takeFromCollectionEdge(node1, node2) or + mapIteratorEdge(node1, node2) or + pairEdge(node1, node2) +} + +/** + * Various methods that adds to a collection. + */ +class AddToCollection extends Function { + AddToCollection() { + hasName("push_back") or + hasName("insert") or + hasName("emplace_back") or + hasName("emplace") or + hasName("push") or + hasName("push_front") + } +} + +/** + * Various methods that takes from a collection. + */ +class TakeFromCollection extends Function { + TakeFromCollection() { + hasName("find") or + getName().matches("pop_%") or + hasName("pop") or + hasName("find_if") or + hasName("front") + } +} + +/** + * Loose heuristics for collection types that are like vector, list etc. + */ +class ListType extends Class { + ListType() { + exists(Function f, Type t | + f.hasName("push_back") or f.hasName("front") or f.hasName("push") or f.hasName("pop") + | t = f.getDeclaringType() and + this = t.stripType().(ClassTemplateInstantiation) + ) + } + + /** + * Gets the type of the component in the container. + */ + Type getComponentType() { + result = getComponentType*(this.getTemplateArgument(0)) and + not collectionTemplateType(result) + } +} + +/** + * General type that represents a set. + */ +class SetType extends Class { + SetType() { + this.getName().matches("set<%") or + this.getName().matches("flat_set<%") + } + + Type getComponentType() { + result = getComponentType*(this.getTemplateArgument(0)) and + not collectionTemplateType(result) + } +} + +/** + * Type that represents a tuple. + */ +class TupleType extends ClassTemplateInstantiation { + TupleType() { + getName().matches("pair%") or + getName().matches("tuple%") + } +} + +class ValueType extends ClassTemplateInstantiation { + ValueType() { + this.getName().matches("__value_type%") + } +} + +class HashValueType extends ClassTemplateInstantiation { + HashValueType() { + this.getName().matches("__has_value_type%") + } +} + + +/** + * General method to get the component type of different containers. + */ +Type getComponentType(Type t) { + if collectionTemplateType(t) then + result = t.(ListType).getComponentType() or + result = t.(TupleType).getATemplateArgument() or + result = t.(ValueType).getATemplateArgument() or + result = t.(HashValueType).getATemplateArgument() or + result = t.(MapType).getComponentType() + else + result = t +} + +/** Holds if `t` is a collection type.*/ +predicate collectionTemplateType(Type t) { + t instanceof ListType or + t instanceof TupleType or + t instanceof ValueType or + t instanceof HashValueType or + t instanceof MapType +} + +/** + * Heuristics for the map type. + */ +class MapType extends Class { + MapType() { + ( + exists(Function f, Type t | + f.hasName("find") or f.hasName("insert")| + t = f.getDeclaringType() and + this = t.stripType().(ClassTemplateInstantiation) + ) + ) + and + not this.getName().matches("__tuple_leaf%") and + not this.getName().matches("pair%") and + not this.getName().matches("union%") + } + + /** Component 0 is the key type, component 1 is the value type.*/ + Type getComponentTypeAt(int i) { + ( + result = getComponentType*(this.getTemplateArgument(i)) and + not collectionTemplateType(result) + ) + } + + Type getComponentType() { + ( + result = getComponentType*(this.getTemplateArgument(0)) and + not collectionTemplateType(result) + ) or + ( + result = getComponentType*(this.getTemplateArgument(1)) and + not collectionTemplateType(result) + ) + } +} + +/** A general field that is of `Collection` type.*/ +abstract class CollectionField extends Field { + + /** An expression that resets an element of the collection field. */ + abstract Expr getAReset(); +} + +class MapField extends CollectionField { + MapField() { + getType() instanceof MapType + } + + override Expr getAReset() { + exists(FunctionCall fc | fc.getTarget().hasName("erase") or + fc.getTarget().getName().matches("pop%") or fc.getTarget().hasName("clear") or + fc.getTarget().hasName("insert") | + fc.getQualifier() = this.getAnAccess() and + result = fc + ) + or + exists(FunctionCall fc | fc.getTarget().hasName("operator[]") and + fc.getQualifier() = this.getAnAccess() and + not exists(FunctionCall assign | assign.getTarget().hasName("operator=") and + fc = assign.getArgument(0) + ) and + not exists(AssignExpr assign | assign.getRValue() = fc) and + result = fc + ) + } +} + +class ListField extends CollectionField { + ListField() { + getType() instanceof ListType + } + + override Expr getAReset() { + exists(FunctionCall fc | fc.getTarget().hasName("erase") or + fc.getTarget().getName().matches("pop%") or fc.getTarget().hasName("clear") | + fc.getQualifier() = this.getAnAccess() and + result = fc + ) + or + exists(FunctionCall fc | fc.getTarget().hasName("operator[]") and + fc.getQualifier() = this.getAnAccess() and + not exists(FunctionCall assign | assign.getTarget().hasName("operator=") and + fc = assign.getArgument(0) + ) and + not exists(AssignExpr assign | assign.getRValue() = fc) and + result = fc + ) + } +} \ No newline at end of file diff --git a/CodeQL_Queries/cpp/Chrome/common.qll b/CodeQL_Queries/cpp/Chrome/common.qll new file mode 100644 index 0000000..ef11c49 --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/common.qll @@ -0,0 +1,177 @@ +import cpp +import collections +import pointers.managed_ptr +import semmle.code.cpp.dataflow.DataFlow + +/** + * Common utilities that is adapted to the specific coding pattern in chrome. + */ + + +//--------- dataflow edges + +/** + * A dataflow edge that allows propagation through operator= as well as normal assignment. + * See `GeneralAssignment` for details. + */ +predicate generalAssignEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(GeneralAssignment expr | node1.asExpr() = expr.getRValue() and + node2.asExpr() = expr.getLValue() + ) +} + +/** + * A dataflow edge that allows propagation into loop variable of the form + * ``` + * for (int x : xs) { + * ... + * } + * ``` + * i.e. goes from `xs` to `x`. + */ +predicate forRangeEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(RangeBasedForStmt stmt | node1.asExpr() = stmt.getRange() and + node2.asExpr() = stmt.getRangeVariable().getAnAccess() + ) +} + +/** + * Allows flows of the form: + * ``` + * *x = a; //<-- a tainted + * b = x; //<-- b now tainted. + * ``` + */ +predicate useUsePairEdge(DataFlow::Node node1, DataFlow::Node node2) { + useUsePair(_, node1.asExpr(), node2.asExpr()) +} + +predicate copyConstructorEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall cp | cp.getTarget() instanceof CopyConstructor and + cp.getArgument(0) = node1.asExpr() and + cp = node2.asExpr() + ) +} + +predicate newEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(NewExpr expr | expr = node2.asExpr() and + node1.asExpr() = expr.getInitializer() + ) +} + +/** + * Allows dataflow to go through polymorphic function call. Exclude some functions that + * are frequently overloaded to cut down on noise, probably needs tweaking. + */ +predicate polymorphicCallEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc, MemberFunction g, int i | fc.getArgument(i) = node1.asExpr() and + overrides*(g, fc.getTarget()) and node2.asParameter() = g.getParameter(i)| + not fc.getTarget() instanceof AddToCollection and + not fc.getTarget() instanceof TakeFromCollection and + not fc.getTarget() instanceof PointerTransferFunction and + not g.getDeclaringType() instanceof ManagedPtr and + not count(g) = 1 + ) +} + +/** + * Edge that takes into account of general constructions like make_unique, + */ +predicate constructorEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc, Constructor cstor, Class c, Parameter p, int idx | p.getAnAccess() = node2.asExpr() and + constructionCall(c, fc) and p = cstor.getParameter(idx) and + cstor.getDeclaringType() = c and + cstor.getNumberOfParameters() = fc.getNumberOfArguments() and + node1.asExpr() = fc.getArgument(idx) + ) +} + +//---------- End dataflow edges + +/** + * This should be used instead of the `.getQualifier` method in `FunctionCall` and `FieldAccess` because + * the operator -> is often overriden in Chrome. This general method skip the overriden operator -> to find the + * true qualifier in those circumstances. + */ +Expr getQualifier(Expr fc) { + exists(Expr qualifier | qualifier = fc.(FunctionCall).getQualifier() or qualifier = fc.(FieldAccess).getQualifier() | + if qualifier.(FunctionCall).getTarget().hasName("operator->") then + result = getQualifier(qualifier) + else + result = qualifier + ) +} + +/** + * Again, this should be used instead of `Assignment` because the operator= is often overloaded. This general + * expression takes operator= into account. + */ +class GeneralAssignment extends Expr { + GeneralAssignment() { + this instanceof Assignment or + this.(FunctionCall).getTarget().hasName("operator=") + } + + Expr getLValue() { + result = this.(Assignment).getLValue() or + result = this.(FunctionCall).getQualifier() + } + + Expr getRValue() { + result = this.(Assignment).getRValue() or + result = this.(FunctionCall).getArgument(0) + } +} + +/** + * General construction calls that takes `make_unique` and `MakeRefCounted` into account. + */ +predicate constructionCall(Class c, FunctionCall fc) { + (fc instanceof ConstructorCall and fc.getTarget().getDeclaringType() = c) or + ( + (fc.getTarget().getName().matches("make_unique%") or fc.getTarget().getName().matches("MakeRefCounted%")) + and + fc.getTemplateArgument(0) = c + ) +} + +/** + * General construction call that takes `make_unique` and `MakeRefCounted` into account, but also + * constructor of base class from derived classes. + */ +predicate polyConstructionCall(Class c, FunctionCall fc) { + (fc instanceof ConstructorCall and fc.getTarget().getDeclaringType().getABaseClass*() = c and + not fc instanceof ConstructorBaseInit and not fc.isCompilerGenerated()) or + ( + (fc.getTarget().getName().matches("make_unique%") or fc.getTarget().getName().matches("MakeRefCounted%")) + and + fc.getTemplateArgument(0).(Class).getABaseClass*() = c + ) +} + + +/** + * Convenient function to extend `stripType` to include managed pointers also. + */ +Type generalStripType(Type t) { + exists(Type st | st = t.stripType() | + if st instanceof ManagedPtr then + result = st.(ManagedPtr).getManagedType().stripType() + else + result = st + ) +} + +/** For Optional, UnretainedWrapper, RetainedWrapper to get the underlying type*/ +//TODO: Decide whether to include StructPtr or not +Type unwrapType(ClassTemplateInstantiation t) { + if + ( + t.getName().matches("Optional<%") or + t.getName().matches("UnretainedWrapper<%") or + t.getName().matches("RetainedRefWrapper<%") + ) then + result = t.getTemplateArgument(0) + else + result = t +} \ No newline at end of file diff --git a/CodeQL_Queries/cpp/Chrome/field.qll b/CodeQL_Queries/cpp/Chrome/field.qll new file mode 100644 index 0000000..6c4e89a --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/field.qll @@ -0,0 +1,34 @@ +import cpp +import common +import collections +import pointers.raw_ptr + +/** + * An expression that assigns values to a general field. Can be an assignment, + * or an index expression that modifies a collection etc. + */ +Expr generalAssignValue(Field f) { + result = f.getAnAssignedValue() or + //normal assignment + exists(GeneralAssignment expr | expr.getLValue() = f.getAnAccess() and + expr.getRValue() = result + ) + or + //Adding to a collection field + exists(FunctionCall fc | fc.getTarget() instanceof AddToCollection and result = fc.getAnArgument() and + getQualifier(fc) = f.getAnAccess() + ) + or + //setting managed pointers. + exists(FunctionCall fc | fc.getTarget() instanceof ManagedPtrSetFunction and + getQualifier(fc) = f.getAnAccess() and + result = fc.getAnArgument() + ) + or + //index operator to assign values to a collection field. + exists(FunctionCall indexer, GeneralAssignment expr | expr.getLValue() = indexer and + indexer.getTarget().hasName("operator[]") and + expr.getRValue() = result and + getQualifier(indexer) = f.getAnAccess() + ) +} diff --git a/CodeQL_Queries/cpp/Chrome/object_lifetime/lifetime_management.qll b/CodeQL_Queries/cpp/Chrome/object_lifetime/lifetime_management.qll new file mode 100644 index 0000000..0177f22 --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/object_lifetime/lifetime_management.qll @@ -0,0 +1,72 @@ +import cpp +import callbacks + +/** + * Models various mechanism in Chrome that is used for managing object lifetime. + */ + + /** + * A map field that has pointer as key and managed pointer of the same type as value. + * Usually the value are backing the keys, so it is usually ok. + */ +class ManagedKeyValueField extends MapField { + ManagedKeyValueField() { + exists(PointerType key, ManagedPtr value | + this.getType().(MapType).getComponentTypeAt(0) = key and + this.getType().(MapType).getComponentTypeAt(1) = value and + key.stripType() = value.getManagedType() + ) + } +} + +/** + * A wrapper of `destructorCleanup` for raw pointer fields that are removed when the type is destroyed. + */ +predicate cleanupInDestructor(GeneralPointerField f) { + exists(Destructor d, Expr fa | destructorCleanup(f, d, fa)) +} + +/** + * Whether the destructor of a type will remove the field f when executed. + */ +predicate destructorCleanup(GeneralPointerField f, Destructor d, Expr fa) { + f.getPointerType() = d.getDeclaringType().getABaseClass*() and + fa = f.getACleanup() and + reach*(d, fa.getEnclosingFunction()) +} + +/** + * FrameServiceBase is a class that observes the lifetime of RenderFrameHostImpl and + * so raw pointer of rfh inside it is usually ok. + */ +class FrameServiceBase extends ClassTemplateInstantiation { + FrameServiceBase() { + getName().matches("FrameServiceBase<%") + } + + Type getService() { + result = getTemplateArgument(0) + } +} + +predicate frameServiceBaseProtected(Field f) { + f.hasName("render_frame_host_") and + exists(FrameServiceBase fsb | fsb.getService() = f.getDeclaringType()) +} + +/** + * An expression that is not inside the constructor of a class. This and + * the following few predicates are useful to see if a managed pointer reset is + * inside constructor/destructor etc., which usually makes them ok. + */ +predicate notInsideConstructor(Class c, Expr e) { + not exists(Constructor f | f = e.getEnclosingFunction() and + f.getDeclaringType() = c + ) +} + +predicate notInsideDestructor(Class c, Expr e) { + not exists(Destructor f | f = e.getEnclosingFunction() and + f.getDeclaringType() = c + ) +} diff --git a/CodeQL_Queries/cpp/Chrome/object_lifetime/obj_lifetime.qll b/CodeQL_Queries/cpp/Chrome/object_lifetime/obj_lifetime.qll new file mode 100644 index 0000000..5525700 --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/object_lifetime/obj_lifetime.qll @@ -0,0 +1,68 @@ +import cpp +import pointers.managed_ptr + +/** + * Long live classes, e.g. Singleton, classes owned by BrowserLoop etc. + */ + +class OwnedByBrowserContext extends Class { + OwnedByBrowserContext() { + this.getName() = "VideoDecodePerfHistory" or + this.getName() = "ServiceInstanceGroupHolder" or + this.getName() = "BrowserContextServiceManagerConnectionHolder" or + this.getName() = "ContentServiceDelegateHolder" or + this.getName() = "FileServiceHolder" or + this.getName() = "PermissionControllerImpl" or + this.getName() = "BrowsingDataRemoverImpl" or + this.getName() = "StoragePartitionImplMap" or + this.getName() = "DownloadManager" or + this.getName() = "BrowsingDataRemover" or + this.getName() = "PermissionController" + } +} + +class Singleton extends Type { + Singleton() { + this.hasName("MediaInternals") or + hasName("NetworkConnectionTracker") or + hasName("ChildProcessSecurityPolicyImpl") or + hasName("MprisService") or + hasName("PluginServiceFilter") or + hasName("TickClock") or + exists(Class c | c.getName().matches("Singleton<%") and + this = c.getTemplateArgument(0) + ) or + exists(Variable v | v.isStatic() and + v.getType().getName().matches("NoDestructor<%") and + v.getType().(Class).getTemplateArgument(0) = this and + not this.getName().matches("vector<%") and + not this.getName().matches("atomic<%") and + not this.getName().matches("set<%") and + not this.getName().matches("basic_string<%") and + not this.getName().matches("map<%") and + not this.getName().matches("CallbackList<%") and + not this.getName().matches("RepeatingCallback<%") and + not this.getName().matches("OnceCallback<%") and + not this.getName().matches("unique_ptr<%") and + not this.getName().matches("scoped_refptr<%") and + not this.getName().matches("WeakPtr<%") and + not this.getName().matches("ThreadLocalPointer<%") and + not this.getName().matches("ReceiverSetBase<%") and + not this.getName().matches("SequenceLocalStorageSlot<%") + ) + } +} + +/** + * Types that are owned by browser main loop. These types are usually long lived. + */ +class OwnedByBrowserMainLoop extends Class { + OwnedByBrowserMainLoop() { + exists(GeneralManagedField f | f.getManagedType() = this and + f.getDeclaringType().hasName("BrowserMainLoop") + ) or + getName() = "UserInputMonitor" or + getName() = "UserInputMonitorBase" or + getName() = "BrowserGpuChannelHostFactory" + } +} diff --git a/CodeQL_Queries/cpp/Chrome/pointers/managed_ptr.qll b/CodeQL_Queries/cpp/Chrome/pointers/managed_ptr.qll new file mode 100644 index 0000000..9af48b3 --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/pointers/managed_ptr.qll @@ -0,0 +1,281 @@ +import cpp +import semmle.code.cpp.dataflow.DataFlow +import collections + +/** + * General library code to deal with managed pointer types. + */ + +//------------ dataflow edges + +/** + * `x -> x.get();` + * `x` managed pointer + */ +predicate getEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget() instanceof PointerGet and + node1.asExpr() = fc.getQualifier() and node2.asExpr() = fc + ) +} + +/** + * `x -> std::move(x);` + * `x` managed pointer. + */ +predicate stdMoveEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget() instanceof StdMove and + fc.getAnArgument() = node1.asExpr() and + fc = node2.asExpr() + ) +} + +/** + * `x -> x.release + */ +predicate stdReleaseEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget() instanceof StdRelease and + fc.getQualifier() = node1.asExpr() and + fc = node2.asExpr() + ) +} + +/** + * `x -> std::forward(x);` + */ +predicate stdForwardEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget() instanceof StdForward and + fc.getAnArgument() = node1.asExpr() and + fc = node2.asExpr() + ) +} + +predicate wrapUniqueEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget().getName().matches("WrapUnique%") and + fc = node2.asExpr() and fc.getArgument(0) = node1.asExpr() + ) +} + +predicate makeUniqueEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget().getName().matches("make_unique%") and + fc = node2.asExpr() and fc.getArgument(0) = node1.asExpr() + ) +} + +predicate makeRefEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget().getName().matches("MakeRefCounted%") and + fc = node2.asExpr() and fc.getArgument(0) = node1.asExpr() + ) +} + +predicate wrapRefCountedEdge(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionCall fc | fc.getTarget().getName() = "WrapRefCounted" and + fc = node2.asExpr() and fc.getArgument(0) = node1.asExpr() + ) +} + +predicate pointerWrapperEdge(DataFlow::Node node1, DataFlow::Node node2) { + makeRefEdge(node1, node2) or + makeUniqueEdge(node1, node2) or + wrapUniqueEdge(node1, node2) or + wrapRefCountedEdge(node1, node2) +} + +predicate pointerTransferEdge(DataFlow::Node node1, DataFlow::Node node2) { + stdMoveEdge(node1, node2) or + stdForwardEdge(node1, node2) or + stdReleaseEdge(node1, node2) +} + +//-------------dataflow ends + +/** + * General managed pointer type. Only includes `scoped_refptr` and `unique_ptr` + * at the moment. (`WeakPtr` not too interesting for memory corruption problem) + * Includes normal type or container types. + */ +abstract class GeneralManagedType extends Class { + /** + * Gets the underlying managed type. + */ + abstract Type getManagedType(); + + /** + * Holds if the type is owned completely, i.e. `unique_ptr`. + */ + abstract predicate isOwner(); +} + +/** + * General managed pointer field. Excludes `WeakPtr` as it is less interesting. + * Includes normal field or collection field. + */ +abstract class GeneralManagedField extends Field { + + /** + * Gets the underlying type that is being managed. + */ + abstract Type getManagedType(); + + /** + * Gets an expression that resets the pointer, which can free the underlying object. + */ + abstract Expr getAManagedReset(); + + /** + * Holds if the field is owned completely, i.e. `unique_ptr`. + */ + abstract predicate isOwner(); +} + +/** Function that transfers ownership of a managed pointer.*/ +abstract class PointerTransferFunction extends Function { +} + +class StdMove extends PointerTransferFunction { + StdMove() { + this.getQualifiedName() = "std::__Cr::move" + } +} + +class StdForward extends PointerTransferFunction { + StdForward() { + this.getQualifiedName() = "std::__Cr::forward" + } +} + +class StdRelease extends PointerTransferFunction { + StdRelease() { + this.getName() = "release" and + this.getDeclaringType() instanceof ManagedPtr + } +} + +/** The `get` function from a managed pointer that returns the underlying raw pointer.*/ +class PointerGet extends Function { + PointerGet() { + this.hasName("get") and + this.getDeclaringType().stripType().getName().matches("%_ptr%") + } +} + +/** Normal managed pointers.*/ +class ManagedPtr extends GeneralManagedType { + ManagedPtr() { + this.getName().matches("unique_ptr%") or + this.getName().matches("scoped_refptr%") + } + + override Type getManagedType() {result = this.getTemplateArgument(0)} + + override predicate isOwner() {getName().matches("unique_ptr%")} +} + +/** Normal managed pointer fields.*/ +class ManagedPtrField extends GeneralManagedField { + ManagedPtrField() { + this.getType().stripType() instanceof ManagedPtr + } + + override Type getManagedType() {result = getType().stripType().(ManagedPtr).getManagedType()} + + override Expr getAManagedReset() { + result.(FunctionCall).getTarget() instanceof ManagedPtrSetFunction and + getQualifier(result) = this.getAnAccess() + } + + override predicate isOwner() { + this.getType().stripType().(ManagedPtr).isOwner() + } +} + +/** A function that resets a managed pointer.*/ +class ManagedPtrSetFunction extends Function { + ManagedPtrSetFunction() { + hasName("operator=") or + hasName("reset") + } +} + +/** + * A managed pointer that is inside a collection. + */ +abstract class ManagedCollectionType extends GeneralManagedType { + ManagedCollectionType() { + this.stripType().getName().matches("%unique_ptr%") or + this.stripType().getName().matches("%scoped_refptr%") + } + + override predicate isOwner() { + this.stripType().getName().matches("%unique_ptr%") + } +} + +/** + * A managed pointer inside a map. + */ +class ManagedMapType extends ManagedCollectionType, MapType { + + ManagedMapType() { + getComponentType() instanceof ManagedPtr + } + + override Type getManagedType() { + exists(Type t | + t = getComponentType() and + result = t.(ManagedPtr).getManagedType() + ) + } +} + +/** A managed pointer inside a list type. */ +class ManagedListType extends ManagedCollectionType, ListType { + + ManagedListType() { + getComponentType() instanceof ManagedPtr + } + + override Type getManagedType() { + exists(Type t | + t = getComponentType() and + result = t.(ManagedPtr).getManagedType() + ) + } +} + +/** + * A field that stores managed pointers in a map. + */ +class ManagedMapField extends MapField, GeneralManagedField { + ManagedMapField() { + // binding set is not likely to have problem. + not (getName() = "bindings_" and getDeclaringType().getName().matches("BindingSet%")) and + getType() instanceof ManagedMapType + } + + override Type getManagedType() { + result = getType().(ManagedMapType).getManagedType() + } + + override Expr getAManagedReset() {result = getAReset()} + + override predicate isOwner() {getType().(ManagedMapType).isOwner()} +} + +/** + * A field that stores managed pointers in a list/vector type. + */ +class ManagedListField extends ListField, GeneralManagedField { + ManagedListField() { + // binding set is not likely to have problem. + not (getName() = "bindings_" and getDeclaringType().getName().matches("BindingSet%")) and + getType() instanceof ManagedListType + } + + override Type getManagedType() { + result = getType().(ManagedListType).getManagedType() + } + + override Expr getAManagedReset() {result = getAReset()} + + override predicate isOwner() {getType().(ManagedListType).isOwner()} +} \ No newline at end of file diff --git a/CodeQL_Queries/cpp/Chrome/pointers/raw_ptr.qll b/CodeQL_Queries/cpp/Chrome/pointers/raw_ptr.qll new file mode 100644 index 0000000..c17c113 --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/pointers/raw_ptr.qll @@ -0,0 +1,115 @@ +import cpp +import common + +/** + * General library code to deal with raw pointer types. + */ + +/** + * General type that contains raw pointers, e.g. raw pointer fields, + * pointers in collections etc. + */ +abstract class GeneralPointerType extends Type { + /** + * Gets the underlying raw pointer type. + */ + abstract Type getPointerType(); +} + +/** + * General field that are `GeneralPointerType`. + */ +abstract class GeneralPointerField extends Field { + + /** + * Gets the underlying raw pointer type + */ + abstract Type getPointerType(); + + /** + * Gets a clean up that will reset the raw pointer. + */ + abstract Expr getACleanup(); +} + +/** + * A normal raw pointer field. + */ +class PointerField extends GeneralPointerField { + PointerField() { + getType().getUnspecifiedType() instanceof PointerType and + //Needs to exclude some types that are less interesting (mostly generated code) + not getDeclaringType().getName().matches("ArrayDataViewImpl%") and + not getDeclaringType().getName().matches("AllocatedData") and + not getDeclaringType().getName().matches("AlignedUnion") and + not getDeclaringType().getName().matches("ArrayViewBase%") and + not getDeclaringType().getName().matches("AssociatedInterface%") and + not hasName("receiver_") and + ( + //Only interested in certain area of the code. Can tweak + getFile().getAbsolutePath().matches("%/browser/%") or + getFile().getAbsolutePath().matches("%/media/%") or + getFile().getAbsolutePath().matches("%/components/%") + ) + } + + override Type getPointerType() {result = this.getType().stripType()} + + override Expr getACleanup() { + exists(GeneralAssignment assign | assign.getLValue() = this.getAnAccess() and + assign.getRValue() instanceof Zero and + result = assign.getLValue() + ) + } +} +/** + * A field that stores raw pointers in map. + */ +class PointerMapField extends MapField, GeneralPointerField { + PointerMapField() { + getType().(MapType).getComponentType() instanceof PointerType + } + + override Type getPointerType() { + exists(Type t | + t = getType().(MapType).getComponentType() and + t instanceof PointerType and + result = t.stripType() + ) + } + + override Expr getACleanup() { + exists(FunctionCall fc | fc.getTarget().hasName("erase") or + fc.getTarget().hasName("clear") or fc.getTarget().hasName("remove") | + getQualifier(fc) = this.getAnAccess() and + result = fc + ) + } + +} + +/** + * A field that stores raw pointers in list/vector. + */ +class PointerListField extends ListField, GeneralPointerField { + PointerListField() { + getType().(MapType).getComponentType() instanceof PointerType + } + + override Type getPointerType() { + exists(Type t | + t = getType().(ListType).getComponentType() and + t instanceof PointerType and + result = t.stripType() + ) + } + + override Expr getACleanup() { + exists(FunctionCall fc | fc.getTarget().hasName("erase") or + fc.getTarget().hasName("clear") or fc.getTarget().hasName("remove") | + getQualifier(fc) = this.getAnAccess() and + result = fc + ) + } + +} diff --git a/CodeQL_Queries/cpp/Chrome/queries/README.md b/CodeQL_Queries/cpp/Chrome/queries/README.md new file mode 100644 index 0000000..4de6cc4 --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/queries/README.md @@ -0,0 +1,30 @@ +# Example queries + +The queries in this directory are more of an example of how to use the CodeQL libraries in the repository, rather than concrete queries use for finding bugs. Many of these requires tweaking of some filters to restrict the results to some subset of the Chromium codebase. + +Here is an overview of what they do: + +## Callback tracking + +These queries uses the `callbacks.qll` library and are useful in checking raw pointers that are stored in a particular callback. The library `callbacks.qll` divides callbacks into the following types: `CallbackFieldSink`, `CallbackPostTaskSink`, `CallbackRunSink`, `CallbackInterfacePtrSink` that allows further specification of callback types. In general, it is recommended to run this query without restriction first, and use the result as a look up table during manual inspection, instead of running the query every time with different restrictions to the sink, as the former will be more efficient. + +### `callback_unretained.ql` + +Use to identify the pointer types that are stored inside a callback. This is a very useful query for investigation as callbacks often contain other callbacks and it can take sometime to figure out what is store in them. + +### `callback_unretained_field.ql` + +Like `callback_unretained.ql`, but specifically look for callback fields and output the specific field associated with the callback. This is often useful as callback fields can be hard to track manually. + +## Pointer cleanup + +These queries are used for looking up the cleanup pattern of raw pointer fields. This is generally achieved by matching types of the field with cleanup operations performed on fields of the same type, rather than tracking the precise movement of the field itself. This is a compromised that has to be made in order to reduce the complexity of the queries and make them feasible, both in terms of the manual effort involved in writing the libraries and the running time of the queries. In practice, this compromised works mostly ok. + +### `no_cleanup.ql` + +A query that is used to identify raw pointer fields that does not get set to nullptr, or gets erase from container (clean up), while removing some cases that are less likely to be dangerous. + +### `non_trivial_cleanup.ql` + +This is similar to no_cleanup.ql, but instead looks for raw pointers that does get clean up, but the clean up is not called (transitively) from the destructor of the pointer type. The idea is that there may be ways to destroy the pointer without removing the field (as the destructor does not do the clean up). + diff --git a/CodeQL_Queries/cpp/Chrome/queries/callback_unretained.ql b/CodeQL_Queries/cpp/Chrome/queries/callback_unretained.ql new file mode 100644 index 0000000..5bdebfd --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/queries/callback_unretained.ql @@ -0,0 +1,22 @@ +/** + * @name callback_unretained + * @description For each callback, get the types that are unretained in the callback. + * @kind problem + * @problem.severity warning + */ + +import cpp +import callbacks +import object_lifetime.obj_lifetime + +//Can change to different types of CallbackSinks for more specific investigation +from CallbackSinks sink, Type unretainedType +where not exists(FunctionCall fc | fc.getTarget().hasName("set_connection_error_handler") and + fc.getAnArgument() = sink.asExpr() + ) +and not sink.asExpr().getFile().getBaseName().matches("%test-utils%") and +unretainedType = sink.getAnUnretainedType() and +not unretainedType instanceof Singleton and +not unretainedType instanceof OwnedByBrowserMainLoop + +select sink, unretainedType diff --git a/CodeQL_Queries/cpp/Chrome/queries/callback_unretained_field.ql b/CodeQL_Queries/cpp/Chrome/queries/callback_unretained_field.ql new file mode 100644 index 0000000..e64f0fb --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/queries/callback_unretained_field.ql @@ -0,0 +1,22 @@ +/** + * @name callback_unretained_field + * @description For each callback field, get the types that are unretained in the callback. (specialized version of callback_unretained) + * @kind problem + * @problem.severity warning + */ + +import cpp +import callbacks +import object_lifetime.obj_lifetime + +from CallbackFieldSink sink, Field f, Type unretainedType +//Heuristics from observation +where not exists(FunctionCall fc | fc.getTarget().hasName("set_connection_error_handler") and + fc.getAnArgument() = sink.asExpr() + ) +and not sink.asExpr().getFile().getBaseName().matches("%test-utils%") +and sink.asExpr() = generalAssignValue(f) and +unretainedType = sink.getAnUnretainedType() and +not unretainedType instanceof Singleton and +not unretainedType instanceof OwnedByBrowserMainLoop +select f, sink, unretainedType diff --git a/CodeQL_Queries/cpp/Chrome/queries/no_cleanup.ql b/CodeQL_Queries/cpp/Chrome/queries/no_cleanup.ql new file mode 100644 index 0000000..ed455e7 --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/queries/no_cleanup.ql @@ -0,0 +1,73 @@ +/** + * @name no cleanup + * @description raw pointer fields that does not have any cleanup or managed objects that backs them. + * @kind problem + * @problem.severity warning + */ + +import cpp +import common +import pointers.raw_ptr +import pointers.managed_ptr +import object_lifetime.lifetime_management +import object_lifetime.obj_lifetime + +from GeneralPointerField f +where +//The type of the pointer field contains a managed field that is the declaring type of the pointer field, e.g. +//``` class A { +// F* f; +// } +// class F { +// unique_ptr a; +// } +//``` +// often, `F` is the owner of `A`. +not exists(GeneralManagedField mf | f.getPointerType() = mf.getDeclaringType().getABaseClass*() + and mf.getManagedType() = f.getDeclaringType() and + mf.isOwner() +) +and +//Cases like: +//``` +// class A { +// F* f; +// } +// class B { +// std::unique_ptr a; +// std::unique_ptr f; +// } +//``` +// Class B should be managing both `A` and `F`, this can have interesting consequences if `a` gets reset and `f` doesn't. +// Leave it out for now. +// +not exists(GeneralManagedField ptr_mf, GeneralManagedField mf | f.getDeclaringType() = mf.getManagedType() and + f.getPointerType() = ptr_mf.getManagedType() and + mf.getDeclaringType() = ptr_mf.getDeclaringType() +) +and +//Restrict to files of interests. +(f.getFile().getAbsolutePath().matches("%/browser/%") or f.getFile().getAbsolutePath().matches("%/components/%")) and +//exclude protobuf +not f.getFile().getBaseName().matches("%.pb.%") and +//raw pointer has a clean up +not exists(Expr p | p = f.getACleanup()) and +//exclude field that is probably safe (See `ManagedKeyValueField`) +not f instanceof ManagedKeyValueField +and +//FrameServiceBase and raw pointer is `render_frame_host_`, most likely ok as FrameServiceBase observes lifetime of rfh. +not exists(FrameServiceBase fsb | + (fsb.getService() = f.getDeclaringType() or fsb = f.getDeclaringType())and + f.getName() = "render_frame_host_" +) and +//Unlikely to be able to delete. +not f.getPointerType() instanceof OwnedByBrowserMainLoop and +//Unlikely to be able to delete. +not f.getPointerType() instanceof Singleton and +//Generated +not f.getFile().getAbsolutePath().matches("%/out/%") and +//BrowserContext only destroyed during shutdown +not f.getPointerType().hasName("BrowserContext") and +not f.getPointerType() instanceof OwnedByBrowserContext + +select f, f.getDeclaringType() diff --git a/CodeQL_Queries/cpp/Chrome/queries/non_trivial_cleanup.ql b/CodeQL_Queries/cpp/Chrome/queries/non_trivial_cleanup.ql new file mode 100644 index 0000000..9c83f7d --- /dev/null +++ b/CodeQL_Queries/cpp/Chrome/queries/non_trivial_cleanup.ql @@ -0,0 +1,75 @@ +/** + * @name non trivial cleanup + * @description raw pointer fields that does has some clean up, but not trivial (i.e. executes in destructor) or managed objects that backs them. + * @kind problem + * @problem.severity warning + */ + +import cpp +import common +import pointers.raw_ptr +import pointers.managed_ptr +import object_lifetime.lifetime_management +import object_lifetime.obj_lifetime + +from GeneralPointerField f +where +//The type of the pointer field contains a managed field that is the declaring type of the pointer field, e.g. +//``` class A { +// F* f; +// } +// class F { +// unique_ptr a; +// } +//``` +// often, `F` is the owner of `A`. +not exists(GeneralManagedField mf | f.getPointerType() = mf.getDeclaringType().getABaseClass*() + and mf.getManagedType() = f.getDeclaringType() and + mf.isOwner() +) +and +//Cases like: +//``` +// class A { +// F* f; +// } +// class B { +// std::unique_ptr a; +// std::unique_ptr f; +// } +//``` +// Class B should be managing both `A` and `F`, this can have interesting consequences if `a` gets reset and `f` doesn't. +// Leave it out for now. +// +not exists(GeneralManagedField ptr_mf, GeneralManagedField mf | f.getDeclaringType() = mf.getManagedType() and + f.getPointerType() = ptr_mf.getManagedType() and + mf.getDeclaringType() = ptr_mf.getDeclaringType() +) +and +//Restrict to files of interests. +(f.getFile().getAbsolutePath().matches("%/browser/%") or f.getFile().getAbsolutePath().matches("%/components/%")) and +//exclude protobuf +not f.getFile().getBaseName().matches("%.pb.%") and +//raw pointer has a clean up +exists(Expr p | p = f.getACleanup()) and +//but clean up is not in destructor, so may be skipped +not cleanupInDestructor(f) and +//exclude field that is probably safe (See `ManagedKeyValueField`) +not f instanceof ManagedKeyValueField +and +//FrameServiceBase and raw pointer is `render_frame_host_`, most likely ok as FrameServiceBase observes lifetime of rfh. +not exists(FrameServiceBase fsb | + (fsb.getService() = f.getDeclaringType() or fsb = f.getDeclaringType())and + f.getName() = "render_frame_host_" +) and +//Unlikely to be able to delete. +not f.getPointerType() instanceof OwnedByBrowserMainLoop and +//Unlikely to be able to delete. +not f.getPointerType() instanceof Singleton and +//Generated +not f.getFile().getAbsolutePath().matches("%/out/%") and +//BrowserContext only destroyed during shutdown +not f.getPointerType().hasName("BrowserContext") and +not f.getPointerType() instanceof OwnedByBrowserContext + +select f, f.getDeclaringType()