Merge branch 'master' into rc/1.24

This commit is contained in:
Max Schaefer
2020-06-11 08:10:22 +01:00
288 changed files with 16145 additions and 3558 deletions

View File

@@ -0,0 +1,11 @@
{
"extensions": [
"github.vscode-codeql",
"slevesque.vscode-zipexplorer"
],
"settings": {
"codeQL.experimentalBqrsParsing": true,
"codeQL.experimentalFeatures": true,
"codeQL.runningQueries.debug": true
}
}

View File

@@ -20,7 +20,7 @@ jobs:
echo "Done"
cd $HOME
echo "Downloading CodeQL CLI..."
curl https://github.com/github/codeql-cli-binaries/releases/download/v2.0.3/codeql.zip -L -o codeql.zip
curl https://github.com/github/codeql-cli-binaries/releases/download/v2.1.1/codeql.zip -L -o codeql.zip
echo "Done"
echo "Unpacking CodeQL CLI..."
unzip -q codeql.zip
@@ -53,7 +53,7 @@ jobs:
echo "Done"
cd $HOME
echo "Downloading CodeQL CLI..."
curl https://github.com/github/codeql-cli-binaries/releases/download/v2.0.3/codeql.zip -L -o codeql.zip
curl https://github.com/github/codeql-cli-binaries/releases/download/v2.1.1/codeql.zip -L -o codeql.zip
echo "Done"
echo "Unpacking CodeQL CLI..."
unzip -q codeql.zip
@@ -86,10 +86,10 @@ jobs:
echo "Done"
cd "$HOME"
echo "Downloading CodeQL CLI..."
Invoke-WebRequest -Uri https://github.com/github/codeql-cli-binaries/releases/download/v2.0.3/codeql.zip -OutFile codeql.zip
Invoke-WebRequest -Uri https://github.com/github/codeql-cli-binaries/releases/download/v2.1.1/codeql.zip -OutFile codeql.zip
echo "Done"
echo "Unpacking CodeQL CLI..."
unzip -q codeql.zip
Expand-Archive codeql.zip -DestinationPath $HOME
rm -fo codeql.zip
echo "Done"

1
.gitignore vendored
View File

@@ -22,3 +22,4 @@ tools/linux64
tools/osx64
tools/win64
tools/tokenizer.jar
main

View File

@@ -18,7 +18,7 @@ CODEQL_TOOLS = $(addprefix codeql-tools/,autobuild.cmd autobuild.sh index.cmd in
EXTRACTOR_PACK_OUT = build/codeql-extractor-go
BINARIES = go-extractor go-tokenizer go-autobuilder go-bootstrap
BINARIES = go-extractor go-tokenizer go-autobuilder go-bootstrap go-gen-dbscheme
.PHONY: tools tools-codeql tools-codeql-full clean \
tools-linux64 tools-osx64 tools-win64
@@ -81,8 +81,8 @@ tools/net/sourceforge/pmd/cpd/GoLanguage.class: extractor/net/sourceforge/pmd/cp
rm tools/net/sourceforge/pmd/cpd/TokenEntry.class
rm tools/net/sourceforge/pmd/cpd/Tokenizer.class
ql/src/go.dbscheme: tools/$(CODEQL_PLATFORM)/go-extractor$(EXE)
env TRAP_FOLDER=/tmp $< --dbscheme $@
ql/src/go.dbscheme: tools/$(CODEQL_PLATFORM)/go-gen-dbscheme$(EXE)
$< $@
build/stats/src.stamp:
mkdir -p $(@D)/src
@@ -109,3 +109,10 @@ build/testdb/go.dbscheme: upgrades/initial/go.dbscheme
rm -rf build/testdb
echo >build/empty.trap
codeql dataset import -S upgrades/initial/go.dbscheme build/testdb build/empty.trap
.PHONY: sync-dataflow-libraries
sync-dataflow-libraries:
for f in DataFlowImpl.qll DataFlowImplCommon.qll tainttracking1/TaintTrackingImpl.qll;\
do\
curl -s -o ./ql/src/semmle/go/dataflow/internal/$$f https://raw.githubusercontent.com/github/codeql/master/java/ql/src/semmle/code/java/dataflow/internal/$$f;\
done

View File

@@ -12,6 +12,9 @@ It contains two major components:
The goal of this project is to provide comprehensive static analysis support for Go in CodeQL.
For the queries and libraries that power CodeQL support for other languages, visit [the CodeQL
repository](https://github.com/github/codeql).
## Installation
Simply clone this repository. There are no external dependencies.
@@ -21,9 +24,10 @@ Code workspace.
## Usage
To analyze a Go codebase, either use the CodeQL command-line interface to create a database
yourself, or download a pre-built database from LGTM.com. You can then run any of the queries
contained in this repository either on the command line or using the VS Code extension.
To analyze a Go codebase, either use the [CodeQL command-line
interface](https://help.semmle.com/codeql/codeql-cli.html) to create a database yourself, or
download a pre-built database from [LGTM.com](https://lgtm.com/). You can then run any of the
queries contained in this repository either on the command line or using the VS Code extension.
Note that the [lgtm.com](https://github.com/github/codeql-go/tree/lgtm.com) branch of this
repository corresponds to the version of the queries that is currently deployed on LGTM.com.

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* The query "Bad redirect check" (`go/bad-redirect-check`) now requires that the checked variable is actually used in a redirect as opposed to relying on a name-based heuristic. This eliminates some false positive results, and adds more true positive results.

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* Basic support for the [Macaron](https://go-macaron.com/) HTTP library has been added, which may lead to more results from the security queries.

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* The query "Clear-text logging of sensitive information" has been improved to recognize more logging APIs, which may lead to more alerts.

View File

@@ -0,0 +1,3 @@
lgtm,codescanning
* Basic support for the [Mux](https://github.com/gorilla/mux/) HTTP library has been added, which
may lead to more results from the security queries.

View File

@@ -0,0 +1,3 @@
lgtm,codescanning
* The data-flow library has been improved, which affects and improves most security queries. In particular,
flow through functions involving nested field reads and writes is now modeled more fully.

View File

@@ -0,0 +1,3 @@
lgtm,codescanning
* The query "Reflected cross-site scripting" has been improved to recognize more cases where the
value should be considered to be safe, which should lead to fewer false positive results.

View File

@@ -0,0 +1,4 @@
lgtm,codescanning
* The queries "Uncontrolled data used in path expression" and "Arbitrary file write during zip
extraction ("zip slip")" have been improved to recognize more file APIs, which may lead to more
alerts.

View File

@@ -0,0 +1,3 @@
lgtm,codescanning
* Modeling of the standard `io` library has been improved, which may lead to more results from the
security queries.

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* A new query "Redundant call to recover" (`go/redundant-recover`) has been added. The query detects calls to `recover` that have no effect.

View File

@@ -0,0 +1,3 @@
lgtm,codescanning
* Modeling of the `go.mongodb.org/mongo-driver/mongo` package has been added, which may lead to more
results from the security queries.

View File

@@ -0,0 +1,3 @@
lgtm,codescanning
* Modeling of several WebSocket libraries has been added, which may lead to more results from the
security queries.

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* The query "Open URL redirect" (`go/unvalidated-url-redirection`) now recognizes values returned by method `http.Request.FormValue` as possibly user controlled, allowing it to flag more true positive results.

View File

@@ -8,8 +8,6 @@ import (
"runtime/pprof"
"strings"
"github.com/github/codeql-go/extractor/dbscheme"
"github.com/github/codeql-go/extractor"
)
@@ -20,12 +18,10 @@ func usage() {
fmt.Fprintf(os.Stderr, "Usage:\n\n %s [<flag>...] [<buildflag>...] [--] <file>...\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Flags:\n\n")
fmt.Fprintf(os.Stderr, "--help Print this help.\n")
fmt.Fprintf(os.Stderr, "--dbscheme string Write dbscheme to this file.\n")
}
func parseFlags(args []string) ([]string, []string, string) {
func parseFlags(args []string) ([]string, []string) {
i := 0
var dumpDbscheme string
buildFlags := []string{}
for i < len(args) && strings.HasPrefix(args[i], "-") {
if args[i] == "--" {
@@ -33,12 +29,7 @@ func parseFlags(args []string) ([]string, []string, string) {
break
}
if strings.HasPrefix(args[i], "--dbscheme=") {
dumpDbscheme = strings.TrimPrefix(args[i], "--dbscheme=")
} else if args[i] == "--dbscheme" {
i++
dumpDbscheme = args[i]
} else if args[i] == "--help" {
if args[i] == "--help" {
usage()
os.Exit(0)
} else {
@@ -51,21 +42,11 @@ func parseFlags(args []string) ([]string, []string, string) {
cpuprofile = os.Getenv("CODEQL_EXTRACTOR_GO_CPU_PROFILE")
memprofile = os.Getenv("CODEQL_EXTRACTOR_GO_MEM_PROFILE")
return buildFlags, args[i:], dumpDbscheme
return buildFlags, args[i:]
}
func main() {
buildFlags, patterns, dumpDbscheme := parseFlags(os.Args[1:])
if dumpDbscheme != "" {
f, err := os.Create(dumpDbscheme)
if err != nil {
log.Fatalf("Unable to open file %s for writing.", dumpDbscheme)
}
dbscheme.PrintDbScheme(f)
f.Close()
log.Printf("Dbscheme written to file %s.", dumpDbscheme)
}
buildFlags, patterns := parseFlags(os.Args[1:])
if cpuprofile != "" {
f, err := os.Create(cpuprofile)

View File

@@ -0,0 +1,31 @@
package main
import (
"fmt"
"os"
"github.com/github/codeql-go/extractor/dbscheme"
)
func usage() {
fmt.Fprintf(os.Stderr, "%s is a program for generating the dbscheme for CodeQL Go databases.\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Usage:\n\n %s <output file>\n\n", os.Args[0])
}
func main() {
if len(os.Args) != 2 {
usage()
os.Exit(2)
}
out := os.Args[1]
f, err := os.Create(out)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to open file %s for writing.", out)
os.Exit(1)
}
dbscheme.PrintDbScheme(f)
f.Close()
fmt.Printf("Dbscheme written to file %s.", out)
}

View File

@@ -4,6 +4,7 @@ import (
"go/ast"
"go/token"
gotypes "go/types"
"golang.org/x/tools/go/packages"
)
var defaultSnippet = AddDefaultSnippet(`
@@ -642,6 +643,20 @@ var ModLParenType = ModExprKind.NewBranch("@modlparen")
// ModRParenType is the type of go.mod line block end AST nodes
var ModRParenType = ModExprKind.NewBranch("@modrparen")
// ErrorType is the type of frontend errors
var ErrorType = NewPrimaryKeyType("@error")
// ErrorKind is a case type for distinguishing different kinds of frontend errors
var ErrorKind = NewCaseType(ErrorType, "kind")
// ErrorTypes is a map from error kinds to the corresponding type
var ErrorTypes = map[packages.ErrorKind]*BranchType{
packages.UnknownError: ErrorKind.NewBranch("@unknownerror"),
packages.ListError: ErrorKind.NewBranch("@listerror"),
packages.ParseError: ErrorKind.NewBranch("@parseerror"),
packages.TypeError: ErrorKind.NewBranch("@typeerror"),
}
// LocationsDefaultTable is the table defining location objects
var LocationsDefaultTable = NewTable("locations_default",
EntityColumn(LocationDefaultType, "id").Key(),
@@ -915,3 +930,16 @@ var ModTokensTable = NewTable("modtokens",
EntityColumn(ModExprType, "parent"),
IntColumn("idx"),
).KeySet("parent", "idx")
// ErrorsTable is the table describing frontend errors
var ErrorsTable = NewTable("errors",
EntityColumn(ErrorType, "id").Key(),
IntColumn("kind"),
StringColumn("msg"),
StringColumn("rawpos"),
StringColumn("file"),
IntColumn("line"),
IntColumn("col"),
EntityColumn(PackageType, "package"),
IntColumn("idx"),
).KeySet("package", "idx")

View File

@@ -11,6 +11,7 @@ import (
"log"
"os"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
@@ -57,13 +58,6 @@ func ExtractWithFlags(buildFlags []string, patterns []string) error {
packages.Visit(pkgs, func(pkg *packages.Package) bool {
return true
}, func(pkg *packages.Package) {
if len(pkg.Errors) != 0 {
log.Printf("Warning: encountered errors extracting package `%s`:", pkg.PkgPath)
for _, err := range pkg.Errors {
log.Printf(" %s", err.Error())
}
}
tw, err := trap.NewWriter(pkg.PkgPath, pkg)
if err != nil {
log.Fatal(err)
@@ -74,6 +68,14 @@ func ExtractWithFlags(buildFlags []string, patterns []string) error {
tw.ForEachObject(extractObjectType)
lbl := tw.Labeler.GlobalID(pkg.PkgPath + ";pkg")
dbscheme.PackagesTable.Emit(tw, lbl, pkg.Name, pkg.PkgPath, scope)
if len(pkg.Errors) != 0 {
log.Printf("Warning: encountered errors extracting package `%s`:", pkg.PkgPath)
for i, err := range pkg.Errors {
log.Printf(" %s", err.Error())
extractError(tw, err, lbl, i)
}
}
})
// this sets the number of threads that the Go runtime will spawn; this is separate
@@ -253,6 +255,50 @@ func extractObjectType(tw *trap.Writer, obj types.Object, lbl trap.Label) {
}
}
var (
// file:line:col
threePartPos = regexp.MustCompile(`^(.+):(\d+):(\d+)$`)
// file:line
twoPartPos = regexp.MustCompile(`^(.+):(\d+)$`)
)
// extractError extracts the message and location of a frontend error
func extractError(tw *trap.Writer, err packages.Error, pkglbl trap.Label, idx int) {
var (
lbl = tw.Labeler.FreshID()
kind = dbscheme.ErrorTypes[err.Kind].Index()
pos = err.Pos
file = ""
line = 0
col = 0
e error
)
if parts := threePartPos.FindStringSubmatch(pos); parts != nil {
// "file:line:col"
col, e = strconv.Atoi(parts[3])
if e != nil {
log.Printf("Warning: malformed column number `%s`: %v", parts[3], e)
}
line, e = strconv.Atoi(parts[2])
if e != nil {
log.Printf("Warning: malformed line number `%s`: %v", parts[2], e)
}
file = parts[1]
} else if parts := twoPartPos.FindStringSubmatch(pos); parts != nil {
// "file:line"
line, e = strconv.Atoi(parts[2])
if e != nil {
log.Printf("Warning: malformed line number `%s`: %v", parts[2], e)
}
file = parts[1]
} else if pos != "" && pos != "-" {
log.Printf("Warning: malformed error position `%s`", pos)
}
file = filepath.ToSlash(srcarchive.TransformPath(file))
dbscheme.ErrorsTable.Emit(tw, lbl, kind, err.Msg, pos, file, line, col, pkglbl, idx)
}
// extractPackage extracts AST information for all files in the given package
func extractPackage(pkg *packages.Package, wg *sync.WaitGroup,
goroutineSem *semaphore, fdSem *semaphore) {

View File

@@ -0,0 +1,54 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
The built-in <code>recover</code> function is only useful inside deferred
functions. Calling it in a function that is never deferred means that it will
always return <code>nil</code> and it will never regain control of a panicking
goroutine. The same is true of calling <code>recover</code> directly in a defer
statement.
</p>
</overview>
<recommendation>
<p>
Carefully inspect the code to determine whether it is a mistake that should be
fixed.
</p>
</recommendation>
<example>
<p>
In the example below, the function <code>fun1</code> is intended to recover
from the panic. However, the function that is deferred calls another function,
which then calls <code>recover</code>:
</p>
<sample src="RedundantRecover1.go" />
<p>
This problem can be fixed by deferring the call to the function which calls
<code>recover</code>:
</p>
<sample src="RedundantRecover1Good.go" />
<p>
In the following example, <code>recover</code> is called directly in a defer
statement, which has no effect, so the panic is not caught.
</p>
<sample src="RedundantRecover2.go" />
<p>
We can fix this by instead deferring an anonymous function which calls
<code>recover</code>.
</p>
<sample src="RedundantRecover2Good.go" />
</example>
<references>
<li>
<a href="https://blog.golang.org/defer-panic-and-recover">Defer, Panic, and Recover - The Go Blog</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,33 @@
/**
* @name Redundant call to recover
* @description Calling 'recover' in a function which isn't called using a defer
* statement has no effect. Also, putting 'recover' directly in a
* defer statement has no effect.
* @kind problem
* @problem.severity warning
* @id go/redundant-recover
* @tags maintainability
* correctness
* @precision high
*/
import go
predicate isDeferred(DataFlow::CallNode call) {
exists(DeferStmt defer | defer.getCall() = call.asExpr())
}
from DataFlow::CallNode recoverCall, FuncDef f, string msg
where
recoverCall.getTarget() = Builtin::recover() and
f = recoverCall.getEnclosingCallable() and
(
isDeferred(recoverCall) and
msg = "Deferred calls to 'recover' have no effect."
or
not isDeferred(recoverCall) and
exists(f.getACall()) and
not isDeferred(f.getACall()) and
msg = "This call to 'recover' has no effect because $@ is never called using a defer statement."
)
select recoverCall, msg, f, "the enclosing function"

View File

@@ -0,0 +1,16 @@
package main
import "fmt"
func callRecover1() {
if recover() != nil {
fmt.Printf("recovered")
}
}
func fun1() {
defer func() {
callRecover1()
}()
panic("1")
}

View File

@@ -0,0 +1,14 @@
package main
import "fmt"
func callRecover1Good() {
if recover() != nil {
fmt.Printf("recovered")
}
}
func fun1Good() {
defer callRecover1Good()
panic("1")
}

View File

@@ -0,0 +1,6 @@
package main
func fun2() {
defer recover()
panic("2")
}

View File

@@ -0,0 +1,6 @@
package main
func fun2Good() {
defer func() { recover() }()
panic("2")
}

View File

@@ -3,8 +3,8 @@
* @description A redirect check that checks for a leading slash but not two
* leading slashes or a leading slash followed by a backslash is
* incomplete.
* @kind problem
* @problem.severity warning
* @kind path-problem
* @problem.severity error
* @id go/bad-redirect-check
* @tags security
* external/cwe/cwe-601
@@ -12,6 +12,8 @@
*/
import go
import semmle.go.security.OpenUrlRedirectCustomizations
import DataFlow::PathGraph
StringOps::HasPrefix checkForLeadingSlash(SsaWithFields v) {
exists(DataFlow::Node substr |
@@ -21,43 +23,154 @@ StringOps::HasPrefix checkForLeadingSlash(SsaWithFields v) {
)
}
DataFlow::Node checkForSecondSlash(SsaWithFields v) {
exists(StringOps::HasPrefix hp | result = hp and hp.getBaseString() = v.getAUse() |
predicate isCheckedForSecondSlash(SsaWithFields v) {
exists(StringOps::HasPrefix hp | hp.getBaseString() = v.getAUse() |
hp.getSubstring().getStringValue() = "//"
)
or
exists(DataFlow::EqualityTestNode eq, DataFlow::Node slash, DataFlow::ElementReadNode er |
result = eq
|
slash.getStringValue() = "/" and
er.getBase() = v.getAUse() and
er.getIndex().getIntValue() = 1 and
eq.eq(_, er, slash)
)
or
// a call to path.Clean will strip away multiple leading slashes
isCleaned(v.getAUse())
}
DataFlow::Node checkForSecondBackslash(SsaWithFields v) {
exists(StringOps::HasPrefix hp | result = hp and hp.getBaseString() = v.getAUse() |
/**
* Holds if `nd` is the result of a call to `path.Clean`, or flows into the first argument
* of such a call, possibly inter-procedurally.
*/
predicate isCleaned(DataFlow::Node nd) {
exists(Function clean | clean.hasQualifiedName("path", "Clean") |
nd = clean.getACall()
or
nd = clean.getACall().getArgument(0)
)
or
isCleaned(nd.getAPredecessor())
or
exists(FuncDef f, FunctionInput inp | nd = inp.getExitNode(f) |
forex(DataFlow::CallNode call | call.getACallee() = f | isCleaned(inp.getEntryNode(call)))
)
}
predicate isCheckedForSecondBackslash(SsaWithFields v) {
exists(StringOps::HasPrefix hp | hp.getBaseString() = v.getAUse() |
hp.getSubstring().getStringValue() = "/\\"
)
or
exists(DataFlow::EqualityTestNode eq, DataFlow::Node slash, DataFlow::ElementReadNode er |
result = eq
|
slash.getStringValue() = "\\" and
er.getBase() = v.getAUse() and
er.getIndex().getIntValue() = 1 and
eq.eq(_, er, slash)
)
or
// if this variable comes from or is a net/url.URL.Path, backslashes are most likely sanitized,
// as the parse functions turn them into "%5C"
urlPath(v.getAUse())
}
from DataFlow::Node node, SsaWithFields v
/**
* Holds if `nd` derives its value from the field `url.URL.Path`, possibly inter-procedurally.
*/
predicate urlPath(DataFlow::Node nd) {
exists(Field f |
f.hasQualifiedName("net/url", "URL", "Path") and
nd = f.getARead()
)
or
urlPath(nd.getAPredecessor())
or
exists(FuncDef f, FunctionInput inp | nd = inp.getExitNode(f) |
forex(DataFlow::CallNode call | call.getACallee() = f | urlPath(inp.getEntryNode(call)))
)
}
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "BadRedirectCheck" }
override predicate isSource(DataFlow::Node source) { this.isSource(source, _) }
/**
* Holds if `source` is the first node that flows into a use of a variable that is checked by a
* bad redirect check `check`..
*/
predicate isSource(DataFlow::Node source, DataFlow::Node check) {
exists(SsaWithFields v |
DataFlow::localFlow(source, v.getAUse()) and
not exists(source.getAPredecessor()) and
isBadRedirectCheckOrWrapper(check, v)
)
}
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
// this is very over-approximate, because most filtering is done by the isSource predicate
exists(Write w | w.writesField(succ, _, pred))
}
override predicate isSanitizerOut(DataFlow::Node node) {
// assume this value is safe if something is prepended to it.
exists(StringOps::Concatenation conc, int i, int j | i < j |
node = conc.getOperand(j) and
exists(conc.getOperand(i))
)
or
exists(DataFlow::CallNode call, int i | call.getTarget().hasQualifiedName("path", "Join") |
i > 0 and node = call.getArgument(i)
)
}
override predicate isSink(DataFlow::Node sink) { sink instanceof OpenUrlRedirect::Sink }
}
/**
* Holds there is a check `check` that is a bad redirect check, and `v` is either
* checked directly by `check` or checked by a function that contains `check`.
*/
predicate isBadRedirectCheckOrWrapper(DataFlow::Node check, SsaWithFields v) {
isBadRedirectCheck(check, v)
or
exists(DataFlow::CallNode call, FuncDef f, FunctionInput input |
call = f.getACall() and
input.getEntryNode(call) = v.getAUse() and
isBadRedirectCheckWrapper(check, f, input)
)
}
/**
* Holds if `check` checks that `v` has a leading slash, but not whether it has another slash or a
* backslash in its second position.
*/
predicate isBadRedirectCheck(DataFlow::Node check, SsaWithFields v) {
// a check for a leading slash
check = checkForLeadingSlash(v) and
// where there does not exist a check for both a second slash and a second backslash
// (we allow those checks to be on variables that are most likely equivalent to `v`
// to rule out false positives due to minor variations in data flow)
not (
isCheckedForSecondSlash(v.similar()) and
isCheckedForSecondBackslash(v.similar())
)
}
/**
* Holds if `f` contains a bad redirect check `check`, that checks the parameter `input`.
*/
predicate isBadRedirectCheckWrapper(DataFlow::Node check, FuncDef f, FunctionInput input) {
exists(SsaWithFields v |
v.getAUse().getAPredecessor*() = input.getExitNode(f) and
isBadRedirectCheck(check, v)
)
}
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node check
where
// there is a check for a leading slash
node = checkForLeadingSlash(v) and
// but not a check for both a second slash and a second backslash
not (exists(checkForSecondSlash(v)) and exists(checkForSecondBackslash(v))) and
v.getQualifiedName().regexpMatch("(?i).*url.*|.*redir.*|.*target.*")
select node,
"This expression checks '$@' for a leading slash but checks do not exist for both '/' and '\\' in the second position.",
v, v.getQualifiedName()
cfg.isSource(source.getNode(), check) and
cfg.hasFlowPath(source, sink)
select check, source, sink,
"This is a check that $@, which flows into a $@, has a leading slash, but not that it does not have '/' or '\\' in its second position.",
source.getNode(), "this value", sink.getNode(), "redirect"

View File

@@ -2,3 +2,8 @@
- qlpack: codeql-go
- apply: lgtm-selectors.yml
from: codeql-suite-helpers
# These are only for IDE use.
- exclude:
tags contain:
- ide-contextual-queries/local-definitions
- ide-contextual-queries/local-references

View File

@@ -0,0 +1,4 @@
- description: Security-and-quality queries for Go
- qlpack: codeql-go
- apply: security-and-quality-selectors.yml
from: codeql-suite-helpers

View File

@@ -0,0 +1,4 @@
- description: Security-extended queries for Go
- qlpack: codeql-go
- apply: security-extended-selectors.yml
from: codeql-suite-helpers

View File

@@ -0,0 +1,13 @@
package main
import (
"net/http"
"net/smtp"
)
func mail(w http.ResponseWriter, r *http.Request) {
host := r.Header.Get("Host")
token := backend.getUserSecretResetToken(email)
body := "Click to reset password: " + host + "/" + token
smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(body))
}

View File

@@ -0,0 +1,13 @@
package main
import (
"net/http"
"net/smtp"
)
func mailGood(w http.ResponseWriter, r *http.Request) {
host := config.Get("Host")
token := backend.getUserSecretResetToken(email)
body := "Click to reset password: " + host + "/" + token
smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(body))
}

View File

@@ -0,0 +1,46 @@
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
<qhelp>
<overview>
<p>
Using untrusted input to construct an email can cause multiple security
vulnerabilities. For instance, inclusion of an untrusted input in an email body
may allow an attacker to conduct Cross Site Scripting (XSS) attacks, while
inclusion of an HTTP header may allow a full account compromise as shown in the
example below.
</p>
</overview>
<recommendation>
<p>
Any data which is passed to an email subject or body must be sanitized before use.
</p>
</recommendation>
<example>
<p>
In the following example snippet, the <code>host</code> field is user controlled.
</p>
<p>
A malicious user can send an HTTP request to the targeted web site,
but with a Host header that refers to their own web site. This means the
emails will be sent out to potential victims, originating from a server
they trust, but with links leading to a malicious web site.
</p>
<p>
If the email contains a password reset link, and should the victim click
the link, the secret reset token will be leaked to the attacker. Using the
leaked token, the attacker can then construct the real reset link and use it to
change the victim's password.
</p>
<sample src="EmailBad.go" />
<p>
One way to prevent this is to load the host name from a trusted configuration file instead.
</p>
<sample src="EmailGood.go" />
</example>
<references>
<li>
OWASP
<a href="https://owasp.org/www-community/attacks/Content_Spoofing">Content Spoofing</a>
.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,19 @@
/**
* @name Email content injection
* @description Incorporating untrusted input directly into an email message can enable
* content spoofing, which in turn may lead to information leaks and other
* security issues.
* @id go/email-injection
* @kind path-problem
* @problem.severity error
* @tags security
* external/cwe/cwe-640
*/
import go
import DataFlow::PathGraph
import EmailInjection::EmailInjection
from DataFlow::PathNode source, DataFlow::PathNode sink, Configuration config
where config.hasFlowPath(source, sink)
select sink, source, sink, "Email content may contain $@.", source.getNode(), "untrusted input"

View File

@@ -0,0 +1,29 @@
/**
* Provides a taint-tracking configuration for reasoning about
* server-side email-injection vulnerabilities.
*
* Note, for performance reasons: only import this file if
* `EmailInjection::Configuration` is needed, otherwise
* `EmailInjectionCustomizations` should be imported instead.
*/
import go
/**
* Provides a taint-tracking configuration for reasoning about
* email-injection vulnerabilities.
*/
module EmailInjection {
import EmailInjectionCustomizations::EmailInjection
/**
* A taint-tracking configuration for reasoning about email-injection vulnerabilities.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "Email Injection" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
}
}

View File

@@ -0,0 +1,30 @@
/** Provides classes for reasoning about email-injection vulnerabilities. */
import go
/**
* Provides a library for reasoning about email-injection vulnerabilities.
*/
module EmailInjection {
/**
* A data-flow node that should be considered a source of untrusted data for email-injection vulnerabilities.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data-flow node that should be considered a sink for email-injection vulnerabilities.
*/
abstract class Sink extends DataFlow::Node { }
/** A source of untrusted data, considered as a taint source for email injection. */
class UntrustedFlowSourceAsSource extends Source {
UntrustedFlowSourceAsSource() { this instanceof UntrustedFlowSource }
}
/**
* A data-flow node that becomes part of an email considered as a taint sink for email injection.
*/
class MailDataAsSink extends Sink {
MailDataAsSink() { this instanceof EmailData }
}
}

View File

@@ -0,0 +1,20 @@
package main
import (
"strconv"
)
func parseAllocateBad1(wanted string) int32 {
parsed, err := strconv.Atoi(wanted)
if err != nil {
panic(err)
}
return int32(parsed)
}
func parseAllocateBad2(wanted string) int32 {
parsed, err := strconv.ParseInt(wanted, 10, 64)
if err != nil {
panic(err)
}
return int32(parsed)
}

View File

@@ -0,0 +1,65 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
If a numeric value string is parsed using <code>strconv.Atoi</code> into an int, and subsequently that int
is converted into another type of a smaller size, the result can produce unexpected values.
</p>
<p>
This also applies to the results of <code>strconv.ParseFloat</code>, <code>strconv.ParseInt</code>,
and <code>strconv.ParseUint</code> when the specified size is larger than the size of the
type that number is converted to.
</p>
</overview>
<recommendation>
<p>
If you need to parse numeric values with specific bit sizes, avoid <code>strconv.Atoi</code>, and instead
use the functions specific to each type (<code>strconv.ParseFloat</code>, <code>strconv.ParseInt</code>,
<code>strconv.ParseUint</code>) that also allow to specify the wanted bit size.
</p>
<p>
When using those functions, be careful to not convert the result to another type with a smaller bit size than
the bit size you specified when parsing the number.
</p>
<p>
If this is not possible, then add upper (and lower) bound checks specific to each type and
bit size (you can find the minimum and maximum value for each type in the `math` package).
</p>
</recommendation>
<example>
<p>
In the first example, assume that an input string is passed to <code>parseAllocateBad1</code> function,
parsed by <code>strconv.Atoi</code>, and then converted into an <code>int32</code> type:
</p>
<sample src="IncorrectNumericConversion.go"/>
<p>
The bounds are not checked, so this means that if the provided number is greater than the maximum value of type <code>int32</code>,
the resulting value from the conversion will be different from the actual provided value.
</p>
<p>
To avoid unexpected values, you should either use the other functions provided by the <code>strconv</code>
package to parse the specific types and bit sizes as shown in the
<code>parseAllocateGood2</code> function; or check bounds as in the <code>parseAllocateGood1</code>
function.
</p>
<sample src="IncorrectNumericConversionGood.go"/>
</example>
<example>
<p>
In the second example, assume that an input string is passed to <code>parseAllocateBad2</code> function,
parsed by <code>strconv.ParseInt</code> with a bit size set to 64, and then converted into an <code>int32</code> type:
</p>
<sample src="IncorrectNumericConversion.go"/>
<p>
If the provided number is greater than the maximum value of type <code>int32</code>, the resulting value from the conversion will be
different from the actual provided value.
</p>
<p>
To avoid unexpected values, you should specify the correct bit size as in <code>parseAllocateGood3</code>;
or check bounds before making the conversion as in <code>parseAllocateGood4</code>.
</p>
<sample src="IncorrectNumericConversionGood.go"/>
</example>
</qhelp>

View File

@@ -0,0 +1,266 @@
/**
* @name Incorrect conversion between numeric types
* @description Converting the result of strconv.Atoi (and other parsers from strconv package)
* to numeric types of smaller bit size can produce unexpected values.
* @kind path-problem
* @problem.severity warning
* @id go/incorrect-numeric-conversion
* @tags security
* external/cwe/cwe-190
* external/cwe/cwe-681
*/
import go
import DataFlow::PathGraph
/** A function that parses integers. */
class Atoi extends Function {
Atoi() { this.hasQualifiedName("strconv", "Atoi") }
}
/** A function that parses floating-point numbers. */
class ParseFloat extends Function {
ParseFloat() { this.hasQualifiedName("strconv", "ParseFloat") }
}
/** A function that parses integers with a specifiable bitSize. */
class ParseInt extends Function {
ParseInt() { this.hasQualifiedName("strconv", "ParseInt") }
}
/** A function that parses unsigned integers with a specifiable bitSize. */
class ParseUint extends Function {
ParseUint() { this.hasQualifiedName("strconv", "ParseUint") }
}
/** Provides a class for modeling calls to number-parsing functions. */
module ParserCall {
/**
* A data-flow call node that parses a number.
*/
abstract class Range extends DataFlow::CallNode {
/** Gets the bit size of the result number. */
abstract int getTargetBitSize();
/** Gets the name of the parser function. */
abstract string getParserName();
}
}
class ParserCall extends DataFlow::CallNode {
ParserCall::Range self;
ParserCall() { this = self }
int getTargetBitSize() { result = self.getTargetBitSize() }
string getParserName() { result = self.getParserName() }
}
class AtoiCall extends DataFlow::CallNode, ParserCall::Range {
AtoiCall() { exists(Atoi atoi | this = atoi.getACall()) }
override int getTargetBitSize() { result = 0 }
override string getParserName() { result = "strconv.Atoi" }
}
class ParseIntCall extends DataFlow::CallNode, ParserCall::Range {
ParseIntCall() { exists(ParseInt parseInt | this = parseInt.getACall()) }
override int getTargetBitSize() { result = this.getArgument(2).getIntValue() }
override string getParserName() { result = "strconv.ParseInt" }
}
class ParseUintCall extends DataFlow::CallNode, ParserCall::Range {
ParseUintCall() { exists(ParseUint parseUint | this = parseUint.getACall()) }
override int getTargetBitSize() { result = this.getArgument(2).getIntValue() }
override string getParserName() { result = "strconv.ParseUint" }
}
class ParseFloatCall extends DataFlow::CallNode, ParserCall::Range {
ParseFloatCall() { exists(ParseFloat parseFloat | this = parseFloat.getACall()) }
override int getTargetBitSize() { result = this.getArgument(1).getIntValue() }
override string getParserName() { result = "strconv.ParseFloat" }
}
class NumericConversionExpr extends ConversionExpr {
string fullTypeName;
int bitSize;
NumericConversionExpr() {
exists(NumericType conv |
conv = getTypeExpr().getType().getUnderlyingType() and
fullTypeName = conv.getName() and
bitSize = conv.getSize()
)
}
string getFullTypeName() { result = fullTypeName }
int getBitSize() { result = bitSize }
}
/**
* An `if` statement with the condition being either a relational comparison,
* or one or more `&&`.
*/
class IfRelationalComparison extends IfStmt {
IfRelationalComparison() {
this.getCond() instanceof RelationalComparisonExpr or this.getCond() instanceof LandExpr
}
RelationalComparisonExpr getComparison() { result = this.getCond().(RelationalComparisonExpr) }
LandExpr getLandExpr() { result = this.getCond().(LandExpr) }
}
/**
* Flow of result of parsing a 64 bit number, to conversion to lower bit numbers.
*/
class Lt64BitFlowConfig extends TaintTracking::Configuration, DataFlow::Configuration {
Lt64BitFlowConfig() { this = "Lt64BitFlowConfig" }
override predicate isSource(DataFlow::Node source) {
exists(ParserCall call | call.getTargetBitSize() = [0, 64] | source = call)
}
override predicate isSink(DataFlow::Node sink) {
exists(NumericConversionExpr conv | conv.getBitSize() = [32, 16, 8] | sink.asExpr() = conv)
}
override predicate isSanitizerIn(DataFlow::Node node) { isSanitizedInsideAnIfBoundCheck(node) }
}
/**
* Flow of result of parsing a 32 bit number, to conversion to lower bit numbers.
*/
class Lt32BitFlowConfig extends TaintTracking::Configuration, DataFlow::Configuration {
Lt32BitFlowConfig() { this = "Lt32BitFlowConfig" }
override predicate isSource(DataFlow::Node source) {
// NOTE: target bit size 0 is already addressed in Lt64BitFlowConfig.
exists(ParserCall call | call.getTargetBitSize() = [/*0,*/ 32] | source = call)
}
override predicate isSink(DataFlow::Node sink) {
exists(NumericConversionExpr conv | conv.getBitSize() = [16, 8] | sink.asExpr() = conv)
}
override predicate isSanitizerIn(DataFlow::Node node) { isSanitizedInsideAnIfBoundCheck(node) }
}
/**
* Flow of result of parsing a 16 bit number, to conversion to lower bit numbers.
*/
class Lt16BitFlowConfig extends TaintTracking::Configuration, DataFlow::Configuration {
Lt16BitFlowConfig() { this = "Lt16BitFlowConfig" }
override predicate isSource(DataFlow::Node source) {
exists(ParserCall call | call.getTargetBitSize() = 16 | source = call)
}
override predicate isSink(DataFlow::Node sink) {
exists(NumericConversionExpr conv | conv.getBitSize() = 8 | sink.asExpr() = conv)
}
override predicate isSanitizerIn(DataFlow::Node node) { isSanitizedInsideAnIfBoundCheck(node) }
}
/**
* Check if the node is a numeric conversion inside an `if` body, where
* the `if` condition contains an upper bound check on the conversion operand.
*/
predicate isSanitizedInsideAnIfBoundCheck(DataFlow::Node node) {
exists(IfRelationalComparison comp, NumericConversionExpr conv |
conv = node.asExpr().(NumericConversionExpr) and
conv.getBitSize() = [8, 16, 32] and
comp.getThen().getAChild*() = conv and
(
// If the conversion is inside an `if` block that compares the source as
// `source > 0` or `source >= 0`, then that sanitizes conversion of int to int32;
conv.getFullTypeName() = "int32" and
comp.getComparison().getLesserOperand().getNumericValue() = 0 and
comp.getComparison().getGreaterOperand().getGlobalValueNumber() =
conv.getOperand().getGlobalValueNumber()
or
comparisonGreaterOperandValueIsEqual("int8", comp, conv, getMaxInt8())
or
comparisonGreaterOperandValueIsEqual("int16", comp, conv, getMaxInt16())
or
comparisonGreaterOperandValueIsEqual("int32", comp, conv, getMaxInt32())
or
comparisonGreaterOperandValueIsEqual("uint8", comp, conv, getMaxUint8())
or
comparisonGreaterOperandValueIsEqual("uint16", comp, conv, getMaxUint16())
)
)
}
int getMaxInt8() { result = 2.pow(7) - 1 }
int getMaxInt16() { result = 2.pow(15) - 1 }
int getMaxInt32() { result = 2.pow(31) - 1 }
int getMaxUint8() { result = 2.pow(8) - 1 }
int getMaxUint16() { result = 2.pow(16) - 1 }
/**
* The `if` relational comparison (which can also be inside a `LandExpr`) stating that
* the greater operand is equal to `value`, and the lesses operand is the conversion operand.
*/
predicate comparisonGreaterOperandValueIsEqual(
string typeName, IfRelationalComparison ifExpr, NumericConversionExpr conv, int value
) {
conv.getFullTypeName() = typeName and
(
// exclude cases like: if parsed < math.MaxInt8 {return int8(parsed)}
exists(RelationalComparisonExpr comp | comp = ifExpr.getComparison() |
// greater operand is equal to value:
comp.getGreaterOperand().getNumericValue() = value and
// and lesser is the conversion operand:
comp.getLesserOperand().getGlobalValueNumber() = conv.getOperand().getGlobalValueNumber()
)
or
// exclude cases like: if err == nil && parsed < math.MaxInt8 {return int8(parsed)}
exists(RelationalComparisonExpr andExpr |
andExpr = ifExpr.getLandExpr().getAnOperand().(RelationalComparisonExpr)
|
// greater operand is equal to value:
andExpr.getGreaterOperand().getNumericValue() = value and
// and lesser is the conversion operand:
andExpr.getLesserOperand().getGlobalValueNumber() = conv.getOperand().getGlobalValueNumber()
)
)
}
string formatBitSize(ParserCall call) {
call.getTargetBitSize() = 0 and result = "(arch-dependent)"
or
call.getTargetBitSize() > 0 and result = call.getTargetBitSize().toString()
}
from DataFlow::PathNode source, DataFlow::PathNode sink
where
(
exists(Lt64BitFlowConfig cfg | cfg.hasFlowPath(source, sink))
or
exists(Lt32BitFlowConfig cfg | cfg.hasFlowPath(source, sink))
or
exists(Lt16BitFlowConfig cfg | cfg.hasFlowPath(source, sink))
) and
// Exclude results in test files:
exists(File fl | fl = sink.getNode().asExpr().(NumericConversionExpr).getFile() |
not fl instanceof TestFile
)
select source.getNode(), source, sink,
"Incorrect conversion of a " + formatBitSize(source.getNode().(ParserCall)) + "-bit number from " +
source.getNode().(ParserCall).getParserName() + " result to a lower bit size type " +
sink.getNode().asExpr().(NumericConversionExpr).getFullTypeName()

View File

@@ -0,0 +1,51 @@
package main
import (
"math"
"strconv"
)
func main() {
}
const DefaultAllocate int32 = 256
func parseAllocateGood1(desired string) int32 {
parsed, err := strconv.Atoi(desired)
if err != nil {
return DefaultAllocate
}
// GOOD: check for lower and uppper bounds
if parsed > 0 && parsed <= math.MaxInt32 {
return int32(parsed)
}
return DefaultAllocate
}
func parseAllocateGood2(desired string) int32 {
// GOOD: parse specifying the bit size
parsed, err := strconv.ParseInt(desired, 10, 32)
if err != nil {
return DefaultAllocate
}
return int32(parsed)
}
func parseAllocateGood3(wanted string) int32 {
parsed, err := strconv.ParseInt(wanted, 10, 32)
if err != nil {
panic(err)
}
return int32(parsed)
}
func parseAllocateGood4(wanted string) int32 {
parsed, err := strconv.ParseInt(wanted, 10, 64)
if err != nil {
panic(err)
}
// GOOD: check for lower and uppper bounds
if parsed > 0 && parsed <= math.MaxInt32 {
return int32(parsed)
}
return DefaultAllocate
}

View File

@@ -0,0 +1,31 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Testing untrusted user input against a fixed constant results in
a bypass of the conditional check as the attacker may alter the input to match the constant.
When an incorrect check of this type is used to guard a potentially sensitive block,
it results an attacker gaining access to the sensitive block.
</p>
</overview>
<recommendation>
<p>
Never decide whether to authenticate a user based on data that may be controlled by that user.
If necessary, ensure that the data is validated extensively when it is input before any
authentication checks are performed.
</p>
<p>
It is still possible to have a system that "remembers" users, thus not requiring
the user to login on every interaction. For example, personalization settings can be applied
without authentication because this is not sensitive information. However, users
should be allowed to take sensitive actions only when they have been fully authenticated.
</p>
</recommendation>
<example>
<p>
The following example shows a comparison where an user controlled
expression is used to guard a sensitive method. This should be avoided.:
</p>
<sample src="SensitiveConditionBypassBad.go" />
</example>
</qhelp>

View File

@@ -0,0 +1,31 @@
/**
* @name User-controlled bypassing of sensitive action
* @description This query tests for user-controlled bypassing
* of sensitive actions.
* @id go/sensitive-condition-bypass
* @kind problem
* @problem.severity warning
* @tags external/cwe/cwe-807
* external/cwe/cwe-247
* external/cwe/cwe-350
*/
import go
import SensitiveConditionBypass
from
ControlFlow::ConditionGuardNode guard, DataFlow::Node sensitiveSink,
SensitiveExpr::Classification classification, Configuration config, DataFlow::PathNode source,
DataFlow::PathNode operand, ComparisonExpr comp
where
// there should be a flow between source and the operand sink
config.hasFlowPath(source, operand) and
// both the operand should belong to the same comparision expression
operand.getNode().asExpr() = comp.getAnOperand() and
// get the ConditionGuardNode corresponding to the comparision expr.
guard.getCondition() = comp and
// the sink `sensitiveSink` should be sensitive,
isSensitive(sensitiveSink, classification) and
// the guard should control the sink
guard.dominates(sensitiveSink.getBasicBlock())
select comp, "This sensitive comparision check can potentially be bypassed."

View File

@@ -0,0 +1,68 @@
import go
import semmle.go.security.SensitiveActions
/**
* Holds if `sink` is used in a context that suggests it may hold sensitive data of
* the given `type`.
*/
predicate isSensitive(DataFlow::Node sink, SensitiveExpr::Classification type) {
exists(Write write, string name |
write.getRhs() = sink and
name = write.getLhs().getName() and
// whitelist obvious test password variables
not name.regexpMatch(HeuristicNames::notSensitive())
|
name.regexpMatch(HeuristicNames::maybeSensitive(type))
)
or
exists(SensitiveCall a | sink.asExpr() = a and a.getClassification() = type)
or
exists(SensitiveExpr a | sink.asExpr() = a and a.getClassification() = type)
or
exists(SensitiveAction a | a = sink and type = SensitiveExpr::secret())
}
private class ConstComparisonExpr extends ComparisonExpr {
string constString;
ConstComparisonExpr() {
exists(DataFlow::Node n |
n.getASuccessor*() = DataFlow::exprNode(this.getAnOperand()) and
constString = n.getStringValue()
)
}
predicate isPotentialFalsePositive() {
// if its an empty string
constString.length() = 0 or
// // if it is uri path
constString.matches("/%") or
constString.matches("%/") or
constString.matches("%/%")
}
}
/**
* A data-flow configuration for reasoning about
* user-controlled bypassing of sensitive actions.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "Condtional Expression Check Bypass" }
override predicate isSource(DataFlow::Node source) {
source instanceof UntrustedFlowSource
or
exists(DataFlow::FieldReadNode f |
f.getField().hasQualifiedName("net/http", "Request", "Host")
|
source = f
)
}
override predicate isSink(DataFlow::Node sink) {
exists(ConstComparisonExpr c |
c.getAnOperand() = sink.asExpr() and
not c.isPotentialFalsePositive()
)
}
}

View File

@@ -0,0 +1,10 @@
package main
import "net/http"
func example(w http.ResponseWriter, r *http.Request) {
test2 := "test"
if r.Header.Get("X-Password") != test2 {
login()
}
}

View File

@@ -0,0 +1,26 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Conditional checks that compare two values that are both controlled by an untrusted user against
each other are easy to bypass and should not be used in security-critical contexts.
</p>
</overview>
<recommendation>
<p>
To guard against bypass, it is advisable to avoid framing a comparison where both sides are
untrusted user inputs. Instead, use a configuration to store and access the values required.
</p>
</recommendation>
<example>
<p>
The following example shows a comparison where both the sides are from attacker-controlled request
headers. This should be avoided:
</p>
<sample src="ConditionalBypassBad.go" />
<p>
One way to remedy the problem is to test against a value stored in a configuration:
</p>
<sample src="ConditionalBypassGood.go" />
</example>
</qhelp>

View File

@@ -0,0 +1,40 @@
/**
* @name User-controlled bypass of condition
* @description A check that compares two user-controlled inputs with each other can be bypassed
* by a malicious user.
* @id go/user-controlled-bypass
* @kind problem
* @problem.severity warning
* @tags external/cwe/cwe-840
*/
import go
/**
* A taint-tracking configuration for reasoning about conditional bypass.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "ConditionalBypass" }
override predicate isSource(DataFlow::Node source) {
source instanceof UntrustedFlowSource
or
source = any(Field f | f.hasQualifiedName("net/http", "Request", "Host")).getARead()
}
override predicate isSink(DataFlow::Node sink) {
exists(ComparisonExpr c | c.getAnOperand() = sink.asExpr())
}
}
from
Configuration config, DataFlow::PathNode lhsSource, DataFlow::PathNode lhs,
DataFlow::PathNode rhsSource, DataFlow::PathNode rhs, ComparisonExpr c
where
config.hasFlowPath(rhsSource, rhs) and
rhs.getNode().asExpr() = c.getRightOperand() and
config.hasFlowPath(lhsSource, lhs) and
lhs.getNode().asExpr() = c.getLeftOperand()
select c,
"This comparison compares user-controlled values from $@ and $@, and hence can be bypassed.",
lhsSource, "here", rhsSource, "here"

View File

@@ -0,0 +1,12 @@
package main
import (
"net/http"
)
func exampleHandlerBad(w http.ResponseWriter, r *http.Request) {
// BAD: the Origin and Host headers are user controlled
if r.Header.Get("Origin") != "http://"+r.Host {
//do something
}
}

View File

@@ -0,0 +1,12 @@
package main
import (
"net/http"
)
func exampleHandlerGood(w http.ResponseWriter, r *http.Request) {
// GOOD: the configuration is not user controlled
if r.Header.Get("Origin") != config.get("Host") {
//do something
}
}

View File

@@ -0,0 +1,169 @@
/**
* Provides classes for working with untrusted flow sources from the `github.com/gin-gonic/gin` package.
*/
import go
private module Gin {
/**
* Data from a `Context` struct, considered as a source of untrusted flow.
*/
private class GithubComGinGonicGinContextSource extends UntrustedFlowSource::Range {
GithubComGinGonicGinContextSource() {
exists(string packagePath, string typeName |
packagePath = "github.com/gin-gonic/gin" and
typeName = "Context"
|
// Method calls:
exists(DataFlow::MethodCallNode call, string methodName |
call.getTarget().hasQualifiedName(packagePath, typeName, methodName) and
(
methodName = "FullPath"
or
methodName = "GetHeader"
or
methodName = "QueryArray"
or
methodName = "Query"
or
methodName = "PostFormArray"
or
methodName = "PostForm"
or
methodName = "Param"
or
methodName = "GetStringSlice"
or
methodName = "GetString"
or
methodName = "GetRawData"
or
methodName = "ClientIP"
or
methodName = "ContentType"
or
methodName = "Cookie"
or
methodName = "GetQueryArray"
or
methodName = "GetQuery"
or
methodName = "GetPostFormArray"
or
methodName = "GetPostForm"
or
methodName = "DefaultPostForm"
or
methodName = "DefaultQuery"
or
methodName = "GetPostFormMap"
or
methodName = "GetQueryMap"
or
methodName = "GetStringMap"
or
methodName = "GetStringMapString"
or
methodName = "GetStringMapStringSlice"
or
methodName = "PostFormMap"
or
methodName = "QueryMap"
)
|
this = call.getResult(0)
)
or
// Field reads:
exists(DataFlow::Field fld |
fld.hasQualifiedName(packagePath, typeName, "Accepted") and
this = fld.getARead()
)
)
}
}
/**
* Data from a `Params` slice, considered as a source of untrusted flow.
*/
private class GithubComGinGonicGinParamsSource extends UntrustedFlowSource::Range {
GithubComGinGonicGinParamsSource() {
exists(string packagePath, string typeName |
packagePath = "github.com/gin-gonic/gin" and
typeName = "Params"
|
// Any read of a variable of this type:
exists(DataFlow::ReadNode read | read.getType().hasQualifiedName(packagePath, typeName) |
this = read
)
or
// Method calls:
exists(DataFlow::MethodCallNode call |
call.getTarget().hasQualifiedName(packagePath, typeName, ["ByName", "Get"])
|
this = call.getResult(0)
)
)
}
}
/**
* Data from a `Param` struct, considered as a source of untrusted flow.
*/
private class GithubComGinGonicGinParamSource extends UntrustedFlowSource::Range {
GithubComGinGonicGinParamSource() {
exists(string packagePath, string typeName |
packagePath = "github.com/gin-gonic/gin" and
typeName = "Param"
|
// Any read of a variable of this type:
exists(DataFlow::ReadNode read | read.getType().hasQualifiedName(packagePath, typeName) |
this = read
)
or
// Field reads:
exists(DataFlow::Field fld | fld.hasQualifiedName(packagePath, typeName, ["Key", "Value"]) |
this = fld.getARead()
)
)
}
}
/**
* A call to a method on `Context` struct that unmarshals data into a target.
*/
private class GithubComGinGonicGinContextBindSource extends UntrustedFlowSource::Range {
GithubComGinGonicGinContextBindSource() {
exists(string packagePath, string typeName |
packagePath = "github.com/gin-gonic/gin" and
typeName = "Context"
|
exists(DataFlow::MethodCallNode call, string methodName |
call.getTarget().hasQualifiedName(packagePath, typeName, methodName) and
(
methodName = "BindJSON" or
methodName = "BindYAML" or
methodName = "BindXML" or
methodName = "BindUri" or
methodName = "BindQuery" or
methodName = "BindWith" or
methodName = "BindHeader" or
methodName = "MustBindWith" or
methodName = "Bind" or
methodName = "ShouldBind" or
methodName = "ShouldBindBodyWith" or
methodName = "ShouldBindJSON" or
methodName = "ShouldBindQuery" or
methodName = "ShouldBindUri" or
methodName = "ShouldBindHeader" or
methodName = "ShouldBindWith" or
methodName = "ShouldBindXML" or
methodName = "ShouldBindYAML"
)
|
this = call.getArgument(0)
)
)
}
}
}

View File

@@ -124,6 +124,10 @@ modexprs(unique int id: @modexpr, int kind: int ref, int parent: @modexprparent
#keyset[parent, idx]
modtokens(string token: string ref, int parent: @modexpr ref, int idx: int ref);
#keyset[package, idx]
errors(unique int id: @error, int kind: int ref, string msg: string ref, string rawpos: string ref,
string file: string ref, int line: int ref, int col: int ref, int package: @package ref, int idx: int ref);
@container = @file | @folder;
@locatable = @node | @localscope;
@@ -418,3 +422,9 @@ case @modexpr.kind of
| 3 = @modlparen
| 4 = @modrparen;
case @error.kind of
0 = @unknownerror
| 1 = @listerror
| 2 = @parseerror
| 3 = @typeerror;

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@ import semmle.go.AST
import semmle.go.Comments
import semmle.go.Concepts
import semmle.go.Decls
import semmle.go.Errors
import semmle.go.Expr
import semmle.go.Files
import semmle.go.GoMod
@@ -16,18 +17,23 @@ import semmle.go.Scopes
import semmle.go.Stmt
import semmle.go.StringOps
import semmle.go.Types
import semmle.go.Util
import semmle.go.controlflow.BasicBlocks
import semmle.go.controlflow.ControlFlowGraph
import semmle.go.controlflow.IR
import semmle.go.dataflow.DataFlow
import semmle.go.dataflow.GlobalValueNumbering
import semmle.go.dataflow.TaintTracking
import semmle.go.dataflow.SSA
import semmle.go.dataflow.TaintTracking
import semmle.go.frameworks.Email
import semmle.go.frameworks.HTTP
import semmle.go.frameworks.SystemCommandExecutors
import semmle.go.frameworks.Macaron
import semmle.go.frameworks.Mux
import semmle.go.frameworks.NoSQL
import semmle.go.frameworks.SQL
import semmle.go.frameworks.XPath
import semmle.go.frameworks.Stdlib
import semmle.go.frameworks.SystemCommandExecutors
import semmle.go.frameworks.Testing
import semmle.go.frameworks.WebSocket
import semmle.go.frameworks.XPath
import semmle.go.security.FlowSources
import semmle.go.Util

View File

@@ -0,0 +1,19 @@
/**
* @name Jump-to-definition links
* @description Generates use-definition pairs that provide the data
* for jump-to-definition in the code viewer.
* @kind definitions
* @id go/ide-jump-to-definition
* @tags ide-contextual-queries/local-definitions
*/
import go
external string selectedSourceFile();
cached
File getEncodedFile(string name) { result.getAbsolutePath().replaceAll(":", "_") = name }
from Ident def, Ident use, Entity e
where use.uses(e) and def.declares(e) and use.getFile() = getEncodedFile(selectedSourceFile())
select use, def, "V"

19
ql/src/localReferences.ql Normal file
View File

@@ -0,0 +1,19 @@
/**
* @name Find-references links
* @description Generates use-definition pairs that provide the data
* for find-references in the code viewer.
* @kind definitions
* @id go/ide-find-references
* @tags ide-contextual-queries/local-references
*/
import go
external string selectedSourceFile();
cached
File getEncodedFile(string name) { result.getAbsolutePath().replaceAll(":", "_") = name }
from Ident def, Ident use, Entity e
where use.uses(e) and def.declares(e) and def.getFile() = getEncodedFile(selectedSourceFile())
select use, def, "V"

View File

@@ -6,6 +6,14 @@ import go
/**
* A code comment.
*
* Examples:
*
* <pre>
* // a line comment
* /* a block
* comment *&#47
* </pre>
*/
class Comment extends @comment, AstNode {
/**
@@ -24,6 +32,21 @@ class Comment extends @comment, AstNode {
/**
* A comment group, that is, a sequence of comments without any intervening tokens or
* empty lines.
*
* Examples:
*
* <pre>
* // a line comment
* // another line comment
*
* // a line comment
* /* a block
* comment *&#47
*
* /* a block
* comment *&#47
* /* another block comment *&#47
* </pre>
*/
class CommentGroup extends @comment_group, AstNode {
/** Gets the `i`th comment in this group (0-based indexing). */
@@ -39,7 +62,23 @@ class CommentGroup extends @comment_group, AstNode {
}
/**
* A program element to which a documentation comment group may be attached.
* A program element to which a documentation comment group may be attached:
* a file, a field, a specifier, a generic declaration, a function declaration
* or a go.mod expression.
*
* Examples:
*
* ```go
* // function documentation
* func double(x int) int { return 2 * x }
*
* // generic declaration documentation
* const (
* // specifier documentation
* size int64 = 1024
* eof = -1 // not specifier documentation
* )
* ```
*/
class Documentable extends AstNode, @documentable {
/** Gets the documentation comment group attached to this element, if any. */
@@ -48,6 +87,20 @@ class Documentable extends AstNode, @documentable {
/**
* A comment group that is attached to a program element as documentation.
*
* Examples:
*
* ```go
* // function documentation
* func double(x int) int { return 2 * x }
*
* // generic declaration documentation
* const (
* // specifier documentation
* size int64 = 1024
* eof = -1 // not specifier documentation
* )
* ```
*/
class DocComment extends CommentGroup {
Documentable node;
@@ -60,21 +113,47 @@ class DocComment extends CommentGroup {
/**
* A single-line comment starting with `//`.
*
* Examples:
*
* ```go
* // Single line comment
* ```
*/
class SlashSlashComment extends @slashslashcomment, Comment { }
/**
* A block comment starting with `/*` and ending with <code>*&#47;</code>.
*
* Examples:
*
* <pre>
* /* a block
* comment *&#47
* </pre>
*/
class SlashStarComment extends @slashstarcomment, Comment { }
/**
* A single-line comment starting with `//`.
*
* Examples:
*
* ```go
* // Single line comment
* ```
*/
class LineComment = SlashSlashComment;
/**
* A block comment starting with `/*` and ending with <code>*&#47;</code>.
*
* Examples:
*
* <pre>
* /* a block
* comment *&#47
* </pre>
*/
class BlockComment = SlashStarComment;
@@ -96,6 +175,13 @@ private Comment getInitialComment(File f, int i) {
/**
* A build constraint comment of the form `// +build ...`.
*
* Examples:
*
* ```go
* // +build darwin freebsd netbsd openbsd
* // +build !linux
* ```
*/
class BuildConstraintComment extends LineComment {
BuildConstraintComment() {

View File

@@ -11,7 +11,7 @@ import semmle.go.dataflow.FunctionInputsAndOutputs
* A data-flow node that executes an operating system command,
* for instance by spawning a new process.
*
* Extends this class to refine existing API models. If you want to model new APIs,
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `SystemCommandExecution::Range` instead.
*/
class SystemCommandExecution extends DataFlow::Node {
@@ -366,8 +366,8 @@ module HTTP {
* extend `HTTP::ResponseWriter` instead.
*/
abstract class Range extends Variable {
/** Gets a data-flow node that represents this response writer. */
DataFlow::Node getANode() { result = this.getARead().getASuccessor*() }
/** Gets a data-flow node that is a use of this response writer. */
abstract DataFlow::Node getANode();
}
}
@@ -391,7 +391,7 @@ module HTTP {
/** Gets a redirect that is sent in this HTTP response. */
Redirect getARedirect() { result.getResponseWriter() = this }
/** Gets a data-flow node that represents this response writer. */
/** Gets a data-flow node that is a use of this response writer. */
DataFlow::Node getANode() { result = self.getANode() }
}

View File

@@ -0,0 +1,53 @@
/** Provides classes for working with Go frontend errors recorded during extraction. */
import go
/**
* An error reported by the Go frontend during extraction.
*/
class Error extends @error {
/** Gets the message associated with this error. */
string getMessage() { errors(this, _, result, _, _, _, _, _, _) }
/** Gets the raw position reported by the frontend for this error. */
string getRawPosition() { errors(this, _, _, result, _, _, _, _, _) }
/** Gets the package in which this error was reported. */
Package getPackage() { errors(this, _, _, _, _, _, _, result, _) }
/** Gets the index of this error among all errors reported for the same package. */
int getIndex() { errors(this, _, _, _, _, _, _, _, result) }
/** Gets the file in which this error was reported, if it can be determined. */
File getFile() { hasLocationInfo(result.getAbsolutePath(), _, _, _, _) }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [LGTM locations](https://lgtm.com/help/ql/locations).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
errors(this, _, _, _, filepath, startline, startcolumn, _, _) and
endline = startline and
endcolumn = startcolumn
}
/** Gets a textual representation of this error. */
string toString() { result = getMessage() }
}
/** An error reported by an unknown part of the Go frontend. */
class UnknownError extends Error, @unknownerror { }
/** An error reported by the Go frontend driver. */
class ListError extends Error, @listerror { }
/** An error reported by the Go parser. */
class ParseError extends Error, @parseerror { }
/** An error reported by the Go type checker. */
class TypeError extends Error, @typeerror { }

File diff suppressed because it is too large Load Diff

View File

@@ -24,3 +24,15 @@ class Package extends @package {
/** Gets a textual representation of this element. */
string toString() { result = "package " + getPath() }
}
/**
* Gets an import path that identifies a package in module `mod` with the given path,
* possibly modulo [semantic import versioning](https://github.com/golang/go/wiki/Modules#semantic-import-versioning).
*
* For example, `package("github.com/go-pg/pg", "types")` gets an import path that can
* refer to `"github.com/go-pg/pg/types"`, but also to `"github.com/go-pg/pg/v10/types"`.
*/
bindingset[result, mod, path]
string package(string mod, string path) {
result.regexpMatch("\\Q" + mod + "\\E([/.]v[^/]+)?($|/)\\Q" + path + "\\E")
}

View File

@@ -406,6 +406,9 @@ class Method extends Function {
result = this.getReceiverType().getPackage()
}
/** Holds if this method is declared in an interface. */
predicate isInterfaceMethod() { getReceiverType().getUnderlyingType() instanceof InterfaceType }
/** Gets the receiver variable of this method. */
Variable getReceiver() { result = receiver }
@@ -464,8 +467,14 @@ class Method extends Function {
* Holds if this method implements the method `m`, that is, if `m` is a method
* on an interface, and this is a method with the same name on a type that
* implements that interface.
*
* Note that all methods implement themselves, and interface methods _only_
* implement themselves.
*/
predicate implements(Method m) {
this = m
or
not isInterfaceMethod() and
exists(Type t |
this = t.getMethod(m.getName()) and
t.implements(m.getReceiverType().getUnderlyingType())

View File

@@ -6,6 +6,18 @@ import go
/**
* A statement.
*
* Examples:
*
* ```go
* a = 0
*
* if x := f(); x < y {
* return y - x
* } else {
* return x - y
* }
* ```
*/
class Stmt extends @stmt, ExprParent, StmtParent {
/**
@@ -30,6 +42,13 @@ class Stmt extends @stmt, ExprParent, StmtParent {
/**
* A bad statement, that is, a statement that could not be parsed.
*
* Examples:
*
* ```go
* go fmt.Println
* defer int
* ```
*/
class BadStmt extends @badstmt, Stmt {
override string toString() { result = "bad statement" }
@@ -37,6 +56,14 @@ class BadStmt extends @badstmt, Stmt {
/**
* A declaration statement.
*
* Examples:
*
* ```go
* var i int
* const pi = 3.14159
* type Printer interface{ Print() }
* ```
*/
class DeclStmt extends @declstmt, Stmt, DeclParent {
/** Gets the declaration in this statement. */
@@ -49,6 +76,12 @@ class DeclStmt extends @declstmt, Stmt, DeclParent {
/**
* An empty statement.
*
* Examples:
*
* ```go
* ;
* ```
*/
class EmptyStmt extends @emptystmt, Stmt {
override string toString() { result = "empty statement" }
@@ -56,6 +89,12 @@ class EmptyStmt extends @emptystmt, Stmt {
/**
* A labeled statement.
*
* Examples:
*
* ```go
* Error: log.Panic("error encountered")
* ```
*/
class LabeledStmt extends @labeledstmt, Stmt {
/** Gets the identifier representing the label. */
@@ -74,6 +113,15 @@ class LabeledStmt extends @labeledstmt, Stmt {
/**
* An expression statement.
*
* Examples:
*
* ```go
* h(x+y)
* f.Close()
* <-ch
* (<-ch)
* ```
*/
class ExprStmt extends @exprstmt, Stmt {
/** Gets the expression. */
@@ -86,6 +134,12 @@ class ExprStmt extends @exprstmt, Stmt {
/**
* A send statement.
*
* Examples:
*
* ```go
* ch <- 3
* ```
*/
class SendStmt extends @sendstmt, Stmt {
/** Gets the expression representing the channel. */
@@ -101,6 +155,13 @@ class SendStmt extends @sendstmt, Stmt {
/**
* An increment or decrement statement.
*
* Examples:
*
* ```go
* a++
* b--
* ```
*/
class IncDecStmt extends @incdecstmt, Stmt {
/** Gets the expression being incremented or decremented. */
@@ -114,6 +175,12 @@ class IncDecStmt extends @incdecstmt, Stmt {
/**
* An increment statement.
*
* Examples:
*
* ```go
* a++
* ```
*/
class IncStmt extends @incstmt, IncDecStmt {
override string getOperator() { result = "++" }
@@ -123,6 +190,12 @@ class IncStmt extends @incstmt, IncDecStmt {
/**
* A decrement statement.
*
* Examples:
*
* ```go
* b--
* ```
*/
class DecStmt extends @decstmt, IncDecStmt {
override string getOperator() { result = "--" }
@@ -132,6 +205,16 @@ class DecStmt extends @decstmt, IncDecStmt {
/**
* A (simple or compound) assignment statement.
*
* Examples:
*
* ```go
* x := 1
* *p = f()
* a[i] = 23
* (k) = <-ch // same as: k = <-ch
* a += 2
* ```
*/
class Assignment extends @assignment, Stmt {
/** Gets the `i`th left-hand side of this assignment (0-based). */
@@ -177,11 +260,28 @@ class Assignment extends @assignment, Stmt {
/**
* A simple assignment statement, that is, an assignment without a compound operator.
*
* Examples:
*
* ```go
* x := 1
* *p = f()
* a[i] = 23
* (k) = <-ch // same as: k = <-ch
* ```
*/
class SimpleAssignStmt extends @simpleassignstmt, Assignment { }
/**
* A plain assignment statement.
*
* Examples:
*
* ```go
* *p = f()
* a[i] = 23
* (k) = <-ch // same as: k = <-ch
* ```
*/
class AssignStmt extends @assignstmt, SimpleAssignStmt {
override string getOperator() { result = "=" }
@@ -189,6 +289,12 @@ class AssignStmt extends @assignstmt, SimpleAssignStmt {
/**
* A define statement.
*
* Examples:
*
* ```go
* x := 1
* ```
*/
class DefineStmt extends @definestmt, SimpleAssignStmt {
override string getOperator() { result = ":=" }
@@ -196,11 +302,24 @@ class DefineStmt extends @definestmt, SimpleAssignStmt {
/**
* A compound assignment statement.
*
* Examples:
*
* ```go
* a += 2
* a /= 2
* ```
*/
class CompoundAssignStmt extends @compoundassignstmt, Assignment { }
/**
* An add-assign statement using `+=`.
*
* Examples:
*
* ```go
* a += 2
* ```
*/
class AddAssignStmt extends @addassignstmt, CompoundAssignStmt {
override string getOperator() { result = "+=" }
@@ -208,6 +327,12 @@ class AddAssignStmt extends @addassignstmt, CompoundAssignStmt {
/**
* A subtract-assign statement using `-=`.
*
* Examples:
*
* ```go
* a -= 2
* ```
*/
class SubAssignStmt extends @subassignstmt, CompoundAssignStmt {
override string getOperator() { result = "-=" }
@@ -215,6 +340,12 @@ class SubAssignStmt extends @subassignstmt, CompoundAssignStmt {
/**
* A multiply-assign statement using `*=`.
*
* Examples:
*
* ```go
* a *= 2
* ```
*/
class MulAssignStmt extends @mulassignstmt, CompoundAssignStmt {
override string getOperator() { result = "*=" }
@@ -222,6 +353,12 @@ class MulAssignStmt extends @mulassignstmt, CompoundAssignStmt {
/**
* A divide-assign statement using `/=`.
*
* Examples:
*
* ```go
* a /= 2
* ```
*/
class QuoAssignStmt extends @quoassignstmt, CompoundAssignStmt {
override string getOperator() { result = "/=" }
@@ -231,6 +368,12 @@ class DivAssignStmt = QuoAssignStmt;
/**
* A modulo-assign statement using `%=`.
*
* Examples:
*
* ```go
* a %= 2
* ```
*/
class RemAssignStmt extends @remassignstmt, CompoundAssignStmt {
override string getOperator() { result = "%=" }
@@ -240,6 +383,12 @@ class ModAssignStmt = RemAssignStmt;
/**
* An and-assign statement using `&=`.
*
* Examples:
*
* ```go
* a &= 2
* ```
*/
class AndAssignStmt extends @andassignstmt, CompoundAssignStmt {
override string getOperator() { result = "&=" }
@@ -247,6 +396,12 @@ class AndAssignStmt extends @andassignstmt, CompoundAssignStmt {
/**
* An or-assign statement using `|=`.
*
* Examples:
*
* ```go
* a |= 2
* ```
*/
class OrAssignStmt extends @orassignstmt, CompoundAssignStmt {
override string getOperator() { result = "|=" }
@@ -254,6 +409,12 @@ class OrAssignStmt extends @orassignstmt, CompoundAssignStmt {
/**
* An xor-assign statement using `^=`.
*
* Examples:
*
* ```go
* a ^= 2
* ```
*/
class XorAssignStmt extends @xorassignstmt, CompoundAssignStmt {
override string getOperator() { result = "^=" }
@@ -261,6 +422,12 @@ class XorAssignStmt extends @xorassignstmt, CompoundAssignStmt {
/**
* A left-shift-assign statement using `<<=`.
*
* Examples:
*
* ```go
* a <<= 2
* ```
*/
class ShlAssignStmt extends @shlassignstmt, CompoundAssignStmt {
override string getOperator() { result = "<<=" }
@@ -270,6 +437,12 @@ class LShiftAssignStmt = ShlAssignStmt;
/**
* A right-shift-assign statement using `>>=`.
*
* Examples:
*
* ```go
* a >>= 2
* ```
*/
class ShrAssignStmt extends @shrassignstmt, CompoundAssignStmt {
override string getOperator() { result = ">>=" }
@@ -279,6 +452,12 @@ class RShiftAssignStmt = ShrAssignStmt;
/**
* An and-not-assign statement using `&^=`.
*
* Examples:
*
* ```go
* a &^= 2
* ```
*/
class AndNotAssignStmt extends @andnotassignstmt, CompoundAssignStmt {
override string getOperator() { result = "&^=" }
@@ -286,6 +465,12 @@ class AndNotAssignStmt extends @andnotassignstmt, CompoundAssignStmt {
/**
* A `go` statement.
*
* Examples:
*
* ```go
* go fillPixels(row)
* ```
*/
class GoStmt extends @gostmt, Stmt {
/** Gets the call. */
@@ -298,6 +483,12 @@ class GoStmt extends @gostmt, Stmt {
/**
* A `defer` statement.
*
* Examples:
*
* ```go
* defer mutex.Unlock()
* ```
*/
class DeferStmt extends @deferstmt, Stmt {
/** Gets the call being deferred. */
@@ -310,6 +501,12 @@ class DeferStmt extends @deferstmt, Stmt {
/**
* A `return` statement.
*
* Examples:
*
* ```go
* return x
* ```
*/
class ReturnStmt extends @returnstmt, Stmt {
/** Gets the `i`th returned expression (0-based) */
@@ -331,6 +528,17 @@ class ReturnStmt extends @returnstmt, Stmt {
/**
* A branch statement, for example a `break` or `goto`.
*
* Examples:
*
* ```go
* break
* break OuterLoop
* continue
* continue RowLoop
* goto Error
* fallthrough
* ```
*/
class BranchStmt extends @branchstmt, Stmt {
/** Gets the expression denoting the target label of the branch, if any. */
@@ -340,27 +548,72 @@ class BranchStmt extends @branchstmt, Stmt {
string getLabel() { result = getLabelExpr().getName() }
}
/** A `break` statement. */
/**
* A `break` statement.
*
* Examples:
*
* ```go
* break
* break OuterLoop
* ```
*/
class BreakStmt extends @breakstmt, BranchStmt {
override string toString() { result = "break statement" }
}
/** A `continue` statement. */
/**
* A `continue` statement.
*
* Examples:
*
* ```go
* continue
* continue RowLoop
* ```
*/
class ContinueStmt extends @continuestmt, BranchStmt {
override string toString() { result = "continue statement" }
}
/** A `goto` statement. */
/**
* A `goto` statement.
*
* Examples:
*
* ```go
* goto Error
* ```
*/
class GotoStmt extends @gotostmt, BranchStmt {
override string toString() { result = "goto statement" }
}
/** A `fallthrough` statement. */
/**
* A `fallthrough` statement.
*
* Examples:
*
* ```go
* fallthrough
* ```
*/
class FallthroughStmt extends @fallthroughstmt, BranchStmt {
override string toString() { result = "fallthrough statement" }
}
/** A block statement. */
/**
* A block statement.
*
* Examples:
*
* ```go
* {
* fmt.Printf("iteration %d\n", i)
* f(i)
* }
* ```
*/
class BlockStmt extends @blockstmt, Stmt, ScopeNode {
/** Gets the `i`th statement in this block (0-based). */
Stmt getStmt(int i) { result = getChildStmt(i) }
@@ -376,7 +629,19 @@ class BlockStmt extends @blockstmt, Stmt, ScopeNode {
override string toString() { result = "block statement" }
}
/** An `if` statement. */
/**
* An `if` statement.
*
* Examples:
*
* ```go
* if x := f(); x < y {
* return y - x
* } else {
* return x - y
* }
* ```
*/
class IfStmt extends @ifstmt, Stmt, ScopeNode {
/** Gets the init statement of this `if` statement, if any. */
Stmt getInit() { result = getChildStmt(0) }
@@ -400,7 +665,23 @@ class IfStmt extends @ifstmt, Stmt, ScopeNode {
override string toString() { result = "if statement" }
}
/** A `case` or `default` clause in a `switch` statement. */
/**
* A `case` or `default` clause in a `switch` statement.
*
* Examples:
*
* ```go
* case 0, 1:
* a = 1
* fallthrough
*
* default:
* b = 2
*
* case func(int) float64:
* printFunction(i)
* ```
*/
class CaseClause extends @caseclause, Stmt, ScopeNode {
/** Gets the `i`th expression of this `case` clause (0-based). */
Expr getExpr(int i) { result = getChildExpr(-(i + 1)) }
@@ -430,6 +711,29 @@ class CaseClause extends @caseclause, Stmt, ScopeNode {
/**
* A `switch` statement, that is, either an expression switch or a type switch.
*
* Examples:
*
* ```go
* switch x := f(); x {
* case 0, 1:
* a = 1
* fallthrough
* default:
* b = 2
* }
*
* switch i := x.(type) {
* default:
* printString("don't know the type")
* case nil:
* printString("x is nil")
* case int:
* printInt(i)
* case func(int) float64:
* printFunction(i)
* }
* ```
*/
class SwitchStmt extends @switchstmt, Stmt, ScopeNode {
/** Gets the init statement of this `switch` statement, if any. */
@@ -465,6 +769,18 @@ class SwitchStmt extends @switchstmt, Stmt, ScopeNode {
/**
* An expression-switch statement.
*
* Examples:
*
* ```go
* switch x := f(); x {
* case 0, 1:
* a = 1
* fallthrough
* default:
* b = 2
* }
* ```
*/
class ExpressionSwitchStmt extends @exprswitchstmt, SwitchStmt {
/** Gets the switch expression of this `switch` statement. */
@@ -480,6 +796,21 @@ class ExpressionSwitchStmt extends @exprswitchstmt, SwitchStmt {
/**
* A type-switch statement.
*
* Examples:
*
* ```go
* switch i := x.(type) {
* default:
* printString("don't know the type") // type of i is type of x (interface{})
* case nil:
* printString("x is nil") // type of i is type of x (interface{})
* case int:
* printInt(i) // type of i is int
* case func(int) float64:
* printFunction(i) // type of i is func(int) float64
* }
* ```
*/
class TypeSwitchStmt extends @typeswitchstmt, SwitchStmt {
/** Gets the assign statement of this type-switch statement. */
@@ -495,6 +826,26 @@ class TypeSwitchStmt extends @typeswitchstmt, SwitchStmt {
/**
* A comm clause, that is, a `case` or `default` clause in a `select` statement.
*
* Examples:
*
* ```go
* case i1 = <-c1:
* print("received ", i1, " from c1\n")
*
* case c2 <- i2:
* print("sent ", i2, " to c2\n")
*
* case i3, ok := (<-c3): // same as: i3, ok := <-c3
* if ok {
* print("received ", i3, " from c3\n")
* } else {
* print("c3 is closed\n")
* }
*
* default:
* print("no communication\n")
* ```
*/
class CommClause extends @commclause, Stmt, ScopeNode {
/** Gets the comm statement of this clause, if any. */
@@ -516,6 +867,14 @@ class CommClause extends @commclause, Stmt, ScopeNode {
/**
* A receive statement in a comm clause.
*
* Examples:
*
* ```go
* i1 = <-c1
* i3, ok := <-c3
* i3, ok := (<-c3)
* ```
*/
class RecvStmt extends Stmt {
RecvStmt() { this = any(CommClause cc).getComm() and not this instanceof SendStmt }
@@ -535,6 +894,25 @@ class RecvStmt extends Stmt {
/**
* A `select` statement.
*
* Examples:
*
* ```go
* select {
* case i1 = <-c1:
* print("received ", i1, " from c1\n")
* case c2 <- i2:
* print("sent ", i2, " to c2\n")
* case i3, ok := (<-c3): // same as: i3, ok := <-c3
* if ok {
* print("received ", i3, " from c3\n")
* } else {
* print("c3 is closed\n")
* }
* default:
* print("no communication\n")
* }
* ```
*/
class SelectStmt extends @selectstmt, Stmt {
/** Gets the body of this `select` statement. */
@@ -576,6 +954,22 @@ class SelectStmt extends @selectstmt, Stmt {
/**
* A loop, that is, either a `for` statement or a `range` statement.
*
* Examples:
*
* ```go
* for a < b {
* a *= 2
* }
*
* for i := 0; i < 10; i++ {
* f(i)
* }
*
* for key, value := range mymap {
* fmt.Printf("mymap[%s] = %d\n", key, value)
* }
* ```
*/
class LoopStmt extends @loopstmt, Stmt, ScopeNode {
/** Gets the body of this loop. */
@@ -584,6 +978,18 @@ class LoopStmt extends @loopstmt, Stmt, ScopeNode {
/**
* A `for` statement.
*
* Examples:
*
* ```go
* for a < b {
* a *= 2
* }
*
* for i := 0; i < 10; i++ {
* f(i)
* }
* ```
*/
class ForStmt extends @forstmt, LoopStmt {
/** Gets the init statement of this `for` statement, if any. */
@@ -609,6 +1015,26 @@ class ForStmt extends @forstmt, LoopStmt {
/**
* A `range` statement.
*
* Examples:
*
* ```go
* for key, value := range mymap {
* fmt.Printf("mymap[%s] = %d\n", key, value)
* }
*
* for _, value = range array {
* fmt.Printf("array contains: %d\n", value)
* }
*
* for index, _ := range str {
* fmt.Printf("str[%d] = ?\n", index)
* }
*
* for value = range ch {
* fmt.Printf("value from channel: %d\n", value)
* }
* ```
*/
class RangeStmt extends @rangestmt, LoopStmt {
/** Gets the expression denoting the key of this `range` statement. */

View File

@@ -9,7 +9,7 @@ module StringOps {
/**
* An expression that is equivalent to `strings.HasPrefix(A, B)` or `!strings.HasPrefix(A, B)`.
*
* Extends this class to refine existing API models. If you want to model new APIs,
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `StringOps::HasPrefix::Range` instead.
*/
class HasPrefix extends DataFlow::Node {
@@ -43,7 +43,7 @@ module StringOps {
/**
* An expression that is equivalent to `strings.HasPrefix(A, B)` or `!strings.HasPrefix(A, B)`.
*
* Extends this class to model new APIs. If you want to refine existing API models, extend
* Extend this class to model new APIs. If you want to refine existing API models, extend
* `StringOps::HasPrefix` instead.
*/
abstract class Range extends DataFlow::Node {

View File

@@ -25,11 +25,17 @@ class Type extends @type {
/**
* Gets the qualified name of this type, if any.
*
* Only (defined) named types like `io.Writer` have a qualified name. Basic types like `int`,
* pointer types like `*io.Writer`, and other composite types do not have a qualified name.
*/
string getQualifiedName() { result = getEntity().getQualifiedName() }
/**
* Holds if this type is declared in a package with path `pkg` and has name `name`.
*
* Only (defined) named types like `io.Writer` have a qualified name. Basic types like `int`,
* pointer types like `*io.Writer`, and other composite types do not have a qualified name.
*/
predicate hasQualifiedName(string pkg, string name) { getEntity().hasQualifiedName(pkg, name) }

View File

@@ -0,0 +1,68 @@
/**
* Provides implementations of some commonly used barrier guards for sanitizing untrusted URLs.
*/
import go
/**
* A call to a function called `isLocalUrl`, `isValidRedirect`, or similar, which is
* considered a barrier guard for sanitizing untrusted URLs.
*/
class RedirectCheckBarrierGuard extends DataFlow::BarrierGuard, DataFlow::CallNode {
RedirectCheckBarrierGuard() {
this.getCalleeName().regexpMatch("(?i)(is_?)?(local_?url|valid_?redir(ect)?)")
}
override predicate checks(Expr e, boolean outcome) {
// `isLocalUrl(e)` is a barrier for `e` if it evaluates to `true`
getAnArgument().asExpr() = e and
outcome = true
}
}
/**
* An equality check comparing a data-flow node against a constant string, considered as
* a barrier guard for sanitizing untrusted URLs.
*
* Additionally, a check comparing `url.Hostname()` against a constant string is also
* considered a barrier guard for `url`.
*/
class UrlCheck extends DataFlow::BarrierGuard, DataFlow::EqualityTestNode {
DataFlow::Node url;
UrlCheck() {
exists(this.getAnOperand().getStringValue()) and
(
url = this.getAnOperand()
or
exists(DataFlow::MethodCallNode mc | mc = this.getAnOperand() |
mc.getTarget().getName() = "Hostname" and
url = mc.getReceiver()
)
)
}
override predicate checks(Expr e, boolean outcome) {
e = url.asExpr() and outcome = this.getPolarity()
}
}
/**
* A call to a regexp match function, considered as a barrier guard for sanitizing untrusted URLs.
*
* This is overapproximate: we do not attempt to reason about the correctness of the regexp.
*/
class RegexpCheck extends DataFlow::BarrierGuard {
RegexpMatchFunction matchfn;
DataFlow::CallNode call;
RegexpCheck() {
matchfn.getACall() = call and
this = matchfn.getResult().getNode(call).getASuccessor*()
}
override predicate checks(Expr e, boolean branch) {
e = matchfn.getValue().getNode(call).asExpr() and
(branch = false or branch = true)
}
}

View File

@@ -4,6 +4,7 @@
*/
import go
private import semmle.go.dataflow.internal.DataFlowPrivate
/**
* An abstract representation of an input to a function, which is either a parameter
@@ -11,7 +12,14 @@ import go
*/
private newtype TFunctionInput =
TInParameter(int i) { exists(SignatureType s | exists(s.getParameterType(i))) } or
TInReceiver()
TInReceiver() or
TInResult(int index) {
// the one and only result
index = -1
or
// one among several results
exists(SignatureType s | exists(s.getResultType(index)))
}
/**
* An abstract representation of an input to a function, which is either a parameter
@@ -24,6 +32,12 @@ class FunctionInput extends TFunctionInput {
/** Holds if this represents the receiver of a function. */
predicate isReceiver() { none() }
/** Holds if this represents the result of a function. */
predicate isResult() { none() }
/** Holds if this represents the `i`th result of a function. */
predicate isResult(int i) { none() }
/** Gets the data-flow node corresponding to this input for the call `c`. */
final DataFlow::Node getNode(DataFlow::CallNode c) { result = getEntryNode(c) }
@@ -69,6 +83,51 @@ private class ReceiverInput extends FunctionInput, TInReceiver {
override string toString() { result = "receiver" }
}
/**
* A result position of a function, viewed as an input.
*
* Results are usually outputs rather than inputs, but for taint tracking it can be useful to
* think of taint propagating backwards from a result of a function to its arguments. For instance,
* the function `bufio.NewWriter` returns a writer `bw` that buffers write operations to an
* underlying writer `w`. If tainted data is written to `bw`, then it makes sense to propagate
* that taint back to the underlying writer `w`, which can be modeled by saying that
* `bufio.NewWriter` propagates taint from its result to its first argument.
*/
private class ResultInput extends FunctionInput, TInResult {
int index;
ResultInput() { this = TInResult(index) }
override predicate isResult() { index = -1 }
override predicate isResult(int i) {
i = 0 and isResult()
or
i = index and i >= 0
}
override DataFlow::Node getEntryNode(DataFlow::CallNode c) {
exists(DataFlow::PostUpdateNode pun, DataFlow::Node init |
pun = result and
init = pun.(DataFlow::SsaNode).getInit()
|
index = -1 and
init = c.getResult()
or
index >= 0 and
init = c.getResult(index)
)
}
override DataFlow::Node getExitNode(FuncDef f) { none() }
override string toString() {
index = -1 and result = "result"
or
index >= 0 and result = "result " + index
}
}
/**
* An abstract representation of an output of a function, which is one of its results.
*/
@@ -126,7 +185,11 @@ private class OutResult extends FunctionOutput, TOutResult {
override predicate isResult() { index = -1 }
override predicate isResult(int i) { i = index and i >= 0 }
override predicate isResult(int i) {
i = 0 and isResult()
or
i = index and i >= 0
}
override DataFlow::Node getEntryNode(FuncDef f) {
// return expressions
@@ -181,8 +244,8 @@ private class OutParameter extends FunctionOutput, TOutParameter {
}
override DataFlow::Node getExitNode(DataFlow::CallNode c) {
exists(DataFlow::ArgumentNode arg |
arg.argumentOf(c.asExpr(), index) and
exists(DataFlow::Node arg |
arg = getArgument(c, index) and
result.(DataFlow::PostUpdateNode).getPreUpdateNode() = arg
)
}

View File

@@ -338,6 +338,13 @@ class SsaWithFields extends TSsaWithFields {
/** Gets a use that refers to this SSA variable with fields. */
DataFlow::Node getAUse() { this = accessPath(result.asInstruction()) }
/** Gets the type of this SSA variable with fields. */
Type getType() {
exists(SsaVariable var | this = TRoot(var) | result = var.getType())
or
exists(Field f | this = TStep(_, f) | result = f.getType())
}
/** Gets a textual representation of this element. */
string toString() {
exists(SsaVariable var | this = TRoot(var) | result = "(" + var + ")")
@@ -345,6 +352,15 @@ class SsaWithFields extends TSsaWithFields {
exists(SsaWithFields base, Field f | this = TStep(base, f) | result = base + "." + f.getName())
}
/**
* Gets an SSA-with-fields variable that is similar to this SSA-with-fields variable in the
* sense that it has the same root variable and the same sequence of field accesses.
*/
SsaWithFields similar() {
result.getBaseVariable().getSourceVariable() = this.getBaseVariable().getSourceVariable() and
result.getQualifiedName() = this.getQualifiedName()
}
/**
* Gets the qualified name of the source variable or variable and fields that this represents.
*

File diff suppressed because it is too large Load Diff

View File

@@ -26,13 +26,30 @@ private module Cached {
)
}
/** Provides predicates for calculating flow-through summaries. */
pragma[nomagic]
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) {
viableCallable(call) = result.getCallable() and
kind = result.getKind()
}
/**
* Holds if a value at return position `pos` can be returned to `out` via `call`,
* taking virtual dispatch into account.
*/
cached
predicate viableReturnPosOut(DataFlowCall call, ReturnPosition pos, Node out) {
exists(ReturnKindExt kind |
pos = viableReturnPos(call, kind) and
out = kind.getAnOutNode(call)
)
}
/** Provides predicates for calculating flow-through summaries. */
private module FlowThrough {
/**
* The first flow-through approximation:
*
* - Input/output access paths are abstracted with a Boolean parameter
* - Input access paths are abstracted with a Boolean parameter
* that indicates (non-)emptiness.
*/
private module Cand {
@@ -40,83 +57,47 @@ private module Cached {
* Holds if `p` can flow to `node` in the same callable using only
* value-preserving steps.
*
* `read` indicates whether it is contents of `p` that can flow to `node`,
* and `stored` indicates whether it flows to contents of `node`.
* `read` indicates whether it is contents of `p` that can flow to `node`.
*/
pragma[nomagic]
private predicate parameterValueFlowCand(
ParameterNode p, Node node, boolean read, boolean stored
) {
private predicate parameterValueFlowCand(ParameterNode p, Node node, boolean read) {
p = node and
read = false and
stored = false
read = false
or
// local flow
exists(Node mid |
parameterValueFlowCand(p, mid, read, stored) and
parameterValueFlowCand(p, mid, read) and
simpleLocalFlowStep(mid, node)
)
or
// read
exists(Node mid, boolean readMid, boolean storedMid |
parameterValueFlowCand(p, mid, readMid, storedMid) and
readStep(mid, _, node) and
stored = false
|
// value neither read nor stored prior to read
readMid = false and
storedMid = false and
read = true
or
// value (possibly read and then) stored prior to read (same content)
read = readMid and
storedMid = true
)
or
// store
exists(Node mid |
parameterValueFlowCand(p, mid, read, false) and
storeStep(mid, _, node) and
stored = true
parameterValueFlowCand(p, mid, false) and
readStep(mid, _, node) and
read = true
)
or
// flow through: no prior read or store
// flow through: no prior read
exists(ArgumentNode arg |
parameterValueFlowArgCand(p, arg, false, false) and
argumentValueFlowsThroughCand(arg, node, read, stored)
parameterValueFlowArgCand(p, arg, false) and
argumentValueFlowsThroughCand(arg, node, read)
)
or
// flow through: no read or store inside method
// flow through: no read inside method
exists(ArgumentNode arg |
parameterValueFlowArgCand(p, arg, read, stored) and
argumentValueFlowsThroughCand(arg, node, false, false)
)
or
// flow through: possible prior read and prior store with compatible
// flow-through method
exists(ArgumentNode arg, boolean mid |
parameterValueFlowArgCand(p, arg, read, mid) and
argumentValueFlowsThroughCand(arg, node, mid, stored)
parameterValueFlowArgCand(p, arg, read) and
argumentValueFlowsThroughCand(arg, node, false)
)
}
pragma[nomagic]
private predicate parameterValueFlowArgCand(
ParameterNode p, ArgumentNode arg, boolean read, boolean stored
) {
parameterValueFlowCand(p, arg, read, stored)
private predicate parameterValueFlowArgCand(ParameterNode p, ArgumentNode arg, boolean read) {
parameterValueFlowCand(p, arg, read)
}
pragma[nomagic]
predicate parameterValueFlowsToPreUpdateCand(ParameterNode p, PostUpdateNode n) {
parameterValueFlowCand(p, n.getPreUpdateNode(), false, false)
}
pragma[nomagic]
private predicate parameterValueFlowsToPostUpdateCand(
ParameterNode p, PostUpdateNode n, boolean read
) {
parameterValueFlowCand(p, n, read, true)
parameterValueFlowCand(p, n.getPreUpdateNode(), false)
}
/**
@@ -125,33 +106,21 @@ private module Cached {
* into account.
*
* `read` indicates whether it is contents of `p` that can flow to the return
* node, and `stored` indicates whether it flows to contents of the return
* node.
*/
predicate parameterValueFlowReturnCand(
ParameterNode p, ReturnKindExt kind, boolean read, boolean stored
) {
predicate parameterValueFlowReturnCand(ParameterNode p, ReturnKind kind, boolean read) {
exists(ReturnNode ret |
parameterValueFlowCand(p, ret, read, stored) and
kind = TValueReturn(ret.getKind())
)
or
exists(ParameterNode p2, int pos2, PostUpdateNode n |
parameterValueFlowsToPostUpdateCand(p, n, read) and
parameterValueFlowsToPreUpdateCand(p2, n) and
p2.isParameterOf(_, pos2) and
kind = TParamUpdate(pos2) and
p != p2 and
stored = true
parameterValueFlowCand(p, ret, read) and
kind = ret.getKind()
)
}
pragma[nomagic]
private predicate argumentValueFlowsThroughCand0(
DataFlowCall call, ArgumentNode arg, ReturnKindExt kind, boolean read, boolean stored
DataFlowCall call, ArgumentNode arg, ReturnKind kind, boolean read
) {
exists(ParameterNode param | viableParamArg(call, param, arg) |
parameterValueFlowReturnCand(param, kind, read, stored)
parameterValueFlowReturnCand(param, kind, read)
)
}
@@ -159,22 +128,19 @@ private module Cached {
* Holds if `arg` flows to `out` through a call using only value-preserving steps,
* not taking call contexts into account.
*
* `read` indicates whether it is contents of `arg` that can flow to `out`, and
* `stored` indicates whether it flows to contents of `out`.
* `read` indicates whether it is contents of `arg` that can flow to `out`.
*/
predicate argumentValueFlowsThroughCand(
ArgumentNode arg, Node out, boolean read, boolean stored
) {
exists(DataFlowCall call, ReturnKindExt kind |
argumentValueFlowsThroughCand0(call, arg, kind, read, stored) and
out = kind.getAnOutNode(call)
predicate argumentValueFlowsThroughCand(ArgumentNode arg, Node out, boolean read) {
exists(DataFlowCall call, ReturnKind kind |
argumentValueFlowsThroughCand0(call, arg, kind, read) and
out = getAnOutNode(call, kind)
)
}
predicate cand(ParameterNode p, Node n) {
parameterValueFlowCand(p, n, _, _) and
parameterValueFlowCand(p, n, _) and
(
parameterValueFlowReturnCand(p, _, _, _)
parameterValueFlowReturnCand(p, _, _)
or
parameterValueFlowsToPreUpdateCand(p, _)
)
@@ -187,7 +153,6 @@ private module Cached {
(
n instanceof ParameterNode or
n instanceof OutNode or
n instanceof PostUpdateNode or
readStep(_, _, n) or
n instanceof CastNode
)
@@ -200,10 +165,6 @@ private module Cached {
or
n instanceof ReturnNode
or
Cand::parameterValueFlowsToPreUpdateCand(_, n)
or
storeStep(n, _, _)
or
readStep(n, _, _)
or
n instanceof CastNode
@@ -237,230 +198,140 @@ private module Cached {
/**
* The final flow-through calculation:
*
* - Input/output access paths are abstracted with a `ContentOption` parameter
* - Input access paths are abstracted with a `ContentOption` parameter
* that represents the head of the access path. `TContentNone()` means that
* the access path is unrestricted.
* - Types are checked using the `compatibleTypes()` relation.
*/
cached
module Final {
private module Final {
/**
* Holds if `p` can flow to `node` in the same callable using only
* value-preserving steps, not taking call contexts into account.
*
* `contentIn` describes the content of `p` that can flow to `node`
* (if any), and `contentOut` describes the content of `node` that
* it flows to (if any).
* (if any).
*/
private predicate parameterValueFlow(
ParameterNode p, Node node, ContentOption contentIn, ContentOption contentOut
) {
parameterValueFlow0(p, node, contentIn, contentOut) and
predicate parameterValueFlow(ParameterNode p, Node node, ContentOption contentIn) {
parameterValueFlow0(p, node, contentIn) and
if node instanceof CastingNode
then
// normal flow through
contentIn = TContentNone() and
contentOut = TContentNone() and
compatibleTypes(getErasedNodeTypeBound(p), getErasedNodeTypeBound(node))
or
// getter
exists(Content fIn |
contentIn.getContent() = fIn and
contentOut = TContentNone() and
compatibleTypes(fIn.getType(), getErasedNodeTypeBound(node))
)
or
// (getter+)setter
exists(Content fOut |
contentOut.getContent() = fOut and
compatibleTypes(fOut.getContainerType(), getErasedNodeTypeBound(node))
)
else any()
}
pragma[nomagic]
private predicate parameterValueFlow0(
ParameterNode p, Node node, ContentOption contentIn, ContentOption contentOut
) {
private predicate parameterValueFlow0(ParameterNode p, Node node, ContentOption contentIn) {
p = node and
Cand::cand(p, _) and
contentIn = TContentNone() and
contentOut = TContentNone()
contentIn = TContentNone()
or
// local flow
exists(Node mid |
parameterValueFlow(p, mid, contentIn, contentOut) and
parameterValueFlow(p, mid, contentIn) and
LocalFlowBigStep::localFlowBigStep(mid, node)
)
or
// read
exists(Node mid, Content f, ContentOption contentInMid, ContentOption contentOutMid |
parameterValueFlow(p, mid, contentInMid, contentOutMid) and
readStep(mid, f, node)
|
// value neither read nor stored prior to read
contentInMid = TContentNone() and
contentOutMid = TContentNone() and
contentIn.getContent() = f and
contentOut = TContentNone() and
Cand::parameterValueFlowReturnCand(p, _, true, _) and
compatibleTypes(getErasedNodeTypeBound(p), f.getContainerType())
or
// value (possibly read and then) stored prior to read (same content)
contentIn = contentInMid and
contentOutMid.getContent() = f and
contentOut = TContentNone()
)
or
// store
exists(Node mid, Content f |
parameterValueFlow(p, mid, contentIn, TContentNone()) and
storeStep(mid, f, node) and
contentOut.getContent() = f
|
contentIn = TContentNone() and
compatibleTypes(getErasedNodeTypeBound(p), f.getType())
or
compatibleTypes(contentIn.getContent().getType(), f.getType())
parameterValueFlow(p, mid, TContentNone()) and
readStep(mid, f, node) and
contentIn.getContent() = f and
Cand::parameterValueFlowReturnCand(p, _, true) and
compatibleTypes(getErasedNodeTypeBound(p), f.getContainerType())
)
or
// flow through: no prior read or store
// flow through: no prior read
exists(ArgumentNode arg |
parameterValueFlowArg(p, arg, TContentNone(), TContentNone()) and
argumentValueFlowsThrough(_, arg, contentIn, contentOut, node)
parameterValueFlowArg(p, arg, TContentNone()) and
argumentValueFlowsThrough(arg, contentIn, node)
)
or
// flow through: no read or store inside method
// flow through: no read inside method
exists(ArgumentNode arg |
parameterValueFlowArg(p, arg, contentIn, contentOut) and
argumentValueFlowsThrough(_, arg, TContentNone(), TContentNone(), node)
)
or
// flow through: possible prior read and prior store with compatible
// flow-through method
exists(ArgumentNode arg, ContentOption contentMid |
parameterValueFlowArg(p, arg, contentIn, contentMid) and
argumentValueFlowsThrough(_, arg, contentMid, contentOut, node)
parameterValueFlowArg(p, arg, contentIn) and
argumentValueFlowsThrough(arg, TContentNone(), node)
)
}
pragma[nomagic]
private predicate parameterValueFlowArg(
ParameterNode p, ArgumentNode arg, ContentOption contentIn, ContentOption contentOut
ParameterNode p, ArgumentNode arg, ContentOption contentIn
) {
parameterValueFlow(p, arg, contentIn, contentOut) and
Cand::argumentValueFlowsThroughCand(arg, _, _, _)
parameterValueFlow(p, arg, contentIn) and
Cand::argumentValueFlowsThroughCand(arg, _, _)
}
pragma[nomagic]
private predicate argumentValueFlowsThrough0(
DataFlowCall call, ArgumentNode arg, ReturnKindExt kind, ContentOption contentIn,
ContentOption contentOut
DataFlowCall call, ArgumentNode arg, ReturnKind kind, ContentOption contentIn
) {
exists(ParameterNode param | viableParamArg(call, param, arg) |
parameterValueFlowReturn(param, _, kind, contentIn, contentOut)
parameterValueFlowReturn(param, kind, contentIn)
)
}
/**
* Holds if `arg` flows to `out` through `call` using only value-preserving steps,
* Holds if `arg` flows to `out` through a call using only value-preserving steps,
* not taking call contexts into account.
*
* `contentIn` describes the content of `arg` that can flow to `out` (if any), and
* `contentOut` describes the content of `out` that it flows to (if any).
* `contentIn` describes the content of `arg` that can flow to `out` (if any).
*/
cached
predicate argumentValueFlowsThrough(
DataFlowCall call, ArgumentNode arg, ContentOption contentIn, ContentOption contentOut,
Node out
) {
exists(ReturnKindExt kind |
argumentValueFlowsThrough0(call, arg, kind, contentIn, contentOut) and
out = kind.getAnOutNode(call)
pragma[nomagic]
predicate argumentValueFlowsThrough(ArgumentNode arg, ContentOption contentIn, Node out) {
exists(DataFlowCall call, ReturnKind kind |
argumentValueFlowsThrough0(call, arg, kind, contentIn) and
out = getAnOutNode(call, kind)
|
// normal flow through
contentIn = TContentNone() and
contentOut = TContentNone() and
compatibleTypes(getErasedNodeTypeBound(arg), getErasedNodeTypeBound(out))
or
// getter
exists(Content fIn |
contentIn.getContent() = fIn and
contentOut = TContentNone() and
compatibleTypes(getErasedNodeTypeBound(arg), fIn.getContainerType()) and
compatibleTypes(fIn.getType(), getErasedNodeTypeBound(out))
)
or
// setter
exists(Content fOut |
contentIn = TContentNone() and
contentOut.getContent() = fOut and
compatibleTypes(getErasedNodeTypeBound(arg), fOut.getType()) and
compatibleTypes(fOut.getContainerType(), getErasedNodeTypeBound(out))
)
or
// getter+setter
exists(Content fIn, Content fOut |
contentIn.getContent() = fIn and
contentOut.getContent() = fOut and
compatibleTypes(getErasedNodeTypeBound(arg), fIn.getContainerType()) and
compatibleTypes(fOut.getContainerType(), getErasedNodeTypeBound(out))
)
)
}
/**
* Holds if `p` can flow to the pre-update node associated with post-update
* node `n`, in the same callable, using only value-preserving steps.
*/
cached
predicate parameterValueFlowsToPreUpdate(ParameterNode p, PostUpdateNode n) {
parameterValueFlow(p, n.getPreUpdateNode(), TContentNone(), TContentNone())
}
pragma[nomagic]
private predicate parameterValueFlowsToPostUpdate(
ParameterNode p, PostUpdateNode n, ContentOption contentIn, ContentOption contentOut
) {
parameterValueFlow(p, n, contentIn, contentOut) and
contentOut.hasContent()
}
/**
* Holds if `p` can flow to a return node of kind `kind` in the same
* callable using only value-preserving steps.
*
* `contentIn` describes the content of `p` that can flow to the return
* node (if any), and `contentOut` describes the content of the return
* node that it flows to (if any).
* node (if any).
*/
cached
predicate parameterValueFlowReturn(
ParameterNode p, Node ret, ReturnKindExt kind, ContentOption contentIn,
ContentOption contentOut
private predicate parameterValueFlowReturn(
ParameterNode p, ReturnKind kind, ContentOption contentIn
) {
ret =
any(ReturnNode n |
parameterValueFlow(p, n, contentIn, contentOut) and
kind = TValueReturn(n.getKind())
)
or
ret =
any(PostUpdateNode n |
exists(ParameterNode p2, int pos2 |
parameterValueFlowsToPostUpdate(p, n, contentIn, contentOut) and
parameterValueFlowsToPreUpdate(p2, n) and
p2.isParameterOf(_, pos2) and
kind = TParamUpdate(pos2) and
p != p2
)
)
exists(ReturnNode ret |
parameterValueFlow(p, ret, contentIn) and
kind = ret.getKind()
)
}
}
import Final
}
/**
* Holds if `p` can flow to the pre-update node associated with post-update
* node `n`, in the same callable, using only value-preserving steps.
*/
cached
predicate parameterValueFlowsToPreUpdate(ParameterNode p, PostUpdateNode n) {
parameterValueFlow(p, n.getPreUpdateNode(), TContentNone())
}
/**
* Holds if data can flow from `node1` to `node2` via a direct assignment to
* `f`.
@@ -469,14 +340,14 @@ private module Cached {
* been stored into, in order to handle cases like `x.f1.f2 = y`.
*/
cached
predicate storeDirect(Node node1, Content f, Node node2) {
predicate store(Node node1, Content f, Node node2) {
storeStep(node1, f, node2) and readStep(_, f, _)
or
exists(Node n1, Node n2 |
n1 = node1.(PostUpdateNode).getPreUpdateNode() and
n2 = node2.(PostUpdateNode).getPreUpdateNode()
|
argumentValueFlowsThrough(_, n2, TContentSome(f), TContentNone(), n1)
argumentValueFlowsThrough(n2, TContentSome(f), n1)
or
readStep(n2, f, n1)
)
@@ -520,6 +391,21 @@ private module Cached {
newtype TReturnKindExt =
TValueReturn(ReturnKind kind) or
TParamUpdate(int pos) { exists(ParameterNode p | p.isParameterOf(_, pos)) }
cached
newtype TBooleanOption =
TBooleanNone() or
TBooleanSome(boolean b) { b = true or b = false }
cached
newtype TAccessPathFront =
TFrontNil(DataFlowType t) or
TFrontHead(Content f)
cached
newtype TAccessPathFrontOption =
TAccessPathFrontNone() or
TAccessPathFrontSome(AccessPathFront apf)
}
/**
@@ -529,8 +415,7 @@ class CastingNode extends Node {
CastingNode() {
this instanceof ParameterNode or
this instanceof CastNode or
this instanceof OutNode or
this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode
this instanceof OutNodeExt
}
}
@@ -538,7 +423,7 @@ newtype TContentOption =
TContentNone() or
TContentSome(Content f)
class ContentOption extends TContentOption {
private class ContentOption extends TContentOption {
Content getContent() { this = TContentSome(result) }
predicate hasContent() { exists(this.getContent()) }
@@ -678,6 +563,18 @@ class ReturnNodeExt extends Node {
}
}
/**
* A node to which data can flow from a call. Either an ordinary out node
* or a post-update node associated with a call argument.
*/
class OutNodeExt extends Node {
OutNodeExt() {
this instanceof OutNode
or
this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode
}
}
/**
* An extended return kind. A return kind describes how data can be returned
* from a callable. This can either be through a returned value or an updated
@@ -688,7 +585,7 @@ abstract class ReturnKindExt extends TReturnKindExt {
abstract string toString();
/** Gets a node corresponding to data flow out of `call`. */
abstract Node getAnOutNode(DataFlowCall call);
abstract OutNodeExt getAnOutNode(DataFlowCall call);
}
class ValueReturnKind extends ReturnKindExt, TValueReturn {
@@ -700,7 +597,9 @@ class ValueReturnKind extends ReturnKindExt, TValueReturn {
override string toString() { result = kind.toString() }
override Node getAnOutNode(DataFlowCall call) { result = getAnOutNode(call, this.getKind()) }
override OutNodeExt getAnOutNode(DataFlowCall call) {
result = getAnOutNode(call, this.getKind())
}
}
class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate {
@@ -712,9 +611,9 @@ class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate {
override string toString() { result = "param update " + pos }
override PostUpdateNode getAnOutNode(DataFlowCall call) {
override OutNodeExt getAnOutNode(DataFlowCall call) {
exists(ArgumentNode arg |
result.getPreUpdateNode() = arg and
result.(PostUpdateNode).getPreUpdateNode() = arg and
arg.argumentOf(call, this.getPosition())
)
}
@@ -779,77 +678,58 @@ DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) {
result = viableCallable(call) and cc instanceof CallContextReturn
}
newtype TSummary =
TSummaryVal() or
TSummaryTaint() or
TSummaryReadVal(Content f) or
TSummaryReadTaint(Content f) or
TSummaryTaintStore(Content f)
/**
* A summary of flow through a callable. This can either be value-preserving
* if no additional steps are used, taint-flow if at least one additional step
* is used, or any one of those combined with a store or a read. Summaries
* recorded at a return node are restricted to include at least one additional
* step, as the value-based summaries are calculated independent of the
* configuration.
*/
class Summary extends TSummary {
string toString() {
result = "Val" and this = TSummaryVal()
or
result = "Taint" and this = TSummaryTaint()
or
exists(Content f |
result = "ReadVal " + f.toString() and this = TSummaryReadVal(f)
or
result = "ReadTaint " + f.toString() and this = TSummaryReadTaint(f)
or
result = "TaintStore " + f.toString() and this = TSummaryTaintStore(f)
)
}
/** Gets the summary that results from extending this with an additional step. */
Summary additionalStep() {
this = TSummaryVal() and result = TSummaryTaint()
or
this = TSummaryTaint() and result = TSummaryTaint()
or
exists(Content f | this = TSummaryReadVal(f) and result = TSummaryReadTaint(f))
or
exists(Content f | this = TSummaryReadTaint(f) and result = TSummaryReadTaint(f))
}
/** Gets the summary that results from extending this with a read. */
Summary readStep(Content f) { this = TSummaryVal() and result = TSummaryReadVal(f) }
/** Gets the summary that results from extending this with a store. */
Summary storeStep(Content f) { this = TSummaryTaint() and result = TSummaryTaintStore(f) }
/** Gets the summary that results from extending this with `step`. */
bindingset[this, step]
Summary compose(Summary step) {
this = TSummaryVal() and result = step
or
this = TSummaryTaint() and
(step = TSummaryTaint() or step = TSummaryTaintStore(_)) and
result = step
or
exists(Content f |
this = TSummaryReadVal(f) and step = TSummaryTaint() and result = TSummaryReadTaint(f)
)
or
this = TSummaryReadTaint(_) and step = TSummaryTaint() and result = this
}
/** Holds if this summary does not include any taint steps. */
predicate isPartial() {
this = TSummaryVal() or
this = TSummaryReadVal(_)
}
}
pragma[noinline]
DataFlowType getErasedNodeTypeBound(Node n) { result = getErasedRepr(n.getTypeBound()) }
predicate readDirect = readStep/3;
predicate read = readStep/3;
/** An optional Boolean value. */
class BooleanOption extends TBooleanOption {
string toString() {
this = TBooleanNone() and result = "<none>"
or
this = TBooleanSome(any(boolean b | result = b.toString()))
}
}
/**
* The front of an access path. This is either a head or a nil.
*/
abstract class AccessPathFront extends TAccessPathFront {
abstract string toString();
abstract DataFlowType getType();
abstract boolean toBoolNonEmpty();
predicate headUsesContent(Content f) { this = TFrontHead(f) }
}
class AccessPathFrontNil extends AccessPathFront, TFrontNil {
override string toString() {
exists(DataFlowType t | this = TFrontNil(t) | result = ppReprType(t))
}
override DataFlowType getType() { this = TFrontNil(result) }
override boolean toBoolNonEmpty() { result = false }
}
class AccessPathFrontHead extends AccessPathFront, TFrontHead {
override string toString() { exists(Content f | this = TFrontHead(f) | result = f.toString()) }
override DataFlowType getType() {
exists(Content head | this = TFrontHead(head) | result = head.getContainerType())
}
override boolean toBoolNonEmpty() { result = true }
}
/** An optional access path front. */
class AccessPathFrontOption extends TAccessPathFrontOption {
string toString() {
this = TAccessPathFrontNone() and result = "<none>"
or
this = TAccessPathFrontSome(any(AccessPathFront apf | result = apf.toString()))
}
}

View File

@@ -3,8 +3,7 @@ private import DataFlowUtil
private import DataFlowImplCommon
private newtype TReturnKind =
TSingleReturn() or
TMultiReturn(int i) { exists(SignatureType st | exists(st.getResultType(i))) }
MkReturnKind(int i) { exists(SignatureType st | exists(st.getResultType(i))) }
/**
* A return kind. A return kind describes how a value can be returned
@@ -13,23 +12,14 @@ private newtype TReturnKind =
*/
class ReturnKind extends TReturnKind {
/** Gets a textual representation of this return kind. */
string toString() {
this = TSingleReturn() and
result = "return"
or
exists(int i | this = TMultiReturn(i) | result = "return[" + i + "]")
}
string toString() { exists(int i | this = MkReturnKind(i) | result = "return[" + i + "]") }
}
/** A data flow node that represents returning a value from a function. */
class ReturnNode extends ResultNode {
ReturnKind kind;
ReturnNode() {
exists(int nr | nr = fd.getType().getNumResult() |
if nr = 1 then kind = TSingleReturn() else kind = TMultiReturn(i)
)
}
ReturnNode() { kind = MkReturnKind(i) }
/** Gets the kind of this returned value. */
ReturnKind getKind() { result = kind }
@@ -40,12 +30,7 @@ class OutNode extends DataFlow::Node {
DataFlow::CallNode call;
int i;
OutNode() {
this = call.getResult() and
i = -1
or
this = call.getResult(i)
}
OutNode() { this = call.getResult(i) }
/** Gets the underlying call. */
DataFlowCall getCall() { result = call.asExpr() }
@@ -56,11 +41,8 @@ class OutNode extends DataFlow::Node {
* `kind`.
*/
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
exists(DataFlow::CallNode c | c.asExpr() = call |
kind = TSingleReturn() and
result = c.getResult()
or
exists(int i | kind = TMultiReturn(i) | result = c.getResult(i))
exists(DataFlow::CallNode c, int i | c.asExpr() = call and kind = MkReturnKind(i) |
result = c.getResult(i)
)
}
@@ -272,3 +254,14 @@ predicate isUnreachableInCall(Node n, DataFlowCall call) {
}
int accessPathLimit() { result = 5 }
/**
* Gets the `i`th argument of call `c`, where the receiver of a method call
* counts as argument -1.
*/
Node getArgument(CallNode c, int i) {
result = c.getArgument(i)
or
result = c.(MethodCallNode).getReceiver() and
i = -1
}

View File

@@ -4,6 +4,7 @@
import go
import semmle.go.dataflow.FunctionInputsAndOutputs
private import DataFlowPrivate
cached
private newtype TNode =
@@ -326,8 +327,15 @@ class CallNode extends ExprNode {
/** Gets a function passed as the `i`th argument of this call. */
FunctionNode getCallback(int i) { result.getASuccessor*() = this.getArgument(i) }
/** Gets the data-flow node corresponding to the `i`th result of this call. */
Node getResult(int i) { result = extractTupleElement(this, i) }
/**
* Gets the data-flow node corresponding to the `i`th result of this call.
*
* If there is a single result then it is considered to be the 0th result. */
Node getResult(int i) {
i = 0 and result = getResult()
or
result = extractTupleElement(this, i)
}
/**
* Gets the data-flow node corresponding to the result of this call.
@@ -337,6 +345,9 @@ class CallNode extends ExprNode {
*/
Node getResult() { not getType() instanceof TupleType and result = this }
/** Gets a result of this call. */
Node getAResult() { result = this.getResult(_) }
/** Gets the data flow node corresponding to the receiver of this call, if any. */
Node getReceiver() { result = getACalleeSource().(MethodReadNode).getReceiver() }
}
@@ -423,17 +434,6 @@ class PostUpdateNode extends Node {
Node getPreUpdateNode() { result = preupd }
}
/**
* Gets the `i`th argument of call `c`, where the receiver of a method call
* counts as argument -1.
*/
private Node getArgument(CallNode c, int i) {
result = c.getArgument(i)
or
result = c.(MethodCallNode).getReceiver() and
i = -1
}
/**
* A data-flow node that occurs as an argument in a call, including receiver arguments.
*/
@@ -473,11 +473,12 @@ class ArgumentNode extends Node {
* mutate it or something it points to.
*/
predicate mutableType(Type tp) {
tp instanceof ArrayType or
tp instanceof SliceType or
tp instanceof MapType or
tp instanceof PointerType or
tp instanceof InterfaceType
exists(Type underlying | underlying = tp.getUnderlyingType() |
not underlying instanceof BoolType and
not underlying instanceof NumericType and
not underlying instanceof StringType and
not underlying instanceof LiteralType
)
}
/**
@@ -766,6 +767,9 @@ class EqualityTestNode extends BinaryOperationNode, ExprNode {
outcome = expr.getPolarity() and
expr.hasOperands(lhs.asExpr(), rhs.asExpr())
}
/** Gets the polarity of this equality test, that is, `true` for `==` and `false` for `!=`. */
boolean getPolarity() { result = expr.getPolarity() }
}
/**

View File

@@ -79,13 +79,6 @@ abstract class Configuration extends DataFlow::Configuration {
defaultTaintBarrier(node)
}
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
isSanitizerEdge(node1, node2)
}
/** Holds if data flow into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }

View File

@@ -0,0 +1,112 @@
/** Provides classes for working with email-related APIs. */
import go
/**
* A data-flow node that represents data written to an email, either as part
* of the headers or as part of the body.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `EmailData::Range` instead.
*/
class EmailData extends DataFlow::Node {
EmailData::Range self;
EmailData() { this = self }
}
/** Provides classes for working with data that is incorporated into an email. */
module EmailData {
/**
* A data-flow node that represents data which is written to an email, either as part
* of the headers or as part of the body.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `EmailData` instead.
*/
abstract class Range extends DataFlow::Node { }
/** A data-flow node that is written to an email using the net/smtp package. */
private class SmtpData extends Range {
SmtpData() {
// func (c *Client) Data() (io.WriteCloser, error)
exists(Method data |
data.hasQualifiedName("net/smtp", "Client", "Data") and
this.(DataFlow::SsaNode).getInit() = data.getACall().getResult(0)
)
or
// func SendMail(addr string, a Auth, from string, to []string, msg []byte) error
exists(Function sendMail |
sendMail.hasQualifiedName("net/smtp", "SendMail") and
this = sendMail.getACall().getArgument(4)
)
}
}
/** Gets the package name `github.com/sendgrid/sendgrid-go/helpers/mail`. */
private string sendgridMail() { result = "github.com/sendgrid/sendgrid-go/helpers/mail" }
private class NewContent extends TaintTracking::FunctionModel {
NewContent() {
// func NewContent(contentType string, value string) *Content
this.hasQualifiedName(sendgridMail(), "NewContent")
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isParameter(1) and output.isResult()
}
}
/** A data-flow node that is written to an email using the sendgrid/sendgrid-go package. */
private class SendGridEmail extends Range {
SendGridEmail() {
// func NewSingleEmail(from *Email, subject string, to *Email, plainTextContent string, htmlContent string) *SGMailV3
exists(Function newSingleEmail |
newSingleEmail.hasQualifiedName(sendgridMail(), "NewSingleEmail") and
this = newSingleEmail.getACall().getArgument([1, 3, 4])
)
or
// func NewV3MailInit(from *Email, subject string, to *Email, content ...*Content) *SGMailV3
exists(Function newv3MailInit |
newv3MailInit.hasQualifiedName(sendgridMail(), "NewV3MailInit") and
this = newv3MailInit.getACall().getArgument(any(int i | i = 1 or i >= 3))
)
or
// func (s *SGMailV3) AddContent(c ...*Content) *SGMailV3
exists(Method addContent |
addContent.hasQualifiedName(sendgridMail(), "SGMailV3", "AddContent") and
this = addContent.getACall().getAnArgument()
)
}
}
}
/**
* A taint model of the `Writer.CreatePart` method from `mime/multipart`.
*
* If tainted data is written to the multipart section created by this method, the underlying writer
* should be considered tainted as well.
*/
private class MultipartWriterCreatePartModel extends TaintTracking::FunctionModel, Method {
MultipartWriterCreatePartModel() {
this.hasQualifiedName("mime/multipart", "Writer", "CreatePart")
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isResult(0) and output.isReceiver()
}
}
/**
* A taint model of the `NewWriter` function from `mime/multipart`.
*
* If tainted data is written to the writer created by this function, the underlying writer
* should be considered tainted as well.
*/
private class MultipartNewWriterModel extends TaintTracking::FunctionModel {
MultipartNewWriterModel() { this.hasQualifiedName("mime/multipart", "NewWriter") }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isResult() and output.isParameter(0)
}
}

View File

@@ -54,13 +54,21 @@ private module StdlibHttp {
}
}
/** The declaration of a variable which either is or has a field that implements the http.ResponseWriter type */
private class StdlibResponseWriter extends HTTP::ResponseWriter::Range {
StdlibResponseWriter() { this.getType().implements("net/http", "ResponseWriter") }
SsaWithFields v;
StdlibResponseWriter() {
this = v.getBaseVariable().getSourceVariable() and
exists(Type t | t.implements("net/http", "ResponseWriter") | v.getType() = t)
}
override DataFlow::Node getANode() { result = v.similar().getAUse().getASuccessor*() }
/** Gets a header object that corresponds to this HTTP response. */
DataFlow::MethodCallNode getAHeaderObject() {
result.getTarget().hasQualifiedName("net/http", _, "Header") and
this.getARead() = result.getReceiver()
result.getTarget().getName() = "Header" and
this.getANode() = result.getReceiver()
}
}

View File

@@ -0,0 +1,30 @@
/**
* Provides classes for working with concepts relating to the Macaron web framework
*/
import go
private module Macaron {
private class Context extends HTTP::ResponseWriter::Range {
SsaWithFields v;
Context() {
this = v.getBaseVariable().getSourceVariable() and
exists(Method m | m.hasQualifiedName("gopkg.in/macaron.v1", "Context", "Redirect") |
v.getType().getMethod("Redirect") = m
)
}
override DataFlow::Node getANode() { result = v.similar().getAUse().getASuccessor*() }
}
private class RedirectCall extends HTTP::Redirect::Range, DataFlow::MethodCallNode {
RedirectCall() {
this.getTarget().hasQualifiedName("gopkg.in/macaron.v1", "Context", "Redirect")
}
override DataFlow::Node getUrl() { result = this.getArgument(0) }
override HTTP::ResponseWriter getResponseWriter() { result.getANode() = this.getReceiver() }
}
}

View File

@@ -0,0 +1,15 @@
/**
* Provides classes for working with concepts in the Mux HTTP middleware library.
*/
import go
/**
* Provides classes for working with concepts in the Mux HTTP middleware library.
*/
module Mux {
/** An access to a Mux middleware variable. */
class RequestVars extends DataFlow::UntrustedFlowSource::Range, DataFlow::CallNode {
RequestVars() { this.getTarget().hasQualifiedName("github.com/gorilla/mux", "Vars") }
}
}

View File

@@ -0,0 +1,120 @@
/**
* Provides classes for working with NoSQL-related concepts such as queries.
*/
import go
/** Provides classes for working with NoSQL-related APIs. */
module NoSQL {
/**
* A data-flow node whose value is interpreted as (part of) a NoSQL query.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `NoSQL::Query::Range` instead.
*/
class Query extends DataFlow::Node {
Query::Range self;
Query() { this = self }
}
/** Provides classes for working with NoSQL queries. */
module Query {
/**
* A data-flow node whose value is interpreted as (part of) a NoSQL query.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `NoSQL::Query` instead.
*/
abstract class Range extends DataFlow::Node { }
/**
* Holds if method `name` of struct `Collection` from package
* [go.mongodb.org/mongo-driver/mongo](https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo)
* interprets parameter `n` as a query.
*/
private predicate mongoDbCollectionMethod(string name, int n) {
// func (coll *Collection) CountDocuments(ctx context.Context, filter interface{},
// opts ...*options.CountOptions) (int64, error)
name = "CountDocuments" and n = 1
or
// func (coll *Collection) DeleteMany(ctx context.Context, filter interface{},
// opts ...*options.DeleteOptions) (*DeleteResult, error)
name = "DeleteMany" and n = 1
or
// func (coll *Collection) DeleteOne(ctx context.Context, filter interface{},
// opts ...*options.DeleteOptions) (*DeleteResult, error)
name = "DeleteOne" and n = 1
or
// func (coll *Collection) Distinct(ctx context.Context, fieldName string, filter interface{},
// ...) ([]interface{}, error)
name = "Distinct" and n = 2
or
// func (coll *Collection) Find(ctx context.Context, filter interface{},
// opts ...*options.FindOptions) (*Cursor, error)
name = "Find" and n = 1
or
// func (coll *Collection) FindOne(ctx context.Context, filter interface{},
// opts ...*options.FindOneOptions) *SingleResult
name = "FindOne" and n = 1
or
// func (coll *Collection) FindOneAndDelete(ctx context.Context, filter interface{}, ...)
// *SingleResult
name = "FindOneAndDelete" and n = 1
or
// func (coll *Collection) FindOneAndReplace(ctx context.Context, filter interface{},
// replacement interface{}, ...) *SingleResult
name = "FindOneAndReplace" and n = 1
or
// func (coll *Collection) FindOneAndUpdate(ctx context.Context, filter interface{},
// update interface{}, ...) *SingleResult
name = "FindOneAndUpdate" and n = 1
or
// func (coll *Collection) ReplaceOne(ctx context.Context, filter interface{},
// replacement interface{}, ...) (*UpdateResult, error)
name = "ReplaceOne" and n = 1
or
// func (coll *Collection) UpdateMany(ctx context.Context, filter interface{},
// update interface{}, ...) (*UpdateResult, error)
name = "UpdateMany" and n = 1
or
// func (coll *Collection) UpdateOne(ctx context.Context, filter interface{},
// update interface{}, ...) (*UpdateResult, error)
name = "UpdateOne" and n = 1
or
// func (coll *Collection) Watch(ctx context.Context, pipeline interface{}, ...)
// (*ChangeStream, error)
name = "Watch" and n = 1
or
// func (coll *Collection) Aggregate(ctx context.Context, pipeline interface{},
// opts ...*options.AggregateOptions) (*Cursor, error)
name = "Aggregate" and n = 1
}
/**
* A query used in an API function acting on a `Collection` struct of package
* [go.mongodb.org/mongo-driver/mongo](https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo).
*/
private class MongoDbCollectionQuery extends Range {
MongoDbCollectionQuery() {
exists(Method meth, string methodName, int n |
mongoDbCollectionMethod(methodName, n) and
meth.hasQualifiedName("go.mongodb.org/mongo-driver/mongo", "Collection", methodName) and
this = meth.getACall().getArgument(n)
)
}
}
}
/**
* Holds if taint flows from `pred` to `succ` through a MongoDB-specific API.
*/
predicate isAdditionalMongoTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
// Taint an entry if the `Value` is tainted
exists(Write w, DataFlow::Node base, Field f | w.writesField(base, f, pred) |
base = succ.(DataFlow::PostUpdateNode).getPreUpdateNode() and
base.getType().hasQualifiedName("go.mongodb.org/mongo-driver/bson/primitive", "E") and
f.getName() = "Value"
)
}
}

View File

@@ -9,7 +9,7 @@ module SQL {
/**
* A data-flow node whose string value is interpreted as (part of) a SQL query.
*
* Extends this class to refine existing API models. If you want to model new APIs,
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `SQL::QueryString::Range` instead.
*/
class QueryString extends DataFlow::Node {
@@ -76,11 +76,11 @@ module SQL {
/** A string that might identify package `go-pg/pg` or a specific version of it. */
bindingset[result]
private string gopg() { result.regexpMatch("github.com/go-pg/pg(/v[^/]+)?") }
private string gopg() { result = package("github.com/go-pg/pg", "") }
/** A string that might identify package `go-pg/pg/orm` or a specific version of it. */
bindingset[result]
private string gopgorm() { result.regexpMatch("github.com/go-pg/pg(/v[^/]+)?/orm") }
private string gopgorm() { result = package("github.com/go-pg/pg", "orm") }
/**
* A string argument to an API of `go-pg/pg` that is directly interpreted as SQL without

View File

@@ -62,7 +62,18 @@ module PathFilePath {
override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) {
inp.isParameter(_) and
(outp.isResult() or outp.isResult(_))
outp.isResult(_)
}
}
}
/** Provides models of commonly used functions in the `bytes` package. */
private module Bytes {
private class BufferBytes extends TaintTracking::FunctionModel, Method {
BufferBytes() { this.hasQualifiedName("bytes", "Buffer", ["Bytes", "String"]) }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isReceiver() and output.isResult()
}
}
}
@@ -71,39 +82,272 @@ module PathFilePath {
module Fmt {
/** The `Sprint` function or one of its variants. */
class Sprinter extends TaintTracking::FunctionModel {
Sprinter() {
exists(string sprint | sprint.matches("Sprint%") | hasQualifiedName("fmt", sprint))
}
Sprinter() { this.hasQualifiedName("fmt", ["Sprint", "Sprintf", "Sprintln"]) }
override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) {
inp.isParameter(_) and outp.isResult()
}
}
/** The `Print` function or one of its variants. */
private class Printer extends Function {
Printer() { this.hasQualifiedName("fmt", ["Print", "Printf", "Println"]) }
}
/** A call to `Print`, `Fprint`, or similar. */
private class PrintCall extends LoggerCall::Range, DataFlow::CallNode {
PrintCall() {
exists(string fn |
fn = "Print%"
or
fn = "Fprint%"
|
this.getTarget().hasQualifiedName("fmt", fn)
)
}
PrintCall() { this.getTarget() instanceof Printer or this.getTarget() instanceof Fprinter }
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
}
/** The `Fprint` function or one of its variants. */
private class Fprinter extends TaintTracking::FunctionModel {
Fprinter() { this.hasQualifiedName("fmt", ["Fprint", "Fprintf", "Fprintln"]) }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isParameter(any(int i | i > 0)) and output.isParameter(0)
}
}
/** The `Sscan` function or one of its variants. */
private class Sscanner extends TaintTracking::FunctionModel {
Sscanner() { this.hasQualifiedName("fmt", ["Sscan", "Sscanf", "Sscanln"]) }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isParameter(0) and
exists(int i | if getName() = "Sscanf" then i > 1 else i > 0 | output.isParameter(i))
}
}
}
/** Provides models of commonly used functions in the `io` package. */
module Io {
private class ReaderRead extends TaintTracking::FunctionModel, Method {
ReaderRead() {
exists(Method im | im.hasQualifiedName("io", "Reader", "Read") | this.implements(im))
private class Copy extends TaintTracking::FunctionModel {
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 inp, FunctionOutput outp) {
inp.isReceiver() and outp.isParameter(0)
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isParameter(1) and output.isParameter(0)
}
}
private class Pipe extends TaintTracking::FunctionModel {
Pipe() {
// func Pipe() (*PipeReader, *PipeWriter)
hasQualifiedName("io", "Pipe")
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isResult(0) and output.isResult(1)
}
}
private class ReadAtLeast extends TaintTracking::FunctionModel {
ReadAtLeast() {
// 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() {
// func 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() {
// func 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() {
// func Read(p []byte) (n int, err error)
this.implements("io", "Reader", "Read")
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isReceiver() and output.isParameter(0)
}
}
private class LimitReader extends TaintTracking::FunctionModel {
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 {
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 {
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() {
// func ReadAt(p []byte, off int64) (n int, err error)
this.implements("io", "ReaderAt", "ReadAt")
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isReceiver() and output.isParameter(0)
}
}
private class ReaderFromReadFrom extends TaintTracking::FunctionModel, Method {
ReaderFromReadFrom() {
// func 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() {
// func 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 {
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()
}
}
private class StringWriterWriteString extends TaintTracking::FunctionModel, Method {
StringWriterWriteString() {
// func 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() {
// func 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 MultiWriter extends TaintTracking::FunctionModel {
MultiWriter() {
// func MultiWriter(writers ...Writer) Writer
hasQualifiedName("io", "MultiWriter")
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isResult() and output.isParameter(_)
}
}
private class WriterAtWriteAt extends TaintTracking::FunctionModel, Method {
WriterAtWriteAt() {
// func 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() {
// func 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)
}
}
}
/** Provides models of commonly used functions in the `bufio` package. */
module Bufio {
private class NewWriter extends TaintTracking::FunctionModel {
NewWriter() { this.hasQualifiedName("bufio", "NewWriter") }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
input.isResult() and output.isParameter(0)
}
}
}
@@ -115,11 +359,13 @@ module IoUtil {
exists(string fn | getTarget().hasQualifiedName("io/ioutil", fn) |
fn = "ReadDir" or
fn = "ReadFile" or
fn = "TempDir" or
fn = "TempFile" or
fn = "WriteFile"
)
}
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
override DataFlow::Node getAPathArgument() { result = getAnArgument() }
}
/**
@@ -226,7 +472,7 @@ module Path {
override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) {
inp.isParameter(_) and
(outp.isResult() or outp.isResult(_))
outp.isResult(_)
}
}
}
@@ -438,8 +684,7 @@ module URL {
}
override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) {
inp.isReceiver() and
if getName() = "Password" then outp.isResult(0) else outp.isResult()
inp.isReceiver() and outp.isResult(0)
}
}
@@ -545,7 +790,8 @@ module Log {
/** Provides models of some functions in the `encoding/json` package. */
module EncodingJson {
private class MarshalFunction extends TaintTracking::FunctionModel, MarshalingFunction::Range {
/** The `Marshal` or `MarshalIndent` function in the `encoding/json` package. */
class MarshalFunction extends TaintTracking::FunctionModel, MarshalingFunction::Range {
MarshalFunction() {
this.hasQualifiedName("encoding/json", "Marshal") or
this.hasQualifiedName("encoding/json", "MarshalIndent")

View File

@@ -29,6 +29,16 @@ private class SystemCommandExecutors extends SystemCommandExecution::Range, Data
pkg = "os/exec" and name = "Command" and cmdArg = 0
or
pkg = "os/exec" and name = "CommandContext" and cmdArg = 1
or
// NOTE: syscall.ForkExec exists only on unix.
// NOTE: syscall.CreateProcess and syscall.CreateProcessAsUser exist only on windows.
pkg = "syscall" and
(name = "Exec" or name = "ForkExec" or name = "StartProcess" or name = "CreateProcess") and
cmdArg = 0
or
pkg = "syscall" and
name = "CreateProcessAsUser" and
cmdArg = 1
)
}

View File

@@ -0,0 +1,274 @@
/** Provides classes for working with WebSocket-related APIs. */
import go
/**
* A function call that establishes a new WebSocket connection.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `WebSocketRequestCall::Range` instead.
*/
class WebSocketRequestCall extends DataFlow::CallNode {
WebSocketRequestCall::Range self;
WebSocketRequestCall() { this = self }
/** Gets the URL of the request. */
DataFlow::Node getRequestUrl() { result = self.getRequestUrl() }
}
/** Provides classes for working with WebSocket request functions. */
module WebSocketRequestCall {
/**
* A function call that establishes a new WebSocket connection.
*
* Extend this class to model new APIs. If you want to refine existing
* API models, extend `WebSocketRequestCall` instead.
*/
abstract class Range extends DataFlow::CallNode {
/** Gets the URL of the request. */
abstract DataFlow::Node getRequestUrl();
}
/**
* A call to the `Dial` function of the `golang.org/x/net/websocket` package.
*/
private class GolangXNetDialFunc extends Range {
GolangXNetDialFunc() {
// func Dial(url_, protocol, origin string) (ws *Conn, err error)
this.getTarget().hasQualifiedName(package("golang.org/x/net", "websocket"), "Dial")
}
override DataFlow::Node getRequestUrl() { result = this.getArgument(0) }
}
/**
* A call to the `DialConfig` function of the `golang.org/x/net/websocket` package.
*/
private class GolangXNetDialConfigFunc extends Range {
GolangXNetDialConfigFunc() {
// func DialConfig(config *Config) (ws *Conn, err error)
this.getTarget().hasQualifiedName(package("golang.org/x/net", "websocket"), "DialConfig")
}
override DataFlow::Node getRequestUrl() {
exists(DataFlow::CallNode cn |
// func NewConfig(server, origin string) (config *Config, err error)
cn.getTarget().hasQualifiedName(package("golang.org/x/net", "websocket"), "NewConfig") and
this.getArgument(0) = cn.getResult(0).getASuccessor*() and
result = cn.getArgument(0)
)
}
}
/**
* A call to the `Dialer` or `DialContext` function of the `github.com/gorilla/websocket` package.
*/
private class GorillaWebSocketDialFunc extends Range {
DataFlow::Node url;
GorillaWebSocketDialFunc() {
// func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error)
// func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error)
exists(string name, Method f |
f = this.getTarget() and
f.hasQualifiedName(package("github.com/gorilla", "websocket"), "Dialer", name)
|
name = "Dial" and this.getArgument(0) = url
or
name = "DialContext" and this.getArgument(1) = url
)
}
override DataFlow::Node getRequestUrl() { result = url }
}
/**
* A call to the `Dialer.Dial` method of the `github.com/gobwas/ws` package.
*/
private class GobwasWsDialFunc extends Range {
GobwasWsDialFunc() {
// func (d Dialer) Dial(ctx context.Context, urlstr string) (conn net.Conn, br *bufio.Reader, hs Handshake, err error)
exists(Method m |
m.hasQualifiedName(package("github.com/gobwas", "ws"), "Dialer", "Dial") and
m = this.getTarget()
)
or
// func Dial(ctx context.Context, urlstr string) (net.Conn, *bufio.Reader, Handshake, error)
this.getTarget().hasQualifiedName(package("github.com/gobwas", "ws"), "Dial")
}
override DataFlow::Node getRequestUrl() { result = this.getArgument(1) }
}
/**
* A call to the `Dial` function of the `nhooyr.io/websocket` package.
*/
private class NhooyrWebSocketDialFunc extends Range {
NhooyrWebSocketDialFunc() {
// func Dial(ctx context.Context, u string, opts *DialOptions) (*Conn, *http.Response, error)
this.getTarget().hasQualifiedName(package("nhooyr.io", "websocket"), "Dial")
}
override DataFlow::Node getRequestUrl() { result = this.getArgument(1) }
}
/**
* A call to the `BuildProxy` or `New` function of the `github.com/sacOO7/gowebsocket` package.
*/
private class SacOO7DialFunc extends Range {
SacOO7DialFunc() {
// func BuildProxy(Url string) func(*http.Request) (*url.URL, error)
// func New(url string) Socket
this.getTarget().hasQualifiedName("github.com/sacOO7/gowebsocket", ["BuildProxy", "New"])
}
override DataFlow::Node getRequestUrl() { result = this.getArgument(0) }
}
}
/**
* A message written to a WebSocket, considered as a flow sink for reflected XSS.
*/
class WebSocketReaderAsSource extends UntrustedFlowSource::Range {
WebSocketReaderAsSource() {
exists(WebSocketReader r | this = r.getAnOutput().getNode(r.getACall()))
}
}
/**
* A function or a method which reads a message from a WebSocket connection.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `WebSocketReader::Range` instead.
*/
class WebSocketReader extends Function {
WebSocketReader::Range self;
WebSocketReader() { this = self }
/** Gets an output of this function containing data that is read from a WebSocket connection. */
FunctionOutput getAnOutput() { result = self.getAnOutput() }
}
/** Provides classes for working with messages read from a WebSocket. */
module WebSocketReader {
/**
* A function or a method which reads a message from a WebSocket connection
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `WebSocketReader` instead.
*/
abstract class Range extends Function {
/** Gets an output of this function containing data that is read from a WebSocket connection. */
abstract FunctionOutput getAnOutput();
}
/**
* The `Codec.Receive` method of the `golang.org/x/net/websocket` package.
*/
private class GolangXNetCodecRecv extends Range, Method {
GolangXNetCodecRecv() {
// func (cd Codec) Receive(ws *Conn, v interface{}) (err error)
this.hasQualifiedName("golang.org/x/net/websocket", "Codec", "Receive")
}
override FunctionOutput getAnOutput() { result.isParameter(1) }
}
/**
* The `Conn.Read` method of the `golang.org/x/net/websocket` package.
*/
private class GolangXNetConnRead extends Range, Method {
GolangXNetConnRead() {
// func (ws *Conn) Read(msg []byte) (n int, err error)
this.hasQualifiedName("golang.org/x/net/websocket", "Conn", "Read")
}
override FunctionOutput getAnOutput() { result.isParameter(0) }
}
/**
* The `Conn.Read` method of the `nhooyr.io/websocket` package.
*/
private class NhooyrWebSocketRead extends Range, Method {
NhooyrWebSocketRead() {
// func (c *Conn) Read(ctx context.Context) (MessageType, []byte, error)
this.hasQualifiedName("nhooyr.io/websocket", "Conn", "Read")
}
override FunctionOutput getAnOutput() { result.isResult(1) }
}
/**
* The `Conn.Reader` method of the `nhooyr.io/websocket` package.
*/
private class NhooyrWebSocketReader extends Range, Method {
NhooyrWebSocketReader() {
// func (c *Conn) Reader(ctx context.Context) (MessageType, io.Reader, error)
this.hasQualifiedName("nhooyr.io/websocket", "Conn", "Reader")
}
override FunctionOutput getAnOutput() { result.isResult(1) }
}
/**
* The `ReadFrame` function of the `github.com/gobwas/ws` package.
*/
private class GobwasWsReadFrame extends Range {
GobwasWsReadFrame() {
// func ReadFrame(r io.Reader) (f Frame, err error)
this.hasQualifiedName("github.com/gobwas/ws", "ReadFrame")
}
override FunctionOutput getAnOutput() { result.isResult(0) }
}
/**
* The `ReadHeader` function of the `github.com/gobwas/ws` package.
*/
private class GobwasWsReadHeader extends Range {
GobwasWsReadHeader() {
// func ReadHeader(r io.Reader) (h Header, err error)
this.hasQualifiedName("github.com/gobwas/ws", "ReadHeader")
}
override FunctionOutput getAnOutput() { result.isResult(0) }
}
/**
* The `ReadJson` function of the `github.com/gorilla/websocket` package.
*/
private class GorillaWebSocketReadJson extends Range {
GorillaWebSocketReadJson() {
// func ReadJSON(c *Conn, v interface{}) error
this.hasQualifiedName("github.com/gorilla/websocket", "ReadJSON")
}
override FunctionOutput getAnOutput() { result.isParameter(1) }
}
/**
* The `Conn.ReadJson` method of the `github.com/gorilla/websocket` package.
*/
private class GorillaWebSocketConnReadJson extends Range, Method {
GorillaWebSocketConnReadJson() {
// func (c *Conn) ReadJSON(v interface{}) error
this.hasQualifiedName("github.com/gorilla/websocket", "Conn", "ReadJSON")
}
override FunctionOutput getAnOutput() { result.isParameter(0) }
}
/**
* The `Conn.ReadMessage` method of the `github.com/gorilla/websocket` package.
*/
private class GorillaWebSocketReadMessage extends Range, Method {
GorillaWebSocketReadMessage() {
// func (c *Conn) ReadMessage() (messageType int, p []byte, err error)
this.hasQualifiedName("github.com/gorilla/websocket", "Conn", "ReadMessage")
}
override FunctionOutput getAnOutput() { result.isResult(1) }
}
}

View File

@@ -51,9 +51,7 @@ module AllocationSizeOverflow {
exists(MarshalingFunction marshal, DataFlow::CallNode call |
call = marshal.getACall() and
// rule out cases where we can tell that the result will always be small
not forall(FunctionInput inp | inp = marshal.getAnInput() |
isSmall(inp.getNode(call).asExpr())
) and
exists(FunctionInput inp | inp = marshal.getAnInput() | isBig(inp.getNode(call).asExpr())) and
this = marshal.getOutput().getNode(call)
)
}
@@ -152,26 +150,17 @@ module AllocationSizeOverflow {
exists(StructType st | st = t | forall(Field f | f = st.getField(_) | isSmallType(f.getType())))
}
/** Holds if `e` is an expression whose values are likely to marshal to relatively small blobs. */
private predicate isSmall(Expr e) {
isSmallType(e.getType())
or
e.isConst()
/** Holds if `e` is an expression whose values might marshal to relatively large blobs. */
private predicate isBig(Expr e) {
not isSmallType(e.getType()) and
not e.isConst()
or
exists(KeyValueExpr kv | kv = e |
isSmall(kv.getKey()) and
isSmall(kv.getValue())
isBig(kv.getKey()) or
isBig(kv.getValue())
)
or
isSmallCompositeLit(e, 0)
}
/** Holds if elements `n` and above of `lit` are small. */
private predicate isSmallCompositeLit(CompositeLit lit, int n) {
n = lit.getNumElement()
or
isSmall(lit.getElement(n)) and
isSmallCompositeLit(lit, n + 1)
isBig(e.(CompositeLit).getAnElement())
}
/**

View File

@@ -7,6 +7,7 @@
import go
import UrlConcatenation
import SafeUrlFlowCustomizations
import semmle.go.dataflow.BarrierGuardUtil
/**
* Provides extension points for customizing the taint-tracking configuration for reasoning about
@@ -57,7 +58,6 @@ module OpenUrlRedirect {
|
methName = "Cookie" or
methName = "Cookies" or
methName = "FormValue" or
methName = "MultipartReader" or
methName = "PostFormValues" or
methName = "Referer" or
@@ -104,62 +104,22 @@ module OpenUrlRedirect {
/**
* A call to a function called `isLocalUrl`, `isValidRedirect`, or similar, which is
* considered a barrier for purposes of URL redirection.
* considered a barrier guard for sanitizing untrusted URLs.
*/
class RedirectCheckBarrierGuard extends BarrierGuard, DataFlow::CallNode {
RedirectCheckBarrierGuard() {
this.getCalleeName().regexpMatch("(?i)(is_?)?(local_?url|valid_?redir(ect)?)")
}
override predicate checks(Expr e, boolean outcome) {
// `isLocalUrl(e)` is a barrier for `e` if it evaluates to `true`
getAnArgument().asExpr() = e and
outcome = true
}
}
class RedirectCheckBarrierGuardAsBarrierGuard extends RedirectCheckBarrierGuard, BarrierGuard { }
/**
* A check against a constant value, considered a barrier for redirection.
*/
class EqualityTestGuard extends BarrierGuard, DataFlow::EqualityTestNode {
DataFlow::Node url;
EqualityTestGuard() {
exists(this.getAnOperand().getStringValue()) and
(
url = this.getAnOperand()
or
exists(DataFlow::MethodCallNode mc | mc = this.getAnOperand() |
mc.getTarget().getName() = "Hostname" and
url = mc.getReceiver()
)
)
}
override predicate checks(Expr e, boolean outcome) {
e = url.asExpr() and this.eq(outcome, _, _)
}
}
/**
* A call to a regexp match function, considered as a barrier guard for unvalidated URLs.
* A call to a regexp match function, considered as a barrier guard for sanitizing untrusted URLs.
*
* This is overapproximate: we do not attempt to reason about the correctness of the regexp.
*/
class RegexpCheck extends BarrierGuard {
RegexpMatchFunction matchfn;
DataFlow::CallNode call;
class RegexpCheckAsBarrierGuard extends RegexpCheck, BarrierGuard { }
RegexpCheck() {
matchfn.getACall() = call and
this = matchfn.getResult().getNode(call).getASuccessor*()
}
override predicate checks(Expr e, boolean branch) {
e = matchfn.getValue().getNode(call).asExpr() and
(branch = false or branch = true)
}
}
/**
* A check against a constant value or the `Hostname` function,
* considered a barrier guard for url flow.
*/
class UrlCheckAsBarrierGuard extends UrlCheck, BarrierGuard { }
}
/** A sink for an open redirect, considered as a sink for safe URL flow. */

View File

@@ -59,7 +59,7 @@ module ReflectedXss {
not htmlTypeSpecified(body) and
(
exists(HTTP::HeaderWrite hw | hw = body.getResponseWriter().getAHeaderWrite() |
hw.definesHeader("content-type", _)
hw.getName().getStringValue().toLowerCase() = "content-type"
)
or
exists(DataFlow::CallNode call | call.getTarget().hasQualifiedName("fmt", "Fprintf") |
@@ -69,6 +69,14 @@ module ReflectedXss {
// - '%', which could be a format string.
call.getArgument(1).getStringValue().regexpMatch("^[^<%].*")
)
or
exists(DataFlow::Node pred | body = pred.getASuccessor*() |
// data starting with a character other than `<` cannot cause an HTML content type to be detected.
pred.getStringValue().regexpMatch("^[^<].*")
or
// json data cannot begin with `<`
exists(EncodingJson::MarshalFunction mf | pred = mf.getOutput().getNode(mf.getACall()))
)
)
}
@@ -95,4 +103,15 @@ module ReflectedXss {
)
}
}
/**
* A check against a constant value, considered a barrier for reflected XSS.
*/
class EqualityTestGuard extends SanitizerGuard, DataFlow::EqualityTestNode {
override predicate checks(Expr e, boolean outcome) {
this.getAnOperand().isConst() and
e = this.getAnOperand().asExpr() and
outcome = this.getPolarity()
}
}
}

View File

@@ -5,6 +5,7 @@
import go
import UrlConcatenation
import SafeUrlFlowCustomizations
import semmle.go.dataflow.BarrierGuardUtil
/** Provides classes and predicates for the request forgery query. */
module RequestForgery {
@@ -52,6 +53,19 @@ module RequestForgery {
override string getKind() { result = "URL" }
}
/**
* The URL of a WebSocket request, viewed as a sink for request forgery.
*/
class WebSocketCallAsSink extends Sink {
WebSocketRequestCall request;
WebSocketCallAsSink() { this = request.getRequestUrl() }
override DataFlow::Node getARequest() { result = request }
override string getKind() { result = "WebSocket URL" }
}
/**
* A value that is the result of prepending a string that prevents any value from controlling the
* host of a URL.
@@ -59,6 +73,29 @@ module RequestForgery {
private class HostnameSanitizer extends SanitizerEdge {
HostnameSanitizer() { hostnameSanitizingPrefixEdge(this, _) }
}
/**
* A call to a function called `isLocalUrl`, `isValidRedirect`, or similar, which is
* considered a barrier guard.
*/
class RedirectCheckBarrierGuardAsBarrierGuard extends RedirectCheckBarrierGuard, SanitizerGuard {
}
/**
* A call to a regexp match function, considered as a barrier guard for sanitizing untrusted URLs.
*
* This is overapproximate: we do not attempt to reason about the correctness of the regexp.
*/
class RegexpCheckAsBarrierGuard extends RegexpCheck, SanitizerGuard { }
/**
* An equality check comparing a data-flow node against a constant string, considered as
* a barrier guard for sanitizing untrusted URLs.
*
* Additionally, a check comparing `url.Hostname()` against a constant string is also
* considered a barrier guard for `url`.
*/
class UrlCheckAsBarrierGuard extends UrlCheck, SanitizerGuard { }
}
/** A sink for request forgery, considered as a sink for safe URL flow. */

View File

@@ -23,6 +23,10 @@ module SqlInjection {
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
NoSQL::isAdditionalMongoTaintStep(pred, succ)
}
override predicate isSanitizer(DataFlow::Node node) {
super.isSanitizer(node) or
node instanceof Sanitizer

View File

@@ -39,4 +39,9 @@ module SqlInjection {
class SqlQueryAsSink extends Sink {
SqlQueryAsSink() { this instanceof SQL::QueryString }
}
/** A NoSQL query, considered as a taint sink for SQL injection. */
class NoSqlQueryAsSink extends Sink {
NoSqlQueryAsSink() { this instanceof NoSQL::Query }
}
}

View File

@@ -0,0 +1,16 @@
/**
* @name Unexpected frontend error
* @description This query produces a list of all errors produced by the Go frontend
* during extraction, except for those occurring in files annotated with
* "// codeql test: expect frontend errors".
* @id go/unexpected-frontend-error
*/
import go
from Error e
where
not exists(Comment c | c.getFile() = e.getFile() |
c.getText().trim() = "codeql test: expect frontend errors"
)
select e

View File

@@ -0,0 +1,7 @@
package main
// Example file with a syntax error to demonstrate use of "expect frontend errors" directive
// codeql test: expect frontend errors
This is not a valid Go program

View File

@@ -0,0 +1,43 @@
edges
| email.go:24:10:24:17 | selection of Header : Header | email.go:27:56:27:67 | type conversion |
| email.go:34:21:34:31 | call to Referer : string | email.go:36:57:36:78 | type conversion |
| email.go:42:21:42:31 | call to Referer : string | email.go:45:3:45:7 | definition of write |
| email.go:51:21:51:31 | call to Referer : string | email.go:57:46:57:59 | untrustedInput |
| email.go:51:21:51:31 | call to Referer : string | email.go:58:52:58:65 | untrustedInput |
| email.go:63:21:63:31 | call to Referer : string | email.go:68:16:68:22 | content |
| email.go:73:21:73:31 | call to Referer : string | email.go:81:50:81:56 | content |
| email.go:73:21:73:31 | call to Referer : string | email.go:81:59:81:65 | content |
| email.go:73:21:73:31 | call to Referer : string | email.go:82:16:82:22 | content |
| email.go:87:21:87:31 | call to Referer : string | email.go:94:37:94:50 | untrustedInput |
| email.go:87:21:87:31 | call to Referer : string | email.go:98:16:98:23 | content2 |
nodes
| email.go:24:10:24:17 | selection of Header : Header | semmle.label | selection of Header : Header |
| email.go:27:56:27:67 | type conversion | semmle.label | type conversion |
| email.go:34:21:34:31 | call to Referer : string | semmle.label | call to Referer : string |
| email.go:36:57:36:78 | type conversion | semmle.label | type conversion |
| email.go:42:21:42:31 | call to Referer : string | semmle.label | call to Referer : string |
| email.go:45:3:45:7 | definition of write | semmle.label | definition of write |
| email.go:51:21:51:31 | call to Referer : string | semmle.label | call to Referer : string |
| email.go:57:46:57:59 | untrustedInput | semmle.label | untrustedInput |
| email.go:58:52:58:65 | untrustedInput | semmle.label | untrustedInput |
| email.go:63:21:63:31 | call to Referer : string | semmle.label | call to Referer : string |
| email.go:68:16:68:22 | content | semmle.label | content |
| email.go:73:21:73:31 | call to Referer : string | semmle.label | call to Referer : string |
| email.go:81:50:81:56 | content | semmle.label | content |
| email.go:81:59:81:65 | content | semmle.label | content |
| email.go:82:16:82:22 | content | semmle.label | content |
| email.go:87:21:87:31 | call to Referer : string | semmle.label | call to Referer : string |
| email.go:94:37:94:50 | untrustedInput | semmle.label | untrustedInput |
| email.go:98:16:98:23 | content2 | semmle.label | content2 |
#select
| email.go:27:56:27:67 | type conversion | email.go:24:10:24:17 | selection of Header : Header | email.go:27:56:27:67 | type conversion | Email content may contain $@. | email.go:24:10:24:17 | selection of Header | untrusted input |
| email.go:36:57:36:78 | type conversion | email.go:34:21:34:31 | call to Referer : string | email.go:36:57:36:78 | type conversion | Email content may contain $@. | email.go:34:21:34:31 | call to Referer | untrusted input |
| email.go:45:3:45:7 | definition of write | email.go:42:21:42:31 | call to Referer : string | email.go:45:3:45:7 | definition of write | Email content may contain $@. | email.go:42:21:42:31 | call to Referer | untrusted input |
| email.go:57:46:57:59 | untrustedInput | email.go:51:21:51:31 | call to Referer : string | email.go:57:46:57:59 | untrustedInput | Email content may contain $@. | email.go:51:21:51:31 | call to Referer | untrusted input |
| email.go:58:52:58:65 | untrustedInput | email.go:51:21:51:31 | call to Referer : string | email.go:58:52:58:65 | untrustedInput | Email content may contain $@. | email.go:51:21:51:31 | call to Referer | untrusted input |
| email.go:68:16:68:22 | content | email.go:63:21:63:31 | call to Referer : string | email.go:68:16:68:22 | content | Email content may contain $@. | email.go:63:21:63:31 | call to Referer | untrusted input |
| email.go:81:50:81:56 | content | email.go:73:21:73:31 | call to Referer : string | email.go:81:50:81:56 | content | Email content may contain $@. | email.go:73:21:73:31 | call to Referer | untrusted input |
| email.go:81:59:81:65 | content | email.go:73:21:73:31 | call to Referer : string | email.go:81:59:81:65 | content | Email content may contain $@. | email.go:73:21:73:31 | call to Referer | untrusted input |
| email.go:82:16:82:22 | content | email.go:73:21:73:31 | call to Referer : string | email.go:82:16:82:22 | content | Email content may contain $@. | email.go:73:21:73:31 | call to Referer | untrusted input |
| email.go:94:37:94:50 | untrustedInput | email.go:87:21:87:31 | call to Referer : string | email.go:94:37:94:50 | untrustedInput | Email content may contain $@. | email.go:87:21:87:31 | call to Referer | untrusted input |
| email.go:98:16:98:23 | content2 | email.go:87:21:87:31 | call to Referer : string | email.go:98:16:98:23 | content2 | Email content may contain $@. | email.go:87:21:87:31 | call to Referer | untrusted input |

View File

@@ -0,0 +1 @@
experimental/CWE-640/EmailInjection.ql

View File

@@ -0,0 +1,115 @@
package main
//go:generate depstubber -vendor github.com/sendgrid/sendgrid-go/helpers/mail "" NewEmail,NewSingleEmail,NewContent,NewV3Mail,NewV3MailInit
import (
"io"
"log"
"net/http"
"net/smtp"
sendgrid "github.com/sendgrid/sendgrid-go/helpers/mail"
)
// OK
func mailGood(w http.ResponseWriter, r *http.Request) {
host := config["Host"]
token := backend.getUserSecretResetToken(email)
body := "Click to reset password: " + host + "/" + token
smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(body))
}
// Not OK
func mail(w http.ResponseWriter, r *http.Request) {
host := r.Header.Get("Host")
token := backend.getUserSecretResetToken(email)
body := "Click to reset password: " + host + "/" + token
smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(body))
}
func main() {
// Not OK
http.HandleFunc("/ex0", func(w http.ResponseWriter, r *http.Request) {
untrustedInput := r.Referer()
smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(untrustedInput))
})
// Not OK
http.HandleFunc("/ex1", func(w http.ResponseWriter, r *http.Request) {
untrustedInput := r.Referer()
s, _ := smtp.Dial("test.test")
write, _ := s.Data()
io.WriteString(write, untrustedInput)
})
// Not OK
http.HandleFunc("/ex2", func(w http.ResponseWriter, r *http.Request) {
untrustedInput := r.Referer()
from := sendgrid.NewEmail("from", "from@from.com")
to := sendgrid.NewEmail("to", "to@to.com")
subject := "test"
body := "body"
sendgrid.NewSingleEmail(from, subject, to, untrustedInput, body)
sendgrid.NewSingleEmail(from, subject, to, body, untrustedInput)
})
// Not OK
http.HandleFunc("/ex3", func(w http.ResponseWriter, r *http.Request) {
untrustedInput := r.Referer()
content := sendgrid.NewContent("text/html", untrustedInput)
v := sendgrid.NewV3Mail()
v.AddContent(content)
})
// Not OK
http.HandleFunc("/ex4", func(w http.ResponseWriter, r *http.Request) {
untrustedInput := r.Referer()
from := sendgrid.NewEmail("from", "from@from.com")
to := sendgrid.NewEmail("to", "to@to.com")
subject := "test"
content := sendgrid.NewContent("text/html", untrustedInput)
v := sendgrid.NewV3MailInit(from, subject, to, content, content)
v.AddContent(content)
})
// Not OK
http.HandleFunc("/ex5", func(w http.ResponseWriter, r *http.Request) {
untrustedInput := r.Referer()
from := sendgrid.NewEmail("from", "from@from.com")
to := sendgrid.NewEmail("to", "to@to.com")
content := sendgrid.NewContent("text/html", "test")
v := sendgrid.NewV3MailInit(from, untrustedInput, to, content, content)
content2 := sendgrid.NewContent("text/html", untrustedInput)
v.AddContent(content2)
})
log.Println(http.ListenAndServe(":80", nil))
}
// Backend is an empty struct
type Backend struct{}
func (*Backend) getUserSecretResetToken(email string) string {
return ""
}
var email = "test@test.com"
var config map[string]string
var backend = &Backend{}

View File

@@ -0,0 +1,7 @@
module main
go 1.14
require (
github.com/sendgrid/sendgrid-go v3.5.0+incompatible
)

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013-2019 Twilio SendGrid, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,391 @@
// Code generated by depstubber. DO NOT EDIT.
// This is a simple stub for github.com/sendgrid/sendgrid-go/helpers/mail, strictly for use in testing.
// See the LICENSE file for information about the licensing of the original library.
// Source: github.com/sendgrid/sendgrid-go/helpers/mail (exports: ; functions: NewEmail,NewSingleEmail,NewContent,NewV3Mail,NewV3MailInit)
// Package mail is a stub of github.com/sendgrid/sendgrid-go/helpers/mail, generated by depstubber.
package mail
import ()
type Asm struct {
GroupID int
GroupsToDisplay []int
}
func (_ *Asm) AddGroupsToDisplay(_ ...int) *Asm {
return nil
}
func (_ *Asm) SetGroupID(_ int) *Asm {
return nil
}
type Attachment struct {
Content string
Type string
Name string
Filename string
Disposition string
ContentID string
}
func (_ *Attachment) SetContent(_ string) *Attachment {
return nil
}
func (_ *Attachment) SetContentID(_ string) *Attachment {
return nil
}
func (_ *Attachment) SetDisposition(_ string) *Attachment {
return nil
}
func (_ *Attachment) SetFilename(_ string) *Attachment {
return nil
}
func (_ *Attachment) SetType(_ string) *Attachment {
return nil
}
type BccSetting struct {
Enable *bool
Email string
}
func (_ *BccSetting) SetEmail(_ string) *BccSetting {
return nil
}
func (_ *BccSetting) SetEnable(_ bool) *BccSetting {
return nil
}
type ClickTrackingSetting struct {
Enable *bool
EnableText *bool
}
func (_ *ClickTrackingSetting) SetEnable(_ bool) *ClickTrackingSetting {
return nil
}
func (_ *ClickTrackingSetting) SetEnableText(_ bool) *ClickTrackingSetting {
return nil
}
type Content struct {
Type string
Value string
}
type Email struct {
Name string
Address string
}
type FooterSetting struct {
Enable *bool
Text string
Html string
}
func (_ *FooterSetting) SetEnable(_ bool) *FooterSetting {
return nil
}
func (_ *FooterSetting) SetHTML(_ string) *FooterSetting {
return nil
}
func (_ *FooterSetting) SetText(_ string) *FooterSetting {
return nil
}
type GaSetting struct {
Enable *bool
CampaignSource string
CampaignTerm string
CampaignContent string
CampaignName string
CampaignMedium string
}
func (_ *GaSetting) SetCampaignContent(_ string) *GaSetting {
return nil
}
func (_ *GaSetting) SetCampaignMedium(_ string) *GaSetting {
return nil
}
func (_ *GaSetting) SetCampaignName(_ string) *GaSetting {
return nil
}
func (_ *GaSetting) SetCampaignSource(_ string) *GaSetting {
return nil
}
func (_ *GaSetting) SetCampaignTerm(_ string) *GaSetting {
return nil
}
func (_ *GaSetting) SetEnable(_ bool) *GaSetting {
return nil
}
type MailSettings struct {
BCC *BccSetting
BypassListManagement *Setting
Footer *FooterSetting
SandboxMode *Setting
SpamCheckSetting *SpamCheckSetting
}
func (_ *MailSettings) SetBCC(_ *BccSetting) *MailSettings {
return nil
}
func (_ *MailSettings) SetBypassListManagement(_ *Setting) *MailSettings {
return nil
}
func (_ *MailSettings) SetFooter(_ *FooterSetting) *MailSettings {
return nil
}
func (_ *MailSettings) SetSandboxMode(_ *Setting) *MailSettings {
return nil
}
func (_ *MailSettings) SetSpamCheckSettings(_ *SpamCheckSetting) *MailSettings {
return nil
}
func NewContent(_ string, _ string) *Content {
return nil
}
func NewEmail(_ string, _ string) *Email {
return nil
}
func NewSingleEmail(_ *Email, _ string, _ *Email, _ string, _ string) *SGMailV3 {
return nil
}
func NewV3Mail() *SGMailV3 {
return nil
}
func NewV3MailInit(_ *Email, _ string, _ *Email, _ ...*Content) *SGMailV3 {
return nil
}
type OpenTrackingSetting struct {
Enable *bool
SubstitutionTag string
}
func (_ *OpenTrackingSetting) SetEnable(_ bool) *OpenTrackingSetting {
return nil
}
func (_ *OpenTrackingSetting) SetSubstitutionTag(_ string) *OpenTrackingSetting {
return nil
}
type Personalization struct {
To []*Email
CC []*Email
BCC []*Email
Subject string
Headers map[string]string
Substitutions map[string]string
CustomArgs map[string]string
DynamicTemplateData map[string]interface{}
Categories []string
SendAt int
}
func (_ *Personalization) AddBCCs(_ ...*Email) {}
func (_ *Personalization) AddCCs(_ ...*Email) {}
func (_ *Personalization) AddTos(_ ...*Email) {}
func (_ *Personalization) SetCustomArg(_ string, _ string) {}
func (_ *Personalization) SetDynamicTemplateData(_ string, _ interface{}) {}
func (_ *Personalization) SetHeader(_ string, _ string) {}
func (_ *Personalization) SetSendAt(_ int) {}
func (_ *Personalization) SetSubstitution(_ string, _ string) {}
type SGMailV3 struct {
From *Email
Subject string
Personalizations []*Personalization
Content []*Content
Attachments []*Attachment
TemplateID string
Sections map[string]string
Headers map[string]string
Categories []string
CustomArgs map[string]string
SendAt int
BatchID string
Asm *Asm
IPPoolID string
MailSettings *MailSettings
TrackingSettings *TrackingSettings
ReplyTo *Email
}
func (_ *SGMailV3) AddAttachment(_ ...*Attachment) *SGMailV3 {
return nil
}
func (_ *SGMailV3) AddCategories(_ ...string) *SGMailV3 {
return nil
}
func (_ *SGMailV3) AddContent(_ ...*Content) *SGMailV3 {
return nil
}
func (_ *SGMailV3) AddPersonalizations(_ ...*Personalization) *SGMailV3 {
return nil
}
func (_ *SGMailV3) AddSection(_ string, _ string) *SGMailV3 {
return nil
}
func (_ *SGMailV3) SetASM(_ *Asm) *SGMailV3 {
return nil
}
func (_ *SGMailV3) SetBatchID(_ string) *SGMailV3 {
return nil
}
func (_ *SGMailV3) SetCustomArg(_ string, _ string) *SGMailV3 {
return nil
}
func (_ *SGMailV3) SetFrom(_ *Email) *SGMailV3 {
return nil
}
func (_ *SGMailV3) SetHeader(_ string, _ string) *SGMailV3 {
return nil
}
func (_ *SGMailV3) SetIPPoolID(_ string) *SGMailV3 {
return nil
}
func (_ *SGMailV3) SetMailSettings(_ *MailSettings) *SGMailV3 {
return nil
}
func (_ *SGMailV3) SetReplyTo(_ *Email) *SGMailV3 {
return nil
}
func (_ *SGMailV3) SetSendAt(_ int) *SGMailV3 {
return nil
}
func (_ *SGMailV3) SetTemplateID(_ string) *SGMailV3 {
return nil
}
func (_ *SGMailV3) SetTrackingSettings(_ *TrackingSettings) *SGMailV3 {
return nil
}
type SandboxModeSetting struct {
Enable *bool
ForwardSpam *bool
SpamCheck *SpamCheckSetting
}
type Setting struct {
Enable *bool
}
type SpamCheckSetting struct {
Enable *bool
SpamThreshold int
PostToURL string
}
func (_ *SpamCheckSetting) SetEnable(_ bool) *SpamCheckSetting {
return nil
}
func (_ *SpamCheckSetting) SetPostToURL(_ string) *SpamCheckSetting {
return nil
}
func (_ *SpamCheckSetting) SetSpamThreshold(_ int) *SpamCheckSetting {
return nil
}
type SubscriptionTrackingSetting struct {
Enable *bool
Text string
Html string
SubstitutionTag string
}
func (_ *SubscriptionTrackingSetting) SetEnable(_ bool) *SubscriptionTrackingSetting {
return nil
}
func (_ *SubscriptionTrackingSetting) SetHTML(_ string) *SubscriptionTrackingSetting {
return nil
}
func (_ *SubscriptionTrackingSetting) SetSubstitutionTag(_ string) *SubscriptionTrackingSetting {
return nil
}
func (_ *SubscriptionTrackingSetting) SetText(_ string) *SubscriptionTrackingSetting {
return nil
}
type TrackingSettings struct {
ClickTracking *ClickTrackingSetting
OpenTracking *OpenTrackingSetting
SubscriptionTracking *SubscriptionTrackingSetting
GoogleAnalytics *GaSetting
BCC *BccSetting
BypassListManagement *Setting
Footer *FooterSetting
SandboxMode *SandboxModeSetting
}
func (_ *TrackingSettings) SetClickTracking(_ *ClickTrackingSetting) *TrackingSettings {
return nil
}
func (_ *TrackingSettings) SetGoogleAnalytics(_ *GaSetting) *TrackingSettings {
return nil
}
func (_ *TrackingSettings) SetOpenTracking(_ *OpenTrackingSetting) *TrackingSettings {
return nil
}
func (_ *TrackingSettings) SetSubscriptionTracking(_ *SubscriptionTrackingSetting) *TrackingSettings {
return nil
}

View File

@@ -0,0 +1,3 @@
# github.com/sendgrid/sendgrid-go v3.5.0+incompatible
## explicit
github.com/sendgrid/sendgrid-go

View File

@@ -0,0 +1,83 @@
edges
| IncorrectNumericConversion.go:26:14:26:28 | call to Atoi : tuple type | IncorrectNumericConversion.go:35:41:35:50 | type conversion |
| IncorrectNumericConversion.go:53:18:53:47 | call to ParseFloat : tuple type | IncorrectNumericConversion.go:57:7:57:19 | type conversion |
| IncorrectNumericConversion.go:60:18:60:47 | call to ParseFloat : tuple type | IncorrectNumericConversion.go:64:7:64:19 | type conversion |
| IncorrectNumericConversion.go:69:18:69:49 | call to ParseInt : tuple type | IncorrectNumericConversion.go:73:7:73:18 | type conversion |
| IncorrectNumericConversion.go:76:18:76:49 | call to ParseInt : tuple type | IncorrectNumericConversion.go:80:7:80:19 | type conversion |
| IncorrectNumericConversion.go:83:18:83:49 | call to ParseInt : tuple type | IncorrectNumericConversion.go:87:7:87:19 | type conversion |
| IncorrectNumericConversion.go:90:18:90:48 | call to ParseInt : tuple type | IncorrectNumericConversion.go:94:7:94:19 | type conversion |
| IncorrectNumericConversion.go:99:18:99:50 | call to ParseUint : tuple type | IncorrectNumericConversion.go:103:7:103:18 | type conversion |
| IncorrectNumericConversion.go:106:18:106:50 | call to ParseUint : tuple type | IncorrectNumericConversion.go:110:7:110:19 | type conversion |
| IncorrectNumericConversion.go:113:18:113:50 | call to ParseUint : tuple type | IncorrectNumericConversion.go:117:7:117:19 | type conversion |
| IncorrectNumericConversion.go:120:18:120:49 | call to ParseUint : tuple type | IncorrectNumericConversion.go:124:7:124:19 | type conversion |
| IncorrectNumericConversion.go:208:18:208:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:212:7:212:18 | type conversion |
| IncorrectNumericConversion.go:215:18:215:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:219:7:219:19 | type conversion |
| IncorrectNumericConversion.go:222:18:222:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:226:7:226:19 | type conversion |
| IncorrectNumericConversion.go:229:18:229:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:233:7:233:19 | type conversion |
| IncorrectNumericConversion.go:236:18:236:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:240:7:240:20 | type conversion |
| IncorrectNumericConversion.go:243:18:243:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:247:7:247:20 | type conversion |
| IncorrectNumericConversion.go:250:18:250:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:254:7:254:21 | type conversion |
| IncorrectNumericConversion.go:257:18:257:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:262:7:262:18 | type conversion |
| IncorrectNumericConversion.go:266:18:266:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:270:7:270:23 | type conversion |
nodes
| IncorrectNumericConversion.go:26:14:26:28 | call to Atoi : tuple type | semmle.label | call to Atoi : tuple type |
| IncorrectNumericConversion.go:35:41:35:50 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:53:18:53:47 | call to ParseFloat : tuple type | semmle.label | call to ParseFloat : tuple type |
| IncorrectNumericConversion.go:57:7:57:19 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:60:18:60:47 | call to ParseFloat : tuple type | semmle.label | call to ParseFloat : tuple type |
| IncorrectNumericConversion.go:64:7:64:19 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:69:18:69:49 | call to ParseInt : tuple type | semmle.label | call to ParseInt : tuple type |
| IncorrectNumericConversion.go:73:7:73:18 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:76:18:76:49 | call to ParseInt : tuple type | semmle.label | call to ParseInt : tuple type |
| IncorrectNumericConversion.go:80:7:80:19 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:83:18:83:49 | call to ParseInt : tuple type | semmle.label | call to ParseInt : tuple type |
| IncorrectNumericConversion.go:87:7:87:19 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:90:18:90:48 | call to ParseInt : tuple type | semmle.label | call to ParseInt : tuple type |
| IncorrectNumericConversion.go:94:7:94:19 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:99:18:99:50 | call to ParseUint : tuple type | semmle.label | call to ParseUint : tuple type |
| IncorrectNumericConversion.go:103:7:103:18 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:106:18:106:50 | call to ParseUint : tuple type | semmle.label | call to ParseUint : tuple type |
| IncorrectNumericConversion.go:110:7:110:19 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:113:18:113:50 | call to ParseUint : tuple type | semmle.label | call to ParseUint : tuple type |
| IncorrectNumericConversion.go:117:7:117:19 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:120:18:120:49 | call to ParseUint : tuple type | semmle.label | call to ParseUint : tuple type |
| IncorrectNumericConversion.go:124:7:124:19 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:208:18:208:36 | call to Atoi : tuple type | semmle.label | call to Atoi : tuple type |
| IncorrectNumericConversion.go:212:7:212:18 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:215:18:215:36 | call to Atoi : tuple type | semmle.label | call to Atoi : tuple type |
| IncorrectNumericConversion.go:219:7:219:19 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:222:18:222:36 | call to Atoi : tuple type | semmle.label | call to Atoi : tuple type |
| IncorrectNumericConversion.go:226:7:226:19 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:229:18:229:36 | call to Atoi : tuple type | semmle.label | call to Atoi : tuple type |
| IncorrectNumericConversion.go:233:7:233:19 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:236:18:236:36 | call to Atoi : tuple type | semmle.label | call to Atoi : tuple type |
| IncorrectNumericConversion.go:240:7:240:20 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:243:18:243:36 | call to Atoi : tuple type | semmle.label | call to Atoi : tuple type |
| IncorrectNumericConversion.go:247:7:247:20 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:250:18:250:36 | call to Atoi : tuple type | semmle.label | call to Atoi : tuple type |
| IncorrectNumericConversion.go:254:7:254:21 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:257:18:257:36 | call to Atoi : tuple type | semmle.label | call to Atoi : tuple type |
| IncorrectNumericConversion.go:262:7:262:18 | type conversion | semmle.label | type conversion |
| IncorrectNumericConversion.go:266:18:266:36 | call to Atoi : tuple type | semmle.label | call to Atoi : tuple type |
| IncorrectNumericConversion.go:270:7:270:23 | type conversion | semmle.label | type conversion |
#select
| IncorrectNumericConversion.go:26:14:26:28 | call to Atoi | IncorrectNumericConversion.go:26:14:26:28 | call to Atoi : tuple type | IncorrectNumericConversion.go:35:41:35:50 | type conversion | Incorrect conversion of a (arch-dependent)-bit number from strconv.Atoi result to a lower bit size type int32 |
| IncorrectNumericConversion.go:53:18:53:47 | call to ParseFloat | IncorrectNumericConversion.go:53:18:53:47 | call to ParseFloat : tuple type | IncorrectNumericConversion.go:57:7:57:19 | type conversion | Incorrect conversion of a 32-bit number from strconv.ParseFloat result to a lower bit size type int16 |
| IncorrectNumericConversion.go:60:18:60:47 | call to ParseFloat | IncorrectNumericConversion.go:60:18:60:47 | call to ParseFloat : tuple type | IncorrectNumericConversion.go:64:7:64:19 | type conversion | Incorrect conversion of a 64-bit number from strconv.ParseFloat result to a lower bit size type int32 |
| IncorrectNumericConversion.go:69:18:69:49 | call to ParseInt | IncorrectNumericConversion.go:69:18:69:49 | call to ParseInt : tuple type | IncorrectNumericConversion.go:73:7:73:18 | type conversion | Incorrect conversion of a 16-bit number from strconv.ParseInt result to a lower bit size type int8 |
| IncorrectNumericConversion.go:76:18:76:49 | call to ParseInt | IncorrectNumericConversion.go:76:18:76:49 | call to ParseInt : tuple type | IncorrectNumericConversion.go:80:7:80:19 | type conversion | Incorrect conversion of a 32-bit number from strconv.ParseInt result to a lower bit size type int16 |
| IncorrectNumericConversion.go:83:18:83:49 | call to ParseInt | IncorrectNumericConversion.go:83:18:83:49 | call to ParseInt : tuple type | IncorrectNumericConversion.go:87:7:87:19 | type conversion | Incorrect conversion of a 64-bit number from strconv.ParseInt result to a lower bit size type int32 |
| IncorrectNumericConversion.go:90:18:90:48 | call to ParseInt | IncorrectNumericConversion.go:90:18:90:48 | call to ParseInt : tuple type | IncorrectNumericConversion.go:94:7:94:19 | type conversion | Incorrect conversion of a (arch-dependent)-bit number from strconv.ParseInt result to a lower bit size type int32 |
| IncorrectNumericConversion.go:99:18:99:50 | call to ParseUint | IncorrectNumericConversion.go:99:18:99:50 | call to ParseUint : tuple type | IncorrectNumericConversion.go:103:7:103:18 | type conversion | Incorrect conversion of a 16-bit number from strconv.ParseUint result to a lower bit size type int8 |
| IncorrectNumericConversion.go:106:18:106:50 | call to ParseUint | IncorrectNumericConversion.go:106:18:106:50 | call to ParseUint : tuple type | IncorrectNumericConversion.go:110:7:110:19 | type conversion | Incorrect conversion of a 32-bit number from strconv.ParseUint result to a lower bit size type int16 |
| IncorrectNumericConversion.go:113:18:113:50 | call to ParseUint | IncorrectNumericConversion.go:113:18:113:50 | call to ParseUint : tuple type | IncorrectNumericConversion.go:117:7:117:19 | type conversion | Incorrect conversion of a 64-bit number from strconv.ParseUint result to a lower bit size type int32 |
| IncorrectNumericConversion.go:120:18:120:49 | call to ParseUint | IncorrectNumericConversion.go:120:18:120:49 | call to ParseUint : tuple type | IncorrectNumericConversion.go:124:7:124:19 | type conversion | Incorrect conversion of a (arch-dependent)-bit number from strconv.ParseUint result to a lower bit size type int32 |
| IncorrectNumericConversion.go:208:18:208:36 | call to Atoi | IncorrectNumericConversion.go:208:18:208:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:212:7:212:18 | type conversion | Incorrect conversion of a (arch-dependent)-bit number from strconv.Atoi result to a lower bit size type int8 |
| IncorrectNumericConversion.go:215:18:215:36 | call to Atoi | IncorrectNumericConversion.go:215:18:215:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:219:7:219:19 | type conversion | Incorrect conversion of a (arch-dependent)-bit number from strconv.Atoi result to a lower bit size type int16 |
| IncorrectNumericConversion.go:222:18:222:36 | call to Atoi | IncorrectNumericConversion.go:222:18:222:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:226:7:226:19 | type conversion | Incorrect conversion of a (arch-dependent)-bit number from strconv.Atoi result to a lower bit size type int32 |
| IncorrectNumericConversion.go:229:18:229:36 | call to Atoi | IncorrectNumericConversion.go:229:18:229:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:233:7:233:19 | type conversion | Incorrect conversion of a (arch-dependent)-bit number from strconv.Atoi result to a lower bit size type uint8 |
| IncorrectNumericConversion.go:236:18:236:36 | call to Atoi | IncorrectNumericConversion.go:236:18:236:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:240:7:240:20 | type conversion | Incorrect conversion of a (arch-dependent)-bit number from strconv.Atoi result to a lower bit size type uint16 |
| IncorrectNumericConversion.go:243:18:243:36 | call to Atoi | IncorrectNumericConversion.go:243:18:243:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:247:7:247:20 | type conversion | Incorrect conversion of a (arch-dependent)-bit number from strconv.Atoi result to a lower bit size type uint32 |
| IncorrectNumericConversion.go:250:18:250:36 | call to Atoi | IncorrectNumericConversion.go:250:18:250:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:254:7:254:21 | type conversion | Incorrect conversion of a (arch-dependent)-bit number from strconv.Atoi result to a lower bit size type float32 |
| IncorrectNumericConversion.go:257:18:257:36 | call to Atoi | IncorrectNumericConversion.go:257:18:257:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:262:7:262:18 | type conversion | Incorrect conversion of a (arch-dependent)-bit number from strconv.Atoi result to a lower bit size type uint8 |
| IncorrectNumericConversion.go:266:18:266:36 | call to Atoi | IncorrectNumericConversion.go:266:18:266:36 | call to Atoi : tuple type | IncorrectNumericConversion.go:270:7:270:23 | type conversion | Incorrect conversion of a (arch-dependent)-bit number from strconv.Atoi result to a lower bit size type int16 |

View File

@@ -0,0 +1,355 @@
package main
import (
"math"
"strconv"
)
func main() {
}
type Something struct {
}
type Config struct {
}
type Registry struct {
}
func LookupTarget(conf *Config, num int32) (int32, error) {
return 567, nil
}
func LookupNumberByName(reg *Registry, name string) (int32, error) {
return 567, nil
}
func lab(s string) (*Something, error) {
num, err := strconv.Atoi(s)
if err != nil {
number, err := LookupNumberByName(&Registry{}, s)
if err != nil {
return nil, err
}
num = int(number)
}
target, err := LookupTarget(&Config{}, int32(num))
if err != nil {
return nil, err
}
// convert the resolved target number back to a string
s = strconv.Itoa(int(target))
return nil, nil
}
const CustomMaxInt16 = 1<<15 - 1
type CustomInt int16
func badParseFloat() {
{
parsed, err := strconv.ParseFloat("1.32", 32)
if err != nil {
panic(err)
}
_ = int16(parsed)
}
{
parsed, err := strconv.ParseFloat("1.32", 64)
if err != nil {
panic(err)
}
_ = int32(parsed)
}
}
func badParseInt() {
{
parsed, err := strconv.ParseInt("3456", 10, 16)
if err != nil {
panic(err)
}
_ = int8(parsed)
}
{
parsed, err := strconv.ParseInt("3456", 10, 32)
if err != nil {
panic(err)
}
_ = int16(parsed)
}
{
parsed, err := strconv.ParseInt("3456", 10, 64)
if err != nil {
panic(err)
}
_ = int32(parsed)
}
{
parsed, err := strconv.ParseInt("3456", 10, 0)
if err != nil {
panic(err)
}
_ = int32(parsed)
}
}
func badParseUint() {
{
parsed, err := strconv.ParseUint("3456", 10, 16)
if err != nil {
panic(err)
}
_ = int8(parsed)
}
{
parsed, err := strconv.ParseUint("3456", 10, 32)
if err != nil {
panic(err)
}
_ = int16(parsed)
}
{
parsed, err := strconv.ParseUint("3456", 10, 64)
if err != nil {
panic(err)
}
_ = int32(parsed)
}
{
parsed, err := strconv.ParseUint("3456", 10, 0)
if err != nil {
panic(err)
}
_ = int32(parsed)
}
}
func goodParseFloat() {
{
parsed, err := strconv.ParseFloat("1.32", 32)
if err != nil {
panic(err)
}
_ = int32(parsed)
}
{
parsed, err := strconv.ParseFloat("1.32", 64)
if err != nil {
panic(err)
}
_ = int64(parsed)
}
}
func goodParseInt() {
{
parsed, err := strconv.ParseInt("3456", 10, 16)
if err != nil {
panic(err)
}
_ = int16(parsed)
}
{
parsed, err := strconv.ParseInt("3456", 10, 32)
if err != nil {
panic(err)
}
_ = int32(parsed)
}
{
parsed, err := strconv.ParseInt("3456", 10, 64)
if err != nil {
panic(err)
}
_ = int64(parsed)
}
{
parsed, err := strconv.ParseInt("3456", 10, 0)
if err != nil {
panic(err)
}
_ = int64(parsed)
}
}
func goodParseUint() {
{
parsed, err := strconv.ParseUint("3456", 10, 16)
if err != nil {
panic(err)
}
_ = int16(parsed)
}
{
parsed, err := strconv.ParseUint("3456", 10, 32)
if err != nil {
panic(err)
}
_ = int32(parsed)
}
{
parsed, err := strconv.ParseUint("3456", 10, 64)
if err != nil {
panic(err)
}
_ = int64(parsed)
}
{
parsed, err := strconv.ParseUint("3456", 10, 0)
if err != nil {
panic(err)
}
_ = int64(parsed)
}
}
// these should be caught:
func upperBoundIsNOTChecked(input string) {
{
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
_ = int8(parsed)
}
{
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
_ = int16(parsed)
}
{
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
_ = int32(parsed)
}
{
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
_ = uint8(parsed)
}
{
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
_ = uint16(parsed)
}
{
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
_ = uint32(parsed)
}
{
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
_ = float32(parsed)
}
{
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
// NOTE: byte is uint8
_ = byte(parsed)
}
{
// using custom type:
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
_ = CustomInt(parsed)
}
}
// these should NOT be caught:
func upperBoundIsChecked(input string) {
{
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
if parsed < math.MaxInt8 {
_ = int8(parsed)
}
}
{
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
if parsed < math.MaxInt16 {
_ = int16(parsed)
}
}
{
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
if parsed > 0 {
_ = int32(parsed)
}
}
{
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
if parsed < math.MaxInt32 {
_ = int32(parsed)
}
}
{
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
if parsed < math.MaxUint8 {
_ = uint8(parsed)
}
}
{
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
if parsed < math.MaxUint16 {
_ = uint16(parsed)
}
}
{
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
if parsed < math.MaxUint8 {
_ = byte(parsed)
}
}
{ // multiple `and` conditions
parsed, err := strconv.Atoi(input)
if err == nil && 1 == 1 && parsed < math.MaxInt8 {
_ = int8(parsed)
}
}
{ // custom maxInt16
parsed, err := strconv.Atoi(input)
if err != nil {
panic(err)
}
if parsed < CustomMaxInt16 {
_ = int16(parsed)
}
}
}

View File

@@ -0,0 +1 @@
experimental/CWE-681/IncorrectNumericConversion.ql

Some files were not shown because too many files have changed in this diff Show More