Skip to content

Commit b243495

Browse files
committed
abstract away some ActiveRecord specific parts of XSS.qll
1 parent 6a32c0c commit b243495

File tree

3 files changed

+54
-30
lines changed

3 files changed

+54
-30
lines changed

ql/lib/codeql/ruby/Concepts.qll

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,3 +538,28 @@ module XmlParserCall {
538538
abstract predicate externalEntitiesEnabled();
539539
}
540540
}
541+
542+
/**
543+
* A data-flow node that may represent a database object in an ORM system.
544+
*
545+
* Extend this class to refine existing API models. If you want to model new APIs,
546+
* extend `OrmInstantiation::Range` instead.
547+
*/
548+
class OrmInstantiation extends DataFlow::Node instanceof OrmInstantiation::Range {
549+
/** Holds if `call` may return a field of this ORM object. */
550+
predicate methodCallMayAccessField(MethodCall call) { super.methodCallMayAccessField(call) }
551+
}
552+
553+
/** Provides a class for modeling new ORM object instantiation APIs. */
554+
module OrmInstantiation {
555+
/**
556+
* A data-flow node that may represent a database object in an ORM system.
557+
*
558+
* Extend this class to model new APIs. If you want to refine existing API models,
559+
* extend `OrmInstantiation` instead.
560+
*/
561+
abstract class Range extends DataFlow::Node {
562+
/** Holds if `call` may return a field of this ORM object. */
563+
abstract predicate methodCallMayAccessField(MethodCall call);
564+
}
565+
}

ql/lib/codeql/ruby/frameworks/ActiveRecord.qll

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,26 @@ class ActiveRecordModelClass extends ClassDeclaration {
4343
other.getModule() = resolveScopeExpr(this.getSuperclassExpr())
4444
)
4545
}
46+
47+
/**
48+
* Returns true if `call` may refer to a method that returns a database value
49+
* if invoked against an instance of this class.
50+
*/
51+
predicate methodCallMayAccessField(MethodCall call) {
52+
not (
53+
// Methods whose names can be hardcoded
54+
isCallToBuiltInMethod(call)
55+
or
56+
// Methods defined in the ActiveRecord model class that do not return database fields
57+
exists(Method m | m = this.getMethod(call.getMethodName()) |
58+
forall(DataFlow::Node returned, ActiveRecordInstanceMethodCall c |
59+
exprNodeReturnedFrom(returned, m) and c.flowsTo(returned)
60+
|
61+
not this.methodCallMayAccessField(returned.asExpr().getExpr())
62+
)
63+
)
64+
)
65+
}
4666
}
4767

4868
/** A class method call whose receiver is an `ActiveRecordModelClass`. */
@@ -181,8 +201,12 @@ private string constantQualifiedName(ConstantWriteAccess w) {
181201
/**
182202
* A node that may evaluate to one or more `ActiveRecordModelClass` instances.
183203
*/
184-
abstract class ActiveRecordModelInstantiation extends DataFlow::Node {
204+
abstract class ActiveRecordModelInstantiation extends OrmInstantiation::Range {
185205
abstract ActiveRecordModelClass getClass();
206+
207+
override predicate methodCallMayAccessField(MethodCall call) {
208+
this.getClass().methodCallMayAccessField(call)
209+
}
186210
}
187211

188212
// Names of class methods on ActiveRecord models that may return one or more
@@ -278,23 +302,3 @@ private predicate isCallToBuiltInMethod(MethodCall c) {
278302
c instanceof BasicObjectInstanceMethodCall or
279303
c instanceof ObjectInstanceMethodCall
280304
}
281-
282-
/**
283-
* Returns true if `call` may refer to a method that returns a database value
284-
* if invoked against a `sourceClass` instance.
285-
*/
286-
predicate activeRecordMethodMayAccessField(ActiveRecordModelClass sourceClass, MethodCall call) {
287-
not (
288-
// Methods whose names can be hardcoded
289-
isCallToBuiltInMethod(call)
290-
or
291-
// Methods defined in `sourceClass` that do not return database fields
292-
exists(Method m | m = sourceClass.getMethod(call.getMethodName()) |
293-
forall(DataFlow::Node returned, ActiveRecordInstanceMethodCall c |
294-
exprNodeReturnedFrom(returned, m) and c.flowsTo(returned)
295-
|
296-
not activeRecordMethodMayAccessField(sourceClass, returned.asExpr().getExpr())
297-
)
298-
)
299-
)
300-
}

ql/lib/codeql/ruby/security/XSS.qll

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ private import codeql.ruby.Concepts
1010
private import codeql.ruby.Frameworks
1111
private import codeql.ruby.frameworks.ActionController
1212
private import codeql.ruby.frameworks.ActionView
13-
private import codeql.ruby.frameworks.ActiveRecord
1413
private import codeql.ruby.dataflow.RemoteFlowSources
1514
private import codeql.ruby.dataflow.BarrierGuards
1615
private import codeql.ruby.dataflow.internal.DataFlowDispatch
@@ -246,10 +245,7 @@ private module OrmTracking {
246245
class Configuration extends DataFlow2::Configuration {
247246
Configuration() { this = "OrmTracking" }
248247

249-
// TODO: abstract to ORMFinderCall concept
250-
override predicate isSource(DataFlow2::Node source) {
251-
source instanceof ActiveRecordModelInstantiation
252-
}
248+
override predicate isSource(DataFlow2::Node source) { source instanceof OrmInstantiation }
253249

254250
// Select any call node and narrow down later
255251
override predicate isSink(DataFlow2::Node sink) { sink instanceof DataFlow2::CallNode }
@@ -291,12 +287,11 @@ module StoredXSS {
291287

292288
predicate isAdditionalXSSTaintStep = Shared::isAdditionalXSSFlowStep/2;
293289

294-
private class ActiveRecordModelFieldAccessAsSource extends Source {
295-
ActiveRecordModelFieldAccessAsSource() {
290+
private class OrmFieldAsSource extends Source {
291+
OrmFieldAsSource() {
296292
exists(OrmTracking::Configuration subConfig, DataFlow2::Node subSrc |
297293
subConfig.hasFlow(subSrc, this) and
298-
activeRecordMethodMayAccessField(subSrc.(ActiveRecordModelInstantiation).getClass(),
299-
this.asExpr().getExpr())
294+
subSrc.(OrmInstantiation).methodCallMayAccessField(this.asExpr().getExpr())
300295
)
301296
}
302297
}

0 commit comments

Comments
 (0)