Merge branch 'main' into aeisenberg/upgrades/work

This commit is contained in:
Andrew Eisenberg
2022-01-11 11:27:35 -08:00
11 changed files with 431 additions and 97 deletions

View File

@@ -374,7 +374,7 @@ func main() {
log.Fatalf("Unable to create path transformer file: %s.", err.Error())
}
defer os.Remove(pt.Name())
_, err = pt.WriteString("#" + srcdir + "\n" + newdir + "//\n")
_, err = pt.WriteString("#" + realSrc + "\n" + newdir + "//\n")
if err != nil {
log.Fatalf("Unable to write path transformer file: %s.", err.Error())
}

View File

@@ -166,6 +166,75 @@ module StringOps {
}
}
/** Provides predicates and classes for working with Printf-style formatters. */
module Formatting {
/**
* Gets a regular expression for matching simple format-string components, including flags,
* width and precision specifiers, not including explicit argument indices.
*/
pragma[noinline]
private string getFormatComponentRegex() {
exists(
string literal, string opt_flag, string width, string prec, string opt_width_and_prec,
string operator, string verb
|
literal = "([^%]|%%)+" and
opt_flag = "[-+ #0]?" and
width = "\\d+|\\*" and
prec = "\\.(\\d+|\\*)" and
opt_width_and_prec = "(" + width + ")?(" + prec + ")?" and
operator = "[bcdeEfFgGoOpqstTxXUv]" and
verb = "(%" + opt_flag + opt_width_and_prec + operator + ")"
|
result = "(" + literal + "|" + verb + ")"
)
}
/**
* A function that performs string formatting in the same manner as `fmt.Printf` etc.
*/
abstract class Range extends Function {
/**
* Gets the parameter index of the format string.
*/
abstract int getFormatStringIndex();
/**
* Gets the parameter index of the first parameter to be formatted.
*/
abstract int getFirstFormattedParameterIndex();
}
/**
* A call to a `fmt.Printf`-style string formatting function.
*/
class StringFormatCall extends DataFlow::CallNode {
string fmt;
Range f;
StringFormatCall() {
this = f.getACall() and
fmt = this.getArgument(f.getFormatStringIndex()).getStringValue() and
fmt.regexpMatch(getFormatComponentRegex() + "*")
}
/**
* Gets the `n`th component of this format string.
*/
string getComponent(int n) { result = fmt.regexpFind(getFormatComponentRegex(), n, _) }
/**
* Gets the `n`th argument formatted by this format call, where `formatDirective` specifies how it will be formatted.
*/
DataFlow::Node getOperand(int n, string formatDirective) {
formatDirective = this.getComponent(n) and
formatDirective.charAt(0) = "%" and
formatDirective.charAt(1) != "%" and
result = this.getArgument((n / 2) + f.getFirstFormattedParameterIndex())
}
}
}
/**
* A data-flow node that performs string concatenation.
*
@@ -233,29 +302,6 @@ module StringOps {
}
}
/**
* Gets a regular expression for matching simple format-string components, including flags,
* width and precision specifiers, but not including `*` specifiers or explicit argument
* indices.
*/
pragma[noinline]
private string getFormatComponentRegex() {
exists(
string literal, string opt_flag, string width, string prec, string opt_width_and_prec,
string operator, string verb
|
literal = "([^%]|%%)+" and
opt_flag = "[-+ #0]?" and
width = "\\d+|\\*" and
prec = "\\.(\\d+|\\*)" and
opt_width_and_prec = "(" + width + ")?(" + prec + ")?" and
operator = "[bcdeEfFgGoOpqstTxXUv]" and
verb = "(%" + opt_flag + opt_width_and_prec + operator + ")"
|
result = "(" + literal + "|" + verb + ")"
)
}
/**
* A call to `fmt.Sprintf`, considered as a string concatenation.
*
@@ -272,42 +318,25 @@ module StringOps {
* node nor a string value. This is because verbs like `%q` perform additional string
* transformations that we cannot easily represent.
*/
private class SprintfConcat extends Range, DataFlow::CallNode {
string fmt;
SprintfConcat() {
exists(Function sprintf | sprintf.hasQualifiedName("fmt", "Sprintf") |
this = sprintf.getACall() and
fmt = this.getArgument(0).getStringValue() and
fmt.regexpMatch(getFormatComponentRegex() + "*")
)
}
/**
* Gets the `n`th component of this format string.
*/
private string getComponent(int n) {
result = fmt.regexpFind(getFormatComponentRegex(), n, _)
}
private class SprintfConcat extends Range instanceof Formatting::StringFormatCall {
SprintfConcat() { this = any(Function f | f.hasQualifiedName("fmt", "Sprintf")).getACall() }
override DataFlow::Node getOperand(int n) {
exists(int i, string part | part = "%s" or part = "%v" |
part = this.getComponent(n) and
i = n / 2 and
result = this.getArgument(i + 1)
)
result = Formatting::StringFormatCall.super.getOperand(n, ["%s", "%v"])
}
override string getOperandStringValue(int n) {
result = Range.super.getOperandStringValue(n)
or
exists(string cmp | cmp = this.getComponent(n) |
exists(string cmp | cmp = Formatting::StringFormatCall.super.getComponent(n) |
(cmp.charAt(0) != "%" or cmp.charAt(1) = "%") and
result = cmp.replaceAll("%%", "%")
)
}
override int getNumOperand() { result = max(int i | exists(this.getComponent(i))) + 1 }
override int getNumOperand() {
result = max(int i | exists(Formatting::StringFormatCall.super.getComponent(i))) + 1
}
}
/**

View File

@@ -3,6 +3,7 @@
*/
import go
private import semmle.go.StringOps
/**
* Provides classes for working with concepts relating to the [github.com/elazarl/goproxy](https://pkg.go.dev/github.com/elazarl/goproxy) package.
@@ -108,8 +109,16 @@ module ElazarlGoproxy {
}
}
private class ProxyLogFunction extends StringOps::Formatting::Range, Method {
ProxyLogFunction() { this.hasQualifiedName(packagePath(), "ProxyCtx", ["Logf", "Warnf"]) }
override int getFormatStringIndex() { result = 0 }
override int getFirstFormattedParameterIndex() { result = 1 }
}
private class ProxyLog extends LoggerCall::Range, DataFlow::MethodCallNode {
ProxyLog() { this.getTarget().hasQualifiedName(packagePath(), "ProxyCtx", ["Logf", "Warnf"]) }
ProxyLog() { this.getTarget() instanceof ProxyLogFunction }
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
}

View File

@@ -4,34 +4,53 @@
*/
import go
private import semmle.go.StringOps
/**
* Provides models of commonly used functions in the `github.com/golang/glog` packages and its
* forks.
*/
module Glog {
private class GlogCall extends LoggerCall::Range, DataFlow::CallNode {
private class GlogFunction extends Function {
int firstPrintedArg;
GlogCall() {
exists(string pkg, Function f, string fn, string level |
GlogFunction() {
exists(string pkg, string fn, string level |
pkg = package(["github.com/golang/glog", "gopkg.in/glog", "k8s.io/klog"], "") and
level = ["Error", "Exit", "Fatal", "Info", "Warning"] and
(
fn = level + ["", "f", "ln"] and firstPrintedArg = 0
or
fn = level + "Depth" and firstPrintedArg = 1
) and
this = f.getACall()
)
|
f.hasQualifiedName(pkg, fn)
this.hasQualifiedName(pkg, fn)
or
f.(Method).hasQualifiedName(pkg, "Verbose", fn)
this.(Method).hasQualifiedName(pkg, "Verbose", fn)
)
}
/**
* Gets the index of the first argument that may be output, including a format string if one is present.
*/
int getFirstPrintedArg() { result = firstPrintedArg }
}
private class StringFormatter extends StringOps::Formatting::Range instanceof GlogFunction {
StringFormatter() { this.getName().matches("%f") }
override int getFormatStringIndex() { result = super.getFirstPrintedArg() }
override int getFirstFormattedParameterIndex() { result = super.getFirstPrintedArg() + 1 }
}
private class GlogCall extends LoggerCall::Range, DataFlow::CallNode {
GlogFunction callee;
GlogCall() { this = callee.getACall() }
override DataFlow::Node getAMessageComponent() {
result = this.getArgument(any(int i | i >= firstPrintedArg))
result = this.getArgument(any(int i | i >= callee.getFirstPrintedArg()))
}
}
}

View File

@@ -1,6 +1,7 @@
/** Provides models of commonly used functions in the `github.com/sirupsen/logrus` package. */
import go
private import semmle.go.StringOps
/** Provides models of commonly used functions in the `github.com/sirupsen/logrus` package. */
module Logrus {
@@ -22,14 +23,31 @@ module Logrus {
result.regexpMatch("With(Context|Error|Fields?|Time)")
}
private class LogCall extends LoggerCall::Range, DataFlow::CallNode {
LogCall() {
private class LogFunction extends Function {
LogFunction() {
exists(string name | name = getALogResultName() or name = getAnEntryUpdatingMethodName() |
this.getTarget().hasQualifiedName(packagePath(), name) or
this.getTarget().(Method).hasQualifiedName(packagePath(), ["Entry", "Logger"], name)
this.hasQualifiedName(packagePath(), name) or
this.(Method).hasQualifiedName(packagePath(), ["Entry", "Logger"], name)
)
}
}
private class LogCall extends LoggerCall::Range, DataFlow::CallNode {
LogCall() { this = any(LogFunction f).getACall() }
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
}
private class StringFormatters extends StringOps::Formatting::Range instanceof LogFunction {
int argOffset;
StringFormatters() {
this.getName().matches("%f") and
if this.getName() = "Logf" then argOffset = 1 else argOffset = 0
}
override int getFormatStringIndex() { result = argOffset }
override int getFirstFormattedParameterIndex() { result = argOffset + 1 }
}
}

View File

@@ -3,6 +3,7 @@
*/
import go
private import semmle.go.StringOps
/**
* Provides models of commonly used functions in the `github.com/davecgh/go-spew/spew` package.
@@ -11,21 +12,37 @@ module Spew {
/** Gets the package path `github.com/davecgh/go-spew/spew`. */
private string packagePath() { result = package("github.com/davecgh/go-spew", "spew") }
private class SpewCall extends LoggerCall::Range, DataFlow::CallNode {
private class SpewFunction extends Function {
int firstPrintedArg;
SpewCall() {
SpewFunction() {
exists(string fn |
fn in ["Dump", "Errorf", "Print", "Printf", "Println"] and firstPrintedArg = 0
or
fn in ["Fdump", "Fprint", "Fprintf", "Fprintln"] and firstPrintedArg = 1
|
this.getTarget().hasQualifiedName(packagePath(), fn)
this.hasQualifiedName(packagePath(), fn)
)
}
int getFirstPrintedArg() { result = firstPrintedArg }
}
private class StringFormatter extends StringOps::Formatting::Range instanceof SpewFunction {
StringFormatter() { this.getName().matches("%f") }
override int getFormatStringIndex() { result = super.getFirstPrintedArg() }
override int getFirstFormattedParameterIndex() { result = super.getFirstPrintedArg() + 1 }
}
private class SpewCall extends LoggerCall::Range, DataFlow::CallNode {
SpewFunction target;
SpewCall() { this = target.getACall() }
override DataFlow::Node getAMessageComponent() {
result = this.getArgument(any(int i | i >= firstPrintedArg))
result = this.getArgument(any(int i | i >= target.getFirstPrintedArg()))
}
}

View File

@@ -3,6 +3,7 @@
*/
import go
private import semmle.go.StringOps
/**
* Provides models of commonly used functions in the `go.uber.org/zap` package.
@@ -14,6 +15,28 @@ module Zap {
/** Gets a suffix for a method on `zap.SugaredLogger`. */
private string getSuffix() { result in ["", "f", "w"] }
private class ZapFunction extends Method {
ZapFunction() {
exists(string fn | fn in ["DPanic", "Debug", "Error", "Fatal", "Info", "Panic", "Warn"] |
this.hasQualifiedName(packagePath(), "Logger", fn)
or
this.hasQualifiedName(packagePath(), "SugaredLogger", fn + getSuffix())
)
or
this.hasQualifiedName(packagePath(), "Logger", ["Named", "With", "WithOptions"])
or
this.hasQualifiedName(packagePath(), "SugaredLogger", ["Named", "With"])
}
}
private class ZapFormatter extends StringOps::Formatting::Range instanceof ZapFunction {
ZapFormatter() { this.getName().matches("%f") }
override int getFormatStringIndex() { result = 0 }
override int getFirstFormattedParameterIndex() { result = 1 }
}
/**
* A call to a logger function in Zap.
*
@@ -21,17 +44,7 @@ module Zap {
* function is called are included.
*/
private class ZapCall extends LoggerCall::Range, DataFlow::MethodCallNode {
ZapCall() {
exists(string fn | fn in ["DPanic", "Debug", "Error", "Fatal", "Info", "Panic", "Warn"] |
this.getTarget().hasQualifiedName(packagePath(), "Logger", fn)
or
this.getTarget().hasQualifiedName(packagePath(), "SugaredLogger", fn + getSuffix())
)
or
this.getTarget().hasQualifiedName(packagePath(), "Logger", ["Named", "With", "WithOptions"])
or
this.getTarget().hasQualifiedName(packagePath(), "SugaredLogger", ["Named", "With"])
}
ZapCall() { this = any(ZapFunction f).getACall() }
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
}

View File

@@ -3,6 +3,7 @@
*/
import go
private import semmle.go.StringOps
/** Provides models of commonly used functions in the `fmt` package. */
module Fmt {
@@ -29,19 +30,11 @@ module Fmt {
Printer() { this.hasQualifiedName("fmt", ["Print", "Printf", "Println"]) }
}
/** A call to `Print`, `Fprint`, or similar. */
/** A call to `Print` or similar. */
private class PrintCall extends LoggerCall::Range, DataFlow::CallNode {
int firstPrintedArg;
PrintCall() { this.getTarget() instanceof Printer }
PrintCall() {
this.getTarget() instanceof Printer and firstPrintedArg = 0
or
this.getTarget() instanceof Fprinter and firstPrintedArg = 1
}
override DataFlow::Node getAMessageComponent() {
result = this.getArgument(any(int i | i >= firstPrintedArg))
}
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
}
/** The `Fprint` function or one of its variants. */
@@ -62,6 +55,25 @@ module Fmt {
}
}
private class FmtStringFormatter extends StringOps::Formatting::Range {
int argOffset;
FmtStringFormatter() {
exists(string fname |
this.hasQualifiedName("fmt", fname) and
(
fname = ["Printf", "Sprintf"] and argOffset = 0
or
fname = "Fprintf" and argOffset = 1
)
)
}
override int getFormatStringIndex() { result = argOffset }
override int getFirstFormattedParameterIndex() { result = argOffset + 1 }
}
/** The `Sscan` function or one of its variants. */
private class Sscanner extends TaintTracking::FunctionModel {
FunctionInput inp;

View File

@@ -3,17 +3,30 @@
*/
import go
private import semmle.go.StringOps
/** Provides models of commonly used functions in the `log` package. */
module Log {
private class LogCall extends LoggerCall::Range, DataFlow::CallNode {
LogCall() {
private class LogFunction extends Function {
LogFunction() {
exists(string fn | fn.matches(["Fatal%", "Panic%", "Print%"]) |
this.getTarget().hasQualifiedName("log", fn)
this.hasQualifiedName("log", fn)
or
this.getTarget().(Method).hasQualifiedName("log", "Logger", fn)
this.(Method).hasQualifiedName("log", "Logger", fn)
)
}
}
private class LogFormatter extends StringOps::Formatting::Range instanceof LogFunction {
LogFormatter() { this.getName().matches("%f") }
override int getFormatStringIndex() { result = 0 }
override int getFirstFormattedParameterIndex() { result = 1 }
}
private class LogCall extends LoggerCall::Range, DataFlow::CallNode {
LogCall() { this = any(LogFunction f).getACall() }
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
}

View File

@@ -4,6 +4,7 @@
*/
import go
private import semmle.go.StringOps
/**
* Provides extension points for customizing the data-flow tracking configuration for reasoning
@@ -58,4 +59,20 @@ module LogInjection {
)
}
}
/**
* An argument that is formatted using the `%q` directive, considered as a sanitizer
* for log injection.
*
* This formatting directive replaces newline characters with escape sequences.
*/
private class SafeFormatArgumentSanitizer extends Sanitizer {
SafeFormatArgumentSanitizer() {
exists(StringOps::Formatting::StringFormatCall call, string safeDirective |
this = call.getOperand(_, safeDirective) and
// Mark "%q" formats as safe, but not "%#q", which would preserve newline characters.
safeDirective.regexpMatch("%[^%#]*q")
)
}
}
}

View File

@@ -32,12 +32,12 @@ func handler(req *http.Request, ctx *goproxy.ProxyCtx) {
testFlag := req.URL.Query()["testFlag"][0]
{
fmt.Print(username) // $ hasTaintFlow="username"
fmt.Printf(username) // $ hasTaintFlow="username"
fmt.Println(username) // $ hasTaintFlow="username"
fmt.Fprint(nil, username) // $ hasTaintFlow="username"
fmt.Fprintf(nil, username) // $ hasTaintFlow="username"
fmt.Fprintln(nil, username) // $ hasTaintFlow="username"
fmt.Print(username) // $ hasTaintFlow="username"
fmt.Printf(username) // $ hasTaintFlow="username"
fmt.Println(username) // $ hasTaintFlow="username"
fmt.Fprint(nil, username) // Fprint functions are only loggers if they target stdout/stderr
fmt.Fprintf(nil, username)
fmt.Fprintln(nil, username)
}
// log
{
@@ -377,3 +377,190 @@ func handlerGood2(req *http.Request) {
escapedUsername = strings.ReplaceAll(escapedUsername, "\r", "")
log.Printf("user %s logged in.\n", escapedUsername)
}
// GOOD: User-provided values formatted using a %q directive, which escapes newlines
func handlerGood3(req *http.Request, ctx *goproxy.ProxyCtx) {
username := req.URL.Query()["username"][0]
testFlag := req.URL.Query()["testFlag"][0]
log.Printf("user %q logged in.\n", username)
// Flags shouldn't make a difference...
log.Printf("user %-50q logged in.\n", username)
// Except for the '#' flag that retains newlines, emitting a backtick-delimited string:
log.Printf("user %#10q logged in.\n", username) // $ hasTaintFlow="username"
// Check this works with fmt:
log.Print(fmt.Sprintf("user %q logged in.\n", username))
log.Print(fmt.Sprintf("user %-50q logged in.\n", username))
log.Print(fmt.Sprintf("user %#10q logged in.\n", username)) // $ hasTaintFlow="call to Sprintf"
// Check this works with a variety of other loggers:
// k8s.io/klog
{
verbose := klog.V(0)
verbose.Infof("user %q logged in.\n", username)
klog.Infof("user %q logged in.\n", username)
klog.Errorf("user %q logged in.\n", username)
klog.Fatalf("user %q logged in.\n", username)
klog.Exitf("user %q logged in.\n", username)
}
// elazarl/goproxy
{
ctx.Logf("user %q logged in.\n", username)
ctx.Warnf("user %q logged in.\n", username)
}
// golang/glog
{
verbose := glog.V(0)
verbose.Infof("user %q logged in.\n", username)
glog.Infof("user %q logged in.\n", username)
glog.Errorf("user %q logged in.\n", username)
glog.Fatalf("user %q logged in.\n", username)
glog.Exitf("user %q logged in.\n", username)
}
// sirupsen/logrus
{
logrus.Debugf("user %q logged in.\n", username)
logrus.Errorf("user %q logged in.\n", username)
logrus.Fatalf("user %q logged in.\n", username)
logrus.Infof("user %q logged in.\n", username)
logrus.Panicf("user %q logged in.\n", username)
logrus.Printf("user %q logged in.\n", username)
logrus.Tracef("user %q logged in.\n", username)
logrus.Warnf("user %q logged in.\n", username)
logrus.Warningf("user %q logged in.\n", username)
fields := make(logrus.Fields)
entry := logrus.WithFields(fields)
entry.Debugf("user %q logged in.\n", username)
entry.Errorf("user %q logged in.\n", username)
entry.Fatalf("user %q logged in.\n", username)
entry.Infof("user %q logged in.\n", username)
entry.Logf(0, "user %q logged in.\n", username)
entry.Panicf("user %q logged in.\n", username)
entry.Printf("user %q logged in.\n", username)
entry.Tracef("user %q logged in.\n", username)
entry.Warnf("user %q logged in.\n", username)
entry.Warningf("user %q logged in.\n", username)
logger := entry.Logger
logger.Debugf("user %q logged in.\n", username)
logger.Errorf("user %q logged in.\n", username)
logger.Fatalf("user %q logged in.\n", username)
logger.Infof("user %q logged in.\n", username)
logger.Logf(0, "user %q logged in.\n", username)
logger.Panicf("user %q logged in.\n", username)
logger.Printf("user %q logged in.\n", username)
logger.Tracef("user %q logged in.\n", username)
logger.Warnf("user %q logged in.\n", username)
logger.Warningf("user %q logged in.\n", username)
}
// davecgh/go-spew/spew
{
spew.Errorf("user %q logged in.\n", username)
spew.Printf("user %q logged in.\n", username)
spew.Fprintf(nil, "user %q logged in.\n", username)
}
// zap
{
logger, _ := zap.NewProduction()
sLogger := logger.Sugar()
sLogger.DPanicf("user %q logged in.\n", username)
sLogger.Debugf("user %q logged in.\n", username)
sLogger.Errorf("user %q logged in.\n", username)
if testFlag == " true" {
sLogger.Fatalf("user %q logged in.\n", username)
}
sLogger.Infof("user %q logged in.\n", username)
if testFlag == " true" {
sLogger.Panicf("user %q logged in.\n", username)
}
sLogger.Warnf("user %q logged in.\n", username)
}
// Check those same loggers recognise that %#q is still dangerous:
// k8s.io/klog
{
verbose := klog.V(0)
verbose.Infof("user %#q logged in.\n", username) // $ hasTaintFlow="username"
klog.Infof("user %#q logged in.\n", username) // $ hasTaintFlow="username"
klog.Errorf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
klog.Fatalf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
klog.Exitf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
}
// elazarl/goproxy
{
ctx.Logf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
ctx.Warnf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
}
// golang/glog
{
verbose := glog.V(0)
verbose.Infof("user %#q logged in.\n", username) // $ hasTaintFlow="username"
glog.Infof("user %#q logged in.\n", username) // $ hasTaintFlow="username"
glog.Errorf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
glog.Fatalf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
glog.Exitf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
}
// sirupsen/logrus
{
logrus.Debugf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logrus.Errorf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logrus.Fatalf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logrus.Infof("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logrus.Panicf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logrus.Printf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logrus.Tracef("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logrus.Warnf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logrus.Warningf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
fields := make(logrus.Fields)
entry := logrus.WithFields(fields)
entry.Debugf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
entry.Errorf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
entry.Fatalf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
entry.Infof("user %#q logged in.\n", username) // $ hasTaintFlow="username"
entry.Logf(0, "user %#q logged in.\n", username) // $ hasTaintFlow="username"
entry.Panicf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
entry.Printf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
entry.Tracef("user %#q logged in.\n", username) // $ hasTaintFlow="username"
entry.Warnf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
entry.Warningf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logger := entry.Logger
logger.Debugf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logger.Errorf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logger.Fatalf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logger.Infof("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logger.Logf(0, "user %#q logged in.\n", username) // $ hasTaintFlow="username"
logger.Panicf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logger.Printf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logger.Tracef("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logger.Warnf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
logger.Warningf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
}
// davecgh/go-spew/spew
{
spew.Errorf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
spew.Printf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
spew.Fprintf(nil, "user %#q logged in.\n", username) // $ hasTaintFlow="username"
}
// zap
{
logger, _ := zap.NewProduction()
sLogger := logger.Sugar()
sLogger.DPanicf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
sLogger.Debugf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
sLogger.Errorf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
if testFlag == " true" {
sLogger.Fatalf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
}
sLogger.Infof("user %#q logged in.\n", username) // $ hasTaintFlow="username"
if testFlag == " true" {
sLogger.Panicf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
}
sLogger.Warnf("user %#q logged in.\n", username) // $ hasTaintFlow="username"
}
}