Merge pull request #7399 from github/ruby/stdlib-logger

Ruby: Model what is written to the log from stdlib `Logger` methods
This commit is contained in:
Alex Ford
2021-12-20 09:52:29 +00:00
committed by GitHub
5 changed files with 233 additions and 0 deletions

View File

@@ -644,3 +644,32 @@ module Path {
abstract class Range extends DataFlow::Node { }
}
}
/**
* A data-flow node that logs data.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `Logging::Range` instead.
*/
class Logging extends DataFlow::Node {
Logging::Range range;
Logging() { this = range }
/** Gets an input that is logged. */
DataFlow::Node getAnInput() { result = range.getAnInput() }
}
/** Provides a class for modeling new logging mechanisms. */
module Logging {
/**
* A data-flow node that logs data.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `Logging` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets an input that is logged. */
abstract DataFlow::Node getAnInput();
}
}

View File

@@ -3,6 +3,8 @@ private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
private import codeql.ruby.ApiGraphs
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.dataflow.internal.DataFlowDispatch
private import codeql.ruby.CFG
/**
* The `Kernel` module is included by the `Object` class, so its methods are available
@@ -347,3 +349,103 @@ class RegexpEscapeSummary extends SummarizedCallable {
preservesValue = false
}
}
/** A reference to a `Logger` instance */
private DataFlow::Node loggerInstance() {
result = API::getTopLevelMember("Logger").getAnInstantiation()
or
exists(DataFlow::Node inst |
inst = loggerInstance() and
inst.(DataFlow::LocalSourceNode).flowsTo(result)
)
or
// Assume that a variable assigned as a `Logger` instance is always a
// `Logger` instance. This covers class and instance variables where we can't
// necessarily trace a dataflow path from assignment to use.
exists(Variable v, Assignment a |
a.getLeftOperand().getAVariable() = v and
a.getRightOperand() = loggerInstance().asExpr().getExpr() and
result.asExpr().getExpr().(VariableReadAccess).getVariable() = v
)
}
/**
* A call to a `Logger` instance method that causes a message to be logged.
*/
abstract class LoggerLoggingCall extends Logging::Range, DataFlow::CallNode {
LoggerLoggingCall() { this.getReceiver() = loggerInstance() }
}
/**
* A call to `Logger#add` or its alias `Logger#log`.
*/
private class LoggerAddCall extends LoggerLoggingCall {
LoggerAddCall() { this.getMethodName() = ["add", "log"] }
override DataFlow::Node getAnInput() {
// Both the message and the progname are form part of the log output:
// Logger#add(severity, message) / Logger#add(severity, message, progname)
result = this.getArgument(1)
or
result = this.getArgument(2)
or
// a return value from the block in Logger#add(severity) <block> or in
// Logger#add(severity, nil, progname) <block>
(
this.getNumberOfArguments() = 1
or
// TODO: this could track the value of the `message` argument to make
// this check more accurate
this.getArgument(1).asExpr().getExpr() instanceof NilLiteral
) and
exprNodeReturnedFrom(result, this.getBlock().asExpr().getExpr())
}
}
/**
* A call to `Logger#<<`.
*/
private class LoggerPushCall extends LoggerLoggingCall {
LoggerPushCall() { this.getMethodName() = "<<" }
override DataFlow::Node getAnInput() {
// Logger#<<(msg)
result = this.getArgument(0)
}
}
/**
* A call to a `Logger` method that logs at a preset severity level.
*
* Specifically, these methods are `debug`, `error`, `fatal`, `info`,
* `unknown`, and `warn`.
*/
private class LoggerInfoStyleCall extends LoggerLoggingCall {
LoggerInfoStyleCall() {
this.getMethodName() = ["debug", "error", "fatal", "info", "unknown", "warn"]
}
override DataFlow::Node getAnInput() {
// `msg` from `Logger#info(msg)`,
// or `progname` from `Logger#info(progname) <block>`
result = this.getArgument(0)
or
// a return value from the block in `Logger#info(progname) <block>`
exprNodeReturnedFrom(result, this.getBlock().asExpr().getExpr())
}
}
/**
* A call to `Logger#progname=`. This sets a default progname.
* This call does not log anything directly, but the assigned value can appear
* in future log messages that do not specify a `progname` argument.
*/
private class LoggerSetPrognameCall extends LoggerLoggingCall {
LoggerSetPrognameCall() { this.getMethodName() = "progname=" }
override DataFlow::Node getAnInput() {
exists(CfgNodes::ExprNodes::AssignExprCfgNode a | this.getArgument(0).asExpr() = a |
result.asExpr() = a.getRhs()
)
}
}