diff --git a/ql/src/semmle/go/frameworks/Stdlib.qll b/ql/src/semmle/go/frameworks/Stdlib.qll index 36c9516a4..554e8192f 100644 --- a/ql/src/semmle/go/frameworks/Stdlib.qll +++ b/ql/src/semmle/go/frameworks/Stdlib.qll @@ -122,28 +122,220 @@ module Fmt { } /** Provides models of commonly used functions in the `io` package. */ -module Io { +module IO { + private class Copy extends TaintTracking::FunctionModel, Function { + Copy() { + // func Copy(dst Writer, src Reader) (written int64, err error) + // func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) + // func CopyN(dst Writer, src Reader, n int64) (written int64, err error) + hasQualifiedName("io", "Copy") or + hasQualifiedName("io", "CopyBuffer") or + hasQualifiedName("io", "CopyN") + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isParameter(1) and output.isParameter(0) + } + } + + private class Pipe extends TaintTracking::FunctionModel, Function { + Pipe() { + // func Pipe() (*PipeReader, *PipeWriter) + hasQualifiedName("io", "Pipe") + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isResult(0) and output.isResult(1) + } + } + + private class IORead extends TaintTracking::FunctionModel, Function { + IORead() { + // func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) + // func ReadFull(r Reader, buf []byte) (n int, err error) + hasQualifiedName("io", "ReadAtLeast") or + hasQualifiedName("io", "ReadFull") + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isParameter(0) and output.isParameter(1) + } + } + + private class WriteString extends TaintTracking::FunctionModel { + WriteString() { + // func WriteString(w Writer, s string) (n int, err error) + this.hasQualifiedName("io", "WriteString") + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isParameter(1) and output.isParameter(0) + } + } + + private class ByteReaderReadByte extends TaintTracking::FunctionModel, Method { + ByteReaderReadByte() { + // ReadByte() (byte, error) + this.implements("io", "ByteReader", "ReadByte") + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isReceiver() and output.isResult(0) + } + } + + private class ByteWriterWriteByte extends TaintTracking::FunctionModel, Method { + ByteWriterWriteByte() { + // WriteByte(c byte) error + this.implements("io", "ByteWriter", "WriteByte") + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isParameter(0) and output.isReceiver() + } + } + private class ReaderRead extends TaintTracking::FunctionModel, Method { - ReaderRead() { this.implements("io", "Reader", "Read") } + ReaderRead() { + // Read(p []byte) (n int, err error) + this.implements("io", "Reader", "Read") + } - override predicate hasTaintFlow(FunctionInput inp, FunctionOutput outp) { - inp.isReceiver() and outp.isParameter(0) + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isReceiver() and output.isParameter(0) + } + } + + private class LimitReader extends TaintTracking::FunctionModel, Function { + LimitReader() { + // func LimitReader(r Reader, n int64) Reader + this.hasQualifiedName("io", "LimitReader") + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isParameter(0) and output.isResult() + } + } + + private class MultiReader extends TaintTracking::FunctionModel, Function { + MultiReader() { + // func MultiReader(readers ...Reader) Reader + this.hasQualifiedName("io", "MultiReader") + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isParameter(_) and output.isResult() + } + } + + private class TeeReader extends TaintTracking::FunctionModel, Function { + TeeReader() { + // func TeeReader(r Reader, w Writer) Reader + this.hasQualifiedName("io", "TeeReader") + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isParameter(0) and output.isResult() + or + input.isParameter(0) and output.isParameter(1) + } + } + + private class ReaderAtReadAt extends TaintTracking::FunctionModel, Method { + ReaderAtReadAt() { this.implements("io", "ReaderAt", "ReadAt") } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + // ReadAt(p []byte, off int64) (n int, err error) + input.isReceiver() and output.isParameter(0) + } + } + + private class ReaderFromReadFrom extends TaintTracking::FunctionModel, Method { + ReaderFromReadFrom() { + // ReadFrom(r Reader) (n int64, err error) + this.implements("io", "ReaderFrom", "ReadFrom") + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isParameter(0) and output.isReceiver() + } + } + + private class RuneReaderReadRune extends TaintTracking::FunctionModel, Method { + RuneReaderReadRune() { + // ReadRune() (r rune, size int, err error) + this.implements("io", "RuneReader", "ReadRune") + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isReceiver() and output.isResult(0) + } + } + + private class NewSectionReader extends TaintTracking::FunctionModel, Function { + NewSectionReader() { + // func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader + this.hasQualifiedName("io", "NewSectionReader") + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isParameter(0) and output.isResult() + } + } + + // A Taint Model for the stdlib io StringWriter interface + private class StringWriterWriteString extends TaintTracking::FunctionModel, Method { + StringWriterWriteString() { + // WriteString(s string) (n int, err error) + this.implements("io", "StringWriter", "WriteString") + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isParameter(0) and output.isReceiver() } } private class WriterWrite extends TaintTracking::FunctionModel, Method { - WriterWrite() { this.implements("io", "Writer", "Write") } + WriterWrite() { + // Write(p []byte) (n int, err error) + this.implements("io", "Writer", "Write") + } override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { input.isParameter(0) and output.isReceiver() } } - private class WriteString extends TaintTracking::FunctionModel { - WriteString() { this.hasQualifiedName("io", "WriteString") } + private class MultiWriter extends TaintTracking::FunctionModel, Function { + MultiWriter() { + // func MultiWriter(writers ...Writer) Writer + hasQualifiedName("io", "MultiWriter") + } override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { - input.isParameter(1) and output.isParameter(0) + input.isResult() and output.isParameter(_) + } + } + + private class WriterAtWriteAt extends TaintTracking::FunctionModel, Method { + WriterAtWriteAt() { + // WriteAt(p []byte, off int64) (n int, err error) + this.implements("io", "WriterAt", "WriteAt") + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isParameter(0) and output.isReceiver() + } + } + + private class WriterToWriteTo extends TaintTracking::FunctionModel, Method { + WriterToWriteTo() { + // WriteTo(w Writer) (n int64, err error) + this.implements("io", "WriterTo", "WriteTo") + } + + override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) { + input.isReceiver() and output.isParameter(0) } } } diff --git a/ql/test/library-tests/semmle/go/frameworks/TaintSteps/TaintStep.expected b/ql/test/library-tests/semmle/go/frameworks/TaintSteps/TaintStep.expected index d912d745d..eeb8e85e2 100644 --- a/ql/test/library-tests/semmle/go/frameworks/TaintSteps/TaintStep.expected +++ b/ql/test/library-tests/semmle/go/frameworks/TaintSteps/TaintStep.expected @@ -5,6 +5,54 @@ | crypto.go:11:18:11:57 | call to Open | crypto.go:11:2:11:57 | ... := ...[0] | | crypto.go:11:18:11:57 | call to Open | crypto.go:11:2:11:57 | ... := ...[1] | | crypto.go:11:42:11:51 | ciphertext | crypto.go:11:2:11:57 | ... := ...[0] | +| io.go:15:3:15:3 | definition of w | io.go:15:23:15:27 | &... | +| io.go:15:3:15:3 | definition of w | io.go:15:30:15:34 | &... | +| io.go:15:23:15:27 | &... | io.go:14:7:14:10 | definition of buf1 | +| io.go:15:24:15:27 | buf1 | io.go:15:23:15:27 | &... | +| io.go:15:30:15:34 | &... | io.go:14:13:14:16 | definition of buf2 | +| io.go:15:31:15:34 | buf2 | io.go:15:30:15:34 | &... | +| io.go:17:14:17:19 | reader | io.go:15:3:15:3 | definition of w | +| io.go:24:19:24:23 | &... | io.go:22:7:22:10 | definition of buf1 | +| io.go:24:20:24:23 | buf1 | io.go:24:19:24:23 | &... | +| io.go:26:21:26:26 | reader | io.go:24:3:24:4 | definition of w2 | +| io.go:32:19:32:23 | &... | io.go:31:7:31:10 | definition of buf1 | +| io.go:32:20:32:23 | buf1 | io.go:32:19:32:23 | &... | +| io.go:34:16:34:21 | reader | io.go:32:3:32:4 | definition of w2 | +| io.go:38:3:38:3 | definition of r | io.go:38:3:38:19 | ... := ...[1] | +| io.go:38:11:38:19 | call to Pipe | io.go:38:3:38:19 | ... := ...[0] | +| io.go:38:11:38:19 | call to Pipe | io.go:38:3:38:19 | ... := ...[1] | +| io.go:39:17:39:31 | "some string\\n" | io.go:38:6:38:6 | definition of w | +| io.go:42:16:42:16 | r | io.go:41:3:41:5 | definition of buf | +| io.go:43:13:43:15 | buf | io.go:43:13:43:24 | call to String | +| io.go:49:18:49:23 | reader | io.go:48:3:48:5 | definition of buf | +| io.go:55:15:55:20 | reader | io.go:54:3:54:5 | definition of buf | +| io.go:60:18:60:21 | &... | io.go:59:7:59:9 | definition of buf | +| io.go:60:19:60:21 | buf | io.go:60:18:60:21 | &... | +| io.go:61:21:61:26 | "test" | io.go:60:3:60:3 | definition of w | +| io.go:66:11:66:16 | reader | io.go:66:3:66:27 | ... := ...[0] | +| io.go:66:11:66:27 | call to ReadByte | io.go:66:3:66:27 | ... := ...[0] | +| io.go:66:11:66:27 | call to ReadByte | io.go:66:3:66:27 | ... := ...[1] | +| io.go:68:21:68:21 | t | io.go:67:7:67:13 | definition of bwriter | +| io.go:74:3:74:8 | reader | io.go:73:3:73:5 | definition of buf | +| io.go:79:3:79:8 | reader | io.go:78:3:78:5 | definition of buf | +| io.go:84:24:84:29 | reader | io.go:84:9:84:33 | call to LimitReader | +| io.go:85:22:85:23 | lr | io.go:85:11:85:19 | selection of Stdout | +| io.go:92:23:92:24 | r1 | io.go:92:8:92:33 | call to MultiReader | +| io.go:92:27:92:28 | r2 | io.go:92:8:92:33 | call to MultiReader | +| io.go:92:31:92:32 | r3 | io.go:92:8:92:33 | call to MultiReader | +| io.go:93:22:93:22 | r | io.go:93:11:93:19 | selection of Stdout | +| io.go:98:23:98:23 | r | io.go:98:10:98:30 | call to TeeReader | +| io.go:98:23:98:23 | r | io.go:98:26:98:29 | &... | +| io.go:98:26:98:29 | &... | io.go:97:7:97:9 | definition of buf | +| io.go:98:27:98:29 | buf | io.go:98:26:98:29 | &... | +| io.go:100:22:100:24 | tee | io.go:100:11:100:19 | selection of Stdout | +| io.go:104:28:104:28 | r | io.go:104:8:104:36 | call to NewSectionReader | +| io.go:105:22:105:22 | s | io.go:105:11:105:19 | selection of Stdout | +| io.go:109:16:109:16 | r | io.go:109:3:109:27 | ... := ...[0] | +| io.go:109:16:109:27 | call to ReadRune | io.go:109:3:109:27 | ... := ...[0] | +| io.go:109:16:109:27 | call to ReadRune | io.go:109:3:109:27 | ... := ...[1] | +| io.go:109:16:109:27 | call to ReadRune | io.go:109:3:109:27 | ... := ...[2] | +| io.go:114:3:114:3 | r | io.go:114:13:114:21 | selection of Stdout | | main.go:11:12:11:26 | call to Marshal | main.go:11:2:11:26 | ... := ...[0] | | main.go:11:12:11:26 | call to Marshal | main.go:11:2:11:26 | ... := ...[1] | | main.go:11:25:11:25 | v | main.go:11:2:11:26 | ... := ...[0] | diff --git a/ql/test/library-tests/semmle/go/frameworks/TaintSteps/io.go b/ql/test/library-tests/semmle/go/frameworks/TaintSteps/io.go new file mode 100644 index 000000000..82aa4d377 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/TaintSteps/io.go @@ -0,0 +1,117 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "os" + "strings" +) + +func io2() { + { + reader := strings.NewReader("some string") + var buf1, buf2 bytes.Buffer + w := io.MultiWriter(&buf1, &buf2) + + io.Copy(w, reader) + } + + { + reader := strings.NewReader("some string") + var buf1 bytes.Buffer + buf := make([]byte, 512) + w2 := io.Writer(&buf1) + + io.CopyBuffer(w2, reader, buf) + } + + { + reader := strings.NewReader("some string") + var buf1 bytes.Buffer + w2 := io.Writer(&buf1) + + io.CopyN(w2, reader, 512) + } + + { + r, w := io.Pipe() + fmt.Fprint(w, "some string\n") + + buf := new(bytes.Buffer) + buf.ReadFrom(r) + fmt.Print(buf.String()) + } + + { + reader := strings.NewReader("some string") + buf := make([]byte, 512) + io.ReadAtLeast(reader, buf, 512) + } + + { + reader := strings.NewReader("some string") + buf := make([]byte, 512) + io.ReadFull(reader, buf) + } + + { + var buf bytes.Buffer + w := io.Writer(&buf) + io.WriteString(w, "test") + } + + { + reader := strings.NewReader("some string") + t, _ := reader.ReadByte() + var bwriter io.ByteWriter + bwriter.WriteByte(t) + } + + { + reader := strings.NewReader("some string") + buf := make([]byte, 512) + reader.Read(buf) + } + { + reader := strings.NewReader("some string") + buf := make([]byte, 512) + reader.ReadAt(buf, 10) + } + + { + reader := strings.NewReader("some string") + lr := io.LimitReader(reader, 4) + io.Copy(os.Stdout, lr) + } + + { + r1 := strings.NewReader("reader1 ") + r2 := strings.NewReader("reader2 ") + r3 := strings.NewReader("reader3") + r := io.MultiReader(r1, r2, r3) + io.Copy(os.Stdout, r) + } + { + r := strings.NewReader("some string") + var buf bytes.Buffer + tee := io.TeeReader(r, &buf) + + io.Copy(os.Stdout, tee) + } + { + r := strings.NewReader("some string") + s := io.NewSectionReader(r, 5, 17) + io.Copy(os.Stdout, s) + } + { + r := strings.NewReader("some string") + run, _, _ := r.ReadRune() + fmt.Println(run) + } + { + r := strings.NewReader("some string") + r.WriteTo(os.Stdout) + } + +}