Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missing string replacement sanitizers to log-injection and string-break #11910

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions config/identical-files.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplForContentDataFlow.qll",
"go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll",
"go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl2.qll",
"go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll",
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll",
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll",
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll",
Expand Down
109 changes: 109 additions & 0 deletions go/ql/lib/semmle/go/StringOps.qll
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import go
private import semmle.go.dataflow.DataFlowForStringsNewReplacer

/** Provides predicates and classes for working with string operations. */
module StringOps {
Expand Down Expand Up @@ -162,6 +163,114 @@ module StringOps {
}
}

/**
* An expression that is equivalent to `strings.ReplaceAll(s, old, new)`.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `StringOps::ReplaceAll::Range` instead.
*/
class ReplaceAll extends DataFlow::Node instanceof ReplaceAll::Range {
/**
* Gets the `old` in `strings.ReplaceAll(s, old, new)`.
*/
string getReplacedString() { result = super.getReplacedString() }
}

/** Provides predicates and classes for working with prefix checks. */
module ReplaceAll {
/**
* An expression that is equivalent to `strings.ReplaceAll(s, old, new)`.
*
* Extend this class to model new APIs. If you want to refine existing API models, extend
* `StringOps::ReplaceAll` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the `old` in `strings.ReplaceAll(s, old, new)`.
*/
abstract string getReplacedString();
}

/**
* A call to `strings.ReplaceAll` or `strings.Replace` with a negative `n`
* so that all instances are replaced.
*/
private class StringsReplaceAll extends Range, DataFlow::CallNode {
StringsReplaceAll() {
exists(string name | this.getTarget().hasQualifiedName("strings", name) |
name = "ReplaceAll"
or
name = "Replace" and
this.getArgument(3).getNumericValue() < 0
)
}

override string getReplacedString() { result = this.getArgument(1).getStringValue() }
}

/**
* A call to `strings.NewReplacer`.
*/
private class StringsNewReplacerCall extends DataFlow::CallNode {
StringsNewReplacerCall() { this.getTarget().hasQualifiedName("strings", "NewReplacer") }

/**
* Gets an argument to this call corresponding to a string that will be
* replaced.
*/
DataFlow::Node getAReplacedArgument() {
exists(int n | n % 2 = 0 and result = this.getArgument(n))
}
}

/**
* A configuration for tracking flow from a call to `strings.NewReplacer` to
* the receiver of a call to `strings.Replacer.Replace` or
* `strings.Replacer.WriteString`.
*/
private class StringsNewReplacerConfiguration extends DataFlowForStringsNewReplacer::Configuration {
StringsNewReplacerConfiguration() { this = "StringsNewReplacerConfiguration" }

override predicate isSource(DataFlow::Node source) {
source instanceof StringsNewReplacerCall
}

override predicate isSink(DataFlow::Node sink) {
exists(DataFlow::MethodCallNode call |
sink = call.getReceiver() and
call.getTarget().hasQualifiedName("strings", "Replacer", ["Replace", "WriteString"])
)
}
}

/**
* A call to `strings.Replacer.Replace` or `strings.Replacer.WriteString`.
*/
private class StringsReplacerReplaceOrWriteString extends Range {
string replacedString;

StringsReplacerReplaceOrWriteString() {
exists(
StringsNewReplacerConfiguration config, StringsNewReplacerCall source,
DataFlow::Node sink, DataFlow::MethodCallNode call
|
config.hasFlow(source, sink) and
sink = call.getReceiver() and
replacedString = source.getAReplacedArgument().getStringValue() and
(
call.getTarget().hasQualifiedName("strings", "Replacer", "Replace") and
this = call.getResult()
or
call.getTarget().hasQualifiedName("strings", "Replacer", "WriteString") and
this = call.getArgument(1)
)
)
}

override string getReplacedString() { result = replacedString }
}
}

/** Provides predicates and classes for working with Printf-style formatters. */
module Formatting {
/**
Expand Down
27 changes: 27 additions & 0 deletions go/ql/lib/semmle/go/dataflow/DataFlowForStringsNewReplacer.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Provides a library for local (intra-procedural) and global (inter-procedural)
* data flow analysis: deciding whether data can flow from a _source_ to a
* _sink_.
*
* Unless configured otherwise, _flow_ means that the exact value of
* the source may reach the sink. We do not track flow across pointer
* dereferences or array indexing. To track these types of flow, where the
* exact value may not be preserved, import
* `semmle.code.go.dataflow.TaintTracking`.
*
* To use global (interprocedural) data flow, extend the class
* `DataFlow::Configuration` as documented on that class. To use local
* (intraprocedural) data flow, invoke `DataFlow::localFlow` or
* `DataFlow::LocalFlowStep` with arguments of type `DataFlow::Node`.
*/

import go

/**
* Provides a library for local (intra-procedural) and global (inter-procedural)
* data flow analysis.
*/
module DataFlowForStringsNewReplacer {
import semmle.go.dataflow.internal.DataFlowImplForStringsNewReplacer
import Properties
}