Repository navigation
Open
Labels
questionFurther information is requestedFurther information is requested
Description
Go: v1.19.1 linux/amd64
CodeQL: v2.11.0
Question: Query the data flow results from the input parameter in the source function to the name parameter in the sink function.
package main
import (
"fmt"
)
type info struct {
name string
}
func main() {
source("input")
}
func source(input string) {
var data []info
data = append(data, info{
name: input,
})
dao(data)
}
func dao(data []info) {
for _, item := range data {
name := item.name
sink(name)
}
}
func sink(name string) {
fmt.Println(name)
}The following is my query statement. After execution, I did not get the expected results.
/**
* @kind path-problem
*/
import go
import DataFlow::PathGraph
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "Configuration" }
override predicate isSource(DataFlow::Node source) {
exists(Function func | func.getName() = "source" | func.getParameter(0) = source.asParameter())
}
override predicate isSink(DataFlow::Node sink) {
exists(DataFlow::CallNode call | call.getTarget().getName() = "sink" |
call.getArgument(0) = sink
)
}
}
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Source: $@ , Sink: $@ .", source.getNode(), source.toString(),
sink.getNode(), sink.toString()When I debug using DataFlow::PartialPathGraph, it seems that the tainted data does not propagate the data flow after passing through the append function.
/**
* @kind path-problem
*/
import go
import DataFlow::PartialPathGraph
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "Configuration" }
override predicate isSource(DataFlow::Node source) {
exists(Function func | func.getName() = "source" | func.getParameter(0) = source.asParameter())
}
override predicate isSink(DataFlow::Node sink) {
exists(DataFlow::CallNode call | call.getTarget().getName() = "sink" |
call.getArgument(0) = sink
)
}
override int explorationLimit() { result = 5 }
}
from Configuration cfg, DataFlow::PartialPathNode source, DataFlow::PartialPathNode sink
where cfg.hasPartialFlow(source, sink, _)
select sink.getNode(), source, sink, "Source: $@ , Sink: $@ .", source.getNode(), source.toString(), sink.getNode(), sink.toString()How to solve this problem?
Metadata
Metadata
Assignees
Labels
questionFurther information is requestedFurther information is requested
Activity
MathiasVP commentedon Sep 1, 2023
Hi @Jokuuy,
This is a tricky issue. I'll provide some background that explains what's going wrong, and end with two ways in which you can fix it yourself to get the flow you want. Finally, I'll add a couple of notes about what we're hopefully doing about this in the future for Go so that everything automagically works.
What you can do to get the flow you want
Let me give you the background first.
The dataflow library has two "kinds" of steps:
DataFlow::Configuration.TaintTracking::Configuration.An example of a "value preserving step" is steps from the right-hand side of an assignment to the next use of the variable. e.g., the step from
42tox(inreturn x) in something like:An example of a "non-value preserving step" could be a step from
xtox + 1. More importantly for this issue:appendis also a non-value preserving step.When the dataflow is tracking flow is keeps track of which fields has been written to (so that we can propagate flow from a write to a field to a subsequent read of that field) using the concept of an "access path". You can think of an access path as a list of fields that have currently been written to along the dataflow path traversed so far. A dataflow path node is roughly the pair (n, ap), where
nis a dataflow node andapis an access path.For example, let's say you want to track the flow from "hello" to the argument of
printin something like:After the line
f.a = "hello"the access path is[a]. This represents that, in order to get to the data we're tracking, we need to read the fielda. After the lineb.f = fthe access path is[f, a]which represents that in order to get to the data we're tracking, we first need to read the fieldf, and then we need to read the fielda. After the linef2 := b.fwe "pop" the first element of the access path (because we read a field that matches the head of the access path) and transform the access path to[a]. Finally, after the linemyString := f2.athe access path is empty, which represents that the data we're tracking is exactly the value ofmyString.There are many which in which a field can be written to. In your example, the field
namehas been written to when you constructed theinfo{name: input}value. As you can see from the partial path, CodeQL ends up tracking the struct literal along with the access path[name].Now, here's the kicker: Non-value preserving steps are only taken into account when the access path is empty. Knowing this, it's hopefully clear why your flow is stopping at
appendbecause:appendis a non-value preserving step, andTo fix this you either need to:
append, orappendas value-preserving stepsTo do 1., you can add an additional flow step to your configuration from the struct literal's
namefield to the struct literal.To do 2., I believe you can change https://github.com/github/codeql/blob/main/go/ql/lib/semmle/go/frameworks/Stdlib.qll#L53 to extend
DataFlow::FunctionModelinstead ofTaintTracking::FunctionModel.I hope this helps! Let me know if you need more pointers 🙂.
What we'll hopefully do in the future:
The underlying issue is really the way we model
appendas a non-value preserving step in Go. In the future, we'd like to change the model so that it's a value-preserving step which reflects that, if tainted data is stored inside the list before the append, then tainted data is also stored inside the list after the append.owen-mc commentedon Jan 23, 2025
This may have been fixed by #16413, which updated the models for
append.