diff --git a/ql/src/semmle/go/frameworks/Stdlib.qll b/ql/src/semmle/go/frameworks/Stdlib.qll index 29d58b89..44a24ff6 100644 --- a/ql/src/semmle/go/frameworks/Stdlib.qll +++ b/ql/src/semmle/go/frameworks/Stdlib.qll @@ -3,6 +3,8 @@ */ import go +import semmle.go.frameworks.stdlib.ArchiveTar +import semmle.go.frameworks.stdlib.ArchiveZip /** A `String()` method. */ class StringMethod extends TaintTracking::FunctionModel, Method { diff --git a/ql/src/semmle/go/frameworks/stdlib/ArchiveTar.qll b/ql/src/semmle/go/frameworks/stdlib/ArchiveTar.qll new file mode 100644 index 00000000..ba776aab --- /dev/null +++ b/ql/src/semmle/go/frameworks/stdlib/ArchiveTar.qll @@ -0,0 +1,63 @@ +/** + * Provides classes modeling security-relevant aspects of the `archive/tar` package. + */ + +import go + +/** Provides models of commonly used functions in the `archive/tar` package. */ +module ArchiveTar { + private class FunctionModels extends TaintTracking::FunctionModel { + FunctionInput inp; + FunctionOutput outp; + + FunctionModels() { + // signature: func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) + hasQualifiedName("archive/tar", "FileInfoHeader") and + (inp.isParameter(0) and outp.isResult(0)) + or + // signature: func NewReader(r io.Reader) *Reader + hasQualifiedName("archive/tar", "NewReader") and + (inp.isParameter(0) and outp.isResult()) + or + // signature: func NewWriter(w io.Writer) *Writer + hasQualifiedName("archive/tar", "NewWriter") and + (inp.isResult() and outp.isParameter(0)) + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input = inp and output = outp + } + } + + private class MethodModels extends TaintTracking::FunctionModel, Method { + FunctionInput inp; + FunctionOutput outp; + + MethodModels() { + // Methods: + // signature: func (*Header).FileInfo() os.FileInfo + this.(Method).hasQualifiedName("archive/tar", "Header", "FileInfo") and + (inp.isReceiver() and outp.isResult()) + or + // signature: func (*Reader).Next() (*Header, error) + this.(Method).hasQualifiedName("archive/tar", "Reader", "Next") and + (inp.isReceiver() and outp.isResult(0)) + or + // signature: func (*Reader).Read(b []byte) (int, error) + this.(Method).hasQualifiedName("archive/tar", "Reader", "Read") and + (inp.isReceiver() and outp.isParameter(0)) + or + // signature: func (*Writer).Write(b []byte) (int, error) + this.(Method).hasQualifiedName("archive/tar", "Writer", "Write") and + (inp.isParameter(0) and outp.isReceiver()) + or + // signature: func (*Writer).WriteHeader(hdr *Header) error + this.(Method).hasQualifiedName("archive/tar", "Writer", "WriteHeader") and + (inp.isParameter(0) and outp.isReceiver()) + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input = inp and output = outp + } + } +} diff --git a/ql/src/semmle/go/frameworks/stdlib/ArchiveZip.qll b/ql/src/semmle/go/frameworks/stdlib/ArchiveZip.qll new file mode 100644 index 00000000..178b3308 --- /dev/null +++ b/ql/src/semmle/go/frameworks/stdlib/ArchiveZip.qll @@ -0,0 +1,59 @@ +/** + * Provides classes modeling security-relevant aspects of the `archive/zip` package. + */ + +import go + +/** Provides models of commonly used functions in the `archive/zip` package. */ +module ArchiveZip { + private class FunctionModels extends TaintTracking::FunctionModel { + FunctionInput inp; + FunctionOutput outp; + + FunctionModels() { + // signature: func FileInfoHeader(fi os.FileInfo) (*FileHeader, error) + hasQualifiedName("archive/zip", "FileInfoHeader") and + (inp.isParameter(0) and outp.isResult(0)) + or + // signature: func NewReader(r io.ReaderAt, size int64) (*Reader, error) + hasQualifiedName("archive/zip", "NewReader") and + (inp.isParameter(0) and outp.isResult(0)) + or + // signature: func NewWriter(w io.Writer) *Writer + hasQualifiedName("archive/zip", "NewWriter") and + (inp.isResult() and outp.isParameter(0)) + or + // signature: func OpenReader(name string) (*ReadCloser, error) + hasQualifiedName("archive/zip", "OpenReader") and + (inp.isParameter(0) and outp.isResult(0)) + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input = inp and output = outp + } + } + + private class MethodModels extends TaintTracking::FunctionModel, Method { + FunctionInput inp; + FunctionOutput outp; + + MethodModels() { + // Methods: + // signature: func (*File).Open() (io.ReadCloser, error) + this.(Method).hasQualifiedName("archive/zip", "File", "Open") and + (inp.isReceiver() and outp.isResult(0)) + or + // signature: func (*Writer).Create(name string) (io.Writer, error) + this.(Method).hasQualifiedName("archive/zip", "Writer", "Create") and + (inp.isResult(0) and outp.isReceiver()) + or + // signature: func (*Writer).CreateHeader(fh *FileHeader) (io.Writer, error) + this.(Method).hasQualifiedName("archive/zip", "Writer", "CreateHeader") and + (inp.isResult(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/StdlibTaintFlow/ArchiveTar.go b/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/ArchiveTar.go new file mode 100644 index 00000000..1e67bd1f --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/ArchiveTar.go @@ -0,0 +1,105 @@ +// Code generated by https://github.com/gagliardetto/codebox. DO NOT EDIT. + +package main + +import ( + "archive/tar" + "io" + "os" +) + +func TaintStepTest_ArchiveTarFileInfoHeader_B0I0O0(sourceCQL interface{}) interface{} { + fromFileInfo656 := sourceCQL.(os.FileInfo) + intoHeader414, _ := tar.FileInfoHeader(fromFileInfo656, "") + return intoHeader414 +} + +func TaintStepTest_ArchiveTarNewReader_B0I0O0(sourceCQL interface{}) interface{} { + fromReader518 := sourceCQL.(io.Reader) + intoReader650 := tar.NewReader(fromReader518) + return intoReader650 +} + +func TaintStepTest_ArchiveTarNewWriter_B0I0O0(sourceCQL interface{}) interface{} { + fromWriter784 := sourceCQL.(*tar.Writer) + var intoWriter957 io.Writer + intermediateCQL := tar.NewWriter(intoWriter957) + link(fromWriter784, intermediateCQL) + return intoWriter957 +} + +func TaintStepTest_ArchiveTarHeaderFileInfo_B0I0O0(sourceCQL interface{}) interface{} { + fromHeader520 := sourceCQL.(tar.Header) + intoFileInfo443 := fromHeader520.FileInfo() + return intoFileInfo443 +} + +func TaintStepTest_ArchiveTarReaderNext_B0I0O0(sourceCQL interface{}) interface{} { + fromReader127 := sourceCQL.(tar.Reader) + intoHeader483, _ := fromReader127.Next() + return intoHeader483 +} + +func TaintStepTest_ArchiveTarReaderRead_B0I0O0(sourceCQL interface{}) interface{} { + fromReader989 := sourceCQL.(tar.Reader) + var intoByte982 []byte + fromReader989.Read(intoByte982) + return intoByte982 +} + +func TaintStepTest_ArchiveTarWriterWrite_B0I0O0(sourceCQL interface{}) interface{} { + fromByte417 := sourceCQL.([]byte) + var intoWriter584 tar.Writer + intoWriter584.Write(fromByte417) + return intoWriter584 +} + +func TaintStepTest_ArchiveTarWriterWriteHeader_B0I0O0(sourceCQL interface{}) interface{} { + fromHeader991 := sourceCQL.(*tar.Header) + var intoWriter881 tar.Writer + intoWriter881.WriteHeader(fromHeader991) + return intoWriter881 +} + +func RunAllTaints_ArchiveTar() { + { + source := newSource(0) + out := TaintStepTest_ArchiveTarFileInfoHeader_B0I0O0(source) + sink(0, out) + } + { + source := newSource(1) + out := TaintStepTest_ArchiveTarNewReader_B0I0O0(source) + sink(1, out) + } + { + source := newSource(2) + out := TaintStepTest_ArchiveTarNewWriter_B0I0O0(source) + sink(2, out) + } + { + source := newSource(3) + out := TaintStepTest_ArchiveTarHeaderFileInfo_B0I0O0(source) + sink(3, out) + } + { + source := newSource(4) + out := TaintStepTest_ArchiveTarReaderNext_B0I0O0(source) + sink(4, out) + } + { + source := newSource(5) + out := TaintStepTest_ArchiveTarReaderRead_B0I0O0(source) + sink(5, out) + } + { + source := newSource(6) + out := TaintStepTest_ArchiveTarWriterWrite_B0I0O0(source) + sink(6, out) + } + { + source := newSource(7) + out := TaintStepTest_ArchiveTarWriterWriteHeader_B0I0O0(source) + sink(7, out) + } +} diff --git a/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/ArchiveZip.go b/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/ArchiveZip.go new file mode 100644 index 00000000..6cbbfcb7 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/ArchiveZip.go @@ -0,0 +1,95 @@ +// Code generated by https://github.com/gagliardetto/codebox. DO NOT EDIT. + +package main + +import ( + "archive/zip" + "io" + "os" +) + +func TaintStepTest_ArchiveZipFileInfoHeader_B0I0O0(sourceCQL interface{}) interface{} { + fromFileInfo656 := sourceCQL.(os.FileInfo) + intoFileHeader414, _ := zip.FileInfoHeader(fromFileInfo656) + return intoFileHeader414 +} + +func TaintStepTest_ArchiveZipNewReader_B0I0O0(sourceCQL interface{}) interface{} { + fromReaderAt518 := sourceCQL.(io.ReaderAt) + intoReader650, _ := zip.NewReader(fromReaderAt518, 0) + return intoReader650 +} + +func TaintStepTest_ArchiveZipNewWriter_B0I0O0(sourceCQL interface{}) interface{} { + fromWriter784 := sourceCQL.(*zip.Writer) + var intoWriter957 io.Writer + intermediateCQL := zip.NewWriter(intoWriter957) + link(fromWriter784, intermediateCQL) + return intoWriter957 +} + +func TaintStepTest_ArchiveZipOpenReader_B0I0O0(sourceCQL interface{}) interface{} { + fromString520 := sourceCQL.(string) + intoReadCloser443, _ := zip.OpenReader(fromString520) + return intoReadCloser443 +} + +func TaintStepTest_ArchiveZipFileOpen_B0I0O0(sourceCQL interface{}) interface{} { + fromFile127 := sourceCQL.(zip.File) + intoReadCloser483, _ := fromFile127.Open() + return intoReadCloser483 +} + +func TaintStepTest_ArchiveZipWriterCreate_B0I0O0(sourceCQL interface{}) interface{} { + fromWriter989 := sourceCQL.(io.Writer) + var intoWriter982 zip.Writer + intermediateCQL, _ := intoWriter982.Create("") + link(fromWriter989, intermediateCQL) + return intoWriter982 +} + +func TaintStepTest_ArchiveZipWriterCreateHeader_B0I0O0(sourceCQL interface{}) interface{} { + fromWriter417 := sourceCQL.(io.Writer) + var intoWriter584 zip.Writer + intermediateCQL, _ := intoWriter584.CreateHeader(nil) + link(fromWriter417, intermediateCQL) + return intoWriter584 +} + +func RunAllTaints_ArchiveZip() { + { + source := newSource(0) + out := TaintStepTest_ArchiveZipFileInfoHeader_B0I0O0(source) + sink(0, out) + } + { + source := newSource(1) + out := TaintStepTest_ArchiveZipNewReader_B0I0O0(source) + sink(1, out) + } + { + source := newSource(2) + out := TaintStepTest_ArchiveZipNewWriter_B0I0O0(source) + sink(2, out) + } + { + source := newSource(3) + out := TaintStepTest_ArchiveZipOpenReader_B0I0O0(source) + sink(3, out) + } + { + source := newSource(4) + out := TaintStepTest_ArchiveZipFileOpen_B0I0O0(source) + sink(4, out) + } + { + source := newSource(5) + out := TaintStepTest_ArchiveZipWriterCreate_B0I0O0(source) + sink(5, out) + } + { + source := newSource(6) + out := TaintStepTest_ArchiveZipWriterCreateHeader_B0I0O0(source) + sink(6, out) + } +} diff --git a/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/StdlibTaintFlow.expected b/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/StdlibTaintFlow.expected new file mode 100644 index 00000000..e69de29b diff --git a/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/StdlibTaintFlow.ql b/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/StdlibTaintFlow.ql new file mode 100644 index 00000000..bc41ecbd --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/StdlibTaintFlow.ql @@ -0,0 +1,56 @@ +/** + * @kind problem + */ + +import go + +/* A special helper function used inside the test code */ +class Link extends TaintTracking::FunctionModel { + Link() { hasQualifiedName(_, "link") } + + override predicate hasTaintFlow(FunctionInput inp, FunctionOutput outp) { + inp.isParameter(0) and outp.isParameter(1) + } +} + +predicate isSource(DataFlow::Node source, DataFlow::CallNode call) { + exists(Function fn | fn.hasQualifiedName(_, "newSource") | + call = fn.getACall() and source = call.getResult() + ) +} + +predicate isSink(DataFlow::Node sink, DataFlow::CallNode call) { + exists(Function fn | fn.hasQualifiedName(_, "sink") | + call = fn.getACall() and sink = call.getArgument(1) + ) +} + +class FlowConf extends TaintTracking::Configuration { + FlowConf() { this = "FlowConf" } + + override predicate isSource(DataFlow::Node source) { isSource(source, _) } + + override predicate isSink(DataFlow::Node sink) { isSink(sink, _) } +} + +/** + * True if the result of the provided sourceCall flows to the corresponding sink, + * both marked by the same numeric first argument. + */ +predicate flowsToSink(DataFlow::CallNode sourceCall) { + exists( + FlowConf cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::CallNode sinkCall + | + cfg.hasFlowPath(source, sink) and + ( + isSource(source.getNode(), sourceCall) and + isSink(sink.getNode(), sinkCall) and + sourceCall.getArgument(0).getIntValue() = sinkCall.getArgument(0).getIntValue() + ) + ) +} + +/* Show only flow sources that DON'T flow to their dedicated sink. */ +from DataFlow::CallNode sourceCall +where isSource(_, sourceCall) and not flowsToSink(sourceCall) +select sourceCall, "No flow to its sink" diff --git a/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/main.go b/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/main.go new file mode 100644 index 00000000..1d1a0104 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/StdlibTaintFlow/main.go @@ -0,0 +1,10 @@ +package main + +func main() {} +func sink(id int, v interface{}) {} + +func link(from interface{}, into interface{}) {} + +func newSource(id int) interface{} { + return nil +}