mirror of
https://github.com/github/codeql.git
synced 2026-01-30 06:42:57 +01:00
Merge branch 'master' into rc/1.24
This commit is contained in:
11
.devcontainer/devcontainer.json
Normal file
11
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extensions": [
|
||||
"github.vscode-codeql",
|
||||
"slevesque.vscode-zipexplorer"
|
||||
],
|
||||
"settings": {
|
||||
"codeQL.experimentalBqrsParsing": true,
|
||||
"codeQL.experimentalFeatures": true,
|
||||
"codeQL.runningQueries.debug": true
|
||||
}
|
||||
}
|
||||
8
.github/workflows/codeqltest.yml
vendored
8
.github/workflows/codeqltest.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -22,3 +22,4 @@ tools/linux64
|
||||
tools/osx64
|
||||
tools/win64
|
||||
tools/tokenizer.jar
|
||||
main
|
||||
|
||||
13
Makefile
13
Makefile
@@ -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
|
||||
|
||||
10
README.md
10
README.md
@@ -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.
|
||||
|
||||
2
change-notes/2020-05-01-bad-redirect-check.md
Normal file
2
change-notes/2020-05-01-bad-redirect-check.md
Normal 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.
|
||||
2
change-notes/2020-05-01-macaron-model.md
Normal file
2
change-notes/2020-05-01-macaron-model.md
Normal 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.
|
||||
2
change-notes/2020-05-05-clear-text-logging.md
Normal file
2
change-notes/2020-05-05-clear-text-logging.md
Normal 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.
|
||||
3
change-notes/2020-05-05-mux-model.md
Normal file
3
change-notes/2020-05-05-mux-model.md
Normal 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.
|
||||
3
change-notes/2020-05-07-update-data-flow.md
Normal file
3
change-notes/2020-05-07-update-data-flow.md
Normal 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.
|
||||
3
change-notes/2020-05-11-reflected-xss.md
Normal file
3
change-notes/2020-05-11-reflected-xss.md
Normal 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.
|
||||
4
change-notes/2020-05-12-tainted-path.md
Normal file
4
change-notes/2020-05-12-tainted-path.md
Normal 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.
|
||||
3
change-notes/2020-05-13-io-model.md
Normal file
3
change-notes/2020-05-13-io-model.md
Normal 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.
|
||||
2
change-notes/2020-05-18-redundant-recover.md
Normal file
2
change-notes/2020-05-18-redundant-recover.md
Normal 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.
|
||||
3
change-notes/2020-05-20-mongodb-model.md
Normal file
3
change-notes/2020-05-20-mongodb-model.md
Normal 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.
|
||||
3
change-notes/2020-05-22-websocket-model.md
Normal file
3
change-notes/2020-05-22-websocket-model.md
Normal 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.
|
||||
2
change-notes/2020-05-29-open-redirect.md
Normal file
2
change-notes/2020-05-29-open-redirect.md
Normal 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.
|
||||
@@ -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)
|
||||
|
||||
31
extractor/cli/go-gen-dbscheme/go-gen-dbscheme.go
Normal file
31
extractor/cli/go-gen-dbscheme/go-gen-dbscheme.go
Normal 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)
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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) {
|
||||
|
||||
54
ql/src/RedundantCode/RedundantRecover.qhelp
Normal file
54
ql/src/RedundantCode/RedundantRecover.qhelp
Normal 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>
|
||||
33
ql/src/RedundantCode/RedundantRecover.ql
Normal file
33
ql/src/RedundantCode/RedundantRecover.ql
Normal 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"
|
||||
16
ql/src/RedundantCode/RedundantRecover1.go
Normal file
16
ql/src/RedundantCode/RedundantRecover1.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func callRecover1() {
|
||||
if recover() != nil {
|
||||
fmt.Printf("recovered")
|
||||
}
|
||||
}
|
||||
|
||||
func fun1() {
|
||||
defer func() {
|
||||
callRecover1()
|
||||
}()
|
||||
panic("1")
|
||||
}
|
||||
14
ql/src/RedundantCode/RedundantRecover1Good.go
Normal file
14
ql/src/RedundantCode/RedundantRecover1Good.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func callRecover1Good() {
|
||||
if recover() != nil {
|
||||
fmt.Printf("recovered")
|
||||
}
|
||||
}
|
||||
|
||||
func fun1Good() {
|
||||
defer callRecover1Good()
|
||||
panic("1")
|
||||
}
|
||||
6
ql/src/RedundantCode/RedundantRecover2.go
Normal file
6
ql/src/RedundantCode/RedundantRecover2.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package main
|
||||
|
||||
func fun2() {
|
||||
defer recover()
|
||||
panic("2")
|
||||
}
|
||||
6
ql/src/RedundantCode/RedundantRecover2Good.go
Normal file
6
ql/src/RedundantCode/RedundantRecover2Good.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package main
|
||||
|
||||
func fun2Good() {
|
||||
defer func() { recover() }()
|
||||
panic("2")
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
4
ql/src/codeql-suites/go-security-and-quality.qls
Normal file
4
ql/src/codeql-suites/go-security-and-quality.qls
Normal 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
|
||||
4
ql/src/codeql-suites/go-security-extended.qls
Normal file
4
ql/src/codeql-suites/go-security-extended.qls
Normal file
@@ -0,0 +1,4 @@
|
||||
- description: Security-extended queries for Go
|
||||
- qlpack: codeql-go
|
||||
- apply: security-extended-selectors.yml
|
||||
from: codeql-suite-helpers
|
||||
13
ql/src/experimental/CWE-640/EmailBad.go
Normal file
13
ql/src/experimental/CWE-640/EmailBad.go
Normal 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))
|
||||
}
|
||||
13
ql/src/experimental/CWE-640/EmailGood.go
Normal file
13
ql/src/experimental/CWE-640/EmailGood.go
Normal 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))
|
||||
}
|
||||
46
ql/src/experimental/CWE-640/EmailInjection.qhelp
Normal file
46
ql/src/experimental/CWE-640/EmailInjection.qhelp
Normal 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>
|
||||
19
ql/src/experimental/CWE-640/EmailInjection.ql
Normal file
19
ql/src/experimental/CWE-640/EmailInjection.ql
Normal 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"
|
||||
29
ql/src/experimental/CWE-640/EmailInjection.qll
Normal file
29
ql/src/experimental/CWE-640/EmailInjection.qll
Normal 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 }
|
||||
}
|
||||
}
|
||||
30
ql/src/experimental/CWE-640/EmailInjectionCustomizations.qll
Normal file
30
ql/src/experimental/CWE-640/EmailInjectionCustomizations.qll
Normal 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 }
|
||||
}
|
||||
}
|
||||
20
ql/src/experimental/CWE-681/IncorrectNumericConversion.go
Normal file
20
ql/src/experimental/CWE-681/IncorrectNumericConversion.go
Normal 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)
|
||||
}
|
||||
65
ql/src/experimental/CWE-681/IncorrectNumericConversion.qhelp
Normal file
65
ql/src/experimental/CWE-681/IncorrectNumericConversion.qhelp
Normal 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>
|
||||
266
ql/src/experimental/CWE-681/IncorrectNumericConversion.ql
Normal file
266
ql/src/experimental/CWE-681/IncorrectNumericConversion.ql
Normal 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()
|
||||
@@ -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
|
||||
}
|
||||
31
ql/src/experimental/CWE-807/SensitiveConditionBypass.qhelp
Normal file
31
ql/src/experimental/CWE-807/SensitiveConditionBypass.qhelp
Normal 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>
|
||||
31
ql/src/experimental/CWE-807/SensitiveConditionBypass.ql
Normal file
31
ql/src/experimental/CWE-807/SensitiveConditionBypass.ql
Normal 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."
|
||||
68
ql/src/experimental/CWE-807/SensitiveConditionBypass.qll
Normal file
68
ql/src/experimental/CWE-807/SensitiveConditionBypass.qll
Normal 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
10
ql/src/experimental/CWE-807/SensitiveConditionBypassBad.go
Normal file
10
ql/src/experimental/CWE-807/SensitiveConditionBypassBad.go
Normal 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()
|
||||
}
|
||||
}
|
||||
26
ql/src/experimental/CWE-840/ConditionalBypass.qhelp
Normal file
26
ql/src/experimental/CWE-840/ConditionalBypass.qhelp
Normal 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>
|
||||
40
ql/src/experimental/CWE-840/ConditionalBypass.ql
Normal file
40
ql/src/experimental/CWE-840/ConditionalBypass.ql
Normal 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"
|
||||
12
ql/src/experimental/CWE-840/ConditionalBypassBad.go
Normal file
12
ql/src/experimental/CWE-840/ConditionalBypassBad.go
Normal 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
|
||||
}
|
||||
}
|
||||
12
ql/src/experimental/CWE-840/ConditionalBypassGood.go
Normal file
12
ql/src/experimental/CWE-840/ConditionalBypassGood.go
Normal 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
|
||||
}
|
||||
}
|
||||
169
ql/src/experimental/frameworks/Gin.qll
Normal file
169
ql/src/experimental/frameworks/Gin.qll
Normal 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)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
19
ql/src/localDefinitions.ql
Normal file
19
ql/src/localDefinitions.ql
Normal 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
19
ql/src/localReferences.ql
Normal 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"
|
||||
@@ -6,6 +6,14 @@ import go
|
||||
|
||||
/**
|
||||
* A code comment.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* <pre>
|
||||
* // a line comment
|
||||
* /* a block
|
||||
* comment */
|
||||
* </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 */
|
||||
*
|
||||
* /* a block
|
||||
* comment */
|
||||
* /* another block comment */
|
||||
* </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>*/</code>.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* <pre>
|
||||
* /* a block
|
||||
* comment */
|
||||
* </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>*/</code>.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* <pre>
|
||||
* /* a block
|
||||
* comment */
|
||||
* </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() {
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
|
||||
53
ql/src/semmle/go/Errors.qll
Normal file
53
ql/src/semmle/go/Errors.qll
Normal 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
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) }
|
||||
|
||||
|
||||
68
ql/src/semmle/go/dataflow/BarrierGuardUtil.qll
Normal file
68
ql/src/semmle/go/dataflow/BarrierGuardUtil.qll
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() }
|
||||
|
||||
|
||||
112
ql/src/semmle/go/frameworks/Email.qll
Normal file
112
ql/src/semmle/go/frameworks/Email.qll
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
30
ql/src/semmle/go/frameworks/Macaron.qll
Normal file
30
ql/src/semmle/go/frameworks/Macaron.qll
Normal 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() }
|
||||
}
|
||||
}
|
||||
15
ql/src/semmle/go/frameworks/Mux.qll
Normal file
15
ql/src/semmle/go/frameworks/Mux.qll
Normal 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") }
|
||||
}
|
||||
}
|
||||
120
ql/src/semmle/go/frameworks/NoSQL.qll
Normal file
120
ql/src/semmle/go/frameworks/NoSQL.qll
Normal 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"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
274
ql/src/semmle/go/frameworks/WebSocket.qll
Normal file
274
ql/src/semmle/go/frameworks/WebSocket.qll
Normal 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) }
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
16
ql/test/consistency/UnexpectedFrontendErrors.ql
Normal file
16
ql/test/consistency/UnexpectedFrontendErrors.ql
Normal 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
|
||||
7
ql/test/consistency/test.go
Normal file
7
ql/test/consistency/test.go
Normal 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
|
||||
43
ql/test/experimental/CWE-640/EmailInjection.expected
Normal file
43
ql/test/experimental/CWE-640/EmailInjection.expected
Normal 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 |
|
||||
1
ql/test/experimental/CWE-640/EmailInjection.qlref
Normal file
1
ql/test/experimental/CWE-640/EmailInjection.qlref
Normal file
@@ -0,0 +1 @@
|
||||
experimental/CWE-640/EmailInjection.ql
|
||||
115
ql/test/experimental/CWE-640/email.go
Normal file
115
ql/test/experimental/CWE-640/email.go
Normal 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{}
|
||||
7
ql/test/experimental/CWE-640/go.mod
Normal file
7
ql/test/experimental/CWE-640/go.mod
Normal file
@@ -0,0 +1,7 @@
|
||||
module main
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/sendgrid/sendgrid-go v3.5.0+incompatible
|
||||
)
|
||||
21
ql/test/experimental/CWE-640/vendor/github.com/sendgrid/sendgrid-go/helpers/LICENSE
generated
vendored
Normal file
21
ql/test/experimental/CWE-640/vendor/github.com/sendgrid/sendgrid-go/helpers/LICENSE
generated
vendored
Normal 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.
|
||||
391
ql/test/experimental/CWE-640/vendor/github.com/sendgrid/sendgrid-go/helpers/mail/stub.go
generated
vendored
Normal file
391
ql/test/experimental/CWE-640/vendor/github.com/sendgrid/sendgrid-go/helpers/mail/stub.go
generated
vendored
Normal 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
|
||||
}
|
||||
3
ql/test/experimental/CWE-640/vendor/modules.txt
vendored
Normal file
3
ql/test/experimental/CWE-640/vendor/modules.txt
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# github.com/sendgrid/sendgrid-go v3.5.0+incompatible
|
||||
## explicit
|
||||
github.com/sendgrid/sendgrid-go
|
||||
@@ -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 |
|
||||
355
ql/test/experimental/CWE-681/IncorrectNumericConversion.go
Normal file
355
ql/test/experimental/CWE-681/IncorrectNumericConversion.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
experimental/CWE-681/IncorrectNumericConversion.ql
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user