@@ -2,6 +2,7 @@ import cpp
22import semmle.code.cpp.security.Security
33private import semmle.code.cpp.ir.dataflow.DataFlow
44private import semmle.code.cpp.ir.dataflow.DataFlow2
5+ private import semmle.code.cpp.ir.dataflow.DataFlow3
56private import semmle.code.cpp.ir.IR
67private import semmle.code.cpp.ir.dataflow.internal.DataFlowDispatch as Dispatch
78private import semmle.code.cpp.models.interfaces.Taint
@@ -171,6 +172,7 @@ private predicate nodeIsBarrierIn(DataFlow::Node node) {
171172 node = getNodeForSource ( any ( Expr e ) )
172173}
173174
175+ cached
174176private predicate instructionTaintStep ( Instruction i1 , Instruction i2 ) {
175177 // Expressions computed from tainted data are also tainted
176178 exists ( CallInstruction call , int argIndex | call = i2 |
@@ -381,3 +383,135 @@ Function resolveCall(Call call) {
381383 result = Dispatch:: viableCallable ( callInstruction )
382384 )
383385}
386+
387+ /**
388+ * Provides definitions for augmenting source/sink pairs with data-flow paths
389+ * between them. From a `@kind path-problem` query, import this module in the
390+ * global scope, extend `TaintTrackingConfiguration`, and use `taintedWithPath`
391+ * in place of `tainted`.
392+ *
393+ * Importing this module will also import the query predicates that contain the
394+ * taint paths.
395+ */
396+ module TaintedWithPath {
397+ /**
398+ * A taint-tracking configuration that matches sources and sinks in the same
399+ * way as the `tainted` predicate.
400+ */
401+ class TaintTrackingConfiguration extends int {
402+ TaintTrackingConfiguration ( ) { this = 1 }
403+
404+ /** Override this to specify which elements are sinks in this configuration. */
405+ abstract predicate isSink ( Element e ) ;
406+ }
407+
408+ private class AdjustedConfiguration extends DataFlow3:: Configuration {
409+ AdjustedConfiguration ( ) { this = "AdjustedConfiguration" }
410+
411+ override predicate isSource ( DataFlow:: Node source ) { source = getNodeForSource ( _) }
412+
413+ override predicate isSink ( DataFlow:: Node sink ) {
414+ exists ( TaintTrackingConfiguration cfg | cfg .isSink ( adjustedSink ( sink ) ) )
415+ }
416+
417+ override predicate isAdditionalFlowStep ( DataFlow:: Node n1 , DataFlow:: Node n2 ) {
418+ instructionTaintStep ( n1 .asInstruction ( ) , n2 .asInstruction ( ) )
419+ }
420+
421+ override predicate isBarrier ( DataFlow:: Node node ) { nodeIsBarrier ( node ) }
422+
423+ override predicate isBarrierIn ( DataFlow:: Node node ) { nodeIsBarrierIn ( node ) }
424+ }
425+
426+ /*
427+ * A sink `Element` may map to multiple `DataFlowX::PathNode`s via (the
428+ * inverse of) `adjustedSink`. For example, an `Expr` maps to all its
429+ * conversions, and a `Variable` maps to all loads and stores from it. Because
430+ * the path node is part of the tuple that constitutes the alert, this leads
431+ * to duplicate alerts.
432+ *
433+ * To avoid showing duplicates, we edit the graph to replace the final node
434+ * coming from the data-flow library with a node that matches exactly the
435+ * `Element` sink that's requested.
436+ *
437+ * The same should ideally be done with the source, but we haven't seen a
438+ * need for it yet.
439+ */
440+
441+ private newtype TPathNode =
442+ TWrapPathNode ( DataFlow3:: PathNode n ) or
443+ TFinalPathNode ( Element e ) { exists ( TaintTrackingConfiguration cfg | cfg .isSink ( e ) ) }
444+
445+ /** An opaque type used for the nodes of a data-flow path. */
446+ class PathNode extends TPathNode {
447+ /** Gets a textual representation of this element. */
448+ string toString ( ) { none ( ) }
449+
450+ /**
451+ * Holds if this element is at the specified location.
452+ * The location spans column `startcolumn` of line `startline` to
453+ * column `endcolumn` of line `endline` in file `filepath`.
454+ * For more information, see
455+ * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
456+ */
457+ predicate hasLocationInfo (
458+ string filepath , int startline , int startcolumn , int endline , int endcolumn
459+ ) {
460+ none ( )
461+ }
462+ }
463+
464+ private class WrapPathNode extends PathNode , TPathNode {
465+ DataFlow3:: PathNode inner ( ) { this = TWrapPathNode ( result ) }
466+
467+ override string toString ( ) { result = this .inner ( ) .toString ( ) }
468+
469+ override predicate hasLocationInfo (
470+ string filepath , int startline , int startcolumn , int endline , int endcolumn
471+ ) {
472+ this .inner ( ) .hasLocationInfo ( filepath , startline , startcolumn , endline , endcolumn )
473+ }
474+ }
475+
476+ private class FinalPathNode extends PathNode , TFinalPathNode {
477+ Element inner ( ) { this = TFinalPathNode ( result ) }
478+
479+ override string toString ( ) { result = this .inner ( ) .toString ( ) }
480+
481+ override predicate hasLocationInfo (
482+ string filepath , int startline , int startcolumn , int endline , int endcolumn
483+ ) {
484+ this
485+ .inner ( )
486+ .getLocation ( )
487+ .hasLocationInfo ( filepath , startline , startcolumn , endline , endcolumn )
488+ }
489+ }
490+
491+ /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
492+ query predicate edges ( PathNode a , PathNode b ) {
493+ DataFlow3:: PathGraph:: edges ( a .( WrapPathNode ) .inner ( ) , b .( WrapPathNode ) .inner ( ) )
494+ or
495+ // To avoid showing trivial-looking steps, we replace the last node instead
496+ // of adding an edge out of it.
497+ exists ( WrapPathNode replaced |
498+ DataFlow3:: PathGraph:: edges ( a .( WrapPathNode ) .inner ( ) , replaced .inner ( ) ) and
499+ b .( FinalPathNode ) .inner ( ) = adjustedSink ( replaced .inner ( ) .getNode ( ) )
500+ )
501+ }
502+
503+ /** Holds if `n` is a node in the graph of data flow path explanations. */
504+ query predicate nodes ( PathNode n , string key , string val ) {
505+ key = "semmle.label" and val = n .toString ( )
506+ }
507+
508+ predicate taintedWithPath ( Expr source , Element tainted , PathNode sourceNode , PathNode sinkNode ) {
509+ exists ( AdjustedConfiguration cfg , DataFlow3:: PathNode sinkInner , DataFlow:: Node sink |
510+ sourceNode .( WrapPathNode ) .inner ( ) .getNode ( ) = getNodeForSource ( source ) and
511+ sinkInner .getNode ( ) = sink and
512+ cfg .hasFlowPath ( sourceNode .( WrapPathNode ) .inner ( ) , sinkInner ) and
513+ tainted = adjustedSink ( sinkInner .getNode ( ) ) and
514+ tainted = sinkNode .( FinalPathNode ) .inner ( )
515+ )
516+ }
517+ }
0 commit comments