Skip to content

Commit e1a70b7

Browse files
committed
Expand log.slog models and add more tests
1 parent 5016fcb commit e1a70b7

6 files changed

Lines changed: 163 additions & 0 deletions

File tree

go/ql/lib/ext/log.slog.model.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,27 @@ extensions:
2727
- ["log/slog", "Logger", True, "ErrorContext", "", "", "Argument[1..2]", "log-injection", "manual"]
2828
- ["log/slog", "Logger", True, "Log", "", "", "Argument[2..3]", "log-injection", "manual"]
2929
- ["log/slog", "Logger", True, "LogAttrs", "", "", "Argument[2..3]", "log-injection", "manual"]
30+
# With/WithGroup add attributes that are included in every subsequent log call.
31+
- ["log/slog", "", False, "With", "", "", "Argument[0]", "log-injection", "manual"]
32+
- ["log/slog", "Logger", True, "With", "", "", "Argument[0]", "log-injection", "manual"]
33+
- ["log/slog", "Logger", True, "WithGroup", "", "", "Argument[0]", "log-injection", "manual"]
34+
- addsTo:
35+
pack: codeql/go-all
36+
extensible: summaryModel
37+
data:
38+
# Constructors for Attr that can carry a tainted string into the result.
39+
- ["log/slog", "", False, "Any", "", "", "Argument[0..1]", "ReturnValue", "taint", "manual"]
40+
- ["log/slog", "", False, "Group", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
41+
- ["log/slog", "", False, "Group", "", "", "Argument[1].ArrayElement", "ReturnValue", "taint", "manual"]
42+
- ["log/slog", "", False, "GroupAttrs", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
43+
- ["log/slog", "", False, "GroupAttrs", "", "", "Argument[1].ArrayElement", "ReturnValue", "taint", "manual"]
44+
- ["log/slog", "", False, "String", "", "", "Argument[0..1]", "ReturnValue", "taint", "manual"]
45+
# Constructors for Value that can carry a tainted string into the result.
46+
- ["log/slog", "", False, "AnyValue", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
47+
- ["log/slog", "", False, "GroupValue", "", "", "Argument[0].ArrayElement", "ReturnValue", "taint", "manual"]
48+
- ["log/slog", "", False, "StringValue", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
49+
# Methods that read a string back out of an Attr or Value.
50+
- ["log/slog", "Attr", True, "String", "", "", "Argument[receiver]", "ReturnValue", "taint", "manual"]
51+
- ["log/slog", "Value", True, "Any", "", "", "Argument[receiver]", "ReturnValue", "taint", "manual"]
52+
- ["log/slog", "Value", True, "Group", "", "", "Argument[receiver]", "ReturnValue.ArrayElement", "taint", "manual"]
53+
- ["log/slog", "Value", True, "String", "", "", "Argument[receiver]", "ReturnValue", "taint", "manual"]

go/ql/test/library-tests/semmle/go/concepts/LoggerCall/slog.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,9 @@ func slogTest() {
3737
slog.InfoContext(ctx, text, key, v) // $ logger=text logger=key logger=v
3838
slog.Log(ctx, slog.LevelInfo, text, key, v) // $ logger=text logger=key logger=v
3939
slog.LogAttrs(ctx, slog.LevelInfo, text, attr) // $ logger=text logger=attr
40+
41+
// With/WithGroup add attributes that are included in every subsequent log call.
42+
logger.With(key, v) // $ logger=key logger=v
43+
logger.WithGroup(text) // $ logger=text
44+
slog.With(key, v) // $ logger=key logger=v
4045
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
invalidModelRow
2+
testFailures
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import go
2+
import semmle.go.dataflow.ExternalFlow
3+
import ModelValidation
4+
import utils.test.InlineFlowTest
5+
6+
module Config implements DataFlow::ConfigSig {
7+
predicate isSource(DataFlow::Node source) {
8+
source.(DataFlow::CallNode).getTarget().getName() = ["getUntrustedData", "getUntrustedString"]
9+
}
10+
11+
predicate isSink(DataFlow::Node sink) { sink = any(LoggerCall log).getAMessageComponent() }
12+
}
13+
14+
import FlowTest<Config, Config>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module codeql-go-tests/frameworks/slog
2+
3+
go 1.26
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
)
7+
8+
func main() {}
9+
10+
func getUntrustedData() interface{} { return nil }
11+
12+
func getUntrustedString() string {
13+
return "tainted string"
14+
}
15+
16+
// Package-level convenience functions.
17+
18+
func testSlogDebug() {
19+
slog.Debug(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
20+
slog.Debug("msg", "key", getUntrustedData()) // $ hasValueFlow="call to getUntrustedData"
21+
slog.Debug("msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
22+
}
23+
24+
func testSlogInfo() {
25+
slog.Info(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
26+
slog.Info("msg", slog.Any("key", getUntrustedData())) // $ hasTaintFlow="call to Any"
27+
slog.Info("msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
28+
}
29+
30+
func testSlogWarn() {
31+
slog.Warn(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
32+
slog.Warn("msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
33+
}
34+
35+
func testSlogError() {
36+
slog.Error(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
37+
slog.Error("msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
38+
}
39+
40+
func testSlogContextVariants(ctx context.Context) {
41+
slog.DebugContext(ctx, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
42+
slog.InfoContext(ctx, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
43+
slog.WarnContext(ctx, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
44+
slog.ErrorContext(ctx, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
45+
slog.InfoContext(ctx, "msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
46+
}
47+
48+
func testSlogLog(ctx context.Context) {
49+
slog.Log(ctx, slog.LevelInfo, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
50+
slog.Log(ctx, slog.LevelInfo, "msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
51+
slog.LogAttrs(ctx, slog.LevelInfo, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
52+
slog.LogAttrs(ctx, slog.LevelInfo, "msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
53+
}
54+
55+
// Methods on *slog.Logger.
56+
57+
func testLoggerMethods(logger *slog.Logger, ctx context.Context) {
58+
logger.Debug(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
59+
logger.Info(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
60+
logger.Warn(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
61+
logger.Error(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
62+
logger.Info("msg", slog.Any("key", getUntrustedData())) // $ hasTaintFlow="call to Any"
63+
logger.InfoContext(ctx, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
64+
logger.Log(ctx, slog.LevelInfo, getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
65+
logger.LogAttrs(ctx, slog.LevelInfo, "msg", slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
66+
}
67+
68+
// With, Logger.With and Logger.WithGroup. Note that for ease of modeling we make these functions
69+
// sinks, although strictly speaking we should consider logging functions called on the returned
70+
// loggers as the sinks.
71+
72+
func testWith(logger *slog.Logger) {
73+
logger1 := logger.With(slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
74+
logger1.Info("hello world")
75+
logger2 := logger.With(slog.Any(getUntrustedString(), nil)) // $ hasTaintFlow="call to Any"
76+
logger2.Info("hello world")
77+
logger.With("key", getUntrustedData()).Info("hello world") // $ hasValueFlow="call to getUntrustedData"
78+
}
79+
80+
func testPackageWith() {
81+
logger := slog.With(slog.String("key", getUntrustedString())) // $ hasTaintFlow="call to String"
82+
logger.Info("hello world")
83+
slog.With("key", getUntrustedData()).Info("hello world") // $ hasValueFlow="call to getUntrustedData"
84+
}
85+
86+
func testWithGroup(logger *slog.Logger) {
87+
grouped := logger.WithGroup(getUntrustedString()) // $ hasValueFlow="call to getUntrustedString"
88+
grouped.Info("hello world")
89+
}
90+
91+
// Summary models: functions relating to Attr/Value that propagate strings.
92+
93+
func testAttrConstructors(logger *slog.Logger) {
94+
logger.Info("msg", slog.Group("group", slog.String("key", getUntrustedString()))) // $ hasTaintFlow="call to Group"
95+
logger.Info("msg", slog.GroupAttrs("group", slog.String("key", getUntrustedString()))) // $ hasTaintFlow="call to GroupAttrs"
96+
}
97+
98+
func testValueConstructors(logger *slog.Logger) {
99+
logger.Info("msg", "key", slog.AnyValue(getUntrustedString())) // $ hasTaintFlow="call to AnyValue"
100+
logger.Info("msg", "key", slog.StringValue(getUntrustedString())) // $ hasTaintFlow="call to StringValue"
101+
attr := slog.String("key", getUntrustedString())
102+
logger.Info("msg", "key", slog.GroupValue(attr)) // $ hasTaintFlow="call to GroupValue"
103+
}
104+
105+
func testAttrAndValueAccessors(logger *slog.Logger) {
106+
attr := slog.String("key", getUntrustedString())
107+
logger.Info("msg", "key", attr.String()) // $ hasTaintFlow="call to String"
108+
109+
v := slog.AnyValue(getUntrustedString())
110+
logger.Info("msg", "key", v.Any()) // $ hasTaintFlow="call to Any"
111+
logger.Info("msg", "key", v.String()) // $ hasTaintFlow="call to String"
112+
113+
group := slog.GroupValue(slog.String("key", getUntrustedString()))
114+
logger.Info("msg", group.Group()[0]) // $ hasTaintFlow="index expression"
115+
}

0 commit comments

Comments
 (0)