mirror of
https://github.com/github/codeql.git
synced 2026-06-30 17:15:34 +02:00
Merge pull request #22006 from owen-mc/go/more-slog-models
Go: more models for `log.slog`
This commit is contained in:
4
go/ql/lib/change-notes/2026-06-30-model-log-slog.md
Normal file
4
go/ql/lib/change-notes/2026-06-30-model-log-slog.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Improved models for the `log/slog` package (Go 1.21+), including `*slog.Logger` methods, `With`/`WithGroup`, and `Attr`/`Value` helpers, improving coverage for the `go/log-injection` and `go/clear-text-logging` queries.
|
||||
@@ -27,3 +27,27 @@ extensions:
|
||||
- ["log/slog", "Logger", True, "ErrorContext", "", "", "Argument[1..2]", "log-injection", "manual"]
|
||||
- ["log/slog", "Logger", True, "Log", "", "", "Argument[2..3]", "log-injection", "manual"]
|
||||
- ["log/slog", "Logger", True, "LogAttrs", "", "", "Argument[2..3]", "log-injection", "manual"]
|
||||
# With/WithGroup add attributes that are included in every subsequent log call.
|
||||
- ["log/slog", "", False, "With", "", "", "Argument[0]", "log-injection", "manual"]
|
||||
- ["log/slog", "Logger", True, "With", "", "", "Argument[0]", "log-injection", "manual"]
|
||||
- ["log/slog", "Logger", True, "WithGroup", "", "", "Argument[0]", "log-injection", "manual"]
|
||||
- addsTo:
|
||||
pack: codeql/go-all
|
||||
extensible: summaryModel
|
||||
data:
|
||||
# Constructors for Attr that can carry a tainted string into the result.
|
||||
- ["log/slog", "", False, "Any", "", "", "Argument[0..1]", "ReturnValue", "taint", "manual"]
|
||||
- ["log/slog", "", False, "Group", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
|
||||
- ["log/slog", "", False, "Group", "", "", "Argument[1].ArrayElement", "ReturnValue", "taint", "manual"]
|
||||
- ["log/slog", "", False, "GroupAttrs", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
|
||||
- ["log/slog", "", False, "GroupAttrs", "", "", "Argument[1].ArrayElement", "ReturnValue", "taint", "manual"]
|
||||
- ["log/slog", "", False, "String", "", "", "Argument[0..1]", "ReturnValue", "taint", "manual"]
|
||||
# Constructors for Value that can carry a tainted string into the result.
|
||||
- ["log/slog", "", False, "AnyValue", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
|
||||
- ["log/slog", "", False, "GroupValue", "", "", "Argument[0].ArrayElement", "ReturnValue", "taint", "manual"]
|
||||
- ["log/slog", "", False, "StringValue", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
|
||||
# Methods that read a string back out of an Attr or Value.
|
||||
- ["log/slog", "Attr", True, "String", "", "", "Argument[receiver]", "ReturnValue", "taint", "manual"]
|
||||
- ["log/slog", "Value", True, "Any", "", "", "Argument[receiver]", "ReturnValue", "taint", "manual"]
|
||||
- ["log/slog", "Value", True, "Group", "", "", "Argument[receiver]", "ReturnValue.ArrayElement", "taint", "manual"]
|
||||
- ["log/slog", "Value", True, "String", "", "", "Argument[receiver]", "ReturnValue", "taint", "manual"]
|
||||
|
||||
@@ -37,4 +37,9 @@ func slogTest() {
|
||||
slog.InfoContext(ctx, text, key, v) // $ logger=text logger=key logger=v
|
||||
slog.Log(ctx, slog.LevelInfo, text, key, v) // $ logger=text logger=key logger=v
|
||||
slog.LogAttrs(ctx, slog.LevelInfo, text, attr) // $ logger=text logger=attr
|
||||
|
||||
// With/WithGroup add attributes that are included in every subsequent log call.
|
||||
logger.With(key, v) // $ logger=key logger=v
|
||||
logger.WithGroup(text) // $ logger=text
|
||||
slog.With(key, v) // $ logger=key logger=v
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
reverseRead
|
||||
| test.go:114:21:114:33 | call to Group | Origin of readStep is missing a PostUpdateNode. |
|
||||
@@ -0,0 +1,2 @@
|
||||
invalidModelRow
|
||||
testFailures
|
||||
@@ -0,0 +1,14 @@
|
||||
import go
|
||||
import semmle.go.dataflow.ExternalFlow
|
||||
import ModelValidation
|
||||
import utils.test.InlineFlowTest
|
||||
|
||||
module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
source.(DataFlow::CallNode).getTarget().getName() = ["getUntrustedData", "getUntrustedString"]
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink = any(LoggerCall log).getAMessageComponent() }
|
||||
}
|
||||
|
||||
import FlowTest<Config, Config>
|
||||
@@ -0,0 +1,3 @@
|
||||
module codeql-go-tests/frameworks/slog
|
||||
|
||||
go 1.26
|
||||
115
go/ql/test/library-tests/semmle/go/frameworks/Slog/test.go
Normal file
115
go/ql/test/library-tests/semmle/go/frameworks/Slog/test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
func main() {}
|
||||
|
||||
func getUntrustedData() interface{} { return nil }
|
||||
|
||||
func getUntrustedString() string {
|
||||
return "tainted string"
|
||||
}
|
||||
|
||||
// Package-level convenience functions.
|
||||
|
||||
func testSlogDebug() {
|
||||
slog.Debug(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
slog.Debug("msg", "key", getUntrustedData()) // $ hasValueFlow="call to getUntrustedData"
|
||||
slog.Debug("msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
|
||||
}
|
||||
|
||||
func testSlogInfo() {
|
||||
slog.Info(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
slog.Info("msg", slog.Any("key", getUntrustedData())) // $ hasTaintFlow="call to Any"
|
||||
slog.Info("msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
|
||||
}
|
||||
|
||||
func testSlogWarn() {
|
||||
slog.Warn(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
slog.Warn("msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
|
||||
}
|
||||
|
||||
func testSlogError() {
|
||||
slog.Error(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
slog.Error("msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
|
||||
}
|
||||
|
||||
func testSlogContextVariants(ctx context.Context) {
|
||||
slog.DebugContext(ctx, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
slog.InfoContext(ctx, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
slog.WarnContext(ctx, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
slog.ErrorContext(ctx, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
slog.InfoContext(ctx, "msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
|
||||
}
|
||||
|
||||
func testSlogLog(ctx context.Context) {
|
||||
slog.Log(ctx, slog.LevelInfo, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
slog.Log(ctx, slog.LevelInfo, "msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
|
||||
slog.LogAttrs(ctx, slog.LevelInfo, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
slog.LogAttrs(ctx, slog.LevelInfo, "msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
|
||||
}
|
||||
|
||||
// Methods on *slog.Logger.
|
||||
|
||||
func testLoggerMethods(logger *slog.Logger, ctx context.Context) {
|
||||
logger.Debug(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
logger.Info(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
logger.Warn(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
logger.Error(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
logger.Info("msg", slog.Any("key", getUntrustedData())) // $ hasTaintFlow="call to Any"
|
||||
logger.InfoContext(ctx, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
logger.Log(ctx, slog.LevelInfo, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
logger.LogAttrs(ctx, slog.LevelInfo, "msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
|
||||
}
|
||||
|
||||
// With, Logger.With and Logger.WithGroup. Note that for ease of modeling we make these functions
|
||||
// sinks, although strictly speaking we should consider logging functions called on the returned
|
||||
// loggers as the sinks.
|
||||
|
||||
func testWith(logger *slog.Logger) {
|
||||
logger1 := logger.With(slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
|
||||
logger1.Info("hello world")
|
||||
logger2 := logger.With(slog.Any(getUntrustedString(), nil)) // $ hasTaintFlow="call to Any"
|
||||
logger2.Info("hello world")
|
||||
logger.With("key", getUntrustedData()).Info("hello world") // $ hasValueFlow="call to getUntrustedData"
|
||||
}
|
||||
|
||||
func testPackageWith() {
|
||||
logger := slog.With(slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
|
||||
logger.Info("hello world")
|
||||
slog.With("key", getUntrustedData()).Info("hello world") // $ hasValueFlow="call to getUntrustedData"
|
||||
}
|
||||
|
||||
func testWithGroup(logger *slog.Logger) {
|
||||
grouped := logger.WithGroup(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
|
||||
grouped.Info("hello world")
|
||||
}
|
||||
|
||||
// Summary models: functions relating to Attr/Value that propagate strings.
|
||||
|
||||
func testAttrConstructors(logger *slog.Logger) {
|
||||
logger.Info("msg", slog.Group("group", slog.String("key", getUntrustedString()))) // $ hasTaintFlow="call to Group"
|
||||
logger.Info("msg", slog.GroupAttrs("group", slog.String("key", getUntrustedString()))) // $ hasTaintFlow="call to GroupAttrs"
|
||||
}
|
||||
|
||||
func testValueConstructors(logger *slog.Logger) {
|
||||
logger.Info("msg", "key", slog.AnyValue(getUntrustedString())) // $ hasTaintFlow="call to AnyValue"
|
||||
logger.Info("msg", "key", slog.StringValue(getUntrustedString())) // $ hasTaintFlow="call to StringValue"
|
||||
attr := slog.String("key", getUntrustedString())
|
||||
logger.Info("msg", "key", slog.GroupValue(attr)) // $ hasTaintFlow="call to GroupValue"
|
||||
}
|
||||
|
||||
func testAttrAndValueAccessors(logger *slog.Logger) {
|
||||
attr := slog.String("key", getUntrustedString())
|
||||
logger.Info("msg", "key", attr.String()) // $ hasTaintFlow="call to String"
|
||||
|
||||
v := slog.AnyValue(getUntrustedString())
|
||||
logger.Info("msg", "key", v.Any()) // $ hasTaintFlow="call to Any"
|
||||
logger.Info("msg", "key", v.String()) // $ hasTaintFlow="call to String"
|
||||
|
||||
group := slog.GroupValue(slog.String("key", getUntrustedString()))
|
||||
logger.Info("msg", group.Group()[0]) // $ hasTaintFlow="index expression"
|
||||
}
|
||||
Reference in New Issue
Block a user