diff --git a/change-notes/2021-02-10-yaml.md b/change-notes/2021-02-10-yaml.md new file mode 100644 index 00000000000..3a23f42a0f5 --- /dev/null +++ b/change-notes/2021-02-10-yaml.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* Added support for the [gopkg.in/yaml](https://pkg.go.dev/gopkg.in/yaml.v3) package, which may lead to more results from the security queries. diff --git a/ql/src/go.qll b/ql/src/go.qll index 6cfb2e5264c..1ef249783a4 100644 --- a/ql/src/go.qll +++ b/ql/src/go.qll @@ -58,5 +58,6 @@ import semmle.go.frameworks.Testing import semmle.go.frameworks.WebSocket import semmle.go.frameworks.XNetHtml import semmle.go.frameworks.XPath +import semmle.go.frameworks.Yaml import semmle.go.frameworks.Zap import semmle.go.security.FlowSources diff --git a/ql/src/semmle/go/frameworks/Yaml.qll b/ql/src/semmle/go/frameworks/Yaml.qll new file mode 100644 index 00000000000..b661021f573 --- /dev/null +++ b/ql/src/semmle/go/frameworks/Yaml.qll @@ -0,0 +1,70 @@ +/** + * Provides classes for working with the [gopkg.in/yaml](https://pkg.go.dev/gopkg.in/yaml.v3) package. + */ + +import go + +/** + * Provides classes for working with the [gopkg.in/yaml](https://pkg.go.dev/gopkg.in/yaml.v3) package. + */ +module Yaml { + /** Gets a package path for the Yaml package. */ + bindingset[result] + string packagePath() { result = package("gopkg.in/yaml", "") } + + private class MarshalFunction extends TaintTracking::FunctionModel, MarshalingFunction::Range { + MarshalFunction() { this.hasQualifiedName(packagePath(), "Marshal") } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input = this.getAnInput() and output = this.getOutput() + } + + override DataFlow::FunctionInput getAnInput() { result.isParameter(0) } + + override DataFlow::FunctionOutput getOutput() { result.isResult(0) } + + override string getFormat() { result = "yaml" } + } + + private class UnmarshalFunction extends TaintTracking::FunctionModel, UnmarshalingFunction::Range { + UnmarshalFunction() { this.hasQualifiedName(packagePath(), ["Unmarshal", "UnmarshalStrict"]) } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input = this.getAnInput() and output = this.getOutput() + } + + override DataFlow::FunctionInput getAnInput() { result.isParameter(0) } + + override DataFlow::FunctionOutput getOutput() { result.isParameter(1) } + + override string getFormat() { result = "yaml" } + } + + private class FunctionModels extends TaintTracking::FunctionModel { + FunctionInput inp; + FunctionOutput outp; + + FunctionModels() { + this.hasQualifiedName(packagePath(), "NewDecoder") and + (inp.isParameter(0) and outp.isResult()) + or + this.hasQualifiedName(packagePath(), "NewEncoder") and + (inp.isResult() and outp.isParameter(0)) + or + exists(Method m | this = m | + m.hasQualifiedName(packagePath(), ["Decoder", "Node"], "Decode") and + (inp.isReceiver() and outp.isParameter(0)) + or + m.hasQualifiedName(packagePath(), ["Encoder", "Node"], "Encode") and + (inp.isParameter(0) and outp.isReceiver()) + or + m.hasQualifiedName(packagePath(), "Node", "SetString") and + (inp.isParameter(0) and outp.isReceiver()) + ) + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input = inp and output = outp + } + } +} diff --git a/ql/test/library-tests/semmle/go/frameworks/Yaml/go.mod b/ql/test/library-tests/semmle/go/frameworks/Yaml/go.mod new file mode 100644 index 00000000000..2604133f64c --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/Yaml/go.mod @@ -0,0 +1,9 @@ +module codeql-go-tests/frameworks/Yaml + +go 1.15 + +require ( + gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 + gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b +) diff --git a/ql/test/library-tests/semmle/go/frameworks/Yaml/tests.expected b/ql/test/library-tests/semmle/go/frameworks/Yaml/tests.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ql/test/library-tests/semmle/go/frameworks/Yaml/tests.ql b/ql/test/library-tests/semmle/go/frameworks/Yaml/tests.ql new file mode 100644 index 00000000000..9c76068a9a0 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/Yaml/tests.ql @@ -0,0 +1,49 @@ +import go +import TestUtilities.InlineExpectationsTest + +class TaintFunctionModelTest extends InlineExpectationsTest { + TaintFunctionModelTest() { this = "TaintFunctionModelTest" } + + override string getARelevantTag() { result = "ttfnmodelstep" } + + override predicate hasActualResult(string file, int line, string element, string tag, string value) { + tag = "ttfnmodelstep" and + exists(TaintTracking::FunctionModel model, DataFlow::CallNode call | call = model.getACall() | + call.hasLocationInfo(file, line, _, _, _) and + element = call.toString() and + value = model.getAnInputNode(call) + " -> " + model.getAnOutputNode(call) + ) + } +} + +class MarshalerTest extends InlineExpectationsTest { + MarshalerTest() { this = "MarshalerTest" } + + override string getARelevantTag() { result = "marshaler" } + + override predicate hasActualResult(string file, int line, string element, string tag, string value) { + tag = "marshaler" and + exists(MarshalingFunction m, DataFlow::CallNode call | call = m.getACall() | + call.hasLocationInfo(file, line, _, _, _) and + element = call.toString() and + value = + m.getFormat() + ": " + m.getAnInput().getNode(call) + " -> " + m.getOutput().getNode(call) + ) + } +} + +class UnmarshalerTest extends InlineExpectationsTest { + UnmarshalerTest() { this = "UnmarshalerTest" } + + override string getARelevantTag() { result = "unmarshaler" } + + override predicate hasActualResult(string file, int line, string element, string tag, string value) { + tag = "unmarshaler" and + exists(UnmarshalingFunction m, DataFlow::CallNode call | call = m.getACall() | + call.hasLocationInfo(file, line, _, _, _) and + element = call.toString() and + value = + m.getFormat() + ": " + m.getAnInput().getNode(call) + " -> " + m.getOutput().getNode(call) + ) + } +} diff --git a/ql/test/library-tests/semmle/go/frameworks/Yaml/vendor/gopkg.in/yaml.v1/stub.go b/ql/test/library-tests/semmle/go/frameworks/Yaml/vendor/gopkg.in/yaml.v1/stub.go new file mode 100644 index 00000000000..c5dd4d66b9e --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/Yaml/vendor/gopkg.in/yaml.v1/stub.go @@ -0,0 +1,16 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for gopkg.in/yaml.v1, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: gopkg.in/yaml.v1 (exports: ; functions: Marshal,Unmarshal) + +// Package yaml is a stub of gopkg.in/yaml.v1, generated by depstubber. +package yaml + +func Marshal(_ interface{}) ([]byte, error) { + return nil, nil +} + +func Unmarshal(_ []byte, _ interface{}) error { + return nil +} diff --git a/ql/test/library-tests/semmle/go/frameworks/Yaml/vendor/gopkg.in/yaml.v2/stub.go b/ql/test/library-tests/semmle/go/frameworks/Yaml/vendor/gopkg.in/yaml.v2/stub.go new file mode 100644 index 00000000000..1e9fb9731bb --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/Yaml/vendor/gopkg.in/yaml.v2/stub.go @@ -0,0 +1,50 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for gopkg.in/yaml.v2, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: gopkg.in/yaml.v2 (exports: ; functions: Marshal,NewDecoder,NewEncoder,Unmarshal,UnmarshalStrict) + +// Package yaml is a stub of gopkg.in/yaml.v2, generated by depstubber. +package yaml + +import ( + io "io" +) + +type Decoder struct{} + +func (_ *Decoder) Decode(_ interface{}) error { + return nil +} + +func (_ *Decoder) SetStrict(_ bool) {} + +type Encoder struct{} + +func (_ *Encoder) Close() error { + return nil +} + +func (_ *Encoder) Encode(_ interface{}) error { + return nil +} + +func Marshal(_ interface{}) ([]byte, error) { + return nil, nil +} + +func NewDecoder(_ io.Reader) *Decoder { + return nil +} + +func NewEncoder(_ io.Writer) *Encoder { + return nil +} + +func Unmarshal(_ []byte, _ interface{}) error { + return nil +} + +func UnmarshalStrict(_ []byte, _ interface{}) error { + return nil +} diff --git a/ql/test/library-tests/semmle/go/frameworks/Yaml/vendor/gopkg.in/yaml.v3/stub.go b/ql/test/library-tests/semmle/go/frameworks/Yaml/vendor/gopkg.in/yaml.v3/stub.go new file mode 100644 index 00000000000..8578c981c2f --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/Yaml/vendor/gopkg.in/yaml.v3/stub.go @@ -0,0 +1,89 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for gopkg.in/yaml.v3, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: gopkg.in/yaml.v3 (exports: Node; functions: Marshal,NewDecoder,NewEncoder,Unmarshal) + +// Package yaml is a stub of gopkg.in/yaml.v3, generated by depstubber. +package yaml + +import ( + io "io" +) + +type Decoder struct{} + +func (_ *Decoder) Decode(_ interface{}) error { + return nil +} + +func (_ *Decoder) KnownFields(_ bool) {} + +type Encoder struct{} + +func (_ *Encoder) Close() error { + return nil +} + +func (_ *Encoder) Encode(_ interface{}) error { + return nil +} + +func (_ *Encoder) SetIndent(_ int) {} + +type Kind uint32 + +func Marshal(_ interface{}) ([]byte, error) { + return nil, nil +} + +func NewDecoder(_ io.Reader) *Decoder { + return nil +} + +func NewEncoder(_ io.Writer) *Encoder { + return nil +} + +type Node struct { + Kind Kind + Style Style + Tag string + Value string + Anchor string + Alias *Node + Content []*Node + HeadComment string + LineComment string + FootComment string + Line int + Column int +} + +func (_ *Node) Decode(_ interface{}) error { + return nil +} + +func (_ *Node) Encode(_ interface{}) error { + return nil +} + +func (_ *Node) IsZero() bool { + return false +} + +func (_ *Node) LongTag() string { + return "" +} + +func (_ *Node) SetString(_ string) {} + +func (_ *Node) ShortTag() string { + return "" +} + +type Style uint32 + +func Unmarshal(_ []byte, _ interface{}) error { + return nil +} diff --git a/ql/test/library-tests/semmle/go/frameworks/Yaml/vendor/modules.txt b/ql/test/library-tests/semmle/go/frameworks/Yaml/vendor/modules.txt new file mode 100644 index 00000000000..cf210e65d86 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/Yaml/vendor/modules.txt @@ -0,0 +1,9 @@ +# gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 +## explicit +gopkg.in/yaml.v1 +# gopkg.in/yaml.v2 v2.4.0 +## explicit +gopkg.in/yaml.v2 +# gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b +## explicit +gopkg.in/yaml.v3 diff --git a/ql/test/library-tests/semmle/go/frameworks/Yaml/yaml.go b/ql/test/library-tests/semmle/go/frameworks/Yaml/yaml.go new file mode 100644 index 00000000000..d2796eb9997 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/Yaml/yaml.go @@ -0,0 +1,41 @@ +package main + +import ( + yaml1 "gopkg.in/yaml.v1" + yaml2 "gopkg.in/yaml.v2" + yaml3 "gopkg.in/yaml.v3" + "io" +) + +func main() { + var in, out interface{} + var inb []byte + + out, _ = yaml1.Marshal(in) // $marshaler=yaml: in -> ... = ...[0] $ttfnmodelstep=in -> ... = ...[0] + yaml1.Unmarshal(inb, out) // $unmarshaler=yaml: inb -> definition of out $ttfnmodelstep=inb -> definition of out + + out, _ = yaml2.Marshal(in) // $marshaler=yaml: in -> ... = ...[0] $ttfnmodelstep=in -> ... = ...[0] + yaml2.Unmarshal(inb, out) // $unmarshaler=yaml: inb -> definition of out $ttfnmodelstep=inb -> definition of out + yaml2.UnmarshalStrict(inb, out) // $unmarshaler=yaml: inb -> definition of out $ttfnmodelstep=inb -> definition of out + + var r io.Reader + d := yaml2.NewDecoder(r) // $ttfnmodelstep=r -> call to NewDecoder + d.Decode(out) // $ttfnmodelstep=d -> definition of out + + var w io.Writer + e := yaml2.NewEncoder(w) // $ttfnmodelstep=definition of e -> definition of w + e.Encode(in) // $ttfnmodelstep=in -> definition of e + + out, _ = yaml3.Marshal(in) // $marshaler=yaml: in -> ... = ...[0] $ttfnmodelstep=in -> ... = ...[0] + yaml3.Unmarshal(inb, out) // $unmarshaler=yaml: inb -> definition of out $ttfnmodelstep=inb -> definition of out + + d1 := yaml3.NewDecoder(r) // $ttfnmodelstep=r -> call to NewDecoder + d1.Decode(out) // $ttfnmodelstep=d1 -> definition of out + + e1 := yaml3.NewEncoder(w) // $ttfnmodelstep=definition of e1 -> definition of w + e1.Encode(in) // $ttfnmodelstep=in -> definition of e1 + + var n1 yaml3.Node + n1.Decode(out) // $ttfnmodelstep=n1 -> definition of out + n1.Encode(in) // $ttfnmodelstep=in -> definition of n1 +}