From d195273bf4f2be923d4355fd2e1c48ee1ce758fd Mon Sep 17 00:00:00 2001
From: Kevin Stubbings
Date: Mon, 14 Oct 2024 19:48:54 -0700
Subject: [PATCH 001/279] Add mux.Vars() and url.Path sanitizers
---
.../go/security/TaintedPathCustomizations.qll | 29 ++
.../2024-10-14-gopathsanitizer.md | 4 +
.../Security/CWE-022/TaintedPath.expected | 32 +--
.../Security/CWE-022/TaintedPath.go | 9 +
.../test/query-tests/Security/CWE-022/go.mod | 5 +
.../vendor/github.com/gorilla/mux/LICENSE | 27 ++
.../vendor/github.com/gorilla/mux/stub.go | 252 ++++++++++++++++++
.../Security/CWE-022/vendor/modules.txt | 3 +
8 files changed, 345 insertions(+), 16 deletions(-)
create mode 100644 go/ql/src/change-notes/2024-10-14-gopathsanitizer.md
create mode 100644 go/ql/test/query-tests/Security/CWE-022/go.mod
create mode 100644 go/ql/test/query-tests/Security/CWE-022/vendor/github.com/gorilla/mux/LICENSE
create mode 100644 go/ql/test/query-tests/Security/CWE-022/vendor/github.com/gorilla/mux/stub.go
create mode 100644 go/ql/test/query-tests/Security/CWE-022/vendor/modules.txt
diff --git a/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll b/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll
index 953d9810d53..f75a16ceccf 100644
--- a/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll
+++ b/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll
@@ -93,6 +93,35 @@ module TaintedPath {
}
}
+ // /**
+ // * A call to `mux.Vars(path)`, considered to sanitize `path` against path traversal.
+ // * Only enabled when `SkipClean` is not set true.
+ // */
+ class MuxVarsSanitizer extends Sanitizer {
+ MuxVarsSanitizer() {
+ exists(Function m |
+ m.hasQualifiedName("github.com/gorilla/mux", "Vars") and
+ this = m.getACall().getResult()
+ ) and
+ not exists(CallExpr f |
+ f.getTarget().hasQualifiedName("github.com/gorilla/mux", "SkipClean") and
+ f.getArgument(0).toString().toLowerCase() = "true"
+ )
+ }
+ }
+
+ // /**
+ // * A read from `net/url` which is sanitized
+ // */
+ class UrlPathSanitizer extends Sanitizer {
+ UrlPathSanitizer() {
+ exists(DataFlow::Field fld |
+ this = fld.getARead() and
+ fld.hasQualifiedName("net/url", "URL", "Path")
+ )
+ }
+ }
+
/**
* A read from the field `Filename` of the type `mime/multipart.FileHeader`,
* considered as a sanitizer for path traversal.
diff --git a/go/ql/src/change-notes/2024-10-14-gopathsanitizer.md b/go/ql/src/change-notes/2024-10-14-gopathsanitizer.md
new file mode 100644
index 00000000000..93371d9f229
--- /dev/null
+++ b/go/ql/src/change-notes/2024-10-14-gopathsanitizer.md
@@ -0,0 +1,4 @@
+---
+category: minorAnalysis
+---
+* Added [github.com/gorilla/mux.Vars](https://pkg.go.dev/github.com/gorilla/mux#Vars) to path sanitizers.
\ No newline at end of file
diff --git a/go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected b/go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected
index 839d35f663c..950ac856352 100644
--- a/go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected
+++ b/go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected
@@ -1,25 +1,25 @@
#select
-| TaintedPath.go:17:29:17:40 | tainted_path | TaintedPath.go:14:18:14:22 | selection of URL | TaintedPath.go:17:29:17:40 | tainted_path | This path depends on a $@. | TaintedPath.go:14:18:14:22 | selection of URL | user-provided value |
-| TaintedPath.go:21:28:21:69 | call to Join | TaintedPath.go:14:18:14:22 | selection of URL | TaintedPath.go:21:28:21:69 | call to Join | This path depends on a $@. | TaintedPath.go:14:18:14:22 | selection of URL | user-provided value |
-| TaintedPath.go:68:28:68:57 | call to Clean | TaintedPath.go:14:18:14:22 | selection of URL | TaintedPath.go:68:28:68:57 | call to Clean | This path depends on a $@. | TaintedPath.go:14:18:14:22 | selection of URL | user-provided value |
+| TaintedPath.go:19:29:19:40 | tainted_path | TaintedPath.go:16:18:16:22 | selection of URL | TaintedPath.go:19:29:19:40 | tainted_path | This path depends on a $@. | TaintedPath.go:16:18:16:22 | selection of URL | user-provided value |
+| TaintedPath.go:23:28:23:69 | call to Join | TaintedPath.go:16:18:16:22 | selection of URL | TaintedPath.go:23:28:23:69 | call to Join | This path depends on a $@. | TaintedPath.go:16:18:16:22 | selection of URL | user-provided value |
+| TaintedPath.go:70:28:70:57 | call to Clean | TaintedPath.go:16:18:16:22 | selection of URL | TaintedPath.go:70:28:70:57 | call to Clean | This path depends on a $@. | TaintedPath.go:16:18:16:22 | selection of URL | user-provided value |
edges
-| TaintedPath.go:14:18:14:22 | selection of URL | TaintedPath.go:14:18:14:30 | call to Query | provenance | Src:MaD:2 MaD:3 |
-| TaintedPath.go:14:18:14:30 | call to Query | TaintedPath.go:17:29:17:40 | tainted_path | provenance | Sink:MaD:1 |
-| TaintedPath.go:14:18:14:30 | call to Query | TaintedPath.go:21:57:21:68 | tainted_path | provenance | |
-| TaintedPath.go:14:18:14:30 | call to Query | TaintedPath.go:68:39:68:56 | ...+... | provenance | |
-| TaintedPath.go:21:57:21:68 | tainted_path | TaintedPath.go:21:28:21:69 | call to Join | provenance | FunctionModel Sink:MaD:1 |
-| TaintedPath.go:68:39:68:56 | ...+... | TaintedPath.go:68:28:68:57 | call to Clean | provenance | MaD:4 Sink:MaD:1 |
+| TaintedPath.go:16:18:16:22 | selection of URL | TaintedPath.go:16:18:16:30 | call to Query | provenance | Src:MaD:2 MaD:3 |
+| TaintedPath.go:16:18:16:30 | call to Query | TaintedPath.go:19:29:19:40 | tainted_path | provenance | Sink:MaD:1 |
+| TaintedPath.go:16:18:16:30 | call to Query | TaintedPath.go:23:57:23:68 | tainted_path | provenance | |
+| TaintedPath.go:16:18:16:30 | call to Query | TaintedPath.go:70:39:70:56 | ...+... | provenance | |
+| TaintedPath.go:23:57:23:68 | tainted_path | TaintedPath.go:23:28:23:69 | call to Join | provenance | FunctionModel Sink:MaD:1 |
+| TaintedPath.go:70:39:70:56 | ...+... | TaintedPath.go:70:28:70:57 | call to Clean | provenance | MaD:4 Sink:MaD:1 |
models
| 1 | Sink: io/ioutil; ; false; ReadFile; ; ; Argument[0]; path-injection; manual |
| 2 | Source: net/http; Request; true; URL; ; ; ; remote; manual |
| 3 | Summary: net/url; URL; true; Query; ; ; Argument[receiver]; ReturnValue; taint; manual |
| 4 | Summary: path; ; false; Clean; ; ; Argument[0]; ReturnValue; taint; manual |
nodes
-| TaintedPath.go:14:18:14:22 | selection of URL | semmle.label | selection of URL |
-| TaintedPath.go:14:18:14:30 | call to Query | semmle.label | call to Query |
-| TaintedPath.go:17:29:17:40 | tainted_path | semmle.label | tainted_path |
-| TaintedPath.go:21:28:21:69 | call to Join | semmle.label | call to Join |
-| TaintedPath.go:21:57:21:68 | tainted_path | semmle.label | tainted_path |
-| TaintedPath.go:68:28:68:57 | call to Clean | semmle.label | call to Clean |
-| TaintedPath.go:68:39:68:56 | ...+... | semmle.label | ...+... |
+| TaintedPath.go:16:18:16:22 | selection of URL | semmle.label | selection of URL |
+| TaintedPath.go:16:18:16:30 | call to Query | semmle.label | call to Query |
+| TaintedPath.go:19:29:19:40 | tainted_path | semmle.label | tainted_path |
+| TaintedPath.go:23:28:23:69 | call to Join | semmle.label | call to Join |
+| TaintedPath.go:23:57:23:68 | tainted_path | semmle.label | tainted_path |
+| TaintedPath.go:70:28:70:57 | call to Clean | semmle.label | call to Clean |
+| TaintedPath.go:70:39:70:56 | ...+... | semmle.label | ...+... |
subpaths
diff --git a/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go b/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go
index e6a1c49f4c5..b57e293a824 100644
--- a/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go
+++ b/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go
@@ -8,6 +8,8 @@ import (
"path/filepath"
"regexp"
"strings"
+
+ "github.com/gorilla/mux"
)
func handler(w http.ResponseWriter, r *http.Request) {
@@ -94,3 +96,10 @@ func handler(w http.ResponseWriter, r *http.Request) {
data, _ = ioutil.ReadFile(part.FileName())
}
+
+// GOOD: Sanitized by Gorilla's cleaner
+func GorillaHandler(w http.ResponseWriter, r *http.Request) {
+ not_tainted_path := mux.Vars(r)
+ data, _ := ioutil.ReadFile(filepath.Join("/home/user/", not_tainted_path))
+ w.Write(data)
+}
diff --git a/go/ql/test/query-tests/Security/CWE-022/go.mod b/go/ql/test/query-tests/Security/CWE-022/go.mod
new file mode 100644
index 00000000000..c173488c7c7
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-022/go.mod
@@ -0,0 +1,5 @@
+module codeql-go-tests/frameworks/Mux
+
+go 1.14
+
+require github.com/gorilla/mux v1.7.4
diff --git a/go/ql/test/query-tests/Security/CWE-022/vendor/github.com/gorilla/mux/LICENSE b/go/ql/test/query-tests/Security/CWE-022/vendor/github.com/gorilla/mux/LICENSE
new file mode 100644
index 00000000000..6903df6386e
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-022/vendor/github.com/gorilla/mux/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/go/ql/test/query-tests/Security/CWE-022/vendor/github.com/gorilla/mux/stub.go b/go/ql/test/query-tests/Security/CWE-022/vendor/github.com/gorilla/mux/stub.go
new file mode 100644
index 00000000000..62510300b2d
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-022/vendor/github.com/gorilla/mux/stub.go
@@ -0,0 +1,252 @@
+// Code generated by depstubber. DO NOT EDIT.
+// This is a simple stub for github.com/gorilla/mux, strictly for use in testing.
+
+// See the LICENSE file for information about the licensing of the original library.
+// Source: github.com/gorilla/mux (exports: ; functions: Vars,NewRouter)
+
+// Package mux is a stub of github.com/gorilla/mux, generated by depstubber.
+package mux
+
+import (
+ http "net/http"
+ url "net/url"
+)
+
+type BuildVarsFunc func(map[string]string) map[string]string
+
+type MatcherFunc func(*http.Request, *RouteMatch) bool
+
+func (_ MatcherFunc) Match(_ *http.Request, _ *RouteMatch) bool {
+ return false
+}
+
+type MiddlewareFunc func(http.Handler) http.Handler
+
+func (_ MiddlewareFunc) Middleware(_ http.Handler) http.Handler {
+ return nil
+}
+
+func NewRouter() *Router {
+ return nil
+}
+
+type Route struct{}
+
+func (_ *Route) BuildOnly() *Route {
+ return nil
+}
+
+func (_ *Route) BuildVarsFunc(_ BuildVarsFunc) *Route {
+ return nil
+}
+
+func (_ *Route) GetError() error {
+ return nil
+}
+
+func (_ *Route) GetHandler() http.Handler {
+ return nil
+}
+
+func (_ *Route) GetHostTemplate() (string, error) {
+ return "", nil
+}
+
+func (_ *Route) GetMethods() ([]string, error) {
+ return nil, nil
+}
+
+func (_ *Route) GetName() string {
+ return ""
+}
+
+func (_ *Route) GetPathRegexp() (string, error) {
+ return "", nil
+}
+
+func (_ *Route) GetPathTemplate() (string, error) {
+ return "", nil
+}
+
+func (_ *Route) GetQueriesRegexp() ([]string, error) {
+ return nil, nil
+}
+
+func (_ *Route) GetQueriesTemplates() ([]string, error) {
+ return nil, nil
+}
+
+func (_ *Route) Handler(_ http.Handler) *Route {
+ return nil
+}
+
+func (_ *Route) HandlerFunc(_ func(http.ResponseWriter, *http.Request)) *Route {
+ return nil
+}
+
+func (_ *Route) Headers(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Route) HeadersRegexp(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Route) Host(_ string) *Route {
+ return nil
+}
+
+func (_ *Route) Match(_ *http.Request, _ *RouteMatch) bool {
+ return false
+}
+
+func (_ *Route) MatcherFunc(_ MatcherFunc) *Route {
+ return nil
+}
+
+func (_ *Route) Methods(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Route) Name(_ string) *Route {
+ return nil
+}
+
+func (_ *Route) Path(_ string) *Route {
+ return nil
+}
+
+func (_ *Route) PathPrefix(_ string) *Route {
+ return nil
+}
+
+func (_ *Route) Queries(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Route) Schemes(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Route) SkipClean() bool {
+ return false
+}
+
+func (_ *Route) Subrouter() *Router {
+ return nil
+}
+
+func (_ *Route) URL(_ ...string) (*url.URL, error) {
+ return nil, nil
+}
+
+func (_ *Route) URLHost(_ ...string) (*url.URL, error) {
+ return nil, nil
+}
+
+func (_ *Route) URLPath(_ ...string) (*url.URL, error) {
+ return nil, nil
+}
+
+type RouteMatch struct {
+ Route *Route
+ Handler http.Handler
+ Vars map[string]string
+ MatchErr error
+}
+
+type Router struct {
+ NotFoundHandler http.Handler
+ MethodNotAllowedHandler http.Handler
+ KeepContext bool
+}
+
+func (_ *Router) BuildVarsFunc(_ BuildVarsFunc) *Route {
+ return nil
+}
+
+func (_ *Router) Get(_ string) *Route {
+ return nil
+}
+
+func (_ *Router) GetRoute(_ string) *Route {
+ return nil
+}
+
+func (_ *Router) Handle(_ string, _ http.Handler) *Route {
+ return nil
+}
+
+func (_ *Router) HandleFunc(_ string, _ func(http.ResponseWriter, *http.Request)) *Route {
+ return nil
+}
+
+func (_ *Router) Headers(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Router) Host(_ string) *Route {
+ return nil
+}
+
+func (_ *Router) Match(_ *http.Request, _ *RouteMatch) bool {
+ return false
+}
+
+func (_ *Router) MatcherFunc(_ MatcherFunc) *Route {
+ return nil
+}
+
+func (_ *Router) Methods(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Router) Name(_ string) *Route {
+ return nil
+}
+
+func (_ *Router) NewRoute() *Route {
+ return nil
+}
+
+func (_ *Router) Path(_ string) *Route {
+ return nil
+}
+
+func (_ *Router) PathPrefix(_ string) *Route {
+ return nil
+}
+
+func (_ *Router) Queries(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Router) Schemes(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Router) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {}
+
+func (_ *Router) SkipClean(_ bool) *Router {
+ return nil
+}
+
+func (_ *Router) StrictSlash(_ bool) *Router {
+ return nil
+}
+
+func (_ *Router) Use(_ ...MiddlewareFunc) {}
+
+func (_ *Router) UseEncodedPath() *Router {
+ return nil
+}
+
+func (_ *Router) Walk(_ WalkFunc) error {
+ return nil
+}
+
+func Vars(_ *http.Request) map[string]string {
+ return nil
+}
+
+type WalkFunc func(*Route, *Router, []*Route) error
diff --git a/go/ql/test/query-tests/Security/CWE-022/vendor/modules.txt b/go/ql/test/query-tests/Security/CWE-022/vendor/modules.txt
new file mode 100644
index 00000000000..d96be1fa71b
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-022/vendor/modules.txt
@@ -0,0 +1,3 @@
+# github.com/gorilla/mux v1.7.4
+## explicit
+github.com/gorilla/mux
From 195b70aca649cb0cf0f70dca0d28c35ae66b9170 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 15 Oct 2024 12:54:30 +0200
Subject: [PATCH 002/279] python: Add test for functional-like programming This
can also serve for a place to add tests for constructs like threading.Thread,
mulitprocess.Process, concurrent.futures.ThreadPoolExecutor, and
concurrent.futures.ProcessPoolExecutor.
---
.../dataflow/coverage/functional.py | 67 +++++++++++++++++++
.../test/library-tests/dataflow/validTest.py | 1 +
2 files changed, 68 insertions(+)
create mode 100644 python/ql/test/library-tests/dataflow/coverage/functional.py
diff --git a/python/ql/test/library-tests/dataflow/coverage/functional.py b/python/ql/test/library-tests/dataflow/coverage/functional.py
new file mode 100644
index 00000000000..4bf9d295a20
--- /dev/null
+++ b/python/ql/test/library-tests/dataflow/coverage/functional.py
@@ -0,0 +1,67 @@
+# All functions starting with "test_" should run and execute `print("OK")` exactly once.
+# This can be checked by running validTest.py.
+
+import sys
+import os
+
+sys.path.append(os.path.dirname(os.path.dirname((__file__))))
+from testlib import expects
+
+# These are defined so that we can evaluate the test code.
+NONSOURCE = "not a source"
+SOURCE = "source"
+
+
+def is_source(x):
+ return x == "source" or x == b"source" or x == 42 or x == 42.0 or x == 42j
+
+
+def SINK(x):
+ if is_source(x):
+ print("OK")
+ else:
+ print("Unexpected flow", x)
+
+
+def SINK_F(x):
+ if is_source(x):
+ print("Unexpected flow", x)
+ else:
+ print("OK")
+
+# ------------------------------------------------------------------------------
+# Actual tests
+# ------------------------------------------------------------------------------
+
+def read_sql(sql):
+ SINK(sql) # $ flow="SOURCE, l:+5 -> sql"
+
+def process(func, arg):
+ func(arg)
+
+process(func=read_sql, arg=SOURCE)
+
+
+def read_sql_star(sql):
+ SINK(sql) # $ MISSING: flow="SOURCE, l:+5 -> sql"
+
+def process_star(func, *args):
+ func(*args)
+
+process_star(read_sql_star, SOURCE)
+
+
+def read_sql_dict(sql):
+ SINK(sql) # $ flow="SOURCE, l:+5 -> sql"
+
+def process_dict(func, **args):
+ func(**args)
+
+process_dict(func=read_sql_dict, sql=SOURCE)
+
+
+# TODO:
+# Consider adding tests for
+# threading.Thread, mulitprocess.Process,
+# concurrent.futures.ThreadPoolExecutor,
+# and concurrent.futures.ProcessPoolExecutor.
diff --git a/python/ql/test/library-tests/dataflow/validTest.py b/python/ql/test/library-tests/dataflow/validTest.py
index 76bd7bb52a5..46c78502d5d 100644
--- a/python/ql/test/library-tests/dataflow/validTest.py
+++ b/python/ql/test/library-tests/dataflow/validTest.py
@@ -66,6 +66,7 @@ if __name__ == "__main__":
check_tests_valid("coverage.datamodel")
check_tests_valid("coverage.test_builtins")
check_tests_valid("coverage.loops")
+ check_tests_valid("coverage.functional")
check_tests_valid("coverage-py2.classes")
check_tests_valid("coverage-py3.classes")
check_tests_valid("variable-capture.in")
From 9ed8fe5dd0525b0077ab8b6894199cda6bb04dba Mon Sep 17 00:00:00 2001
From: yoff
Date: Tue, 15 Oct 2024 17:35:36 +0200
Subject: [PATCH 003/279] Update
python/ql/test/library-tests/dataflow/coverage/functional.py
Co-authored-by: Taus
---
python/ql/test/library-tests/dataflow/coverage/functional.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/test/library-tests/dataflow/coverage/functional.py b/python/ql/test/library-tests/dataflow/coverage/functional.py
index 4bf9d295a20..8ab222814e8 100644
--- a/python/ql/test/library-tests/dataflow/coverage/functional.py
+++ b/python/ql/test/library-tests/dataflow/coverage/functional.py
@@ -62,6 +62,6 @@ process_dict(func=read_sql_dict, sql=SOURCE)
# TODO:
# Consider adding tests for
-# threading.Thread, mulitprocess.Process,
+# threading.Thread, multiprocess.Process,
# concurrent.futures.ThreadPoolExecutor,
# and concurrent.futures.ProcessPoolExecutor.
From 1287f1befc2a942458ebd329de02277ed87067d1 Mon Sep 17 00:00:00 2001
From: Kevin Stubbings
Date: Tue, 15 Oct 2024 14:01:14 -0700
Subject: [PATCH 004/279] Address feedback
---
.../go/security/TaintedPathCustomizations.qll | 20 +++++++++----------
.../Security/CWE-022/TaintedPath.go | 2 +-
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll b/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll
index f75a16ceccf..f505df8b34b 100644
--- a/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll
+++ b/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll
@@ -93,26 +93,26 @@ module TaintedPath {
}
}
- // /**
- // * A call to `mux.Vars(path)`, considered to sanitize `path` against path traversal.
- // * Only enabled when `SkipClean` is not set true.
- // */
+ /**
+ * A call to `mux.Vars(path)`, considered to sanitize `path` against path traversal.
+ * Only enabled when `SkipClean` is not set true.
+ */
class MuxVarsSanitizer extends Sanitizer {
MuxVarsSanitizer() {
exists(Function m |
- m.hasQualifiedName("github.com/gorilla/mux", "Vars") and
+ m.hasQualifiedName(package("github.com/gorilla/mux", ""), "Vars") and
this = m.getACall().getResult()
) and
not exists(CallExpr f |
- f.getTarget().hasQualifiedName("github.com/gorilla/mux", "SkipClean") and
- f.getArgument(0).toString().toLowerCase() = "true"
+ f.getTarget().hasQualifiedName(package("github.com/gorilla/mux", ""), "SkipClean") and
+ f.getArgument(0).getBoolValue() = true
)
}
}
- // /**
- // * A read from `net/url` which is sanitized
- // */
+ /**
+ * A read from the field `Path` of the type `net/url.URL`, which is sanitized.
+ */
class UrlPathSanitizer extends Sanitizer {
UrlPathSanitizer() {
exists(DataFlow::Field fld |
diff --git a/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go b/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go
index b57e293a824..3fed218b700 100644
--- a/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go
+++ b/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go
@@ -99,7 +99,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
// GOOD: Sanitized by Gorilla's cleaner
func GorillaHandler(w http.ResponseWriter, r *http.Request) {
- not_tainted_path := mux.Vars(r)
+ not_tainted_path := mux.Vars(r)["id"]
data, _ := ioutil.ReadFile(filepath.Join("/home/user/", not_tainted_path))
w.Write(data)
}
From 374b13e1bb3eea75ef7ec3ed87d46f1499256148 Mon Sep 17 00:00:00 2001
From: Kevin Stubbings
Date: Tue, 15 Oct 2024 14:34:11 -0700
Subject: [PATCH 005/279] Remove path sanitizer
---
.../semmle/go/security/TaintedPathCustomizations.qll | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll b/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll
index f505df8b34b..3459f414bb2 100644
--- a/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll
+++ b/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll
@@ -110,18 +110,6 @@ module TaintedPath {
}
}
- /**
- * A read from the field `Path` of the type `net/url.URL`, which is sanitized.
- */
- class UrlPathSanitizer extends Sanitizer {
- UrlPathSanitizer() {
- exists(DataFlow::Field fld |
- this = fld.getARead() and
- fld.hasQualifiedName("net/url", "URL", "Path")
- )
- }
- }
-
/**
* A read from the field `Filename` of the type `mime/multipart.FileHeader`,
* considered as a sanitizer for path traversal.
From 8744f158bd5a8dbed353a6d45b82563d11365efb Mon Sep 17 00:00:00 2001
From: Kevin Stubbings
Date: Tue, 12 Nov 2024 15:44:47 -0800
Subject: [PATCH 006/279] New tests
---
.../go/security/TaintedPathCustomizations.qll | 4 +-
.../CWE-022/GorillaMuxDefault/MuxClean.go | 22 ++
.../GorillaMuxDefault/TaintedPath.expected | 4 +
.../GorillaMuxDefault/TaintedPath.qlref | 2 +
.../CWE-022/{ => GorillaMuxDefault}/go.mod | 0
.../vendor/github.com/gorilla/mux/LICENSE | 0
.../vendor/github.com/gorilla/mux/stub.go | 0
.../vendor/modules.txt | 0
.../CWE-022/GorillaMuxSkipClean/MuxClean.go | 22 ++
.../GorillaMuxSkipClean/TaintedPath.expected | 10 +
.../GorillaMuxSkipClean/TaintedPath.qlref | 2 +
.../CWE-022/GorillaMuxSkipClean/go.mod | 5 +
.../vendor/github.com/gorilla/mux/LICENSE | 27 ++
.../vendor/github.com/gorilla/mux/stub.go | 252 ++++++++++++++++++
.../GorillaMuxSkipClean/vendor/modules.txt | 3 +
.../Security/CWE-022/TaintedPath.expected | 25 --
.../Security/CWE-022/TaintedPath.go | 9 -
17 files changed, 352 insertions(+), 35 deletions(-)
create mode 100644 go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/MuxClean.go
create mode 100644 go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/TaintedPath.expected
create mode 100644 go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/TaintedPath.qlref
rename go/ql/test/query-tests/Security/CWE-022/{ => GorillaMuxDefault}/go.mod (100%)
rename go/ql/test/query-tests/Security/CWE-022/{ => GorillaMuxDefault}/vendor/github.com/gorilla/mux/LICENSE (100%)
rename go/ql/test/query-tests/Security/CWE-022/{ => GorillaMuxDefault}/vendor/github.com/gorilla/mux/stub.go (100%)
rename go/ql/test/query-tests/Security/CWE-022/{ => GorillaMuxDefault}/vendor/modules.txt (100%)
create mode 100644 go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/MuxClean.go
create mode 100644 go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/TaintedPath.expected
create mode 100644 go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/TaintedPath.qlref
create mode 100644 go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/go.mod
create mode 100644 go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/vendor/github.com/gorilla/mux/LICENSE
create mode 100644 go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/vendor/github.com/gorilla/mux/stub.go
create mode 100644 go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/vendor/modules.txt
delete mode 100644 go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected
diff --git a/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll b/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll
index 3459f414bb2..df601ce1eb8 100644
--- a/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll
+++ b/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll
@@ -104,7 +104,9 @@ module TaintedPath {
this = m.getACall().getResult()
) and
not exists(CallExpr f |
- f.getTarget().hasQualifiedName(package("github.com/gorilla/mux", ""), "SkipClean") and
+ f.getTarget()
+ .(Method)
+ .hasQualifiedName(package("github.com/gorilla/mux", ""), "Router", "SkipClean") and
f.getArgument(0).getBoolValue() = true
)
}
diff --git a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/MuxClean.go b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/MuxClean.go
new file mode 100644
index 00000000000..25e39a1bfbf
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/MuxClean.go
@@ -0,0 +1,22 @@
+// GOOD: Sanitized by Gorilla's cleaner
+package main
+
+import (
+ "io/ioutil"
+ "net/http"
+ "path/filepath"
+
+ "github.com/gorilla/mux"
+)
+
+func GorillaHandler(w http.ResponseWriter, r *http.Request) {
+ not_tainted_path := mux.Vars(r)["id"]
+ data, _ := ioutil.ReadFile(filepath.Join("/home/user/", not_tainted_path))
+ w.Write(data)
+}
+
+func main() {
+ var router = mux.NewRouter()
+ router.SkipClean(false)
+ router.HandleFunc("/{category}", GorillaHandler)
+}
diff --git a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/TaintedPath.expected b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/TaintedPath.expected
new file mode 100644
index 00000000000..e217064d1df
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/TaintedPath.expected
@@ -0,0 +1,4 @@
+edges
+nodes
+subpaths
+#select
diff --git a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/TaintedPath.qlref b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/TaintedPath.qlref
new file mode 100644
index 00000000000..6de14eaee24
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/TaintedPath.qlref
@@ -0,0 +1,2 @@
+query: Security/CWE-022/TaintedPath.ql
+postprocess: TestUtilities/PrettyPrintModels.ql
\ No newline at end of file
diff --git a/go/ql/test/query-tests/Security/CWE-022/go.mod b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/go.mod
similarity index 100%
rename from go/ql/test/query-tests/Security/CWE-022/go.mod
rename to go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/go.mod
diff --git a/go/ql/test/query-tests/Security/CWE-022/vendor/github.com/gorilla/mux/LICENSE b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/vendor/github.com/gorilla/mux/LICENSE
similarity index 100%
rename from go/ql/test/query-tests/Security/CWE-022/vendor/github.com/gorilla/mux/LICENSE
rename to go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/vendor/github.com/gorilla/mux/LICENSE
diff --git a/go/ql/test/query-tests/Security/CWE-022/vendor/github.com/gorilla/mux/stub.go b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/vendor/github.com/gorilla/mux/stub.go
similarity index 100%
rename from go/ql/test/query-tests/Security/CWE-022/vendor/github.com/gorilla/mux/stub.go
rename to go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/vendor/github.com/gorilla/mux/stub.go
diff --git a/go/ql/test/query-tests/Security/CWE-022/vendor/modules.txt b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/vendor/modules.txt
similarity index 100%
rename from go/ql/test/query-tests/Security/CWE-022/vendor/modules.txt
rename to go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/vendor/modules.txt
diff --git a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/MuxClean.go b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/MuxClean.go
new file mode 100644
index 00000000000..aafc93c75ea
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/MuxClean.go
@@ -0,0 +1,22 @@
+// GOOD: Sanitized by Gorilla's cleaner
+package main
+
+import (
+ "io/ioutil"
+ "net/http"
+ "path/filepath"
+
+ "github.com/gorilla/mux"
+)
+
+func GorillaHandler(w http.ResponseWriter, r *http.Request) {
+ not_tainted_path := mux.Vars(r)["id"]
+ data, _ := ioutil.ReadFile(filepath.Join("/home/user/", not_tainted_path))
+ w.Write(data)
+}
+
+func main() {
+ var router = mux.NewRouter()
+ router.SkipClean(true)
+ router.HandleFunc("/{category}", GorillaHandler)
+}
diff --git a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/TaintedPath.expected b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/TaintedPath.expected
new file mode 100644
index 00000000000..f07dfaec8cc
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/TaintedPath.expected
@@ -0,0 +1,10 @@
+edges
+| MuxClean.go:13:22:13:32 | call to Vars | MuxClean.go:14:58:14:73 | not_tainted_path | provenance | Src:MaD:524 |
+| MuxClean.go:14:58:14:73 | not_tainted_path | MuxClean.go:14:29:14:74 | call to Join | provenance | FunctionModel Sink:MaD:854 |
+nodes
+| MuxClean.go:13:22:13:32 | call to Vars | semmle.label | call to Vars |
+| MuxClean.go:14:29:14:74 | call to Join | semmle.label | call to Join |
+| MuxClean.go:14:58:14:73 | not_tainted_path | semmle.label | not_tainted_path |
+subpaths
+#select
+| MuxClean.go:14:29:14:74 | call to Join | MuxClean.go:13:22:13:32 | call to Vars | MuxClean.go:14:29:14:74 | call to Join | This path depends on a $@. | MuxClean.go:13:22:13:32 | call to Vars | user-provided value |
diff --git a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/TaintedPath.qlref b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/TaintedPath.qlref
new file mode 100644
index 00000000000..6de14eaee24
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/TaintedPath.qlref
@@ -0,0 +1,2 @@
+query: Security/CWE-022/TaintedPath.ql
+postprocess: TestUtilities/PrettyPrintModels.ql
\ No newline at end of file
diff --git a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/go.mod b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/go.mod
new file mode 100644
index 00000000000..c173488c7c7
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/go.mod
@@ -0,0 +1,5 @@
+module codeql-go-tests/frameworks/Mux
+
+go 1.14
+
+require github.com/gorilla/mux v1.7.4
diff --git a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/vendor/github.com/gorilla/mux/LICENSE b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/vendor/github.com/gorilla/mux/LICENSE
new file mode 100644
index 00000000000..6903df6386e
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/vendor/github.com/gorilla/mux/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/vendor/github.com/gorilla/mux/stub.go b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/vendor/github.com/gorilla/mux/stub.go
new file mode 100644
index 00000000000..62510300b2d
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/vendor/github.com/gorilla/mux/stub.go
@@ -0,0 +1,252 @@
+// Code generated by depstubber. DO NOT EDIT.
+// This is a simple stub for github.com/gorilla/mux, strictly for use in testing.
+
+// See the LICENSE file for information about the licensing of the original library.
+// Source: github.com/gorilla/mux (exports: ; functions: Vars,NewRouter)
+
+// Package mux is a stub of github.com/gorilla/mux, generated by depstubber.
+package mux
+
+import (
+ http "net/http"
+ url "net/url"
+)
+
+type BuildVarsFunc func(map[string]string) map[string]string
+
+type MatcherFunc func(*http.Request, *RouteMatch) bool
+
+func (_ MatcherFunc) Match(_ *http.Request, _ *RouteMatch) bool {
+ return false
+}
+
+type MiddlewareFunc func(http.Handler) http.Handler
+
+func (_ MiddlewareFunc) Middleware(_ http.Handler) http.Handler {
+ return nil
+}
+
+func NewRouter() *Router {
+ return nil
+}
+
+type Route struct{}
+
+func (_ *Route) BuildOnly() *Route {
+ return nil
+}
+
+func (_ *Route) BuildVarsFunc(_ BuildVarsFunc) *Route {
+ return nil
+}
+
+func (_ *Route) GetError() error {
+ return nil
+}
+
+func (_ *Route) GetHandler() http.Handler {
+ return nil
+}
+
+func (_ *Route) GetHostTemplate() (string, error) {
+ return "", nil
+}
+
+func (_ *Route) GetMethods() ([]string, error) {
+ return nil, nil
+}
+
+func (_ *Route) GetName() string {
+ return ""
+}
+
+func (_ *Route) GetPathRegexp() (string, error) {
+ return "", nil
+}
+
+func (_ *Route) GetPathTemplate() (string, error) {
+ return "", nil
+}
+
+func (_ *Route) GetQueriesRegexp() ([]string, error) {
+ return nil, nil
+}
+
+func (_ *Route) GetQueriesTemplates() ([]string, error) {
+ return nil, nil
+}
+
+func (_ *Route) Handler(_ http.Handler) *Route {
+ return nil
+}
+
+func (_ *Route) HandlerFunc(_ func(http.ResponseWriter, *http.Request)) *Route {
+ return nil
+}
+
+func (_ *Route) Headers(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Route) HeadersRegexp(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Route) Host(_ string) *Route {
+ return nil
+}
+
+func (_ *Route) Match(_ *http.Request, _ *RouteMatch) bool {
+ return false
+}
+
+func (_ *Route) MatcherFunc(_ MatcherFunc) *Route {
+ return nil
+}
+
+func (_ *Route) Methods(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Route) Name(_ string) *Route {
+ return nil
+}
+
+func (_ *Route) Path(_ string) *Route {
+ return nil
+}
+
+func (_ *Route) PathPrefix(_ string) *Route {
+ return nil
+}
+
+func (_ *Route) Queries(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Route) Schemes(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Route) SkipClean() bool {
+ return false
+}
+
+func (_ *Route) Subrouter() *Router {
+ return nil
+}
+
+func (_ *Route) URL(_ ...string) (*url.URL, error) {
+ return nil, nil
+}
+
+func (_ *Route) URLHost(_ ...string) (*url.URL, error) {
+ return nil, nil
+}
+
+func (_ *Route) URLPath(_ ...string) (*url.URL, error) {
+ return nil, nil
+}
+
+type RouteMatch struct {
+ Route *Route
+ Handler http.Handler
+ Vars map[string]string
+ MatchErr error
+}
+
+type Router struct {
+ NotFoundHandler http.Handler
+ MethodNotAllowedHandler http.Handler
+ KeepContext bool
+}
+
+func (_ *Router) BuildVarsFunc(_ BuildVarsFunc) *Route {
+ return nil
+}
+
+func (_ *Router) Get(_ string) *Route {
+ return nil
+}
+
+func (_ *Router) GetRoute(_ string) *Route {
+ return nil
+}
+
+func (_ *Router) Handle(_ string, _ http.Handler) *Route {
+ return nil
+}
+
+func (_ *Router) HandleFunc(_ string, _ func(http.ResponseWriter, *http.Request)) *Route {
+ return nil
+}
+
+func (_ *Router) Headers(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Router) Host(_ string) *Route {
+ return nil
+}
+
+func (_ *Router) Match(_ *http.Request, _ *RouteMatch) bool {
+ return false
+}
+
+func (_ *Router) MatcherFunc(_ MatcherFunc) *Route {
+ return nil
+}
+
+func (_ *Router) Methods(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Router) Name(_ string) *Route {
+ return nil
+}
+
+func (_ *Router) NewRoute() *Route {
+ return nil
+}
+
+func (_ *Router) Path(_ string) *Route {
+ return nil
+}
+
+func (_ *Router) PathPrefix(_ string) *Route {
+ return nil
+}
+
+func (_ *Router) Queries(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Router) Schemes(_ ...string) *Route {
+ return nil
+}
+
+func (_ *Router) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {}
+
+func (_ *Router) SkipClean(_ bool) *Router {
+ return nil
+}
+
+func (_ *Router) StrictSlash(_ bool) *Router {
+ return nil
+}
+
+func (_ *Router) Use(_ ...MiddlewareFunc) {}
+
+func (_ *Router) UseEncodedPath() *Router {
+ return nil
+}
+
+func (_ *Router) Walk(_ WalkFunc) error {
+ return nil
+}
+
+func Vars(_ *http.Request) map[string]string {
+ return nil
+}
+
+type WalkFunc func(*Route, *Router, []*Route) error
diff --git a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/vendor/modules.txt b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/vendor/modules.txt
new file mode 100644
index 00000000000..d96be1fa71b
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/vendor/modules.txt
@@ -0,0 +1,3 @@
+# github.com/gorilla/mux v1.7.4
+## explicit
+github.com/gorilla/mux
diff --git a/go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected b/go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected
deleted file mode 100644
index 950ac856352..00000000000
--- a/go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected
+++ /dev/null
@@ -1,25 +0,0 @@
-#select
-| TaintedPath.go:19:29:19:40 | tainted_path | TaintedPath.go:16:18:16:22 | selection of URL | TaintedPath.go:19:29:19:40 | tainted_path | This path depends on a $@. | TaintedPath.go:16:18:16:22 | selection of URL | user-provided value |
-| TaintedPath.go:23:28:23:69 | call to Join | TaintedPath.go:16:18:16:22 | selection of URL | TaintedPath.go:23:28:23:69 | call to Join | This path depends on a $@. | TaintedPath.go:16:18:16:22 | selection of URL | user-provided value |
-| TaintedPath.go:70:28:70:57 | call to Clean | TaintedPath.go:16:18:16:22 | selection of URL | TaintedPath.go:70:28:70:57 | call to Clean | This path depends on a $@. | TaintedPath.go:16:18:16:22 | selection of URL | user-provided value |
-edges
-| TaintedPath.go:16:18:16:22 | selection of URL | TaintedPath.go:16:18:16:30 | call to Query | provenance | Src:MaD:2 MaD:3 |
-| TaintedPath.go:16:18:16:30 | call to Query | TaintedPath.go:19:29:19:40 | tainted_path | provenance | Sink:MaD:1 |
-| TaintedPath.go:16:18:16:30 | call to Query | TaintedPath.go:23:57:23:68 | tainted_path | provenance | |
-| TaintedPath.go:16:18:16:30 | call to Query | TaintedPath.go:70:39:70:56 | ...+... | provenance | |
-| TaintedPath.go:23:57:23:68 | tainted_path | TaintedPath.go:23:28:23:69 | call to Join | provenance | FunctionModel Sink:MaD:1 |
-| TaintedPath.go:70:39:70:56 | ...+... | TaintedPath.go:70:28:70:57 | call to Clean | provenance | MaD:4 Sink:MaD:1 |
-models
-| 1 | Sink: io/ioutil; ; false; ReadFile; ; ; Argument[0]; path-injection; manual |
-| 2 | Source: net/http; Request; true; URL; ; ; ; remote; manual |
-| 3 | Summary: net/url; URL; true; Query; ; ; Argument[receiver]; ReturnValue; taint; manual |
-| 4 | Summary: path; ; false; Clean; ; ; Argument[0]; ReturnValue; taint; manual |
-nodes
-| TaintedPath.go:16:18:16:22 | selection of URL | semmle.label | selection of URL |
-| TaintedPath.go:16:18:16:30 | call to Query | semmle.label | call to Query |
-| TaintedPath.go:19:29:19:40 | tainted_path | semmle.label | tainted_path |
-| TaintedPath.go:23:28:23:69 | call to Join | semmle.label | call to Join |
-| TaintedPath.go:23:57:23:68 | tainted_path | semmle.label | tainted_path |
-| TaintedPath.go:70:28:70:57 | call to Clean | semmle.label | call to Clean |
-| TaintedPath.go:70:39:70:56 | ...+... | semmle.label | ...+... |
-subpaths
diff --git a/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go b/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go
index 3fed218b700..e6a1c49f4c5 100644
--- a/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go
+++ b/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go
@@ -8,8 +8,6 @@ import (
"path/filepath"
"regexp"
"strings"
-
- "github.com/gorilla/mux"
)
func handler(w http.ResponseWriter, r *http.Request) {
@@ -96,10 +94,3 @@ func handler(w http.ResponseWriter, r *http.Request) {
data, _ = ioutil.ReadFile(part.FileName())
}
-
-// GOOD: Sanitized by Gorilla's cleaner
-func GorillaHandler(w http.ResponseWriter, r *http.Request) {
- not_tainted_path := mux.Vars(r)["id"]
- data, _ := ioutil.ReadFile(filepath.Join("/home/user/", not_tainted_path))
- w.Write(data)
-}
From 460ed30d05d0c20199c10d04fcf816dab1bfdc75 Mon Sep 17 00:00:00 2001
From: Kevin Stubbings
Date: Tue, 12 Nov 2024 16:08:14 -0800
Subject: [PATCH 007/279] Fixed tests
---
.../GorillaMuxSkipClean/TaintedPath.expected | 11 +++++---
.../Security/CWE-022/TaintedPath.expected | 25 +++++++++++++++++++
2 files changed, 32 insertions(+), 4 deletions(-)
create mode 100644 go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected
diff --git a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/TaintedPath.expected b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/TaintedPath.expected
index f07dfaec8cc..887b9858ef3 100644
--- a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/TaintedPath.expected
+++ b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/TaintedPath.expected
@@ -1,10 +1,13 @@
+#select
+| MuxClean.go:14:29:14:74 | call to Join | MuxClean.go:13:22:13:32 | call to Vars | MuxClean.go:14:29:14:74 | call to Join | This path depends on a $@. | MuxClean.go:13:22:13:32 | call to Vars | user-provided value |
edges
-| MuxClean.go:13:22:13:32 | call to Vars | MuxClean.go:14:58:14:73 | not_tainted_path | provenance | Src:MaD:524 |
-| MuxClean.go:14:58:14:73 | not_tainted_path | MuxClean.go:14:29:14:74 | call to Join | provenance | FunctionModel Sink:MaD:854 |
+| MuxClean.go:13:22:13:32 | call to Vars | MuxClean.go:14:58:14:73 | not_tainted_path | provenance | Src:MaD:2 |
+| MuxClean.go:14:58:14:73 | not_tainted_path | MuxClean.go:14:29:14:74 | call to Join | provenance | FunctionModel Sink:MaD:1 |
+models
+| 1 | Sink: io/ioutil; ; false; ReadFile; ; ; Argument[0]; path-injection; manual |
+| 2 | Source: github.com/gorilla/mux; ; true; Vars; ; ; ReturnValue; remote; manual |
nodes
| MuxClean.go:13:22:13:32 | call to Vars | semmle.label | call to Vars |
| MuxClean.go:14:29:14:74 | call to Join | semmle.label | call to Join |
| MuxClean.go:14:58:14:73 | not_tainted_path | semmle.label | not_tainted_path |
subpaths
-#select
-| MuxClean.go:14:29:14:74 | call to Join | MuxClean.go:13:22:13:32 | call to Vars | MuxClean.go:14:29:14:74 | call to Join | This path depends on a $@. | MuxClean.go:13:22:13:32 | call to Vars | user-provided value |
diff --git a/go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected b/go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected
new file mode 100644
index 00000000000..839d35f663c
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected
@@ -0,0 +1,25 @@
+#select
+| TaintedPath.go:17:29:17:40 | tainted_path | TaintedPath.go:14:18:14:22 | selection of URL | TaintedPath.go:17:29:17:40 | tainted_path | This path depends on a $@. | TaintedPath.go:14:18:14:22 | selection of URL | user-provided value |
+| TaintedPath.go:21:28:21:69 | call to Join | TaintedPath.go:14:18:14:22 | selection of URL | TaintedPath.go:21:28:21:69 | call to Join | This path depends on a $@. | TaintedPath.go:14:18:14:22 | selection of URL | user-provided value |
+| TaintedPath.go:68:28:68:57 | call to Clean | TaintedPath.go:14:18:14:22 | selection of URL | TaintedPath.go:68:28:68:57 | call to Clean | This path depends on a $@. | TaintedPath.go:14:18:14:22 | selection of URL | user-provided value |
+edges
+| TaintedPath.go:14:18:14:22 | selection of URL | TaintedPath.go:14:18:14:30 | call to Query | provenance | Src:MaD:2 MaD:3 |
+| TaintedPath.go:14:18:14:30 | call to Query | TaintedPath.go:17:29:17:40 | tainted_path | provenance | Sink:MaD:1 |
+| TaintedPath.go:14:18:14:30 | call to Query | TaintedPath.go:21:57:21:68 | tainted_path | provenance | |
+| TaintedPath.go:14:18:14:30 | call to Query | TaintedPath.go:68:39:68:56 | ...+... | provenance | |
+| TaintedPath.go:21:57:21:68 | tainted_path | TaintedPath.go:21:28:21:69 | call to Join | provenance | FunctionModel Sink:MaD:1 |
+| TaintedPath.go:68:39:68:56 | ...+... | TaintedPath.go:68:28:68:57 | call to Clean | provenance | MaD:4 Sink:MaD:1 |
+models
+| 1 | Sink: io/ioutil; ; false; ReadFile; ; ; Argument[0]; path-injection; manual |
+| 2 | Source: net/http; Request; true; URL; ; ; ; remote; manual |
+| 3 | Summary: net/url; URL; true; Query; ; ; Argument[receiver]; ReturnValue; taint; manual |
+| 4 | Summary: path; ; false; Clean; ; ; Argument[0]; ReturnValue; taint; manual |
+nodes
+| TaintedPath.go:14:18:14:22 | selection of URL | semmle.label | selection of URL |
+| TaintedPath.go:14:18:14:30 | call to Query | semmle.label | call to Query |
+| TaintedPath.go:17:29:17:40 | tainted_path | semmle.label | tainted_path |
+| TaintedPath.go:21:28:21:69 | call to Join | semmle.label | call to Join |
+| TaintedPath.go:21:57:21:68 | tainted_path | semmle.label | tainted_path |
+| TaintedPath.go:68:28:68:57 | call to Clean | semmle.label | call to Clean |
+| TaintedPath.go:68:39:68:56 | ...+... | semmle.label | ...+... |
+subpaths
From a94ba25ebe51e1acbd2b2a7042f87f0965a498f9 Mon Sep 17 00:00:00 2001
From: Kevin Stubbings
Date: Wed, 13 Nov 2024 14:45:45 -0800
Subject: [PATCH 008/279] Apply suggestions from code review
Co-authored-by: Owen Mansel-Chan <62447351+owen-mc@users.noreply.github.com>
---
go/ql/src/change-notes/2024-10-14-gopathsanitizer.md | 2 +-
.../query-tests/Security/CWE-022/GorillaMuxDefault/MuxClean.go | 2 +-
.../Security/CWE-022/GorillaMuxSkipClean/MuxClean.go | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go/ql/src/change-notes/2024-10-14-gopathsanitizer.md b/go/ql/src/change-notes/2024-10-14-gopathsanitizer.md
index 93371d9f229..e1577bf3a90 100644
--- a/go/ql/src/change-notes/2024-10-14-gopathsanitizer.md
+++ b/go/ql/src/change-notes/2024-10-14-gopathsanitizer.md
@@ -1,4 +1,4 @@
---
category: minorAnalysis
---
-* Added [github.com/gorilla/mux.Vars](https://pkg.go.dev/github.com/gorilla/mux#Vars) to path sanitizers.
\ No newline at end of file
+* Added [github.com/gorilla/mux.Vars](https://pkg.go.dev/github.com/gorilla/mux#Vars) to path sanitizers (disabled if [github.com/gorilla/mix.Router.SkipClean](https://pkg.go.dev/github.com/gorilla/mux#Router.SkipClean) has been called).
\ No newline at end of file
diff --git a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/MuxClean.go b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/MuxClean.go
index 25e39a1bfbf..a5af6de5580 100644
--- a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/MuxClean.go
+++ b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxDefault/MuxClean.go
@@ -1,4 +1,3 @@
-// GOOD: Sanitized by Gorilla's cleaner
package main
import (
@@ -9,6 +8,7 @@ import (
"github.com/gorilla/mux"
)
+// GOOD: Sanitized by Gorilla's cleaner
func GorillaHandler(w http.ResponseWriter, r *http.Request) {
not_tainted_path := mux.Vars(r)["id"]
data, _ := ioutil.ReadFile(filepath.Join("/home/user/", not_tainted_path))
diff --git a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/MuxClean.go b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/MuxClean.go
index aafc93c75ea..cb3b5d2a7b8 100644
--- a/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/MuxClean.go
+++ b/go/ql/test/query-tests/Security/CWE-022/GorillaMuxSkipClean/MuxClean.go
@@ -1,4 +1,3 @@
-// GOOD: Sanitized by Gorilla's cleaner
package main
import (
@@ -9,6 +8,7 @@ import (
"github.com/gorilla/mux"
)
+// BAD: Gorilla's `Vars` is not a sanitizer as `Router.SkipClean` has been called
func GorillaHandler(w http.ResponseWriter, r *http.Request) {
not_tainted_path := mux.Vars(r)["id"]
data, _ := ioutil.ReadFile(filepath.Join("/home/user/", not_tainted_path))
From cf430da602624f68f670e8220c640caf4de96244 Mon Sep 17 00:00:00 2001
From: Paolo Tranquilli
Date: Thu, 23 Jan 2025 09:15:54 +0100
Subject: [PATCH 009/279] Rust/Swift: add integration tests checking env
dumping
---
rust/ql/integration-tests/hello-project/test_project.py | 6 ++++++
swift/ql/integration-tests/posix/hello-world/test.py | 9 +++++++++
2 files changed, 15 insertions(+)
diff --git a/rust/ql/integration-tests/hello-project/test_project.py b/rust/ql/integration-tests/hello-project/test_project.py
index c52002d0b22..5509bc3a93d 100644
--- a/rust/ql/integration-tests/hello-project/test_project.py
+++ b/rust/ql/integration-tests/hello-project/test_project.py
@@ -8,3 +8,9 @@ def test_cargo(codeql, rust, cargo, check_source_archive, rust_check_diagnostics
@pytest.mark.ql_test("steps.ql", expected=".rust-project.expected")
def test_rust_project(codeql, rust, rust_project, check_source_archive, rust_check_diagnostics):
codeql.database.create()
+
+@pytest.mark.ql_test(None)
+def test_do_not_print_env(codeql, rust, cargo, check_env_not_dumped, rust_check_diagnostics):
+ codeql.database.create(_env={
+ "CODEQL_EXTRACTOR_RUST_VERBOSE": "2",
+ })
diff --git a/swift/ql/integration-tests/posix/hello-world/test.py b/swift/ql/integration-tests/posix/hello-world/test.py
index c20dd20abd5..2b81f139383 100644
--- a/swift/ql/integration-tests/posix/hello-world/test.py
+++ b/swift/ql/integration-tests/posix/hello-world/test.py
@@ -1,6 +1,15 @@
+import pytest
+
import runs_on
@runs_on.posix
def test(codeql, swift):
codeql.database.create(command="swift build")
+
+@runs_on.posix
+@pytest.mark.ql_test(None)
+def test_do_not_print_env(codeql, swift, check_env_not_dumped):
+ codeql.database.create(command="swift build", _env={
+ "CODEQL_EXTRACTOR_SWIFT_LOG_LEVELS": "out:text:trace",
+ })
From e205a6811f59954bf25fe7162c3d5d3d2efebc28 Mon Sep 17 00:00:00 2001
From: Mathew Payne
Date: Thu, 23 Jan 2025 13:09:25 +0000
Subject: [PATCH 010/279] feat(rust:) Add initial rusqlite support
---
.../codeql/rust/frameworks/rusqlite.model.yml | 11 ++++
.../frameworks/rusqlite/Rusqlite.expected | 0
.../frameworks/rusqlite/Rusqlite.ql | 20 ++++++++
.../frameworks/rusqlite/cargo.toml.manual | 13 +++++
.../library-tests/frameworks/rusqlite/main.rs | 50 +++++++++++++++++++
.../frameworks/rusqlite/options.yml | 3 ++
6 files changed, 97 insertions(+)
create mode 100644 rust/ql/lib/codeql/rust/frameworks/rusqlite.model.yml
create mode 100644 rust/ql/test/library-tests/frameworks/rusqlite/Rusqlite.expected
create mode 100644 rust/ql/test/library-tests/frameworks/rusqlite/Rusqlite.ql
create mode 100644 rust/ql/test/library-tests/frameworks/rusqlite/cargo.toml.manual
create mode 100644 rust/ql/test/library-tests/frameworks/rusqlite/main.rs
create mode 100644 rust/ql/test/library-tests/frameworks/rusqlite/options.yml
diff --git a/rust/ql/lib/codeql/rust/frameworks/rusqlite.model.yml b/rust/ql/lib/codeql/rust/frameworks/rusqlite.model.yml
new file mode 100644
index 00000000000..23bf804c6f5
--- /dev/null
+++ b/rust/ql/lib/codeql/rust/frameworks/rusqlite.model.yml
@@ -0,0 +1,11 @@
+extensions:
+ - addsTo:
+ pack: codeql/rust-all
+ extensible: sinkModel
+ data:
+ - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::execute", "Argument[0]", "sql-injection", "manual"]
+ - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::execute_batch", "Argument[0]", "sql-injection", "manual"]
+ - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::prepare", "Argument[0]", "sql-injection", "manual"]
+ - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::prepare_with_flags", "Argument[0]", "sql-injection", "manual"]
+ - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::query_row", "Argument[0]", "sql-injection", "manual"]
+ - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::query_row_and_then", "Argument[0]", "sql-injection", "manual"]
diff --git a/rust/ql/test/library-tests/frameworks/rusqlite/Rusqlite.expected b/rust/ql/test/library-tests/frameworks/rusqlite/Rusqlite.expected
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/rust/ql/test/library-tests/frameworks/rusqlite/Rusqlite.ql b/rust/ql/test/library-tests/frameworks/rusqlite/Rusqlite.ql
new file mode 100644
index 00000000000..b8586d39c45
--- /dev/null
+++ b/rust/ql/test/library-tests/frameworks/rusqlite/Rusqlite.ql
@@ -0,0 +1,20 @@
+import rust
+import codeql.rust.security.SqlInjectionExtensions
+import codeql.rust.Concepts
+import utils.test.InlineExpectationsTest
+
+module RusqliteTest implements TestSig {
+ string getARelevantTag() { result = ["sql-sink"] }
+
+ predicate hasActualResult(Location location, string element, string tag, string value) {
+ exists(SqlInjection::Sink sink |
+ location = sink.getLocation() and
+ location.getFile().getBaseName() != "" and
+ element = sink.toString() and
+ tag = "sql-sink" and
+ value = ""
+ )
+ }
+}
+
+import MakeTest
diff --git a/rust/ql/test/library-tests/frameworks/rusqlite/cargo.toml.manual b/rust/ql/test/library-tests/frameworks/rusqlite/cargo.toml.manual
new file mode 100644
index 00000000000..c4c57f4b1ba
--- /dev/null
+++ b/rust/ql/test/library-tests/frameworks/rusqlite/cargo.toml.manual
@@ -0,0 +1,13 @@
+[workspace]
+
+[package]
+name = "rusqlite-test"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+rusqlite = { version = "0.33", features = ["bundled"] }
+
+[[bin]]
+name = "rusqlite"
+path = "./main.rs"
diff --git a/rust/ql/test/library-tests/frameworks/rusqlite/main.rs b/rust/ql/test/library-tests/frameworks/rusqlite/main.rs
new file mode 100644
index 00000000000..98cbaf9c42f
--- /dev/null
+++ b/rust/ql/test/library-tests/frameworks/rusqlite/main.rs
@@ -0,0 +1,50 @@
+
+use rusqlite::Connection;
+
+#[derive(Debug)]
+struct Person {
+ id: i32,
+ name: String,
+ age: i32,
+}
+
+fn main() -> Result<(), Box> {
+ // Get input from CLI
+ let args: Vec = std::env::args().collect();
+ let name = &args[1];
+ let age = &args[2];
+
+ let connection = Connection::open_in_memory()?;
+
+ connection.execute( // $ sql-sink
+ "CREATE TABLE person (
+ id SERIAL PRIMARY KEY,
+ name VARCHAR NOT NULL,
+ age INT NOT NULL
+ )",
+ (),
+ )?;
+
+ let query = format!("INSERT INTO person (name, age) VALUES ('{}', '{}')", name, age);
+
+ connection.execute(&query, ())?; // $ sql-sink
+
+ let person = connection.query_row(&query, (), |row| { // $ sql-sink
+ Ok(Person {
+ id: row.get(0)?,
+ name: row.get(1)?,
+ age: row.get(2)?,
+ })
+ })?;
+
+ let mut stmt = connection.prepare("SELECT id, name, age FROM person")?; // $ sql-sink
+ let people = stmt.query_map([], |row| {
+ Ok(Person {
+ id: row.get(0)?,
+ name: row.get(1)?,
+ age: row.get(2)?,
+ })
+ })?;
+
+ Ok(())
+}
\ No newline at end of file
diff --git a/rust/ql/test/library-tests/frameworks/rusqlite/options.yml b/rust/ql/test/library-tests/frameworks/rusqlite/options.yml
new file mode 100644
index 00000000000..ee37af2b00d
--- /dev/null
+++ b/rust/ql/test/library-tests/frameworks/rusqlite/options.yml
@@ -0,0 +1,3 @@
+qltest_cargo_check: true
+qltest_dependencies:
+ - rusqlite = { version = "0.33", features = ["bundled"] }
From eac63a3840f2af651c87ddcde2e2a216a12a2e41 Mon Sep 17 00:00:00 2001
From: Mathew Payne
Date: Thu, 23 Jan 2025 13:19:07 +0000
Subject: [PATCH 011/279] fix(rust): Update TaintFlowStep
---
.../test/library-tests/dataflow/taint/TaintFlowStep.expected | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rust/ql/test/library-tests/dataflow/taint/TaintFlowStep.expected b/rust/ql/test/library-tests/dataflow/taint/TaintFlowStep.expected
index d9cf909c792..f4dccbe7ecd 100644
--- a/rust/ql/test/library-tests/dataflow/taint/TaintFlowStep.expected
+++ b/rust/ql/test/library-tests/dataflow/taint/TaintFlowStep.expected
@@ -1,5 +1,5 @@
-| file://:0:0:0:0 | [summary param] 0 in lang:alloc::_::crate::fmt::format | file://:0:0:0:0 | [summary] to write: ReturnValue in lang:alloc::_::crate::fmt::format | MaD:24 |
-| file://:0:0:0:0 | [summary param] self in lang:alloc::_::::as_str | file://:0:0:0:0 | [summary] to write: ReturnValue in lang:alloc::_::::as_str | MaD:22 |
+| file://:0:0:0:0 | [summary param] 0 in lang:alloc::_::crate::fmt::format | file://:0:0:0:0 | [summary] to write: ReturnValue in lang:alloc::_::crate::fmt::format | MaD:34 |
+| file://:0:0:0:0 | [summary param] self in lang:alloc::_::::as_str | file://:0:0:0:0 | [summary] to write: ReturnValue in lang:alloc::_::::as_str | MaD:32 |
| file://:0:0:0:0 | [summary param] self in repo:https://github.com/seanmonstar/reqwest:reqwest::_::::text | file://:0:0:0:0 | [summary] to write: ReturnValue.Variant[crate::result::Result::Ok(0)] in repo:https://github.com/seanmonstar/reqwest:reqwest::_::::text | MaD:10 |
| main.rs:4:5:4:8 | 1000 | main.rs:4:5:4:12 | ... + ... | |
| main.rs:4:12:4:12 | i | main.rs:4:5:4:12 | ... + ... | |
From c69bb15335314dcbd2298a1b3f576ebdded156bc Mon Sep 17 00:00:00 2001
From: Mathew Payne <2772944+GeekMasher@users.noreply.github.com>
Date: Thu, 23 Jan 2025 13:32:36 +0000
Subject: [PATCH 012/279] Update
rust/ql/test/library-tests/frameworks/rusqlite/main.rs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
rust/ql/test/library-tests/frameworks/rusqlite/main.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rust/ql/test/library-tests/frameworks/rusqlite/main.rs b/rust/ql/test/library-tests/frameworks/rusqlite/main.rs
index 98cbaf9c42f..54858d20b08 100644
--- a/rust/ql/test/library-tests/frameworks/rusqlite/main.rs
+++ b/rust/ql/test/library-tests/frameworks/rusqlite/main.rs
@@ -18,7 +18,7 @@ fn main() -> Result<(), Box> {
connection.execute( // $ sql-sink
"CREATE TABLE person (
- id SERIAL PRIMARY KEY,
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR NOT NULL,
age INT NOT NULL
)",
From 0c6925399d76ad479fb71c8c3fe0cb47595661fc Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 16 Dec 2024 18:44:55 -0500
Subject: [PATCH 013/279] Java: add qhelp
---
.../CWE-352/CsrfUnprotectedRequestType.qhelp | 43 +++++++++++++++++++
.../CsrfUnprotectedRequestTypeBad.java | 5 +++
.../CsrfUnprotectedRequestTypeGood.java | 5 +++
3 files changed, 53 insertions(+)
create mode 100644 java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp
create mode 100644 java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBad.java
create mode 100644 java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGood.java
diff --git a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp
new file mode 100644
index 00000000000..7517fd5c734
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp
@@ -0,0 +1,43 @@
+
+
+
+
+
When you set up a web server to receive a request from a client without any mechanism
+for verifying that it was intentionally sent, then it is vulnerable to attack. An attacker can
+trick a client into making an unintended request to the web server that will be treated as
+an authentic request. This can be done via a URL, image load, XMLHttpRequest, etc. and can
+result in exposure of data or unintended code execution.
+
+
+
+
When handling requests, make sure any requests that change application state are protected from
+Cross Site Request Forgery (CSRF). Some application frameworks, such as Spring, provide default CSRF
+protection for HTTP request types that may change application state, such as POST. Other HTTP request
+types, such as GET, should not be used for actions that change the state of the application, since these
+request types are not default-protected from CSRF by the framework.
+
+
+
+
The following example shows a Spring request handler using a GET request for a state-changing action.
+Since a GET request does not have default CSRF protection in Spring, this type of request should
+not be used when modifying application state. Instead use one of Spring's default-protected request
+types, such as POST.
+
+
diff --git a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBad.java b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBad.java
new file mode 100644
index 00000000000..c79d15cd828
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBad.java
@@ -0,0 +1,5 @@
+// BAD - a GET request should not be used for a state-changing action like transfer
+@RequestMapping(value="transfer", method=RequestMethod.GET)
+public boolean transfer(HttpServletRequest request, HttpServletResponse response){
+ return doTransfer(request, response);
+}
diff --git a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGood.java b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGood.java
new file mode 100644
index 00000000000..40b54a800e1
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGood.java
@@ -0,0 +1,5 @@
+// GOOD - use a POST request for a state-changing action
+@RequestMapping(value="transfer", method=RequestMethod.POST)
+public boolean transfer(HttpServletRequest request, HttpServletResponse response){
+ return doTransfer(request, response);
+}
From 506d6682895e9e1c3b519435d91520e45206602d Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 26 Nov 2024 20:58:49 -0500
Subject: [PATCH 014/279] Java: add class for Spring request mapping methods
that are not default-protected from CSRF
---
.../CsrfUnprotectedRequestTypeQuery.qll | 29 +++++++++++++++++++
1 file changed, 29 insertions(+)
create mode 100644 java/ql/lib/semmle/code/java/security/CsrfUnprotectedRequestTypeQuery.qll
diff --git a/java/ql/lib/semmle/code/java/security/CsrfUnprotectedRequestTypeQuery.qll b/java/ql/lib/semmle/code/java/security/CsrfUnprotectedRequestTypeQuery.qll
new file mode 100644
index 00000000000..697461a30f1
--- /dev/null
+++ b/java/ql/lib/semmle/code/java/security/CsrfUnprotectedRequestTypeQuery.qll
@@ -0,0 +1,29 @@
+/** Provides classes and predicates to reason about CSRF vulnerabilities due to use of unprotected HTTP request types. */
+
+import java
+private import semmle.code.java.frameworks.spring.SpringController
+
+/** A method that is not protected from CSRF by default. */
+abstract class CsrfUnprotectedMethod extends Method { }
+
+/**
+ * A Spring request mapping method that is not protected from CSRF by default.
+ *
+ * https://docs.spring.io/spring-security/reference/features/exploits/csrf.html#csrf-protection-read-only
+ */
+private class SpringCsrfUnprotectedMethod extends CsrfUnprotectedMethod instanceof SpringRequestMappingMethod
+{
+ SpringCsrfUnprotectedMethod() {
+ this.hasAnnotation("org.springframework.web.bind.annotation", "GetMapping")
+ or
+ this.hasAnnotation("org.springframework.web.bind.annotation", "RequestMapping") and
+ (
+ this.getAnAnnotation().getAnEnumConstantArrayValue("method").getName() =
+ ["GET", "HEAD", "OPTIONS", "TRACE"]
+ or
+ // If no request type is specified with `@RequestMapping`, then all request types
+ // are possible, so we treat this as unsafe; example: @RequestMapping(value = "test").
+ not exists(this.getAnAnnotation().getAnArrayValue("method"))
+ )
+ }
+}
From 8e9f21dc52228965019ae66863f7345903cf5324 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 26 Nov 2024 21:14:32 -0500
Subject: [PATCH 015/279] Java: add a class for MyBatis Mapper methods that
update a database
---
.../CsrfUnprotectedRequestTypeQuery.qll | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/java/ql/lib/semmle/code/java/security/CsrfUnprotectedRequestTypeQuery.qll b/java/ql/lib/semmle/code/java/security/CsrfUnprotectedRequestTypeQuery.qll
index 697461a30f1..e3350217482 100644
--- a/java/ql/lib/semmle/code/java/security/CsrfUnprotectedRequestTypeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/CsrfUnprotectedRequestTypeQuery.qll
@@ -27,3 +27,20 @@ private class SpringCsrfUnprotectedMethod extends CsrfUnprotectedMethod instance
)
}
}
+
+/** A method that updates a database. */
+abstract class DatabaseUpdateMethod extends Method { }
+
+/** A MyBatis Mapper method that updates a database. */
+private class MyBatisMapperDatabaseUpdateMethod extends DatabaseUpdateMethod {
+ MyBatisMapperDatabaseUpdateMethod() {
+ exists(MyBatisMapperSqlOperation mapperXml |
+ (
+ mapperXml instanceof MyBatisMapperInsert or
+ mapperXml instanceof MyBatisMapperUpdate or
+ mapperXml instanceof MyBatisMapperDelete
+ ) and
+ this = mapperXml.getMapperMethod()
+ )
+ }
+}
From b88731df8004d42c0ebe996aa1d6bc8445c0123d Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 26 Nov 2024 21:18:40 -0500
Subject: [PATCH 016/279] Java: move contents of MyBatisMapperXML.qll in src to
MyBatis.qll in lib so importable, and fix experimental files broken by the
move
---
.../semmle/code/java/frameworks/MyBatis.qll | 111 ++++++++++++++++++
.../CsrfUnprotectedRequestTypeQuery.qll | 1 +
.../Security/CWE/CWE-089/MyBatisCommonLib.qll | 1 -
.../MyBatisMapperXmlSqlInjectionLib.qll | 2 +-
4 files changed, 113 insertions(+), 2 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/frameworks/MyBatis.qll b/java/ql/lib/semmle/code/java/frameworks/MyBatis.qll
index 01c8b829de6..c7fc09a33b4 100644
--- a/java/ql/lib/semmle/code/java/frameworks/MyBatis.qll
+++ b/java/ql/lib/semmle/code/java/frameworks/MyBatis.qll
@@ -128,3 +128,114 @@ private class MyBatisProviderStep extends TaintTracking::AdditionalValueStep {
)
}
}
+
+/**
+ * A MyBatis Mapper XML file.
+ */
+class MyBatisMapperXmlFile extends XmlFile {
+ MyBatisMapperXmlFile() {
+ count(XmlElement e | e = this.getAChild()) = 1 and
+ this.getAChild().getName() = "mapper"
+ }
+}
+
+/**
+ * An XML element in a `MyBatisMapperXMLFile`.
+ */
+class MyBatisMapperXmlElement extends XmlElement {
+ MyBatisMapperXmlElement() { this.getFile() instanceof MyBatisMapperXmlFile }
+
+ /**
+ * Gets the value for this element, with leading and trailing whitespace trimmed.
+ */
+ string getValue() { result = this.allCharactersString().trim() }
+
+ /**
+ * Gets the reference type bound to MyBatis Mapper XML File.
+ */
+ RefType getNamespaceRefType() {
+ result.getQualifiedName() = this.getAttribute("namespace").getValue()
+ }
+}
+
+/**
+ * An MyBatis Mapper sql operation element.
+ */
+abstract class MyBatisMapperSqlOperation extends MyBatisMapperXmlElement {
+ /**
+ * Gets the value of the `id` attribute of MyBatis Mapper sql operation element.
+ */
+ string getId() { result = this.getAttribute("id").getValue() }
+
+ /**
+ * Gets the `` element in a `MyBatisMapperSqlOperation`.
+ */
+ MyBatisMapperInclude getInclude() { result = this.getAChild*() }
+
+ /**
+ * Gets the method bound to MyBatis Mapper XML File.
+ */
+ Method getMapperMethod() {
+ result.getName() = this.getId() and
+ result.getDeclaringType() = this.getParent().(MyBatisMapperXmlElement).getNamespaceRefType()
+ }
+}
+
+/**
+ * A `` element in a `MyBatisMapperSqlOperation`.
+ */
+class MyBatisMapperInsert extends MyBatisMapperSqlOperation {
+ MyBatisMapperInsert() { this.getName() = "insert" }
+}
+
+/**
+ * A `` element in a `MyBatisMapperSqlOperation`.
+ */
+class MyBatisMapperUpdate extends MyBatisMapperSqlOperation {
+ MyBatisMapperUpdate() { this.getName() = "update" }
+}
+
+/**
+ * A `` element in a `MyBatisMapperSqlOperation`.
+ */
+class MyBatisMapperDelete extends MyBatisMapperSqlOperation {
+ MyBatisMapperDelete() { this.getName() = "delete" }
+}
+
+/**
+ * A `
-
When handling requests, make sure any requests that change application state are protected from
-Cross Site Request Forgery (CSRF). Some application frameworks, such as Spring, provide default CSRF
-protection for HTTP request types that may change application state, such as POST. Other HTTP request
-types, such as GET, should not be used for actions that change the state of the application, since these
-request types are not default-protected from CSRF by the framework.
+
Make sure any requests that change application state are protected from Cross Site Request Forgery (CSRF).
+Some application frameworks provide default CSRF protection for unsafe HTTP request methods (POST,
+PUT, DELETE, PATCH, CONNECT) which may change the state of
+the application. Safe HTTP request methods (GET, HEAD, OPTIONS,
+TRACE) should be read-only and should not be used for actions that change application state.
+
+
This query currently supports the Spring and Stapler web frameworks. Spring provides default CSRF protection
+for all unsafe HTTP methods. Stapler provides default CSRF protection for the POST method.
-
The following example shows a Spring request handler using a GET request for a state-changing action.
-Since a GET request does not have default CSRF protection in Spring, this type of request should
-not be used when modifying application state. Instead use one of Spring's default-protected request
-types, such as POST.
+
The following examples show Spring request handlers allowing safe HTTP request methods for state-changing actions.
+Since safe HTTP request methods do not have default CSRF protection in Spring, they should not be used when modifying
+application state. Instead use one of the unsafe HTTP methods which Spring default-protects from CSRF.
-
+
-
+
+
+
The following examples show Stapler web methods allowing safe HTTP request methods for state-changing actions.
+Since safe HTTP request methods do not have default CSRF protection in Stapler, they should not be used when modifying
+application state. Instead use the POST method which Stapler default-protects from CSRF.
diff --git a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBad.java b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBad.java
deleted file mode 100644
index c79d15cd828..00000000000
--- a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBad.java
+++ /dev/null
@@ -1,5 +0,0 @@
-// BAD - a GET request should not be used for a state-changing action like transfer
-@RequestMapping(value="transfer", method=RequestMethod.GET)
-public boolean transfer(HttpServletRequest request, HttpServletResponse response){
- return doTransfer(request, response);
-}
diff --git a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBadSpring.java b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBadSpring.java
new file mode 100644
index 00000000000..f3c8bb25906
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBadSpring.java
@@ -0,0 +1,14 @@
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+// BAD - a safe HTTP request like GET should not be used for a state-changing action
+@RequestMapping(value="/transfer", method=RequestMethod.GET)
+public boolean doTransfer(HttpServletRequest request, HttpServletResponse response){
+ return transfer(request, response);
+}
+
+// BAD - no HTTP request type is specified, so safe HTTP requests are allowed
+@RequestMapping(value="/delete")
+public boolean doDelete(HttpServletRequest request, HttpServletResponse response){
+ return delete(request, response);
+}
diff --git a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBadStapler.java b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBadStapler.java
new file mode 100644
index 00000000000..2e924651d67
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBadStapler.java
@@ -0,0 +1,12 @@
+import org.kohsuke.stapler.verb.GET;
+
+// BAD - a safe HTTP request like GET should not be used for a state-changing action
+@GET
+public HttpRedirect doTransfer() {
+ return transfer();
+}
+
+// BAD - no HTTP request type is specified, so safe HTTP requests are allowed
+public HttpRedirect doDelete() {
+ return delete();
+}
diff --git a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGood.java b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGood.java
deleted file mode 100644
index 40b54a800e1..00000000000
--- a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGood.java
+++ /dev/null
@@ -1,5 +0,0 @@
-// GOOD - use a POST request for a state-changing action
-@RequestMapping(value="transfer", method=RequestMethod.POST)
-public boolean transfer(HttpServletRequest request, HttpServletResponse response){
- return doTransfer(request, response);
-}
diff --git a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGoodSpring.java b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGoodSpring.java
new file mode 100644
index 00000000000..df02ccc70c7
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGoodSpring.java
@@ -0,0 +1,15 @@
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.DeleteMapping;
+
+// GOOD - use an unsafe HTTP request like POST
+@RequestMapping(value="/transfer", method=RequestMethod.POST)
+public boolean doTransfer(HttpServletRequest request, HttpServletResponse response){
+ return transfer(request, response);
+}
+
+// GOOD - use an unsafe HTTP request like DELETE
+@DeleteMapping(value="/delete")
+public boolean doDelete(HttpServletRequest request, HttpServletResponse response){
+ return delete(request, response);
+}
diff --git a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGoodStapler.java b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGoodStapler.java
new file mode 100644
index 00000000000..5a837d2af30
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGoodStapler.java
@@ -0,0 +1,13 @@
+import org.kohsuke.stapler.verb.POST;
+
+// GOOD - use POST
+@POST
+public HttpRedirect doTransfer() {
+ return transfer();
+}
+
+// GOOD - use POST
+@POST
+public HttpRedirect doDelete() {
+ return delete();
+}
From 530103e2d93c606b58ea5fa03f0ab7cd53a90266 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 29 Jan 2025 21:30:53 -0500
Subject: [PATCH 043/279] Java: narrow query
remove PUT and DELETE from StaplerCsrfUnprotectedMethod
remove OPTIONS and TRACE from SpringCsrfUnprotectedMethod
---
.../CsrfUnprotectedRequestTypeQuery.qll | 7 +++++--
.../CsrfUnprotectedRequestTypeTest.java | 18 ++++++++++++++----
2 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/CsrfUnprotectedRequestTypeQuery.qll b/java/ql/lib/semmle/code/java/security/CsrfUnprotectedRequestTypeQuery.qll
index 7c8382fb5b4..42d6db246c0 100644
--- a/java/ql/lib/semmle/code/java/security/CsrfUnprotectedRequestTypeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/CsrfUnprotectedRequestTypeQuery.qll
@@ -25,7 +25,7 @@ private class SpringCsrfUnprotectedMethod extends CsrfUnprotectedMethod instance
or
this.hasAnnotation("org.springframework.web.bind.annotation", "RequestMapping") and
(
- this.getMethodValue() = ["GET", "HEAD", "OPTIONS", "TRACE"]
+ this.getMethodValue() = ["GET", "HEAD"]
or
// If no request type is specified with `@RequestMapping`, then all request types
// are possible, so we treat this as unsafe; example: @RequestMapping(value = "test").
@@ -43,7 +43,10 @@ private class StaplerCsrfUnprotectedMethod extends CsrfUnprotectedMethod instanc
{
StaplerCsrfUnprotectedMethod() {
not this.hasAnnotation("org.kohsuke.stapler.interceptor", "RequirePOST") and
- not this.hasAnnotation("org.kohsuke.stapler.verb", "POST")
+ // Jenkins only explicitly protects against CSRF for POST requests, but we
+ // also exclude PUT and DELETE since these request types are only exploitable
+ // if there is a CORS issue.
+ not this.hasAnnotation("org.kohsuke.stapler.verb", ["POST", "PUT", "DELETE"])
}
}
diff --git a/java/ql/test/query-tests/security/CWE-352/CsrfUnprotectedRequestTypeTest.java b/java/ql/test/query-tests/security/CWE-352/CsrfUnprotectedRequestTypeTest.java
index 764ce4ecbea..00b8ea24f71 100644
--- a/java/ql/test/query-tests/security/CWE-352/CsrfUnprotectedRequestTypeTest.java
+++ b/java/ql/test/query-tests/security/CWE-352/CsrfUnprotectedRequestTypeTest.java
@@ -88,6 +88,17 @@ public class CsrfUnprotectedRequestTypeTest {
} catch (SQLException e) { }
}
+ // GOOD: uses OPTIONS or TRACE, which are unlikely to be exploitable via CSRF
+ @RequestMapping(value = "", method = { OPTIONS, TRACE })
+ public void good0() {
+ try {
+ String sql = "DELETE";
+ Connection conn = DriverManager.getConnection("url");
+ PreparedStatement ps = conn.prepareStatement(sql);
+ ps.executeUpdate(); // database update method call
+ } catch (SQLException e) { }
+ }
+
// GOOD: uses POST request when updating a database
@RequestMapping(value = "", method = RequestMethod.POST)
public void good1() {
@@ -430,11 +441,10 @@ public class CsrfUnprotectedRequestTypeTest {
return "post";
}
- // BAD: Stapler web method annotated with `@PUT` and method name that implies a state-change
- // We treat this case as bad for Stapler since the Jenkins docs only say that @POST/@RequirePOST
- // provide default protection against CSRF.
+ // GOOD: Stapler web method annotated with `@PUT` and method name that implies a state-change
+ // We treat this case as good since PUT is only exploitable if there is a CORS issue.
@PUT
- public String doPut(String user) { // $ hasCsrfUnprotectedRequestType
+ public String doPut(String user) {
return "put";
}
From 577152e20faf7798287fa4a942c3134f03553e4b Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 29 Jan 2025 22:21:48 -0500
Subject: [PATCH 044/279] Java: minor qhelp update
---
.../Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp | 7 +++----
.../CWE/CWE-352/CsrfUnprotectedRequestTypeBadStapler.java | 4 ++--
.../CWE/CWE-352/CsrfUnprotectedRequestTypeGoodStapler.java | 4 ++--
3 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp
index d568f236636..5d71f3f6897 100644
--- a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp
+++ b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp
@@ -11,10 +11,9 @@ result in exposure of data or unintended code execution.
Make sure any requests that change application state are protected from Cross Site Request Forgery (CSRF).
-Some application frameworks provide default CSRF protection for unsafe HTTP request methods (POST,
-PUT, DELETE, PATCH, CONNECT) which may change the state of
-the application. Safe HTTP request methods (GET, HEAD, OPTIONS,
-TRACE) should be read-only and should not be used for actions that change application state.
+Some application frameworks provide default CSRF protection for unsafe HTTP request methods (such as
+POST) which may change the state of the application. Safe HTTP request methods (such as
+GET) should be read-only and should not be used for actions that change application state.
This query currently supports the Spring and Stapler web frameworks. Spring provides default CSRF protection
for all unsafe HTTP methods. Stapler provides default CSRF protection for the POST method.
diff --git a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBadStapler.java b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBadStapler.java
index 2e924651d67..d76e85cfd37 100644
--- a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBadStapler.java
+++ b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeBadStapler.java
@@ -7,6 +7,6 @@ public HttpRedirect doTransfer() {
}
// BAD - no HTTP request type is specified, so safe HTTP requests are allowed
-public HttpRedirect doDelete() {
- return delete();
+public HttpRedirect doPost() {
+ return post();
}
diff --git a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGoodStapler.java b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGoodStapler.java
index 5a837d2af30..f8c81633c19 100644
--- a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGoodStapler.java
+++ b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestTypeGoodStapler.java
@@ -8,6 +8,6 @@ public HttpRedirect doTransfer() {
// GOOD - use POST
@POST
-public HttpRedirect doDelete() {
- return delete();
+public HttpRedirect doPost() {
+ return post();
}
From 0071e1acc21159bc11fb6fde2846de483aab630b Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 30 Jan 2025 10:19:21 -0500
Subject: [PATCH 045/279] Java: resolve merge conflict
remove import no longer needed since contents of MyBatisMapperXML.qll have been moved to MyBatis.qll
---
.../Security/CWE/CWE-089/MyBatisMapperXmlSqlInjection.ql | 1 -
1 file changed, 1 deletion(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-089/MyBatisMapperXmlSqlInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-089/MyBatisMapperXmlSqlInjection.ql
index a73bf21c5dd..01b33138d72 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-089/MyBatisMapperXmlSqlInjection.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-089/MyBatisMapperXmlSqlInjection.ql
@@ -15,7 +15,6 @@
import java
deprecated import MyBatisCommonLib
deprecated import MyBatisMapperXmlSqlInjectionLib
-deprecated import semmle.code.xml.MyBatisMapperXML
import semmle.code.java.dataflow.FlowSources
private import semmle.code.java.security.Sanitizers
deprecated import MyBatisMapperXmlSqlInjectionFlow::PathGraph
From a9f107ce06fcc24129b3fb09d7b9baed37201ea5 Mon Sep 17 00:00:00 2001
From: fabienpe <11886801+fabienpe@users.noreply.github.com>
Date: Fri, 31 Jan 2025 15:47:25 +0000
Subject: [PATCH 046/279] Added missing "GOOD" and "BAD" to some examples
---
.../Security/CWE/CWE-020/ExternalAPITaintStepExample.java | 1 +
.../src/Security/CWE/CWE-023/PartialPathTraversalBad.java | 2 +-
.../Security/CWE/CWE-023/PartialPathTraversalGood.java | 2 +-
.../AndroidWebViewAddJavascriptInterfaceExample.java | 2 +-
.../CWE/CWE-079/WebSettingsDisableJavascript.java | 2 +-
.../Security/CWE/CWE-079/WebSettingsEnableJavascript.java | 2 +-
java/ql/src/Security/CWE/CWE-094/GroovyInjectionBad.java | 8 ++++----
java/ql/src/Security/CWE/CWE-094/InstallApkWithFile.java | 2 +-
.../Security/CWE/CWE-094/InstallApkWithFileProvider.java | 2 +-
.../CWE/CWE-094/InstallApkWithPackageInstaller.java | 1 +
java/ql/src/Security/CWE/CWE-094/SSTIBad.java | 2 +-
java/ql/src/Security/CWE/CWE-094/SSTIGood.java | 2 +-
.../CWE-094/SaferJexlExpressionEvaluationWithSandbox.java | 2 +-
...SaferJexlExpressionEvaluationWithUberspectSandbox.java | 2 +-
.../CWE/CWE-094/SaferSpelExpressionEvaluation.java | 4 ++--
.../CWE/CWE-094/UnsafeJexlExpressionEvaluation.java | 2 +-
.../CWE/CWE-094/UnsafeSpelExpressionEvaluation.java | 2 +-
.../CWE/CWE-1204/BadStaticInitializationVector.java | 2 +-
.../CWE/CWE-1204/GoodRandomInitializationVector.java | 2 +-
.../src/Security/CWE/CWE-200/AndroidSensitiveTextBad.java | 2 +-
.../Security/CWE/CWE-200/AndroidSensitiveTextGood.java | 2 +-
.../src/Security/CWE/CWE-200/ContentAccessDisabled.java | 1 +
.../ql/src/Security/CWE/CWE-200/ContentAccessEnabled.java | 1 +
.../src/Security/CWE/CWE-200/WebViewFileAccessSafe.java | 1 +
.../src/Security/CWE/CWE-200/WebViewFileAccessUnsafe.java | 1 +
.../CWE/CWE-330/examples/InsecureRandomnessCookie.java | 2 +-
.../CWE/CWE-330/examples/SecureRandomnessCookie.java | 2 +-
java/ql/src/Security/CWE/CWE-367/TOCTOURace.java | 4 ++--
.../Security/CWE/CWE-502/UnsafeDeserializationBad.java | 2 +-
.../Security/CWE/CWE-502/UnsafeDeserializationGood.java | 2 +-
java/ql/src/Security/CWE/CWE-522/LdapAuthUseLdap.java | 1 +
java/ql/src/Security/CWE/CWE-522/LdapAuthUseLdaps.java | 1 +
java/ql/src/Security/CWE/CWE-522/LdapEnableSasl.java | 1 +
java/ql/src/Security/CWE/CWE-611/XXEBad.java | 2 +-
java/ql/src/Security/CWE/CWE-611/XXEGood.java | 2 +-
.../src/Security/CWE/CWE-798/HardcodedAWSCredentials.java | 2 +-
.../Security/CWE/CWE-798/HardcodedCredentialsApiCall.java | 4 ++--
java/ql/src/Security/CWE/CWE-835/InfiniteLoopBad.java | 2 +-
java/ql/src/Security/CWE/CWE-835/InfiniteLoopGood.java | 2 +-
java/ql/src/Security/CWE/CWE-925/Bad.java | 1 +
java/ql/src/Security/CWE/CWE-925/Good.java | 1 +
41 files changed, 47 insertions(+), 36 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-020/ExternalAPITaintStepExample.java b/java/ql/src/Security/CWE/CWE-020/ExternalAPITaintStepExample.java
index 65735194a69..112519f7dda 100644
--- a/java/ql/src/Security/CWE/CWE-020/ExternalAPITaintStepExample.java
+++ b/java/ql/src/Security/CWE/CWE-020/ExternalAPITaintStepExample.java
@@ -4,6 +4,7 @@ public class SQLInjection extends HttpServlet {
StringBuilder sqlQueryBuilder = new StringBuilder();
sqlQueryBuilder.append("SELECT * FROM user WHERE user_id='");
+ // BAD: a request parameter is concatenated directly into a SQL query
sqlQueryBuilder.append(request.getParameter("user_id"));
sqlQueryBuilder.append("'");
diff --git a/java/ql/src/Security/CWE/CWE-023/PartialPathTraversalBad.java b/java/ql/src/Security/CWE/CWE-023/PartialPathTraversalBad.java
index 961d8c21c6b..98985647332 100644
--- a/java/ql/src/Security/CWE/CWE-023/PartialPathTraversalBad.java
+++ b/java/ql/src/Security/CWE/CWE-023/PartialPathTraversalBad.java
@@ -1,6 +1,6 @@
public class PartialPathTraversalBad {
public void example(File dir, File parent) throws IOException {
- if (!dir.getCanonicalPath().startsWith(parent.getCanonicalPath())) {
+ if (!dir.getCanonicalPath().startsWith(parent.getCanonicalPath())) { // BAD: dir.getCanonicalPath() not slash-terminated
throw new IOException("Path traversal attempt: " + dir.getCanonicalPath());
}
}
diff --git a/java/ql/src/Security/CWE/CWE-023/PartialPathTraversalGood.java b/java/ql/src/Security/CWE/CWE-023/PartialPathTraversalGood.java
index 65392373a25..2ad78d9d39e 100644
--- a/java/ql/src/Security/CWE/CWE-023/PartialPathTraversalGood.java
+++ b/java/ql/src/Security/CWE/CWE-023/PartialPathTraversalGood.java
@@ -2,7 +2,7 @@ import java.io.File;
public class PartialPathTraversalGood {
public void example(File dir, File parent) throws IOException {
- if (!dir.toPath().normalize().startsWith(parent.toPath())) {
+ if (!dir.toPath().normalize().startsWith(parent.toPath())) { // GOOD: Check if dir.Path() is normalised
throw new IOException("Path traversal attempt: " + dir.getCanonicalPath());
}
}
diff --git a/java/ql/src/Security/CWE/CWE-079/AndroidWebViewAddJavascriptInterfaceExample.java b/java/ql/src/Security/CWE/CWE-079/AndroidWebViewAddJavascriptInterfaceExample.java
index fb4e1182a5a..5beeb505000 100644
--- a/java/ql/src/Security/CWE/CWE-079/AndroidWebViewAddJavascriptInterfaceExample.java
+++ b/java/ql/src/Security/CWE/CWE-079/AndroidWebViewAddJavascriptInterfaceExample.java
@@ -20,4 +20,4 @@ webview.addJavaScriptInterface(new ExposedObject(), "exposedObject");
webview.loadData("", "text/html", null);
String name = "Robert'; DROP TABLE students; --";
-webview.loadUrl("javascript:alert(exposedObject.studentEmail(\""+ name +"\"))");
+webview.loadUrl("javascript:alert(exposedObject.studentEmail(\""+ name +"\"))"); // BAD: Untrusted input loaded into WebView
diff --git a/java/ql/src/Security/CWE/CWE-079/WebSettingsDisableJavascript.java b/java/ql/src/Security/CWE/CWE-079/WebSettingsDisableJavascript.java
index 43a8cd92c0e..13b83848135 100644
--- a/java/ql/src/Security/CWE/CWE-079/WebSettingsDisableJavascript.java
+++ b/java/ql/src/Security/CWE/CWE-079/WebSettingsDisableJavascript.java
@@ -1,2 +1,2 @@
WebSettings settings = webview.getSettings();
-settings.setJavaScriptEnabled(false);
+settings.setJavaScriptEnabled(false); // GOOD: webview has JavaScript disabled
diff --git a/java/ql/src/Security/CWE/CWE-079/WebSettingsEnableJavascript.java b/java/ql/src/Security/CWE/CWE-079/WebSettingsEnableJavascript.java
index adfac156009..c75c4c0a2b1 100644
--- a/java/ql/src/Security/CWE/CWE-079/WebSettingsEnableJavascript.java
+++ b/java/ql/src/Security/CWE/CWE-079/WebSettingsEnableJavascript.java
@@ -1,2 +1,2 @@
WebSettings settings = webview.getSettings();
-settings.setJavaScriptEnabled(true);
+settings.setJavaScriptEnabled(true); // BAD: webview has JavaScript enabled
diff --git a/java/ql/src/Security/CWE/CWE-094/GroovyInjectionBad.java b/java/ql/src/Security/CWE/CWE-094/GroovyInjectionBad.java
index 8afe77d2a39..2111d11c9a5 100644
--- a/java/ql/src/Security/CWE/CWE-094/GroovyInjectionBad.java
+++ b/java/ql/src/Security/CWE/CWE-094/GroovyInjectionBad.java
@@ -2,26 +2,26 @@ public class GroovyInjection {
void injectionViaClassLoader(HttpServletRequest request) {
String script = request.getParameter("script");
final GroovyClassLoader classLoader = new GroovyClassLoader();
- Class groovy = classLoader.parseClass(script);
+ Class groovy = classLoader.parseClass(script); // BAD: Groovy code injection
GroovyObject groovyObj = (GroovyObject) groovy.newInstance();
}
void injectionViaEval(HttpServletRequest request) {
String script = request.getParameter("script");
- Eval.me(script);
+ Eval.me(script); // BAD: Groovy code injection
}
void injectionViaGroovyShell(HttpServletRequest request) {
GroovyShell shell = new GroovyShell();
String script = request.getParameter("script");
- shell.evaluate(script);
+ shell.evaluate(script); // BAD: Groovy code injection
}
void injectionViaGroovyShellGroovyCodeSource(HttpServletRequest request) {
GroovyShell shell = new GroovyShell();
String script = request.getParameter("script");
GroovyCodeSource gcs = new GroovyCodeSource(script, "test", "Test");
- shell.evaluate(gcs);
+ shell.evaluate(gcs); // BAD: Groovy code injection
}
}
diff --git a/java/ql/src/Security/CWE/CWE-094/InstallApkWithFile.java b/java/ql/src/Security/CWE/CWE-094/InstallApkWithFile.java
index 39462a43cc9..bbaaa53e8e3 100644
--- a/java/ql/src/Security/CWE/CWE-094/InstallApkWithFile.java
+++ b/java/ql/src/Security/CWE/CWE-094/InstallApkWithFile.java
@@ -9,6 +9,6 @@ import java.io.File;
File file = new File(Environment.getExternalStorageDirectory(), "myapp.apk");
Intent intent = new Intent(Intent.ACTION_VIEW);
/* Set the mimetype to APK */
-intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
+intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); // BAD: The file may be altered by another app
startActivity(intent);
diff --git a/java/ql/src/Security/CWE/CWE-094/InstallApkWithFileProvider.java b/java/ql/src/Security/CWE/CWE-094/InstallApkWithFileProvider.java
index d6e537caf7d..600eaa7dbf9 100644
--- a/java/ql/src/Security/CWE/CWE-094/InstallApkWithFileProvider.java
+++ b/java/ql/src/Security/CWE/CWE-094/InstallApkWithFileProvider.java
@@ -21,7 +21,7 @@ try (InputStream is = getAssets().open(assetName);
/* Expose temporary file with FileProvider */
File toInstall = new File(this.getFilesDir(), tempFilename);
-Uri applicationUri = FileProvider.getUriForFile(this, "com.example.apkprovider", toInstall);
+Uri applicationUri = FileProvider.getUriForFile(this, "com.example.apkprovider", toInstall); // GOOD: The file is protected by FileProvider
/* Create Intent and set data to APK file. */
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
diff --git a/java/ql/src/Security/CWE/CWE-094/InstallApkWithPackageInstaller.java b/java/ql/src/Security/CWE/CWE-094/InstallApkWithPackageInstaller.java
index fbe8eca3f47..909493b47b1 100644
--- a/java/ql/src/Security/CWE/CWE-094/InstallApkWithPackageInstaller.java
+++ b/java/ql/src/Security/CWE/CWE-094/InstallApkWithPackageInstaller.java
@@ -1,3 +1,4 @@
+// GOOD: Package installed using PackageInstaller
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller;
diff --git a/java/ql/src/Security/CWE/CWE-094/SSTIBad.java b/java/ql/src/Security/CWE/CWE-094/SSTIBad.java
index 33210448e18..d1ad7e35e26 100644
--- a/java/ql/src/Security/CWE/CWE-094/SSTIBad.java
+++ b/java/ql/src/Security/CWE/CWE-094/SSTIBad.java
@@ -14,6 +14,6 @@ public class VelocitySSTI {
StringWriter w = new StringWriter();
// evaluate( Context context, Writer out, String logTag, String instring )
- Velocity.evaluate(context, w, "mystring", code);
+ Velocity.evaluate(context, w, "mystring", code); // BAD: code is controlled by the user
}
}
diff --git a/java/ql/src/Security/CWE/CWE-094/SSTIGood.java b/java/ql/src/Security/CWE/CWE-094/SSTIGood.java
index be4120539d5..288dd525901 100644
--- a/java/ql/src/Security/CWE/CWE-094/SSTIGood.java
+++ b/java/ql/src/Security/CWE/CWE-094/SSTIGood.java
@@ -11,7 +11,7 @@ public class VelocitySSTI {
String s = "We are using $project $name to render this.";
StringWriter w = new StringWriter();
- Velocity.evaluate(context, w, "mystring", s);
+ Velocity.evaluate(context, w, "mystring", s); // GOOD: s is a constant string
System.out.println(" string : " + w);
}
}
diff --git a/java/ql/src/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithSandbox.java b/java/ql/src/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithSandbox.java
index 43b3acfb65e..25cc0193198 100644
--- a/java/ql/src/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithSandbox.java
+++ b/java/ql/src/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithSandbox.java
@@ -4,7 +4,7 @@ public void evaluate(Socket socket) throws IOException {
JexlSandbox onlyMath = new JexlSandbox(false);
onlyMath.white("java.lang.Math");
- JexlEngine jexl = new JexlBuilder().sandbox(onlyMath).create();
+ JexlEngine jexl = new JexlBuilder().sandbox(onlyMath).create(); // GOOD: using a sandbox
String input = reader.readLine();
JexlExpression expression = jexl.createExpression(input);
diff --git a/java/ql/src/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithUberspectSandbox.java b/java/ql/src/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithUberspectSandbox.java
index 5952668b8b3..8515083b0ec 100644
--- a/java/ql/src/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithUberspectSandbox.java
+++ b/java/ql/src/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithUberspectSandbox.java
@@ -6,7 +6,7 @@ public void evaluate(Socket socket) throws IOException {
JexlEngine jexl = new JexlBuilder().uberspect(sandbox).create();
String input = reader.readLine();
- JexlExpression expression = jexl.createExpression(input);
+ JexlExpression expression = jexl.createExpression(input); // GOOD: jexl uses a sandbox
JexlContext context = new MapContext();
expression.evaluate(context);
}
diff --git a/java/ql/src/Security/CWE/CWE-094/SaferSpelExpressionEvaluation.java b/java/ql/src/Security/CWE/CWE-094/SaferSpelExpressionEvaluation.java
index 04dc4a5220f..438c48bcfc0 100644
--- a/java/ql/src/Security/CWE/CWE-094/SaferSpelExpressionEvaluation.java
+++ b/java/ql/src/Security/CWE/CWE-094/SaferSpelExpressionEvaluation.java
@@ -4,9 +4,9 @@ public Object evaluate(Socket socket) throws IOException {
String string = reader.readLine();
ExpressionParser parser = new SpelExpressionParser();
- Expression expression = parser.parseExpression(string);
+ Expression expression = parser.parseExpression(string); // AVOID: string is controlled by the user
SimpleEvaluationContext context
= SimpleEvaluationContext.forReadWriteDataBinding().build();
- return expression.getValue(context);
+ return expression.getValue(context); // OK: Untrusted expressions are evaluated in a restricted context
}
}
\ No newline at end of file
diff --git a/java/ql/src/Security/CWE/CWE-094/UnsafeJexlExpressionEvaluation.java b/java/ql/src/Security/CWE/CWE-094/UnsafeJexlExpressionEvaluation.java
index 986fa9833cb..762dfdf2f31 100644
--- a/java/ql/src/Security/CWE/CWE-094/UnsafeJexlExpressionEvaluation.java
+++ b/java/ql/src/Security/CWE/CWE-094/UnsafeJexlExpressionEvaluation.java
@@ -4,7 +4,7 @@ public void evaluate(Socket socket) throws IOException {
String input = reader.readLine();
JexlEngine jexl = new JexlBuilder().create();
- JexlExpression expression = jexl.createExpression(input);
+ JexlExpression expression = jexl.createExpression(input); // BAD: input is controlled by the user
JexlContext context = new MapContext();
expression.evaluate(context);
}
diff --git a/java/ql/src/Security/CWE/CWE-094/UnsafeSpelExpressionEvaluation.java b/java/ql/src/Security/CWE/CWE-094/UnsafeSpelExpressionEvaluation.java
index b16a1eb50d2..d5b8060d2d3 100644
--- a/java/ql/src/Security/CWE/CWE-094/UnsafeSpelExpressionEvaluation.java
+++ b/java/ql/src/Security/CWE/CWE-094/UnsafeSpelExpressionEvaluation.java
@@ -4,7 +4,7 @@ public Object evaluate(Socket socket) throws IOException {
String string = reader.readLine();
ExpressionParser parser = new SpelExpressionParser();
- Expression expression = parser.parseExpression(string);
+ Expression expression = parser.parseExpression(string); // BAD: string is controlled by the user
return expression.getValue();
}
}
\ No newline at end of file
diff --git a/java/ql/src/Security/CWE/CWE-1204/BadStaticInitializationVector.java b/java/ql/src/Security/CWE/CWE-1204/BadStaticInitializationVector.java
index 85e8be6d8ce..b2adaa09e15 100644
--- a/java/ql/src/Security/CWE/CWE-1204/BadStaticInitializationVector.java
+++ b/java/ql/src/Security/CWE/CWE-1204/BadStaticInitializationVector.java
@@ -1,4 +1,4 @@
-byte[] iv = new byte[16]; // all zeroes
+byte[] iv = new byte[16]; // BAD: all zeroes
GCMParameterSpec params = new GCMParameterSpec(128, iv);
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, key, params);
\ No newline at end of file
diff --git a/java/ql/src/Security/CWE/CWE-1204/GoodRandomInitializationVector.java b/java/ql/src/Security/CWE/CWE-1204/GoodRandomInitializationVector.java
index faceb119d64..fa1b0b40739 100644
--- a/java/ql/src/Security/CWE/CWE-1204/GoodRandomInitializationVector.java
+++ b/java/ql/src/Security/CWE/CWE-1204/GoodRandomInitializationVector.java
@@ -1,6 +1,6 @@
byte[] iv = new byte[16];
SecureRandom random = SecureRandom.getInstanceStrong();
-random.nextBytes(iv);
+random.nextBytes(iv); // GOOD: random initialization vector
GCMParameterSpec params = new GCMParameterSpec(128, iv);
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, key, params);
\ No newline at end of file
diff --git a/java/ql/src/Security/CWE/CWE-200/AndroidSensitiveTextBad.java b/java/ql/src/Security/CWE/CWE-200/AndroidSensitiveTextBad.java
index d94e667b3db..73b09395597 100644
--- a/java/ql/src/Security/CWE/CWE-200/AndroidSensitiveTextBad.java
+++ b/java/ql/src/Security/CWE/CWE-200/AndroidSensitiveTextBad.java
@@ -1,2 +1,2 @@
TextView pwView = getViewById(R.id.pw_text);
-pwView.setText("Your password is: " + password);
\ No newline at end of file
+pwView.setText("Your password is: " + password); // BAD: password is shown immediately
\ No newline at end of file
diff --git a/java/ql/src/Security/CWE/CWE-200/AndroidSensitiveTextGood.java b/java/ql/src/Security/CWE/CWE-200/AndroidSensitiveTextGood.java
index 507fdae731c..93c7aeabdfc 100644
--- a/java/ql/src/Security/CWE/CWE-200/AndroidSensitiveTextGood.java
+++ b/java/ql/src/Security/CWE/CWE-200/AndroidSensitiveTextGood.java
@@ -5,6 +5,6 @@ pwView.setText("Your password is: " + password);
Button showButton = findViewById(R.id.show_pw_button);
showButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
- pwView.setVisibility(View.VISIBLE);
+ pwView.setVisibility(View.VISIBLE); // GOOD: password is only shown when the user clicks the button
}
});
diff --git a/java/ql/src/Security/CWE/CWE-200/ContentAccessDisabled.java b/java/ql/src/Security/CWE/CWE-200/ContentAccessDisabled.java
index 25214a69afe..4435d411374 100644
--- a/java/ql/src/Security/CWE/CWE-200/ContentAccessDisabled.java
+++ b/java/ql/src/Security/CWE/CWE-200/ContentAccessDisabled.java
@@ -1,3 +1,4 @@
WebSettings settings = webview.getSettings();
+// GOOD: WebView is configured to disallow content access
settings.setAllowContentAccess(false);
diff --git a/java/ql/src/Security/CWE/CWE-200/ContentAccessEnabled.java b/java/ql/src/Security/CWE/CWE-200/ContentAccessEnabled.java
index 3e5e6cb466b..13f90730f59 100644
--- a/java/ql/src/Security/CWE/CWE-200/ContentAccessEnabled.java
+++ b/java/ql/src/Security/CWE/CWE-200/ContentAccessEnabled.java
@@ -1,3 +1,4 @@
WebSettings settings = webview.getSettings();
+// BAD: WebView is configured to allow content access
settings.setAllowContentAccess(true);
diff --git a/java/ql/src/Security/CWE/CWE-200/WebViewFileAccessSafe.java b/java/ql/src/Security/CWE/CWE-200/WebViewFileAccessSafe.java
index 6002888cba1..e5217d92f86 100644
--- a/java/ql/src/Security/CWE/CWE-200/WebViewFileAccessSafe.java
+++ b/java/ql/src/Security/CWE/CWE-200/WebViewFileAccessSafe.java
@@ -1,5 +1,6 @@
WebSettings settings = view.getSettings();
+// GOOD: WebView is configured to disallow file access
settings.setAllowFileAccess(false);
settings.setAllowFileAccessFromURLs(false);
settings.setAllowUniversalAccessFromURLs(false);
diff --git a/java/ql/src/Security/CWE/CWE-200/WebViewFileAccessUnsafe.java b/java/ql/src/Security/CWE/CWE-200/WebViewFileAccessUnsafe.java
index 6c17d66c3b0..c2496793376 100644
--- a/java/ql/src/Security/CWE/CWE-200/WebViewFileAccessUnsafe.java
+++ b/java/ql/src/Security/CWE/CWE-200/WebViewFileAccessUnsafe.java
@@ -1,5 +1,6 @@
WebSettings settings = view.getSettings();
+// BAD: WebView is configured to allow file access
settings.setAllowFileAccess(true);
settings.setAllowFileAccessFromURLs(true);
settings.setAllowUniversalAccessFromURLs(true);
diff --git a/java/ql/src/Security/CWE/CWE-330/examples/InsecureRandomnessCookie.java b/java/ql/src/Security/CWE/CWE-330/examples/InsecureRandomnessCookie.java
index 151f5cddc29..f3e952b4f0f 100644
--- a/java/ql/src/Security/CWE/CWE-330/examples/InsecureRandomnessCookie.java
+++ b/java/ql/src/Security/CWE/CWE-330/examples/InsecureRandomnessCookie.java
@@ -1,4 +1,4 @@
-Random r = new Random();
+Random r = new Random(); // BAD: Random is not cryptographically secure
byte[] bytes = new byte[16];
r.nextBytes(bytes);
diff --git a/java/ql/src/Security/CWE/CWE-330/examples/SecureRandomnessCookie.java b/java/ql/src/Security/CWE/CWE-330/examples/SecureRandomnessCookie.java
index 62395a7f086..fbe3b722434 100644
--- a/java/ql/src/Security/CWE/CWE-330/examples/SecureRandomnessCookie.java
+++ b/java/ql/src/Security/CWE/CWE-330/examples/SecureRandomnessCookie.java
@@ -1,4 +1,4 @@
-SecureRandom r = new SecureRandom();
+SecureRandom r = new SecureRandom(); // GOOD: SecureRandom is cryptographically secure
byte[] bytes = new byte[16];
r.nextBytes(bytes);
diff --git a/java/ql/src/Security/CWE/CWE-367/TOCTOURace.java b/java/ql/src/Security/CWE/CWE-367/TOCTOURace.java
index 85a49ec9b04..aa08179f98d 100644
--- a/java/ql/src/Security/CWE/CWE-367/TOCTOURace.java
+++ b/java/ql/src/Security/CWE/CWE-367/TOCTOURace.java
@@ -12,14 +12,14 @@ class Resource {
public synchronized void bad(Resource r) {
if (r.isReady()) {
- // r might no longer be ready, another thread might
+ // BAD: r might no longer be ready, another thread might
// have called setReady(false)
r.act();
}
}
public synchronized void good(Resource r) {
- synchronized(r) {
+ synchronized(r) { // GOOD: r is locked
if (r.isReady()) {
r.act();
}
diff --git a/java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationBad.java b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationBad.java
index d5a37b7bf0f..8f05b2ffb61 100644
--- a/java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationBad.java
+++ b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationBad.java
@@ -7,6 +7,6 @@ public MyObject {
public MyObject deserialize(Socket sock) {
try(ObjectInputStream in = new ObjectInputStream(sock.getInputStream())) {
- return (MyObject)in.readObject(); // unsafe
+ return (MyObject)in.readObject(); // BAD: in is from untrusted source
}
}
diff --git a/java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationGood.java b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationGood.java
index 4a1d5e6c3cd..abf5486d576 100644
--- a/java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationGood.java
+++ b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationGood.java
@@ -1,5 +1,5 @@
public MyObject deserialize(Socket sock) {
try(DataInputStream in = new DataInputStream(sock.getInputStream())) {
- return new MyObject(in.readInt());
+ return new MyObject(in.readInt()); // GOOD: read only an int
}
}
diff --git a/java/ql/src/Security/CWE/CWE-522/LdapAuthUseLdap.java b/java/ql/src/Security/CWE/CWE-522/LdapAuthUseLdap.java
index 36826bf27b0..dddcea17a2c 100644
--- a/java/ql/src/Security/CWE/CWE-522/LdapAuthUseLdap.java
+++ b/java/ql/src/Security/CWE/CWE-522/LdapAuthUseLdap.java
@@ -1,3 +1,4 @@
+// BAD: LDAP authentication is used
String ldapUrl = "ldap://ad.your-server.com:389";
Hashtable environment = new Hashtable();
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
diff --git a/java/ql/src/Security/CWE/CWE-522/LdapAuthUseLdaps.java b/java/ql/src/Security/CWE/CWE-522/LdapAuthUseLdaps.java
index e7d8bdefc60..bdefbe79d6c 100644
--- a/java/ql/src/Security/CWE/CWE-522/LdapAuthUseLdaps.java
+++ b/java/ql/src/Security/CWE/CWE-522/LdapAuthUseLdaps.java
@@ -1,3 +1,4 @@
+// GOOD: LDAP connection using LDAPS
String ldapUrl = "ldaps://ad.your-server.com:636";
Hashtable environment = new Hashtable();
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
diff --git a/java/ql/src/Security/CWE/CWE-522/LdapEnableSasl.java b/java/ql/src/Security/CWE/CWE-522/LdapEnableSasl.java
index 2e191c62918..71568c40791 100644
--- a/java/ql/src/Security/CWE/CWE-522/LdapEnableSasl.java
+++ b/java/ql/src/Security/CWE/CWE-522/LdapEnableSasl.java
@@ -1,3 +1,4 @@
+// GOOD: LDAP is used but SASL authentication is enabled
String ldapUrl = "ldap://ad.your-server.com:389";
Hashtable environment = new Hashtable();
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
diff --git a/java/ql/src/Security/CWE/CWE-611/XXEBad.java b/java/ql/src/Security/CWE/CWE-611/XXEBad.java
index 7b7baeadfda..0049b254507 100644
--- a/java/ql/src/Security/CWE/CWE-611/XXEBad.java
+++ b/java/ql/src/Security/CWE/CWE-611/XXEBad.java
@@ -1,5 +1,5 @@
public void parse(Socket sock) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
- builder.parse(sock.getInputStream()); //unsafe
+ builder.parse(sock.getInputStream()); // BAD: DTD parsing is enabled
}
diff --git a/java/ql/src/Security/CWE/CWE-611/XXEGood.java b/java/ql/src/Security/CWE/CWE-611/XXEGood.java
index f1eef22e62a..91c28448299 100644
--- a/java/ql/src/Security/CWE/CWE-611/XXEGood.java
+++ b/java/ql/src/Security/CWE/CWE-611/XXEGood.java
@@ -2,5 +2,5 @@ public void disableDTDParse(Socket sock) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder builder = factory.newDocumentBuilder();
- builder.parse(sock.getInputStream()); //safe
+ builder.parse(sock.getInputStream()); // GOOD: DTD parsing is disabled
}
diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedAWSCredentials.java b/java/ql/src/Security/CWE/CWE-798/HardcodedAWSCredentials.java
index 7528311bc4b..3c94ac4e833 100644
--- a/java/ql/src/Security/CWE/CWE-798/HardcodedAWSCredentials.java
+++ b/java/ql/src/Security/CWE/CWE-798/HardcodedAWSCredentials.java
@@ -3,7 +3,7 @@ import com.amazonaws.auth.BasicAWSCredentials;
public class HardcodedAWSCredentials {
public static void main(String[] args) {
- //Hardcoded credentials for connecting to AWS services
+ // BAD: Hardcoded credentials for connecting to AWS services
//To fix the problem, use other approaches including AWS credentials file, environment variables, or instance/container credentials instead
AWSCredentials creds = new BasicAWSCredentials("ACCESS_KEY", "SECRET_KEY"); //sensitive call
}
diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.java b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.java
index a12a9881d1a..5e996abe89d 100644
--- a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.java
+++ b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.java
@@ -1,8 +1,8 @@
-private static final String p = "123456"; // hard-coded credential
+private static final String p = "123456"; // BAD: hard-coded credential
public static void main(String[] args) throws SQLException {
String url = "jdbc:mysql://localhost/test";
- String u = "admin"; // hard-coded credential
+ String u = "admin"; // BAD: hard-coded credential
getConn(url, u, p);
}
diff --git a/java/ql/src/Security/CWE/CWE-835/InfiniteLoopBad.java b/java/ql/src/Security/CWE/CWE-835/InfiniteLoopBad.java
index 69e13801c22..f0f408f5c45 100644
--- a/java/ql/src/Security/CWE/CWE-835/InfiniteLoopBad.java
+++ b/java/ql/src/Security/CWE/CWE-835/InfiniteLoopBad.java
@@ -1,5 +1,5 @@
for (int i=0; i<10; i++) {
- for (int j=0; i<10; j++) {
+ for (int j=0; i<10; j++) { // BAD: Potential infinite loop: i should be j
// do stuff
if (shouldBreak()) break;
}
diff --git a/java/ql/src/Security/CWE/CWE-835/InfiniteLoopGood.java b/java/ql/src/Security/CWE/CWE-835/InfiniteLoopGood.java
index 06c18c4e68b..01050b38ef3 100644
--- a/java/ql/src/Security/CWE/CWE-835/InfiniteLoopGood.java
+++ b/java/ql/src/Security/CWE/CWE-835/InfiniteLoopGood.java
@@ -1,5 +1,5 @@
for (int i=0; i<10; i++) {
- for (int j=0; j<10; j++) {
+ for (int j=0; j<10; j++) { // GOOD: correct variable j
// do stuff
if (shouldBreak()) break;
}
diff --git a/java/ql/src/Security/CWE/CWE-925/Bad.java b/java/ql/src/Security/CWE/CWE-925/Bad.java
index 376805f824e..1ab7bd38166 100644
--- a/java/ql/src/Security/CWE/CWE-925/Bad.java
+++ b/java/ql/src/Security/CWE/CWE-925/Bad.java
@@ -1,6 +1,7 @@
public class ShutdownReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
+ // BAD: The code does not check if the intent is an ACTION_SHUTDOWN intent
mainActivity.saveLocalData();
mainActivity.stopActivity();
}
diff --git a/java/ql/src/Security/CWE/CWE-925/Good.java b/java/ql/src/Security/CWE/CWE-925/Good.java
index b6ad1c43193..0414d4c198e 100644
--- a/java/ql/src/Security/CWE/CWE-925/Good.java
+++ b/java/ql/src/Security/CWE/CWE-925/Good.java
@@ -1,6 +1,7 @@
public class ShutdownReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
+ // GOOD: The code checks if the intent is an ACTION_SHUTDOWN intent
if (!intent.getAction().equals(Intent.ACTION_SHUTDOWN)) {
return;
}
From 516df3b4be4f75a68438e0a965eca6b915a8e0e9 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 3 Feb 2025 14:52:57 -0500
Subject: [PATCH 047/279] Java: qhelp wording updates
---
.../CWE-352/CsrfUnprotectedRequestType.qhelp | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp
index 5d71f3f6897..c7cfb14bd29 100644
--- a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp
+++ b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp
@@ -3,17 +3,18 @@
When you set up a web server to receive a request from a client without any mechanism
-for verifying that it was intentionally sent, then it is vulnerable to attack. An attacker can
-trick a client into making an unintended request to the web server that will be treated as
-an authentic request. This can be done via a URL, image load, XMLHttpRequest, etc. and can
-result in exposure of data or unintended code execution.
+for verifying that it was intentionally sent, then it is vulnerable to a Cross Site Request
+Forgery (CSRF) attack. An attacker can trick a client into making an unintended request
+to the web server that will be treated as an authentic request. This can be done via a URL,
+image load, XMLHttpRequest, etc. and can result in exposure of data or unintended code execution.
-
Make sure any requests that change application state are protected from Cross Site Request Forgery (CSRF).
-Some application frameworks provide default CSRF protection for unsafe HTTP request methods (such as
-POST) which may change the state of the application. Safe HTTP request methods (such as
-GET) should be read-only and should not be used for actions that change application state.
+
Make sure any requests that change application state are protected from CSRF. Some application
+frameworks provide default CSRF protection for unsafe HTTP request methods (such as POST)
+which may change the state of the application. Safe HTTP request methods (such as GET)
+should only perform read-only operations and should not be used for actions that change application
+state.
This query currently supports the Spring and Stapler web frameworks. Spring provides default CSRF protection
for all unsafe HTTP methods. Stapler provides default CSRF protection for the POST method.
The first parameter of a class method, a new method or any metaclass method
-should be called cls. This makes the purpose of the parameter clear to other developers.
+
The first parameter of a class method (including certain special methods such as __new__), or a method of a metaclass,
+should be named cls.
-
Change the name of the first parameter to cls as recommended by the style guidelines
+
Ensure that the first parameter of class methods is named cls, as recommended by the style guidelines
in PEP 8.
-
In the example, the first parameter to make() is klass which should be changed to cls
-for ease of comprehension.
+
In the following example, the first parameter of the class method make is named self instead of cls.
diff --git a/python/ql/src/Functions/NonCls.ql b/python/ql/src/Functions/NonCls.ql
index ae9ab0846d6..bca9a57c908 100644
--- a/python/ql/src/Functions/NonCls.ql
+++ b/python/ql/src/Functions/NonCls.ql
@@ -1,7 +1,6 @@
/**
* @name First parameter of a class method is not named 'cls'
- * @description Using an alternative name for the first parameter of a class method makes code more
- * difficult to read; PEP8 states that the first parameter to class methods should be 'cls'.
+ * @description By the PEP8 style guide, the first parameter of a class method should be named `cls`.
* @kind problem
* @tags maintainability
* readability
diff --git a/python/ql/src/Functions/NonSelf.py b/python/ql/src/Functions/NonSelf.py
index 00d3c2ff6f5..69e2dde34a9 100644
--- a/python/ql/src/Functions/NonSelf.py
+++ b/python/ql/src/Functions/NonSelf.py
@@ -1,9 +1,9 @@
class Point:
- def __init__(val, x, y): # first parameter is mis-named 'val'
+ def __init__(val, x, y): # BAD: first parameter is mis-named 'val'
val._x = x
val._y = y
class Point2:
- def __init__(self, x, y): # first parameter is correctly named 'self'
+ def __init__(self, x, y): # GOOD: first parameter is correctly named 'self'
self._x = x
self._y = y
\ No newline at end of file
diff --git a/python/ql/src/Functions/NonSelf.qhelp b/python/ql/src/Functions/NonSelf.qhelp
index c4cef70e731..eb796f9b883 100644
--- a/python/ql/src/Functions/NonSelf.qhelp
+++ b/python/ql/src/Functions/NonSelf.qhelp
@@ -6,22 +6,18 @@
Normal methods should have at least one parameter and the first parameter should be called self.
-This makes the purpose of the parameter clear to other developers.
-
If there is at least one parameter, then change the name of the first parameter to self as recommended by the style guidelines
+
Ensure that the first parameter of a normal method is named self, as recommended by the style guidelines
in PEP 8.
-
If there are no parameters, then it cannot be a normal method. It may need to be marked as a staticmethod
-or it could be moved out of the class as a normal function.
+
If a self parameter is unneeded, the method should be decorated with staticmethod, or moved out of the class as a regular function.
-
The following methods can both be used to assign values to variables in a point
-object. The second method makes the association clearer because the self parameter is
-used.
+
In the following cases, the first argument of Point.__init__ is named val instead; whereas in Point2.__init__ it is correctly named self.
.
diff --git a/python/ql/src/Functions/NonSelf.ql b/python/ql/src/Functions/NonSelf.ql
index 94bc8a2fa37..cea15d3661a 100644
--- a/python/ql/src/Functions/NonSelf.ql
+++ b/python/ql/src/Functions/NonSelf.ql
@@ -1,8 +1,6 @@
/**
* @name First parameter of a method is not named 'self'
- * @description Using an alternative name for the first parameter of an instance method makes
- * code more difficult to read; PEP8 states that the first parameter to instance
- * methods should be 'self'.
+ * @description By the PEP8 style guide, the first parameter of a normal method should be named `self`.
* @kind problem
* @tags maintainability
* readability
From 287cf0121df625d27fbad05233c5bb82148959a4 Mon Sep 17 00:00:00 2001
From: Joe Farebrother
Date: Tue, 4 Feb 2025 10:46:05 +0000
Subject: [PATCH 060/279] Fix docs
---
python/ql/src/Functions/NonCls.qhelp | 2 +-
python/ql/src/Functions/NonSelf.qhelp | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/Functions/NonCls.qhelp b/python/ql/src/Functions/NonCls.qhelp
index 418218e5a1f..cf064fc5056 100644
--- a/python/ql/src/Functions/NonCls.qhelp
+++ b/python/ql/src/Functions/NonCls.qhelp
@@ -28,7 +28,7 @@ in PEP 8.
When you set up a web server to receive a request from a client without any mechanism
-for verifying that it was intentionally sent, then it is vulnerable to a Cross Site Request
+for verifying that it was intentionally sent, then it is vulnerable to a Cross-Site Request
Forgery (CSRF) attack. An attacker can trick a client into making an unintended request
to the web server that will be treated as an authentic request. This can be done via a URL,
image load, XMLHttpRequest, etc. and can result in exposure of data or unintended code execution.
@@ -17,13 +17,13 @@ should only perform read-only operations and should not be used for actions that
state.
This query currently supports the Spring and Stapler web frameworks. Spring provides default CSRF protection
-for all unsafe HTTP methods. Stapler provides default CSRF protection for the POST method.
+for all unsafe HTTP methods whereas Stapler provides default CSRF protection for the POST method.
The following examples show Spring request handlers allowing safe HTTP request methods for state-changing actions.
Since safe HTTP request methods do not have default CSRF protection in Spring, they should not be used when modifying
-application state. Instead use one of the unsafe HTTP methods which Spring default-protects from CSRF.
+application state. Instead, use one of the unsafe HTTP methods which Spring default-protects from CSRF.
@@ -31,7 +31,7 @@ application state. Instead use one of the unsafe HTTP methods which Spring defau
The following examples show Stapler web methods allowing safe HTTP request methods for state-changing actions.
Since safe HTTP request methods do not have default CSRF protection in Stapler, they should not be used when modifying
-application state. Instead use the POST method which Stapler default-protects from CSRF.
+application state. Instead, use the POST method which Stapler default-protects from CSRF.
From f4382826740e41c152d5790c48e8b1241c428a51 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 4 Feb 2025 13:21:43 -0500
Subject: [PATCH 062/279] Java: rewrite qhelp overview section; aligns with
overview section used by Python and Ruby
---
.../CWE-352/CsrfUnprotectedRequestType.qhelp | 28 +++++++++++++++----
1 file changed, 23 insertions(+), 5 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp
index 08df43e220d..d2c29bf2a3d 100644
--- a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp
+++ b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp
@@ -2,11 +2,29 @@
-
When you set up a web server to receive a request from a client without any mechanism
-for verifying that it was intentionally sent, then it is vulnerable to a Cross-Site Request
-Forgery (CSRF) attack. An attacker can trick a client into making an unintended request
-to the web server that will be treated as an authentic request. This can be done via a URL,
-image load, XMLHttpRequest, etc. and can result in exposure of data or unintended code execution.
+
+ Cross-site request forgery (CSRF) is a type of vulnerability in which an
+ attacker is able to force a user to carry out an action that the user did
+ not intend.
+
+
+
+ The attacker tricks an authenticated user into submitting a request to the
+ web application. Typically this request will result in a state change on
+ the server, such as changing the user's password. The request can be
+ initiated when the user visits a site controlled by the attacker. If the
+ web application relies only on cookies for authentication, or on other
+ credentials that are automatically included in the request, then this
+ request will appear as legitimate to the server.
+
+
+
+ A common countermeasure for CSRF is to generate a unique token to be
+ included in the HTML sent from the server to a user. This token can be
+ used as a hidden field to be sent back with requests to the server, where
+ the server can then check that the token is valid and associated with the
+ relevant user session.
+
From 03678463338df097c7a6af0339e9b9635e9b1e30 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 4 Feb 2025 13:36:15 -0500
Subject: [PATCH 063/279] Java: remove token section from qhelp overview
discussing tokens is not directly relevant to this query's recommendation and examples
---
.../Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp | 8 --------
1 file changed, 8 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp
index d2c29bf2a3d..8555a37f940 100644
--- a/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp
+++ b/java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.qhelp
@@ -17,14 +17,6 @@
credentials that are automatically included in the request, then this
request will appear as legitimate to the server.
-
-
- A common countermeasure for CSRF is to generate a unique token to be
- included in the HTML sent from the server to a user. This token can be
- used as a hidden field to be sent back with requests to the server, where
- the server can then check that the token is valid and associated with the
- relevant user session.
-
The attacker tricks an authenticated user into submitting a request to the
- web application. Typically this request will result in a state change on
+ web application. Typically, this request will result in a state change on
the server, such as changing the user's password. The request can be
initiated when the user visits a site controlled by the attacker. If the
web application relies only on cookies for authentication, or on other
@@ -51,7 +51,7 @@ application state. Instead, use the POST method which Stapler defau
Spring Security Reference:
From 5feb4016079ef80c4da80e402f5eec4b28761fe6 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 27 Nov 2024 23:49:46 +0100
Subject: [PATCH 072/279] ruby: Add query for hoisting Rails ActiveRecord calls
This does not take assicoations into account. It uses
ActiveRecordModelFinderCall to identify relevant calls. This class has
therefor been made public.
---
.../codeql/ruby/frameworks/ActiveRecord.qll | 5 +-
.../queries/performance/CouldBeHoisted.qhelp | 22 +++++
.../src/queries/performance/CouldBeHoisted.ql | 90 +++++++++++++++++++
.../queries/performance/examples/preload.rb | 14 +++
.../performance/examples/straight_loop.rb | 8 ++
5 files changed, 136 insertions(+), 3 deletions(-)
create mode 100644 ruby/ql/src/queries/performance/CouldBeHoisted.qhelp
create mode 100644 ruby/ql/src/queries/performance/CouldBeHoisted.ql
create mode 100644 ruby/ql/src/queries/performance/examples/preload.rb
create mode 100644 ruby/ql/src/queries/performance/examples/straight_loop.rb
diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll
index deaa0a6427a..42c6286e4ea 100644
--- a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll
+++ b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll
@@ -254,9 +254,8 @@ private Expr getUltimateReceiver(MethodCall call) {
)
}
-// A call to `find`, `where`, etc. that may return active record model object(s)
-private class ActiveRecordModelFinderCall extends ActiveRecordModelInstantiation, DataFlow::CallNode
-{
+/** A call to `find`, `where`, etc. that may return active record model object(s) */
+class ActiveRecordModelFinderCall extends ActiveRecordModelInstantiation, DataFlow::CallNode {
private ActiveRecordModelClass cls;
ActiveRecordModelFinderCall() {
diff --git a/ruby/ql/src/queries/performance/CouldBeHoisted.qhelp b/ruby/ql/src/queries/performance/CouldBeHoisted.qhelp
new file mode 100644
index 00000000000..b476a2c7a00
--- /dev/null
+++ b/ruby/ql/src/queries/performance/CouldBeHoisted.qhelp
@@ -0,0 +1,22 @@
+
+
+
+
+
+When a Rails ActiveRecord query is executed in a loop, it is potentially an n+1 problem.
+This query identifies situations where an ActiveRecord query execution could be pulled out of a loop.
+
+
+
+
If possible, pull the query out of the loop, thus replacing the many calls with a single one.
+
+
+
+
The following (suboptimal) example code queries the User object in each iteration of the loop:
+
+
To improve the performance, we instead query the User object once outside the loop, gathereing all necessary information:
+
+
+
\ No newline at end of file
diff --git a/ruby/ql/src/queries/performance/CouldBeHoisted.ql b/ruby/ql/src/queries/performance/CouldBeHoisted.ql
new file mode 100644
index 00000000000..4f5653a8e45
--- /dev/null
+++ b/ruby/ql/src/queries/performance/CouldBeHoisted.ql
@@ -0,0 +1,90 @@
+/**
+ * @name Could be hoisted
+ * @description Hoist Rails `ActiveRecord::Relation` query calls out of loops.
+ * @kind problem
+ * @problem.severity info
+ * @precision high
+ * @id rb/could-be-hoisted
+ * @tags performance
+ */
+
+// Possible Improvements;
+// - Consider also Associations.
+// Associations are lazy-loading by default, so something like
+// in a loop over `article` do
+// `article.book`
+// if you have 1000 articles it will do a 1000 calls to `book`.
+// If you already did `article includes book`, there should be no problem.
+// - Consider instances of ActiveRecordInstanceMethodCall, for instance
+// calls to `pluck`.
+import ruby
+private import codeql.ruby.AST
+import codeql.ruby.ast.internal.Constant
+import codeql.ruby.Concepts
+import codeql.ruby.frameworks.ActiveRecord
+private import codeql.ruby.TaintTracking
+
+string loopMethodName() {
+ result in [
+ "each", "reverse_each", "map", "map!", "foreach", "flat_map", "in_batches", "one?", "all?",
+ "collect", "collect!", "select", "select!", "reject", "reject!"
+ ]
+}
+
+class LoopingCall extends DataFlow::CallNode {
+ DataFlow::CallableNode loopBlock;
+
+ LoopingCall() {
+ this.getMethodName() = loopMethodName() and loopBlock = this.getBlock().asCallable()
+ }
+
+ DataFlow::CallableNode getLoopBlock() { result = loopBlock }
+}
+
+predicate happensInLoop(LoopingCall loop, DataFlow::CallNode e) {
+ loop.getLoopBlock().asCallableAstNode() = e.asExpr().getScope()
+}
+
+predicate happensInOuterLoop(LoopingCall outerLoop, DataFlow::CallNode e) {
+ exists(LoopingCall innerLoop |
+ happensInLoop(outerLoop, innerLoop) and
+ happensInLoop(innerLoop, e)
+ )
+}
+
+predicate happensInInnermostLoop(LoopingCall loop, DataFlow::CallNode e) {
+ happensInLoop(loop, e) and
+ not happensInOuterLoop(loop, e)
+}
+
+// The ActiveRecord instance is used to potentially control the loop
+predicate usedInLoopControlGuard(ActiveRecordInstance ar, DataFlow::Node guard) {
+ TaintTracking::localTaint(ar, guard) and
+ guard = guardForLoopControl(_, _)
+}
+
+// A guard for controlling the loop
+DataFlow::Node guardForLoopControl(ConditionalExpr cond, Stmt control) {
+ result.asExpr().getAstNode() = cond.getCondition().getAChild*() and
+ (
+ control.(MethodCall).getMethodName() = "raise"
+ or
+ control instanceof NextStmt
+ ) and
+ control = cond.getBranch(_).getAChild()
+}
+
+from LoopingCall loop, DataFlow::CallNode call
+where
+ // Disregard loops over constants
+ not isArrayConstant(loop.getReceiver().asExpr(), _) and
+ // Disregard tests
+ not call.getLocation().getFile().getAbsolutePath().matches("%test%") and
+ // Disregard cases where the looping is influenced by the query result
+ not usedInLoopControlGuard(call, _) and
+ // Only report the inner most loop
+ happensInInnermostLoop(loop, call) and
+ // Only report calls that are likely to be expensive
+ call instanceof ActiveRecordModelFinderCall and
+ not call.getMethodName() in ["new", "create"]
+select call, "This call happens inside $@, and could be hoisted.", loop, "this loop"
diff --git a/ruby/ql/src/queries/performance/examples/preload.rb b/ruby/ql/src/queries/performance/examples/preload.rb
new file mode 100644
index 00000000000..27a313a0f65
--- /dev/null
+++ b/ruby/ql/src/queries/performance/examples/preload.rb
@@ -0,0 +1,14 @@
+# Preload User data
+user_data = User.where(login: repo_names_by_owner.keys).pluck(:login, :id, :type).to_h do |login, id, type|
+ [login, { id: id, type: type == "User" ? "USER" : "ORGANIZATION" }]
+end
+
+repo_names_by_owner.each do |owner_slug, repo_names|
+ owner_info = user_data[owner_slug]
+ owner_id = owner_info[:id]
+ owner_type = owner_info[:type]
+ rel_conditions = { owner_id: owner_id, name: repo_names }
+
+ nwo_rel = nwo_rel.or(RepositorySecurityCenterConfig.where(rel_conditions)) unless neg
+ nwo_rel = nwo_rel.and(RepositorySecurityCenterConfig.where.not(rel_conditions)) if neg
+end
\ No newline at end of file
diff --git a/ruby/ql/src/queries/performance/examples/straight_loop.rb b/ruby/ql/src/queries/performance/examples/straight_loop.rb
new file mode 100644
index 00000000000..4c64d3bb84f
--- /dev/null
+++ b/ruby/ql/src/queries/performance/examples/straight_loop.rb
@@ -0,0 +1,8 @@
+repo_names_by_owner.map do |owner_slug, repo_names|
+ owner_id, owner_type = User.where(login: owner_slug).pluck(:id, :type).first
+ owner_type = owner_type == "User" ? "USER" : "ORGANIZATION"
+ rel_conditions = { owner_id: owner_id, name: repo_names }
+
+ nwo_rel = nwo_rel.or(RepositorySecurityCenterConfig.where(rel_conditions)) unless neg
+ nwo_rel = nwo_rel.and(RepositorySecurityCenterConfig.where.not(rel_conditions)) if neg
+ end
\ No newline at end of file
From 4a4585a526cd21bb2f2b936b91861f8844f4734c Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 5 Feb 2025 11:36:58 -0500
Subject: [PATCH 073/279] Java: move comment
---
.../lib/semmle/code/java/security/PathSanitizer.qll | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/PathSanitizer.qll b/java/ql/lib/semmle/code/java/security/PathSanitizer.qll
index 63654d3faaf..8e97518a13e 100644
--- a/java/ql/lib/semmle/code/java/security/PathSanitizer.qll
+++ b/java/ql/lib/semmle/code/java/security/PathSanitizer.qll
@@ -138,12 +138,6 @@ private class AllowedPrefixSanitizer extends PathInjectionSanitizer {
* been checked for a trusted prefix.
*/
private predicate dotDotCheckGuard(Guard g, Expr e, boolean branch) {
- // Local taint-flow is used here to handle cases where the validated expression comes from the
- // expression reaching the sink, but it's not the same one, e.g.:
- // Path path = source();
- // String strPath = path.toString();
- // if (!strPath.contains("..") && strPath.startsWith("/safe/dir"))
- // sink(path);
pathTraversalGuard(g, e, branch) and
exists(Guard previousGuard |
previousGuard.(AllowedPrefixGuard).controls(g.getBasicBlock(), true)
@@ -365,6 +359,12 @@ private predicate maybeNull(Expr expr) {
/** Holds if `g` is a guard that checks for `..` components. */
private predicate pathTraversalGuard(Guard g, Expr e, boolean branch) {
+ // Local taint-flow is used here to handle cases where the validated expression comes from the
+ // expression reaching the sink, but it's not the same one, e.g.:
+ // Path path = source();
+ // String strPath = path.toString();
+ // if (!strPath.contains("..") && strPath.startsWith("/safe/dir"))
+ // sink(path);
branch = g.(PathTraversalGuard).getBranch() and
localTaintFlowToPathGuard(e, g)
}
From e8724ab2207b0e50154f632f08d1170e0f4dbaad Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 5 Feb 2025 15:46:10 -0500
Subject: [PATCH 074/279] Java: sanitize constructor call instead and update
test cases
---
.../code/java/security/PathSanitizer.qll | 2 +-
.../library-tests/pathsanitizer/Test.java | 20 +++++++++----------
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/PathSanitizer.qll b/java/ql/lib/semmle/code/java/security/PathSanitizer.qll
index 8e97518a13e..a9f1887dd21 100644
--- a/java/ql/lib/semmle/code/java/security/PathSanitizer.qll
+++ b/java/ql/lib/semmle/code/java/security/PathSanitizer.qll
@@ -388,7 +388,7 @@ private class FileConstructorSanitizer extends PathInjectionSanitizer {
arg = ValidationMethod::getAValidatedNode().asExpr() or
TaintTracking::localExprTaint(any(PathNormalizeSanitizer p), arg)
) and
- this.asExpr() = arg
+ this.asExpr() = constrCall
)
}
}
diff --git a/java/ql/test/library-tests/pathsanitizer/Test.java b/java/ql/test/library-tests/pathsanitizer/Test.java
index 71df951214c..1c0b4e7f684 100644
--- a/java/ql/test/library-tests/pathsanitizer/Test.java
+++ b/java/ql/test/library-tests/pathsanitizer/Test.java
@@ -483,7 +483,7 @@ public class Test {
if (!source.contains("..")) {
File f2 = new File(f1, source);
sink(f2); // Safe
- sink(source); // $ MISSING: hasTaintFlow
+ sink(source); // $ hasTaintFlow
} else {
File f3 = new File(f1, source);
sink(f3); // $ hasTaintFlow
@@ -497,7 +497,7 @@ public class Test {
// `f2` is unsafe if `f1` is tainted
File f2 = new File(f1Tainted, source);
sink(f2); // $ hasTaintFlow
- sink(source); // $ MISSING: hasTaintFlow
+ sink(source); // $ hasTaintFlow
} else {
File f3 = new File(f1Tainted, source);
sink(f3); // $ hasTaintFlow
@@ -524,7 +524,7 @@ public class Test {
if (source.indexOf("..") == -1) {
File f2 = new File(f1, source);
sink(f2); // Safe
- sink(source); // $ MISSING: hasTaintFlow
+ sink(source); // $ hasTaintFlow
} else {
File f3 = new File(f1, source);
sink(f3); // $ hasTaintFlow
@@ -541,7 +541,7 @@ public class Test {
} else {
File f3 = new File(f1, source);
sink(f3); // Safe
- sink(source); // $ MISSING: hasTaintFlow
+ sink(source); // $ hasTaintFlow
}
}
{
@@ -550,7 +550,7 @@ public class Test {
if (source.lastIndexOf("..") == -1) {
File f2 = new File(f1, source);
sink(f2); // Safe
- sink(source); // $ MISSING: hasTaintFlow
+ sink(source); // $ hasTaintFlow
} else {
File f3 = new File(f1, source);
sink(f3); // $ hasTaintFlow
@@ -564,7 +564,7 @@ public class Test {
fileConstructorValidation(source);
File f2 = new File(f1, source);
sink(f2); // Safe
- sink(source); // $ MISSING: hasTaintFlow
+ sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
@@ -575,7 +575,7 @@ public class Test {
} else {
File f2 = new File(f1, source);
sink(f2); // Safe
- sink(source); // $ MISSING: hasTaintFlow
+ sink(source); // $ hasTaintFlow
}
}
// PathNormalizeSanitizer
@@ -586,7 +586,7 @@ public class Test {
File f2 = new File(f1, normalized);
sink(f2); // Safe
sink(source); // $ hasTaintFlow
- sink(normalized); // $ MISSING: hasTaintFlow
+ sink(normalized); // $ hasTaintFlow
}
{
File source = (File) source();
@@ -595,7 +595,7 @@ public class Test {
File f2 = new File(f1, normalized);
sink(f2); // Safe
sink(source); // $ hasTaintFlow
- sink(normalized); // $ MISSING: hasTaintFlow
+ sink(normalized); // $ hasTaintFlow
}
{
String source = (String) source();
@@ -604,7 +604,7 @@ public class Test {
File f2 = new File(f1, normalized);
sink(f2); // Safe
sink(source); // $ hasTaintFlow
- sink(normalized); // $ MISSING: hasTaintFlow
+ sink(normalized); // $ hasTaintFlow
}
}
}
From e0034e566f4d77c1c412b298059385b07e6d3619 Mon Sep 17 00:00:00 2001
From: Lindsay Simpkins
Date: Wed, 5 Feb 2025 15:01:12 -0500
Subject: [PATCH 075/279] csharp update MaD for HttpRequestMessage and
UriBuilder
---
csharp/ql/lib/ext/System.Net.Http.model.yml | 2 ++
csharp/ql/lib/ext/System.model.yml | 27 ++++++++++++++
.../dataflow/library/FlowSummaries.expected | 35 +++++++++++++++----
.../library/FlowSummariesFiltered.expected | 35 +++++++++++++++----
4 files changed, 87 insertions(+), 12 deletions(-)
diff --git a/csharp/ql/lib/ext/System.Net.Http.model.yml b/csharp/ql/lib/ext/System.Net.Http.model.yml
index 7d4fbf87a57..2becfec11d6 100644
--- a/csharp/ql/lib/ext/System.Net.Http.model.yml
+++ b/csharp/ql/lib/ext/System.Net.Http.model.yml
@@ -10,6 +10,8 @@ extensions:
data:
- ["System.Net.Http", "HttpRequestMessage", False, "HttpRequestMessage", "(System.Net.Http.HttpMethod,System.String)", "", "Argument[0]", "Argument[this]", "taint", "manual"]
- ["System.Net.Http", "HttpRequestMessage", False, "HttpRequestMessage", "(System.Net.Http.HttpMethod,System.String)", "", "Argument[1]", "Argument[this]", "taint", "manual"]
+ - ["System.Net.Http", "HttpRequestMessage", False, "HttpRequestMessage", "(System.Net.Http.HttpMethod,System.Uri)", "", "Argument[0]", "Argument[this]", "taint", "manual"]
+ - ["System.Net.Http", "HttpRequestMessage", False, "HttpRequestMessage", "(System.Net.Http.HttpMethod,System.Uri)", "", "Argument[1]", "Argument[this]", "taint", "manual"]
- ["System.Net.Http", "HttpRequestOptions", False, "Add", "(System.Collections.Generic.KeyValuePair)", "", "Argument[0].Property[System.Collections.Generic.KeyValuePair`2.Key]", "Argument[this].Element.Property[System.Collections.Generic.KeyValuePair`2.Key]", "value", "manual"]
- ["System.Net.Http", "HttpRequestOptions", False, "Add", "(System.Collections.Generic.KeyValuePair)", "", "Argument[0].Property[System.Collections.Generic.KeyValuePair`2.Value]", "Argument[this].Element.Property[System.Collections.Generic.KeyValuePair`2.Value]", "value", "manual"]
- ["System.Net.Http", "MultipartContent", False, "Add", "(System.Net.Http.HttpContent)", "", "Argument[0]", "Argument[this].Element", "value", "manual"]
diff --git a/csharp/ql/lib/ext/System.model.yml b/csharp/ql/lib/ext/System.model.yml
index 48719f87c4d..b1fd32a8dd6 100644
--- a/csharp/ql/lib/ext/System.model.yml
+++ b/csharp/ql/lib/ext/System.model.yml
@@ -784,6 +784,33 @@ extensions:
- ["System", "Uri", False, "get_OriginalString", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["System", "Uri", False, "get_PathAndQuery", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["System", "Uri", False, "get_Query", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"]
+ - ["System", "UriBuilder", False, "ToString", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"]
+ - ["System", "UriBuilder", False, "UriBuilder", "(System.String,System.String)", "", "Argument[0]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "UriBuilder", "(System.String,System.String)", "", "Argument[1]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "UriBuilder", "(System.String,System.String,System.Int32)", "", "Argument[0]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "UriBuilder", "(System.String,System.String,System.Int32)", "", "Argument[1]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "UriBuilder", "(System.String,System.String,System.Int32)", "", "Argument[2]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "UriBuilder", "(System.String,System.String,System.Int32,System.String)", "", "Argument[0]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "UriBuilder", "(System.String,System.String,System.Int32,System.String)", "", "Argument[1]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "UriBuilder", "(System.String,System.String,System.Int32,System.String)", "", "Argument[2]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "UriBuilder", "(System.String,System.String,System.Int32,System.String)", "", "Argument[3]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "UriBuilder", "(System.String,System.String,System.Int32,System.String,System.String)", "", "Argument[0]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "UriBuilder", "(System.String,System.String,System.Int32,System.String,System.String)", "", "Argument[1]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "UriBuilder", "(System.String,System.String,System.Int32,System.String,System.String)", "", "Argument[2]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "UriBuilder", "(System.String,System.String,System.Int32,System.String,System.String)", "", "Argument[3]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "UriBuilder", "(System.String,System.String,System.Int32,System.String,System.String)", "", "Argument[4]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "get_Fragment", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"]
+ - ["System", "UriBuilder", False, "get_Host", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"]
+ - ["System", "UriBuilder", False, "get_Path", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"]
+ - ["System", "UriBuilder", False, "get_Port", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"]
+ - ["System", "UriBuilder", False, "get_Query", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"]
+ - ["System", "UriBuilder", False, "get_Scheme", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"]
+ - ["System", "UriBuilder", False, "set_Fragment", "(System.String)", "", "Argument[0]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "set_Host", "(System.String)", "", "Argument[0]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "set_Path", "(System.String)", "", "Argument[0]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "set_Port", "(System.Int32)", "", "Argument[0]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "set_Query", "(System.String)", "", "Argument[0]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
+ - ["System", "UriBuilder", False, "set_Scheme", "(System.String)", "", "Argument[0]", "Argument[this].SyntheticField[System.UriBuilder._uri]", "taint", "manual"]
- ["System", "ValueTuple", False, "Create", "(T1,T2,T3,T4,T5,T6,T7,T8)", "", "Argument[0]", "ReturnValue.Field[System.ValueTuple`8.Item1]", "value", "manual"]
- ["System", "ValueTuple", False, "Create", "(T1,T2,T3,T4,T5,T6,T7,T8)", "", "Argument[1]", "ReturnValue.Field[System.ValueTuple`8.Item2]", "value", "manual"]
- ["System", "ValueTuple", False, "Create", "(T1,T2,T3,T4,T5,T6,T7,T8)", "", "Argument[2]", "ReturnValue.Field[System.ValueTuple`8.Item3]", "value", "manual"]
diff --git a/csharp/ql/test/library-tests/dataflow/library/FlowSummaries.expected b/csharp/ql/test/library-tests/dataflow/library/FlowSummaries.expected
index 7fd85f03b05..fe8c4457799 100644
--- a/csharp/ql/test/library-tests/dataflow/library/FlowSummaries.expected
+++ b/csharp/ql/test/library-tests/dataflow/library/FlowSummaries.expected
@@ -13779,8 +13779,8 @@ summary
| System.Net.Http;HttpMethod;get_Method;();Argument[this].SyntheticField[System.Net.Http.HttpMethod._method];ReturnValue;value;dfc-generated |
| System.Net.Http;HttpRequestMessage;HttpRequestMessage;(System.Net.Http.HttpMethod,System.String);Argument[0];Argument[this];taint;manual |
| System.Net.Http;HttpRequestMessage;HttpRequestMessage;(System.Net.Http.HttpMethod,System.String);Argument[1];Argument[this];taint;manual |
-| System.Net.Http;HttpRequestMessage;HttpRequestMessage;(System.Net.Http.HttpMethod,System.Uri);Argument[0];Argument[this].SyntheticField[System.Net.Http.HttpRequestMessage._method];value;dfc-generated |
-| System.Net.Http;HttpRequestMessage;HttpRequestMessage;(System.Net.Http.HttpMethod,System.Uri);Argument[1];Argument[this].SyntheticField[System.Net.Http.HttpRequestMessage._requestUri];value;dfc-generated |
+| System.Net.Http;HttpRequestMessage;HttpRequestMessage;(System.Net.Http.HttpMethod,System.Uri);Argument[0];Argument[this];taint;manual |
+| System.Net.Http;HttpRequestMessage;HttpRequestMessage;(System.Net.Http.HttpMethod,System.Uri);Argument[1];Argument[this];taint;manual |
| System.Net.Http;HttpRequestMessage;ToString;();Argument[this].SyntheticField[System.Net.Http.HttpRequestMessage._method];ReturnValue;taint;dfc-generated |
| System.Net.Http;HttpRequestMessage;ToString;();Argument[this].SyntheticField[System.Net.Http.HttpRequestMessage._requestUri];ReturnValue;taint;dfc-generated |
| System.Net.Http;HttpRequestMessage;get_Properties;();Argument[this].Property[System.Net.Http.HttpRequestMessage.Options];ReturnValue;value;dfc-generated |
@@ -22234,13 +22234,36 @@ summary
| System;Uri;get_Query;();Argument[this];ReturnValue;taint;manual |
| System;Uri;get_Scheme;();Argument[this];ReturnValue;taint;df-generated |
| System;Uri;get_UserInfo;();Argument[this];ReturnValue;taint;df-generated |
+| System;UriBuilder;ToString;();Argument[this];ReturnValue;taint;manual |
| System;UriBuilder;UriBuilder;(System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;dfc-generated |
-| System;UriBuilder;UriBuilder;(System.String,System.String);Argument[0];Argument[this];taint;df-generated |
-| System;UriBuilder;UriBuilder;(System.String,System.String);Argument[1];Argument[this];taint;df-generated |
-| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String);Argument[3];Argument[this];taint;df-generated |
-| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String,System.String);Argument[4];Argument[this];taint;df-generated |
+| System;UriBuilder;UriBuilder;(System.String,System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String);Argument[1];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32);Argument[1];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32);Argument[2];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String);Argument[1];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String);Argument[2];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String);Argument[3];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String,System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String,System.String);Argument[1];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String,System.String);Argument[2];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String,System.String);Argument[3];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String,System.String);Argument[4];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
| System;UriBuilder;UriBuilder;(System.Uri);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];value;dfc-generated |
+| System;UriBuilder;get_Fragment;();Argument[this];ReturnValue;taint;manual |
+| System;UriBuilder;get_Host;();Argument[this];ReturnValue;taint;manual |
+| System;UriBuilder;get_Path;();Argument[this];ReturnValue;taint;manual |
+| System;UriBuilder;get_Port;();Argument[this];ReturnValue;taint;manual |
+| System;UriBuilder;get_Query;();Argument[this];ReturnValue;taint;manual |
+| System;UriBuilder;get_Scheme;();Argument[this];ReturnValue;taint;manual |
| System;UriBuilder;get_Uri;();Argument[this].SyntheticField[System.UriBuilder._uri];ReturnValue;value;dfc-generated |
+| System;UriBuilder;set_Fragment;(System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;set_Host;(System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;set_Path;(System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;set_Port;(System.Int32);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;set_Query;(System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;set_Scheme;(System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
| System;UriFormatException;GetObjectData;(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext);Argument[this];Argument[0];taint;df-generated |
| System;UriParser;GetComponents;(System.Uri,System.UriComponents,System.UriFormat);Argument[0];ReturnValue;taint;df-generated |
| System;UriParser;OnNewUri;();Argument[this];ReturnValue;value;dfc-generated |
diff --git a/csharp/ql/test/library-tests/dataflow/library/FlowSummariesFiltered.expected b/csharp/ql/test/library-tests/dataflow/library/FlowSummariesFiltered.expected
index d40ca375c19..9480f82e9d9 100644
--- a/csharp/ql/test/library-tests/dataflow/library/FlowSummariesFiltered.expected
+++ b/csharp/ql/test/library-tests/dataflow/library/FlowSummariesFiltered.expected
@@ -10452,8 +10452,8 @@
| System.Net.Http;HttpMethod;get_Method;();Argument[this].SyntheticField[System.Net.Http.HttpMethod._method];ReturnValue;value;dfc-generated |
| System.Net.Http;HttpRequestMessage;HttpRequestMessage;(System.Net.Http.HttpMethod,System.String);Argument[0];Argument[this];taint;manual |
| System.Net.Http;HttpRequestMessage;HttpRequestMessage;(System.Net.Http.HttpMethod,System.String);Argument[1];Argument[this];taint;manual |
-| System.Net.Http;HttpRequestMessage;HttpRequestMessage;(System.Net.Http.HttpMethod,System.Uri);Argument[0];Argument[this].SyntheticField[System.Net.Http.HttpRequestMessage._method];value;dfc-generated |
-| System.Net.Http;HttpRequestMessage;HttpRequestMessage;(System.Net.Http.HttpMethod,System.Uri);Argument[1];Argument[this].SyntheticField[System.Net.Http.HttpRequestMessage._requestUri];value;dfc-generated |
+| System.Net.Http;HttpRequestMessage;HttpRequestMessage;(System.Net.Http.HttpMethod,System.Uri);Argument[0];Argument[this];taint;manual |
+| System.Net.Http;HttpRequestMessage;HttpRequestMessage;(System.Net.Http.HttpMethod,System.Uri);Argument[1];Argument[this];taint;manual |
| System.Net.Http;HttpRequestMessage;ToString;();Argument[this].SyntheticField[System.Net.Http.HttpRequestMessage._method];ReturnValue;taint;dfc-generated |
| System.Net.Http;HttpRequestMessage;ToString;();Argument[this].SyntheticField[System.Net.Http.HttpRequestMessage._requestUri];ReturnValue;taint;dfc-generated |
| System.Net.Http;HttpRequestMessage;get_Properties;();Argument[this].Property[System.Net.Http.HttpRequestMessage.Options];ReturnValue;value;dfc-generated |
@@ -16955,13 +16955,36 @@
| System;Uri;get_Query;();Argument[this];ReturnValue;taint;manual |
| System;Uri;get_Scheme;();Argument[this];ReturnValue;taint;df-generated |
| System;Uri;get_UserInfo;();Argument[this];ReturnValue;taint;df-generated |
+| System;UriBuilder;ToString;();Argument[this];ReturnValue;taint;manual |
| System;UriBuilder;UriBuilder;(System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;dfc-generated |
-| System;UriBuilder;UriBuilder;(System.String,System.String);Argument[0];Argument[this];taint;df-generated |
-| System;UriBuilder;UriBuilder;(System.String,System.String);Argument[1];Argument[this];taint;df-generated |
-| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String);Argument[3];Argument[this];taint;df-generated |
-| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String,System.String);Argument[4];Argument[this];taint;df-generated |
+| System;UriBuilder;UriBuilder;(System.String,System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String);Argument[1];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32);Argument[1];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32);Argument[2];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String);Argument[1];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String);Argument[2];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String);Argument[3];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String,System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String,System.String);Argument[1];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String,System.String);Argument[2];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String,System.String);Argument[3];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;UriBuilder;(System.String,System.String,System.Int32,System.String,System.String);Argument[4];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
| System;UriBuilder;UriBuilder;(System.Uri);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];value;dfc-generated |
+| System;UriBuilder;get_Fragment;();Argument[this];ReturnValue;taint;manual |
+| System;UriBuilder;get_Host;();Argument[this];ReturnValue;taint;manual |
+| System;UriBuilder;get_Path;();Argument[this];ReturnValue;taint;manual |
+| System;UriBuilder;get_Port;();Argument[this];ReturnValue;taint;manual |
+| System;UriBuilder;get_Query;();Argument[this];ReturnValue;taint;manual |
+| System;UriBuilder;get_Scheme;();Argument[this];ReturnValue;taint;manual |
| System;UriBuilder;get_Uri;();Argument[this].SyntheticField[System.UriBuilder._uri];ReturnValue;value;dfc-generated |
+| System;UriBuilder;set_Fragment;(System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;set_Host;(System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;set_Path;(System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;set_Port;(System.Int32);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;set_Query;(System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
+| System;UriBuilder;set_Scheme;(System.String);Argument[0];Argument[this].SyntheticField[System.UriBuilder._uri];taint;manual |
| System;UriParser;GetComponents;(System.Uri,System.UriComponents,System.UriFormat);Argument[0];ReturnValue;taint;df-generated |
| System;UriParser;OnNewUri;();Argument[this];ReturnValue;value;dfc-generated |
| System;UriParser;Register;(System.UriParser,System.String,System.Int32);Argument[1];Argument[0];taint;df-generated |
From 6f2832401c7f0cf9cf32a0789b12446a72793a18 Mon Sep 17 00:00:00 2001
From: Lindsay Simpkins
Date: Wed, 5 Feb 2025 16:37:53 -0500
Subject: [PATCH 076/279] csharp MaD add change note
---
...et.http.httprequestmessage-and-system.uribuilder-models.md | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 csharp/ql/lib/change-notes/2025-02-05-update-system.net.http.httprequestmessage-and-system.uribuilder-models.md
diff --git a/csharp/ql/lib/change-notes/2025-02-05-update-system.net.http.httprequestmessage-and-system.uribuilder-models.md b/csharp/ql/lib/change-notes/2025-02-05-update-system.net.http.httprequestmessage-and-system.uribuilder-models.md
new file mode 100644
index 00000000000..df0c3f15af4
--- /dev/null
+++ b/csharp/ql/lib/change-notes/2025-02-05-update-system.net.http.httprequestmessage-and-system.uribuilder-models.md
@@ -0,0 +1,4 @@
+---
+category: minorAnalysis
+---
+* The models for `System.Net.Http.HttpRequestMessage` and `System.UriBuilder` have been modified to better model the flow of tainted URIs.
\ No newline at end of file
From bd47dcc87db5b9bbf1e106ed47a3e3f8cd70fa28 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 5 Feb 2025 16:56:16 -0500
Subject: [PATCH 077/279] Java: check first arg for taint
---
.../code/java/security/PathSanitizer.qll | 25 +++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/PathSanitizer.qll b/java/ql/lib/semmle/code/java/security/PathSanitizer.qll
index a9f1887dd21..2fa32b67e52 100644
--- a/java/ql/lib/semmle/code/java/security/PathSanitizer.qll
+++ b/java/ql/lib/semmle/code/java/security/PathSanitizer.qll
@@ -357,6 +357,21 @@ private predicate maybeNull(Expr expr) {
)
}
+/** A taint-tracking configuration for reasoning about tainted arguments. */
+private module TaintedArgConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node src) {
+ src instanceof ActiveThreatModelSource or
+ src instanceof ApiSourceNode or
+ // for InlineFlowTest
+ src.asExpr().(MethodCall).getMethod().getName() = "source"
+ }
+
+ predicate isSink(DataFlow::Node sink) { exists(Call call | sink.asExpr() = call.getAnArgument()) }
+}
+
+/** Tracks taint flow to any argument. */
+private module TaintedArgFlow = TaintTracking::Global;
+
/** Holds if `g` is a guard that checks for `..` components. */
private predicate pathTraversalGuard(Guard g, Expr e, boolean branch) {
// Local taint-flow is used here to handle cases where the validated expression comes from the
@@ -370,9 +385,12 @@ private predicate pathTraversalGuard(Guard g, Expr e, boolean branch) {
}
/**
- * A sanitizer that considers the second argument to a `File` constructor safe
- * if it is checked for `..` components (`PathTraversalGuard`) or if any internal
+ * A sanitizer that considers a `File` constructor safe if its second argument
+ * is checked for `..` components (`PathTraversalGuard`) or if any internal
* `..` components are removed from it (`PathNormalizeSanitizer`).
+ *
+ * This also requires a check to ensure that the first argument of the
+ * `File` constructor is not tainted.
*/
private class FileConstructorSanitizer extends PathInjectionSanitizer {
FileConstructorSanitizer() {
@@ -382,6 +400,9 @@ private class FileConstructorSanitizer extends PathInjectionSanitizer {
// `java.io.File` documentation states that such cases are
// treated as if invoking the single-argument `File` constructor.
not maybeNull(constrCall.getArgument(0)) and
+ // Since we are sanitizing the constructor call, we need to check
+ // that the parent argument is not tainted.
+ not TaintedArgFlow::flowToExpr(constrCall.getArgument(0)) and
arg = constrCall.getArgument(1) and
(
arg = DataFlow::BarrierGuard::getABarrierNode().asExpr() or
From d21c8d789b110f0fa47c9443fe0cb080e4dc5b60 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 5 Feb 2025 21:19:59 -0500
Subject: [PATCH 078/279] Java: restrict sink to first arg of two-arg
constructor call
---
.../ql/lib/semmle/code/java/security/PathSanitizer.qll | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/PathSanitizer.qll b/java/ql/lib/semmle/code/java/security/PathSanitizer.qll
index 2fa32b67e52..b2bdd584b88 100644
--- a/java/ql/lib/semmle/code/java/security/PathSanitizer.qll
+++ b/java/ql/lib/semmle/code/java/security/PathSanitizer.qll
@@ -366,10 +366,16 @@ private module TaintedArgConfig implements DataFlow::ConfigSig {
src.asExpr().(MethodCall).getMethod().getName() = "source"
}
- predicate isSink(DataFlow::Node sink) { exists(Call call | sink.asExpr() = call.getAnArgument()) }
+ predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() =
+ any(ConstructorCall constrCall |
+ constrCall.getConstructedType() instanceof TypeFile and
+ constrCall.getNumArgument() = 2
+ ).getArgument(0)
+ }
}
-/** Tracks taint flow to any argument. */
+/** Tracks taint flow to the parent argument of a `File` constructor. */
private module TaintedArgFlow = TaintTracking::Global;
/** Holds if `g` is a guard that checks for `..` components. */
From e4a1847dade236bff587894f1912ce0437d72dd3 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 23 Jan 2025 10:15:59 +0100
Subject: [PATCH 079/279] Python: mass enable diff-informed data flow
---
.../dataflow/CleartextLoggingQuery.qll | 2 ++
.../dataflow/CleartextStorageQuery.qll | 2 ++
.../security/dataflow/CodeInjectionQuery.qll | 2 ++
.../dataflow/CommandInjectionQuery.qll | 2 ++
.../security/dataflow/CookieInjectionQuery.qll | 2 ++
.../dataflow/HttpHeaderInjectionQuery.qll | 2 ++
.../security/dataflow/LdapInjectionQuery.qll | 14 ++++++++++++++
.../security/dataflow/LogInjectionQuery.qll | 2 ++
.../security/dataflow/NoSqlInjectionQuery.qll | 2 ++
.../dataflow/PamAuthorizationQuery.qll | 2 ++
.../security/dataflow/PathInjectionQuery.qll | 2 ++
.../security/dataflow/PolynomialReDoSQuery.qll | 7 +++++++
.../security/dataflow/ReflectedXssQuery.qll | 2 ++
.../security/dataflow/RegexInjectionQuery.qll | 6 ++++++
.../dataflow/ServerSideRequestForgeryQuery.qll | 13 +++++++++++++
.../security/dataflow/SqlInjectionQuery.qll | 2 ++
.../dataflow/StackTraceExposureQuery.qll | 2 ++
.../python/security/dataflow/TarSlipQuery.qll | 2 ++
.../dataflow/TemplateInjectionQuery.qll | 2 ++
.../dataflow/UnsafeDeserializationQuery.qll | 2 ++
.../UnsafeShellCommandConstructionQuery.qll | 7 +++++++
.../security/dataflow/UrlRedirectQuery.qll | 2 ++
.../dataflow/WeakSensitiveDataHashingQuery.qll | 12 ++++++++++++
.../python/security/dataflow/XmlBombQuery.qll | 2 ++
.../security/dataflow/XpathInjectionQuery.qll | 2 ++
.../python/security/dataflow/XxeQuery.qll | 2 ++
.../CWE-020-ExternalAPIs/ExternalAPIs.qll | 7 +++++++
.../ql/src/Security/CWE-327/FluentApiModel.qll | 6 ++++++
.../Security/CWE-798/HardcodedCredentials.ql | 2 ++
.../Security/CWE-022bis/TarSlipImprov.ql | 2 ++
.../Security/CWE-091/XsltInjectionQuery.qll | 2 ++
.../src/experimental/Security/CWE-094/Js2Py.ql | 2 ++
.../CWE-176/UnicodeBypassValidationQuery.qll | 2 ++
.../PossibleTimingAttackAgainstHash.ql | 6 ++++++
.../TimingAttackAgainstHash.ql | 6 ++++++
.../TimingAttackAgainstHeaderValue.ql | 2 ++
...PossibleTimingAttackAgainstSensitiveInfo.ql | 2 ++
.../TimingAttackAgainstSensitiveInfo.ql | 2 ++
.../WebAppConstantSecretKey.ql | 2 ++
...UnsafeUsageOfClientSideEncryptionVersion.ql | 2 ++
.../Security/CWE-340/TokenBuiltFromUUID.ql | 2 ++
.../Security/CWE-346/CorsBypass.ql | 2 ++
.../ClientSuppliedIpUsedInSecurityCheck.ql | 2 ++
.../Security/CWE-770/UnicodeDoS.ql | 2 ++
.../Security/UnsafeUnpackQuery.qll | 2 ++
.../semmle/python/libraries/SmtpLib.qll | 6 ++++++
.../python/security/DecompressionBomb.qll | 2 ++
.../python/security/InsecureRandomness.qll | 2 ++
.../python/security/LdapInsecureAuth.qll | 2 ++
.../python/security/RemoteCommandExecution.qll | 2 ++
.../semmle/python/security/TimingAttack.qll | 18 ++++++++++++++++++
.../semmle/python/security/ZipSlip.qll | 2 ++
.../python/security/dataflow/EmailXss.qll | 2 ++
.../python/security/injection/CsvInjection.qll | 2 ++
.../ModificationOfParameterWithDefault.qll | 6 +++++-
55 files changed, 197 insertions(+), 1 deletion(-)
diff --git a/python/ql/lib/semmle/python/security/dataflow/CleartextLoggingQuery.qll b/python/ql/lib/semmle/python/security/dataflow/CleartextLoggingQuery.qll
index 03b1db49d17..def13197d4a 100644
--- a/python/ql/lib/semmle/python/security/dataflow/CleartextLoggingQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/CleartextLoggingQuery.qll
@@ -21,6 +21,8 @@ private module CleartextLoggingConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "Clear-text logging of sensitive information" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/CleartextStorageQuery.qll b/python/ql/lib/semmle/python/security/dataflow/CleartextStorageQuery.qll
index 7ee85230c84..190a8536887 100644
--- a/python/ql/lib/semmle/python/security/dataflow/CleartextStorageQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/CleartextStorageQuery.qll
@@ -21,6 +21,8 @@ private module CleartextStorageConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "Clear-text storage of sensitive information" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/CodeInjectionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/CodeInjectionQuery.qll
index 486d06a6b21..188bf56f30a 100644
--- a/python/ql/lib/semmle/python/security/dataflow/CodeInjectionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/CodeInjectionQuery.qll
@@ -17,6 +17,8 @@ private module CodeInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "code injection" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/CommandInjectionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/CommandInjectionQuery.qll
index 18bcbe8cdd5..cc2358c9a69 100644
--- a/python/ql/lib/semmle/python/security/dataflow/CommandInjectionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/CommandInjectionQuery.qll
@@ -20,6 +20,8 @@ module CommandInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "command injection" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/CookieInjectionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/CookieInjectionQuery.qll
index 2b089fb2779..e017ec959f4 100644
--- a/python/ql/lib/semmle/python/security/dataflow/CookieInjectionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/CookieInjectionQuery.qll
@@ -20,6 +20,8 @@ module CookieInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "cookie injection" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/HttpHeaderInjectionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/HttpHeaderInjectionQuery.qll
index 1583ee70491..82266f53162 100644
--- a/python/ql/lib/semmle/python/security/dataflow/HttpHeaderInjectionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/HttpHeaderInjectionQuery.qll
@@ -16,6 +16,8 @@ private module HeaderInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node node) { node instanceof HttpHeaderInjection::Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof HttpHeaderInjection::Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "HTTP Header injection" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/LdapInjectionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/LdapInjectionQuery.qll
index 527c1cbfe43..a610f229844 100644
--- a/python/ql/lib/semmle/python/security/dataflow/LdapInjectionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/LdapInjectionQuery.qll
@@ -19,6 +19,13 @@ private module LdapInjectionDnConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof DnSink }
predicate isBarrier(DataFlow::Node node) { node instanceof DnSanitizer }
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/src/Security/CWE-090/LdapInjection.ql:26: Column 1 does not select a source or sink originating from the flow call on line 21
+ // ql/src/Security/CWE-090/LdapInjection.ql:27: Column 5 does not select a source or sink originating from the flow call on line 21
+ none()
+ }
}
/** Global taint-tracking for detecting "LDAP injection via the distinguished name (DN) parameter" vulnerabilities. */
@@ -30,6 +37,13 @@ private module LdapInjectionFilterConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof FilterSink }
predicate isBarrier(DataFlow::Node node) { node instanceof FilterSanitizer }
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/src/Security/CWE-090/LdapInjection.ql:26: Column 1 does not select a source or sink originating from the flow call on line 24
+ // ql/src/Security/CWE-090/LdapInjection.ql:27: Column 5 does not select a source or sink originating from the flow call on line 24
+ none()
+ }
}
/** Global taint-tracking for detecting "LDAP injection via the filter parameter" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/LogInjectionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/LogInjectionQuery.qll
index 7204accbdcf..fa392cd2d58 100644
--- a/python/ql/lib/semmle/python/security/dataflow/LogInjectionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/LogInjectionQuery.qll
@@ -17,6 +17,8 @@ private module LogInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "log injection" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/NoSqlInjectionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/NoSqlInjectionQuery.qll
index 5b0daacb737..a1b5eeb6a93 100644
--- a/python/ql/lib/semmle/python/security/dataflow/NoSqlInjectionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/NoSqlInjectionQuery.qll
@@ -56,6 +56,8 @@ module NoSqlInjectionConfig implements DataFlow::StateConfigSig {
predicate isBarrier(DataFlow::Node node) {
node = any(NoSqlSanitizer noSqlSanitizer).getAnInput()
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
module NoSqlInjectionFlow = TaintTracking::GlobalWithState;
diff --git a/python/ql/lib/semmle/python/security/dataflow/PamAuthorizationQuery.qll b/python/ql/lib/semmle/python/security/dataflow/PamAuthorizationQuery.qll
index eb83d0bf84f..8221083b184 100644
--- a/python/ql/lib/semmle/python/security/dataflow/PamAuthorizationQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/PamAuthorizationQuery.qll
@@ -31,6 +31,8 @@ private module PamAuthorizationConfig implements DataFlow::ConfigSig {
// Flow from handle to the authenticate call in the final step
exists(VulnPamAuthCall c | c.getArg(0) = node1 | node2 = c)
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "PAM Authorization" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/PathInjectionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/PathInjectionQuery.qll
index b3081fd9996..f8bca406ece 100644
--- a/python/ql/lib/semmle/python/security/dataflow/PathInjectionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/PathInjectionQuery.qll
@@ -71,6 +71,8 @@ module PathInjectionConfig implements DataFlow::StateConfigSig {
stateFrom instanceof NotNormalized and
stateTo instanceof NormalizedUnchecked
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "path injection" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSQuery.qll b/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSQuery.qll
index 4e082aac26e..a7e9a4ba6a6 100644
--- a/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSQuery.qll
@@ -17,6 +17,13 @@ private module PolynomialReDoSConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/src/Security/CWE-730/PolynomialReDoS.ql:31: Column 1 selects sink.getHighlight
+ // ql/src/Security/CWE-730/PolynomialReDoS.ql:33: Column 5 does not select a source or sink originating from the flow call on line 24
+ none()
+ }
}
/** Global taint-tracking for detecting "polynomial regular expression denial of service (ReDoS)" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/ReflectedXssQuery.qll b/python/ql/lib/semmle/python/security/dataflow/ReflectedXssQuery.qll
index 5f5b2dd58df..223f0643183 100644
--- a/python/ql/lib/semmle/python/security/dataflow/ReflectedXssQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/ReflectedXssQuery.qll
@@ -17,6 +17,8 @@ private module ReflectedXssConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "reflected server-side cross-site scripting" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/RegexInjectionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/RegexInjectionQuery.qll
index ae21270a63e..1d55f592d3f 100644
--- a/python/ql/lib/semmle/python/security/dataflow/RegexInjectionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/RegexInjectionQuery.qll
@@ -18,6 +18,12 @@ private module RegexInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/src/Security/CWE-730/RegexInjection.ql:29: Column 7 selects sink.getRegexExecution
+ none()
+ }
}
/** Global taint-tracking for detecting "regular expression injection" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll b/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll
index 4cae5a301b1..28173eb1328 100644
--- a/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll
@@ -29,6 +29,13 @@ private module FullServerSideRequestForgeryConfig implements DataFlow::ConfigSig
or
node instanceof FullUrlControlSanitizer
}
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll:47: Flow call outside 'select' clause
+ // ql/src/Security/CWE-918/FullServerSideRequestForgery.ql:24: Column 1 selects sink.getRequest
+ none()
+ }
}
/**
@@ -58,6 +65,12 @@ private module PartialServerSideRequestForgeryConfig implements DataFlow::Config
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/src/Security/CWE-918/PartialServerSideRequestForgery.ql:24: Column 1 selects sink.getRequest
+ none()
+ }
}
/**
diff --git a/python/ql/lib/semmle/python/security/dataflow/SqlInjectionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/SqlInjectionQuery.qll
index a63590643f3..cc33baf2dd9 100644
--- a/python/ql/lib/semmle/python/security/dataflow/SqlInjectionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/SqlInjectionQuery.qll
@@ -17,6 +17,8 @@ private module SqlInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "SQL injection" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/StackTraceExposureQuery.qll b/python/ql/lib/semmle/python/security/dataflow/StackTraceExposureQuery.qll
index 57ef6d7ebb2..8249c68a807 100644
--- a/python/ql/lib/semmle/python/security/dataflow/StackTraceExposureQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/StackTraceExposureQuery.qll
@@ -26,6 +26,8 @@ private module StackTraceExposureConfig implements DataFlow::ConfigSig {
nodeTo = attr
)
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "stack trace exposure" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/TarSlipQuery.qll b/python/ql/lib/semmle/python/security/dataflow/TarSlipQuery.qll
index 162bfcd74cc..c00c6017775 100644
--- a/python/ql/lib/semmle/python/security/dataflow/TarSlipQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/TarSlipQuery.qll
@@ -17,6 +17,8 @@ private module TarSlipConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "tar slip" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/TemplateInjectionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/TemplateInjectionQuery.qll
index 22c228f48d5..8764a3203a6 100644
--- a/python/ql/lib/semmle/python/security/dataflow/TemplateInjectionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/TemplateInjectionQuery.qll
@@ -17,6 +17,8 @@ private module TemplateInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node node) { node instanceof Sink }
predicate isBarrierIn(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "template injection" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/UnsafeDeserializationQuery.qll b/python/ql/lib/semmle/python/security/dataflow/UnsafeDeserializationQuery.qll
index dd6925b7998..6edf60dcd36 100644
--- a/python/ql/lib/semmle/python/security/dataflow/UnsafeDeserializationQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/UnsafeDeserializationQuery.qll
@@ -17,6 +17,8 @@ private module UnsafeDeserializationConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "code execution from deserialization" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/UnsafeShellCommandConstructionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/UnsafeShellCommandConstructionQuery.qll
index 51341cfe6cd..89de8e6961f 100644
--- a/python/ql/lib/semmle/python/security/dataflow/UnsafeShellCommandConstructionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/UnsafeShellCommandConstructionQuery.qll
@@ -28,6 +28,13 @@ module UnsafeShellCommandConstructionConfig implements DataFlow::ConfigSig {
// override to require the path doesn't have unmatched return steps
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/src/Security/CWE-078/UnsafeShellCommandConstruction.ql:27: Column 1 selects sink.getStringConstruction
+ // ql/src/Security/CWE-078/UnsafeShellCommandConstruction.ql:29: Column 7 selects sink.getCommandExecution
+ none()
+ }
}
/** Global taint-tracking for detecting "shell command constructed from library input" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/UrlRedirectQuery.qll b/python/ql/lib/semmle/python/security/dataflow/UrlRedirectQuery.qll
index a9526f33ad3..36167cfc103 100644
--- a/python/ql/lib/semmle/python/security/dataflow/UrlRedirectQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/UrlRedirectQuery.qll
@@ -32,6 +32,8 @@ private module UrlRedirectConfig implements DataFlow::StateConfigSig {
) {
any(UrlRedirect::AdditionalFlowStep a).step(nodeFrom, stateFrom, nodeTo, stateTo)
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "URL redirection" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/WeakSensitiveDataHashingQuery.qll b/python/ql/lib/semmle/python/security/dataflow/WeakSensitiveDataHashingQuery.qll
index 04d8846d7d0..9efa8320f97 100644
--- a/python/ql/lib/semmle/python/security/dataflow/WeakSensitiveDataHashingQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/WeakSensitiveDataHashingQuery.qll
@@ -33,6 +33,12 @@ module NormalHashFunction {
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
sensitiveDataExtraStepForCalls(node1, node2)
}
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/lib/semmle/python/security/dataflow/WeakSensitiveDataHashingQuery.qll:88: Flow call outside 'select' clause
+ none()
+ }
}
/** Global taint-tracking for detecting "use of a broken or weak cryptographic hashing algorithm on sensitive data" vulnerabilities. */
@@ -63,6 +69,12 @@ module ComputationallyExpensiveHashFunction {
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
sensitiveDataExtraStepForCalls(node1, node2)
}
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/lib/semmle/python/security/dataflow/WeakSensitiveDataHashingQuery.qll:95: Flow call outside 'select' clause
+ none()
+ }
}
/** Global taint-tracking for detecting "use of a broken or weak cryptographic hashing algorithm on passwords" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/XmlBombQuery.qll b/python/ql/lib/semmle/python/security/dataflow/XmlBombQuery.qll
index e69e8ad63c6..2c445e0aeed 100644
--- a/python/ql/lib/semmle/python/security/dataflow/XmlBombQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/XmlBombQuery.qll
@@ -17,6 +17,8 @@ private module XmlBombConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "XML bomb" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/XpathInjectionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/XpathInjectionQuery.qll
index 2a15669f6ff..3a1f35f3367 100644
--- a/python/ql/lib/semmle/python/security/dataflow/XpathInjectionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/XpathInjectionQuery.qll
@@ -17,6 +17,8 @@ private module XpathInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "Xpath Injection" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/XxeQuery.qll b/python/ql/lib/semmle/python/security/dataflow/XxeQuery.qll
index da7c34a5bac..0347d159b6e 100644
--- a/python/ql/lib/semmle/python/security/dataflow/XxeQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/XxeQuery.qll
@@ -17,6 +17,8 @@ private module XxeConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "XML External Entity (XXE)" vulnerabilities. */
diff --git a/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll b/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll
index d2b47c9a6a7..03f84b7903d 100644
--- a/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll
+++ b/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll
@@ -171,6 +171,13 @@ private module UntrustedDataToExternalApiConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) { sink instanceof ExternalApiDataNode }
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll:181: Flow call outside 'select' clause
+ // ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll:184: Flow call outside 'select' clause
+ none()
+ }
}
/** Global taint-tracking from `RemoteFlowSource`s to `ExternalApiDataNode`s. */
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index ce62a1a590c..d2118493e0f 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -110,6 +110,12 @@ module InsecureContextConfiguration implements DataFlow::StateConfigSig {
)
)
}
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/src/Security/CWE-327/FluentApiModel.qll:130: Flow call outside 'select' clause
+ none()
+ }
}
private module InsecureContextFlow = DataFlow::GlobalWithState;
diff --git a/python/ql/src/Security/CWE-798/HardcodedCredentials.ql b/python/ql/src/Security/CWE-798/HardcodedCredentials.ql
index 6e48ada26a4..c8aecd7204b 100644
--- a/python/ql/src/Security/CWE-798/HardcodedCredentials.ql
+++ b/python/ql/src/Security/CWE-798/HardcodedCredentials.ql
@@ -119,6 +119,8 @@ private module HardcodedCredentialsConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof HardcodedValueSource }
predicate isSink(DataFlow::Node sink) { sink instanceof CredentialSink }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
module HardcodedCredentialsFlow = TaintTracking::Global;
diff --git a/python/ql/src/experimental/Security/CWE-022bis/TarSlipImprov.ql b/python/ql/src/experimental/Security/CWE-022bis/TarSlipImprov.ql
index 431fe293cec..1727da1bcf5 100755
--- a/python/ql/src/experimental/Security/CWE-022bis/TarSlipImprov.ql
+++ b/python/ql/src/experimental/Security/CWE-022bis/TarSlipImprov.ql
@@ -109,6 +109,8 @@ private module TarSlipImprovConfig implements DataFlow::ConfigSig {
nodeFrom = nodeTo.(API::CallNode).getArg(0) and
nodeFrom = tarfileOpen().getReturn().getAValueReachableFromSource()
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting more "TarSlip" vulnerabilities. */
diff --git a/python/ql/src/experimental/Security/CWE-091/XsltInjectionQuery.qll b/python/ql/src/experimental/Security/CWE-091/XsltInjectionQuery.qll
index 4ecae424ed1..1430691bff8 100644
--- a/python/ql/src/experimental/Security/CWE-091/XsltInjectionQuery.qll
+++ b/python/ql/src/experimental/Security/CWE-091/XsltInjectionQuery.qll
@@ -19,6 +19,8 @@ module XsltInjectionConfig implements DataFlow::ConfigSig {
// opted for the more simple approach.
nodeTo = elementTreeConstruction(nodeFrom)
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
module XsltInjectionFlow = TaintTracking::Global;
diff --git a/python/ql/src/experimental/Security/CWE-094/Js2Py.ql b/python/ql/src/experimental/Security/CWE-094/Js2Py.ql
index f5d6e3a6c10..2bb3fea1b32 100644
--- a/python/ql/src/experimental/Security/CWE-094/Js2Py.ql
+++ b/python/ql/src/experimental/Security/CWE-094/Js2Py.ql
@@ -24,6 +24,8 @@ module Js2PyFlowConfig implements DataFlow::ConfigSig {
API::moduleImport("js2py").getMember(["eval_js", "eval_js6", "EvalJs"]).getACall().getArg(_) =
node
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
module Js2PyFlow = TaintTracking::Global;
diff --git a/python/ql/src/experimental/Security/CWE-176/UnicodeBypassValidationQuery.qll b/python/ql/src/experimental/Security/CWE-176/UnicodeBypassValidationQuery.qll
index f2c3b01ac30..c0337117cf0 100644
--- a/python/ql/src/experimental/Security/CWE-176/UnicodeBypassValidationQuery.qll
+++ b/python/ql/src/experimental/Security/CWE-176/UnicodeBypassValidationQuery.qll
@@ -75,6 +75,8 @@ private module UnicodeBypassValidationConfig implements DataFlow::StateConfigSig
) and
state instanceof PostValidation
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "Unicode transformation mishandling" vulnerabilities. */
diff --git a/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.ql b/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.ql
index 82ba11c1d4b..440dd540dbd 100644
--- a/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.ql
+++ b/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.ql
@@ -26,6 +26,12 @@ private module PossibleTimingAttackAgainstHashConfig implements DataFlow::Config
predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.ql:41: Column 5 selects source.getResultType
+ none()
+ }
}
module PossibleTimingAttackAgainstHashFlow =
diff --git a/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/TimingAttackAgainstHash.ql b/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/TimingAttackAgainstHash.ql
index e08f1dbb517..a5338119880 100644
--- a/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/TimingAttackAgainstHash.ql
+++ b/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/TimingAttackAgainstHash.ql
@@ -25,6 +25,12 @@ private module TimingAttackAgainstHashConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/TimingAttackAgainstHash.ql:39: Column 5 selects source.getResultType
+ none()
+ }
}
module TimingAttackAgainstHashFlow = TaintTracking::Global;
diff --git a/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHeaderValue/TimingAttackAgainstHeaderValue.ql b/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHeaderValue/TimingAttackAgainstHeaderValue.ql
index a1da41530a8..c59885c23bb 100644
--- a/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHeaderValue/TimingAttackAgainstHeaderValue.ql
+++ b/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHeaderValue/TimingAttackAgainstHeaderValue.ql
@@ -23,6 +23,8 @@ private module TimingAttackAgainstHeaderValueConfig implements DataFlow::ConfigS
predicate isSource(DataFlow::Node source) { source instanceof ClientSuppliedSecret }
predicate isSink(DataFlow::Node sink) { sink instanceof CompareSink }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
module TimingAttackAgainstHeaderValueFlow =
diff --git a/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/PossibleTimingAttackAgainstSensitiveInfo.ql b/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/PossibleTimingAttackAgainstSensitiveInfo.ql
index cdf350dd7cd..af54b3c2879 100644
--- a/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/PossibleTimingAttackAgainstSensitiveInfo.ql
+++ b/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/PossibleTimingAttackAgainstSensitiveInfo.ql
@@ -23,6 +23,8 @@ private module PossibleTimingAttackAgainstSensitiveInfoConfig implements DataFlo
predicate isSource(DataFlow::Node source) { source instanceof SecretSource }
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
module PossibleTimingAttackAgainstSensitiveInfoFlow =
diff --git a/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/TimingAttackAgainstSensitiveInfo.ql b/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/TimingAttackAgainstSensitiveInfo.ql
index 8ec4fac97e3..c1afcb22e6b 100644
--- a/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/TimingAttackAgainstSensitiveInfo.ql
+++ b/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/TimingAttackAgainstSensitiveInfo.ql
@@ -24,6 +24,8 @@ private module TimingAttackAgainstSensitiveInfoConfig implements DataFlow::Confi
predicate isSource(DataFlow::Node source) { source instanceof SecretSource }
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
module TimingAttackAgainstSensitiveInfoFlow =
diff --git a/python/ql/src/experimental/Security/CWE-287-ConstantSecretKey/WebAppConstantSecretKey.ql b/python/ql/src/experimental/Security/CWE-287-ConstantSecretKey/WebAppConstantSecretKey.ql
index 7bb35012b38..f63f590ba37 100644
--- a/python/ql/src/experimental/Security/CWE-287-ConstantSecretKey/WebAppConstantSecretKey.ql
+++ b/python/ql/src/experimental/Security/CWE-287-ConstantSecretKey/WebAppConstantSecretKey.ql
@@ -52,6 +52,8 @@ private module WebAppConstantSecretKeyConfig implements DataFlow::StateConfigSig
or
state = Django() and DjangoConstantSecretKeyConfig::isSink(sink)
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
module WebAppConstantSecretKeyFlow = TaintTracking::GlobalWithState;
diff --git a/python/ql/src/experimental/Security/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql b/python/ql/src/experimental/Security/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql
index c548eac6836..a0fadbff3f3 100644
--- a/python/ql/src/experimental/Security/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql
+++ b/python/ql/src/experimental/Security/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql
@@ -145,6 +145,8 @@ private module AzureBlobClientConfig implements DataFlow::StateConfigSig {
node = call.getObject()
)
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
module AzureBlobClientFlow = DataFlow::GlobalWithState;
diff --git a/python/ql/src/experimental/Security/CWE-340/TokenBuiltFromUUID.ql b/python/ql/src/experimental/Security/CWE-340/TokenBuiltFromUUID.ql
index b91f2dd6237..ab5a4243a74 100644
--- a/python/ql/src/experimental/Security/CWE-340/TokenBuiltFromUUID.ql
+++ b/python/ql/src/experimental/Security/CWE-340/TokenBuiltFromUUID.ql
@@ -51,6 +51,8 @@ private module TokenBuiltFromUuidConfig implements DataFlow::ConfigSig {
nodeTo = call
)
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "TokenBuiltFromUUID" vulnerabilities. */
diff --git a/python/ql/src/experimental/Security/CWE-346/CorsBypass.ql b/python/ql/src/experimental/Security/CWE-346/CorsBypass.ql
index 4b79b97ff4a..01e661cb0bb 100644
--- a/python/ql/src/experimental/Security/CWE-346/CorsBypass.ql
+++ b/python/ql/src/experimental/Security/CWE-346/CorsBypass.ql
@@ -79,6 +79,8 @@ module CorsBypassConfig implements DataFlow::ConfigSig {
c.getReturn().asSource() = node2 and n.asSource() = node1
)
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
module CorsFlow = TaintTracking::Global;
diff --git a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql
index 219192ce45d..463bf59c436 100644
--- a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql
+++ b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql
@@ -45,6 +45,8 @@ private module ClientSuppliedIpUsedInSecurityCheckConfig implements DataFlow::Co
ss = node.asExpr()
)
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "client ip used in security check" vulnerabilities. */
diff --git a/python/ql/src/experimental/Security/CWE-770/UnicodeDoS.ql b/python/ql/src/experimental/Security/CWE-770/UnicodeDoS.ql
index 47edf3ed0f9..61cdd34920d 100644
--- a/python/ql/src/experimental/Security/CWE-770/UnicodeDoS.ql
+++ b/python/ql/src/experimental/Security/CWE-770/UnicodeDoS.ql
@@ -108,6 +108,8 @@ private module UnicodeDoSConfig implements DataFlow::ConfigSig {
.getACall()
.getArg(_)
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
module UnicodeDoSFlow = TaintTracking::Global;
diff --git a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
index 338a5555c57..64da6b8d799 100644
--- a/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
+++ b/python/ql/src/experimental/Security/UnsafeUnpackQuery.qll
@@ -208,6 +208,8 @@ module UnsafeUnpackConfig implements DataFlow::ConfigSig {
nodeFrom = mcn.getArg(0)
)
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "UnsafeUnpacking" vulnerabilities. */
diff --git a/python/ql/src/experimental/semmle/python/libraries/SmtpLib.qll b/python/ql/src/experimental/semmle/python/libraries/SmtpLib.qll
index f174220727e..6712c9279f2 100644
--- a/python/ql/src/experimental/semmle/python/libraries/SmtpLib.qll
+++ b/python/ql/src/experimental/semmle/python/libraries/SmtpLib.qll
@@ -38,6 +38,12 @@ module SmtpLib {
predicate isSink(DataFlow::Node sink) {
sink = smtpMimeMultipartInstance().getACall().getArgByName("_subparts")
}
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/src/experimental/semmle/python/libraries/SmtpLib.qll:91: Flow call outside 'select' clause
+ none()
+ }
}
module SmtpMessageFlow = TaintTracking::Global;
diff --git a/python/ql/src/experimental/semmle/python/security/DecompressionBomb.qll b/python/ql/src/experimental/semmle/python/security/DecompressionBomb.qll
index 552f901b7e0..a2e50d0ade5 100644
--- a/python/ql/src/experimental/semmle/python/security/DecompressionBomb.qll
+++ b/python/ql/src/experimental/semmle/python/security/DecompressionBomb.qll
@@ -408,6 +408,8 @@ module BombsConfig implements DataFlow::ConfigSig {
isAdditionalTaintStepTextIOWrapper(pred, succ)
)
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
module BombsFlow = TaintTracking::Global;
diff --git a/python/ql/src/experimental/semmle/python/security/InsecureRandomness.qll b/python/ql/src/experimental/semmle/python/security/InsecureRandomness.qll
index 5a32a887bd5..8bc09a7036e 100644
--- a/python/ql/src/experimental/semmle/python/security/InsecureRandomness.qll
+++ b/python/ql/src/experimental/semmle/python/security/InsecureRandomness.qll
@@ -27,6 +27,8 @@ module InsecureRandomness {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "random values that are not cryptographically secure" vulnerabilities. */
diff --git a/python/ql/src/experimental/semmle/python/security/LdapInsecureAuth.qll b/python/ql/src/experimental/semmle/python/security/LdapInsecureAuth.qll
index a63332137d1..630543e6f79 100644
--- a/python/ql/src/experimental/semmle/python/security/LdapInsecureAuth.qll
+++ b/python/ql/src/experimental/semmle/python/security/LdapInsecureAuth.qll
@@ -101,6 +101,8 @@ private module LdapInsecureAuthConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) {
exists(LdapBind ldapBind | not ldapBind.useSsl() and sink = ldapBind.getHost())
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "LDAP insecure authentications" vulnerabilities. */
diff --git a/python/ql/src/experimental/semmle/python/security/RemoteCommandExecution.qll b/python/ql/src/experimental/semmle/python/security/RemoteCommandExecution.qll
index f4eed84c0c1..6f4ea88a747 100644
--- a/python/ql/src/experimental/semmle/python/security/RemoteCommandExecution.qll
+++ b/python/ql/src/experimental/semmle/python/security/RemoteCommandExecution.qll
@@ -10,6 +10,8 @@ module RemoteCommandExecutionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) { sink = any(RemoteCommandExecution rce).getCommand() }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "secondary server command injection" vulnerabilities. */
diff --git a/python/ql/src/experimental/semmle/python/security/TimingAttack.qll b/python/ql/src/experimental/semmle/python/security/TimingAttack.qll
index 6d8cc98f21c..e20e7885352 100644
--- a/python/ql/src/experimental/semmle/python/security/TimingAttack.qll
+++ b/python/ql/src/experimental/semmle/python/security/TimingAttack.qll
@@ -271,6 +271,12 @@ module UserInputSecretConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof CredentialExpr }
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/src/experimental/semmle/python/security/TimingAttack.qll:176: Flow call outside 'select' clause
+ none()
+ }
}
module UserInputSecretFlow = TaintTracking::Global;
@@ -288,6 +294,12 @@ module UserInputInComparisonConfig implements DataFlow::ConfigSig {
sink.asExpr() = [left, right]
)
}
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/src/experimental/semmle/python/security/TimingAttack.qll:165: Flow call outside 'select' clause
+ none()
+ }
}
module UserInputInComparisonFlow = TaintTracking::Global;
@@ -304,6 +316,12 @@ private module ExcludeLenFuncConfig implements DataFlow::ConfigSig {
sink.asExpr() = call.getArg(0)
)
}
+
+ predicate observeDiffInformedIncrementalMode() {
+ // TODO(diff-informed): Manually verify if config can be diff-informed.
+ // ql/src/experimental/semmle/python/security/TimingAttack.qll:347: Flow call outside 'select' clause
+ none()
+ }
}
module ExcludeLenFuncFlow = TaintTracking::Global;
diff --git a/python/ql/src/experimental/semmle/python/security/ZipSlip.qll b/python/ql/src/experimental/semmle/python/security/ZipSlip.qll
index 5f8b4d940ef..a6125015db0 100644
--- a/python/ql/src/experimental/semmle/python/security/ZipSlip.qll
+++ b/python/ql/src/experimental/semmle/python/security/ZipSlip.qll
@@ -34,6 +34,8 @@ private module ZipSlipConfig implements DataFlow::ConfigSig {
) and
not sink.getScope().getLocation().getFile().inStdlib()
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "zip slip" vulnerabilities. */
diff --git a/python/ql/src/experimental/semmle/python/security/dataflow/EmailXss.qll b/python/ql/src/experimental/semmle/python/security/dataflow/EmailXss.qll
index c08a0e6b258..8f392a43a8a 100644
--- a/python/ql/src/experimental/semmle/python/security/dataflow/EmailXss.qll
+++ b/python/ql/src/experimental/semmle/python/security/dataflow/EmailXss.qll
@@ -34,6 +34,8 @@ private module EmailXssConfig implements DataFlow::ConfigSig {
nodeFrom = htmlContentCall.getArg(0)
)
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "Email XSS" vulnerabilities. */
diff --git a/python/ql/src/experimental/semmle/python/security/injection/CsvInjection.qll b/python/ql/src/experimental/semmle/python/security/injection/CsvInjection.qll
index d08e9b090a6..859f6d1e5e8 100644
--- a/python/ql/src/experimental/semmle/python/security/injection/CsvInjection.qll
+++ b/python/ql/src/experimental/semmle/python/security/injection/CsvInjection.qll
@@ -17,6 +17,8 @@ private module CsvInjectionConfig implements DataFlow::ConfigSig {
node = DataFlow::BarrierGuard::getABarrierNode() or
node instanceof ConstCompareBarrier
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
private predicate startsWithCheck(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
diff --git a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll
index 290087f6a71..86b069f2820 100644
--- a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll
+++ b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll
@@ -45,11 +45,15 @@ module ModificationOfParameterWithDefault {
copyTarget(node) and state in [true, false]
}
- private predicate copyTarget(DataFlow::Node node) {
+ private predicate observeDiffInformedIncrementalMode() { any() }
+
+ predicate copyTarget(DataFlow::Node node) {
node = API::moduleImport("copy").getMember(["copy", "deepcopy"]).getACall()
or
node.(DataFlow::MethodCallNode).calls(_, "copy")
}
+
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global data-flow for detecting modifications of a parameters default value. */
From 9dfd1cc608922cad9aef0eb4541af3498eeed5d6 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 23 Jan 2025 13:49:03 +0100
Subject: [PATCH 080/279] Python: Fixup broken patch
---
.../python/functions/ModificationOfParameterWithDefault.qll | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll
index 86b069f2820..d0d2580e767 100644
--- a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll
+++ b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll
@@ -45,9 +45,7 @@ module ModificationOfParameterWithDefault {
copyTarget(node) and state in [true, false]
}
- private predicate observeDiffInformedIncrementalMode() { any() }
-
- predicate copyTarget(DataFlow::Node node) {
+ private predicate copyTarget(DataFlow::Node node) {
node = API::moduleImport("copy").getMember(["copy", "deepcopy"]).getACall()
or
node.(DataFlow::MethodCallNode).calls(_, "copy")
From 15c2ccb880544ca51057d67acf8e6ec021a4f3d8 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 23 Jan 2025 13:50:02 +0100
Subject: [PATCH 081/279] Python: ignore experimental for now
---
.../PossibleTimingAttackAgainstHash.ql | 6 ------
.../TimingAttackAgainstHash.ql | 6 ------
.../semmle/python/security/TimingAttack.qll | 18 ------------------
3 files changed, 30 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.ql b/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.ql
index 440dd540dbd..82ba11c1d4b 100644
--- a/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.ql
+++ b/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.ql
@@ -26,12 +26,6 @@ private module PossibleTimingAttackAgainstHashConfig implements DataFlow::Config
predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
-
- predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.ql:41: Column 5 selects source.getResultType
- none()
- }
}
module PossibleTimingAttackAgainstHashFlow =
diff --git a/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/TimingAttackAgainstHash.ql b/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/TimingAttackAgainstHash.ql
index a5338119880..e08f1dbb517 100644
--- a/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/TimingAttackAgainstHash.ql
+++ b/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/TimingAttackAgainstHash.ql
@@ -25,12 +25,6 @@ private module TimingAttackAgainstHashConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
-
- predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/TimingAttackAgainstHash.ql:39: Column 5 selects source.getResultType
- none()
- }
}
module TimingAttackAgainstHashFlow = TaintTracking::Global;
diff --git a/python/ql/src/experimental/semmle/python/security/TimingAttack.qll b/python/ql/src/experimental/semmle/python/security/TimingAttack.qll
index e20e7885352..6d8cc98f21c 100644
--- a/python/ql/src/experimental/semmle/python/security/TimingAttack.qll
+++ b/python/ql/src/experimental/semmle/python/security/TimingAttack.qll
@@ -271,12 +271,6 @@ module UserInputSecretConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof CredentialExpr }
-
- predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/src/experimental/semmle/python/security/TimingAttack.qll:176: Flow call outside 'select' clause
- none()
- }
}
module UserInputSecretFlow = TaintTracking::Global;
@@ -294,12 +288,6 @@ module UserInputInComparisonConfig implements DataFlow::ConfigSig {
sink.asExpr() = [left, right]
)
}
-
- predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/src/experimental/semmle/python/security/TimingAttack.qll:165: Flow call outside 'select' clause
- none()
- }
}
module UserInputInComparisonFlow = TaintTracking::Global;
@@ -316,12 +304,6 @@ private module ExcludeLenFuncConfig implements DataFlow::ConfigSig {
sink.asExpr() = call.getArg(0)
)
}
-
- predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/src/experimental/semmle/python/security/TimingAttack.qll:347: Flow call outside 'select' clause
- none()
- }
}
module ExcludeLenFuncFlow = TaintTracking::Global;
From 975ce064fc30b83667e96ef9bc023479e05ebe23 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 23 Jan 2025 14:01:46 +0100
Subject: [PATCH 082/279] Python: implement for polynomial redos
---
.../dataflow/PolynomialReDoSCustomizations.qll | 5 +++++
.../python/security/dataflow/PolynomialReDoSQuery.qll | 11 ++++++-----
python/ql/src/Security/CWE-730/PolynomialReDoS.ql | 2 +-
3 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSCustomizations.qll
index 4cc464ca4ca..1e9148517bf 100644
--- a/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSCustomizations.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSCustomizations.qll
@@ -35,6 +35,11 @@ module PolynomialReDoS {
/** Gets the regex that is being executed by this node. */
abstract RegExpTerm getRegExp();
+ /** Gets a term within the regexp that may perform polynomial back-tracking. */
+ final PolynomialBackTrackingTerm getABacktrackingTerm() {
+ result.getRootTerm() = this.getRegExp()
+ }
+
/**
* Gets the node to highlight in the alert message.
*/
diff --git a/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSQuery.qll b/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSQuery.qll
index a7e9a4ba6a6..89aa4961e6e 100644
--- a/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSQuery.qll
@@ -18,11 +18,12 @@ private module PolynomialReDoSConfig implements DataFlow::ConfigSig {
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
- predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/src/Security/CWE-730/PolynomialReDoS.ql:31: Column 1 selects sink.getHighlight
- // ql/src/Security/CWE-730/PolynomialReDoS.ql:33: Column 5 does not select a source or sink originating from the flow call on line 24
- none()
+ predicate observeDiffInformedIncrementalMode() { any() }
+
+ Location getASelectedSinkLocation(DataFlow::Node sink) {
+ result = sink.(Sink).getHighlight().getLocation()
+ or
+ result = sink.(Sink).getABacktrackingTerm().getLocation()
}
}
diff --git a/python/ql/src/Security/CWE-730/PolynomialReDoS.ql b/python/ql/src/Security/CWE-730/PolynomialReDoS.ql
index b3b4a8cac92..f6dbe62c3a5 100644
--- a/python/ql/src/Security/CWE-730/PolynomialReDoS.ql
+++ b/python/ql/src/Security/CWE-730/PolynomialReDoS.ql
@@ -23,7 +23,7 @@ from
where
PolynomialReDoSFlow::flowPath(source, sink) and
sinkNode = sink.getNode() and
- regexp.getRootTerm() = sinkNode.getRegExp()
+ regexp = sinkNode.getABacktrackingTerm()
// not (
// source.getNode().(Source).getKind() = "url" and
// regexp.isAtEndLine()
From d3ee6583992e486bd4d7334ab8b5d50a876b9cfe Mon Sep 17 00:00:00 2001
From: Asger F
Date: Fri, 20 Dec 2024 11:02:40 +0100
Subject: [PATCH 083/279] Python: resolve remaining TODOs
---
.../security/dataflow/LdapInjectionQuery.qll | 14 ++-----------
.../security/dataflow/RegexInjectionQuery.qll | 10 +++++----
.../ServerSideRequestForgeryQuery.qll | 21 +++++++++++--------
.../UnsafeShellCommandConstructionQuery.qll | 13 +++++++-----
.../WeakSensitiveDataHashingQuery.qll | 12 ++---------
.../CWE-020-ExternalAPIs/ExternalAPIs.qll | 5 +----
.../src/Security/CWE-327/FluentApiModel.qll | 4 +---
.../semmle/python/libraries/SmtpLib.qll | 4 +---
8 files changed, 33 insertions(+), 50 deletions(-)
diff --git a/python/ql/lib/semmle/python/security/dataflow/LdapInjectionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/LdapInjectionQuery.qll
index a610f229844..7d0f5da6a5a 100644
--- a/python/ql/lib/semmle/python/security/dataflow/LdapInjectionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/LdapInjectionQuery.qll
@@ -20,12 +20,7 @@ private module LdapInjectionDnConfig implements DataFlow::ConfigSig {
predicate isBarrier(DataFlow::Node node) { node instanceof DnSanitizer }
- predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/src/Security/CWE-090/LdapInjection.ql:26: Column 1 does not select a source or sink originating from the flow call on line 21
- // ql/src/Security/CWE-090/LdapInjection.ql:27: Column 5 does not select a source or sink originating from the flow call on line 21
- none()
- }
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "LDAP injection via the distinguished name (DN) parameter" vulnerabilities. */
@@ -38,12 +33,7 @@ private module LdapInjectionFilterConfig implements DataFlow::ConfigSig {
predicate isBarrier(DataFlow::Node node) { node instanceof FilterSanitizer }
- predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/src/Security/CWE-090/LdapInjection.ql:26: Column 1 does not select a source or sink originating from the flow call on line 24
- // ql/src/Security/CWE-090/LdapInjection.ql:27: Column 5 does not select a source or sink originating from the flow call on line 24
- none()
- }
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "LDAP injection via the filter parameter" vulnerabilities. */
diff --git a/python/ql/lib/semmle/python/security/dataflow/RegexInjectionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/RegexInjectionQuery.qll
index 1d55f592d3f..b7e234fd6cb 100644
--- a/python/ql/lib/semmle/python/security/dataflow/RegexInjectionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/RegexInjectionQuery.qll
@@ -19,10 +19,12 @@ private module RegexInjectionConfig implements DataFlow::ConfigSig {
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
- predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/src/Security/CWE-730/RegexInjection.ql:29: Column 7 selects sink.getRegexExecution
- none()
+ predicate observeDiffInformedIncrementalMode() { any() }
+
+ Location getASelectedSinkLocation(DataFlow::Node sink) {
+ result = sink.(Sink).getLocation()
+ or
+ result = sink.(Sink).getRegexExecution().getLocation()
}
}
diff --git a/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll b/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll
index 28173eb1328..a9d6c6a99b0 100644
--- a/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll
@@ -30,11 +30,12 @@ private module FullServerSideRequestForgeryConfig implements DataFlow::ConfigSig
node instanceof FullUrlControlSanitizer
}
- predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll:47: Flow call outside 'select' clause
- // ql/src/Security/CWE-918/FullServerSideRequestForgery.ql:24: Column 1 selects sink.getRequest
- none()
+ predicate observeDiffInformedIncrementalMode() { any() }
+
+ Location getASelectedSinkLocation(DataFlow::Node sink) {
+ result = sink.(Sink).getLocation()
+ or
+ result = sink.(Sink).getRequest().getLocation()
}
}
@@ -66,10 +67,12 @@ private module PartialServerSideRequestForgeryConfig implements DataFlow::Config
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
- predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/src/Security/CWE-918/PartialServerSideRequestForgery.ql:24: Column 1 selects sink.getRequest
- none()
+ predicate observeDiffInformedIncrementalMode() { any() }
+
+ Location getASelectedSinkLocation(DataFlow::Node sink) {
+ result = sink.(Sink).getLocation()
+ or
+ result = sink.(Sink).getRequest().getLocation()
}
}
diff --git a/python/ql/lib/semmle/python/security/dataflow/UnsafeShellCommandConstructionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/UnsafeShellCommandConstructionQuery.qll
index 89de8e6961f..7ac03b3aa8e 100644
--- a/python/ql/lib/semmle/python/security/dataflow/UnsafeShellCommandConstructionQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/UnsafeShellCommandConstructionQuery.qll
@@ -29,11 +29,14 @@ module UnsafeShellCommandConstructionConfig implements DataFlow::ConfigSig {
// override to require the path doesn't have unmatched return steps
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
- predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/src/Security/CWE-078/UnsafeShellCommandConstruction.ql:27: Column 1 selects sink.getStringConstruction
- // ql/src/Security/CWE-078/UnsafeShellCommandConstruction.ql:29: Column 7 selects sink.getCommandExecution
- none()
+ predicate observeDiffInformedIncrementalMode() { any() }
+
+ Location getASelectedSinkLocation(DataFlow::Node sink) {
+ result = sink.(Sink).getLocation()
+ or
+ result = sink.(Sink).getStringConstruction().getLocation()
+ or
+ result = sink.(Sink).getCommandExecution().getLocation()
}
}
diff --git a/python/ql/lib/semmle/python/security/dataflow/WeakSensitiveDataHashingQuery.qll b/python/ql/lib/semmle/python/security/dataflow/WeakSensitiveDataHashingQuery.qll
index 9efa8320f97..a219eac00b2 100644
--- a/python/ql/lib/semmle/python/security/dataflow/WeakSensitiveDataHashingQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/WeakSensitiveDataHashingQuery.qll
@@ -34,11 +34,7 @@ module NormalHashFunction {
sensitiveDataExtraStepForCalls(node1, node2)
}
- predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/lib/semmle/python/security/dataflow/WeakSensitiveDataHashingQuery.qll:88: Flow call outside 'select' clause
- none()
- }
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "use of a broken or weak cryptographic hashing algorithm on sensitive data" vulnerabilities. */
@@ -70,11 +66,7 @@ module ComputationallyExpensiveHashFunction {
sensitiveDataExtraStepForCalls(node1, node2)
}
- predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/lib/semmle/python/security/dataflow/WeakSensitiveDataHashingQuery.qll:95: Flow call outside 'select' clause
- none()
- }
+ predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "use of a broken or weak cryptographic hashing algorithm on passwords" vulnerabilities. */
diff --git a/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll b/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll
index 03f84b7903d..34649c0fb86 100644
--- a/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll
+++ b/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll
@@ -173,10 +173,7 @@ private module UntrustedDataToExternalApiConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof ExternalApiDataNode }
predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll:181: Flow call outside 'select' clause
- // ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll:184: Flow call outside 'select' clause
- none()
+ none() // Not used for PR analysis
}
}
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index d2118493e0f..8dd90a58821 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -112,9 +112,7 @@ module InsecureContextConfiguration implements DataFlow::StateConfigSig {
}
predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/src/Security/CWE-327/FluentApiModel.qll:130: Flow call outside 'select' clause
- none()
+ none() // Too complicated, but might be possible after some refactoring.
}
}
diff --git a/python/ql/src/experimental/semmle/python/libraries/SmtpLib.qll b/python/ql/src/experimental/semmle/python/libraries/SmtpLib.qll
index 6712c9279f2..de93bac0934 100644
--- a/python/ql/src/experimental/semmle/python/libraries/SmtpLib.qll
+++ b/python/ql/src/experimental/semmle/python/libraries/SmtpLib.qll
@@ -40,9 +40,7 @@ module SmtpLib {
}
predicate observeDiffInformedIncrementalMode() {
- // TODO(diff-informed): Manually verify if config can be diff-informed.
- // ql/src/experimental/semmle/python/libraries/SmtpLib.qll:91: Flow call outside 'select' clause
- none()
+ none() // Used in library model
}
}
From 820d2cbeb8d0406fca8d25c61420537ff539ba36 Mon Sep 17 00:00:00 2001
From: Simon Friis Vindum
Date: Thu, 6 Feb 2025 09:56:56 +0100
Subject: [PATCH 084/279] Shared: Use edge dominance in basic block library
---
.../code/csharp/controlflow/BasicBlocks.qll | 34 +++++-
.../csharp/controlflow/ControlFlowElement.qll | 2 +-
.../code/csharp/dataflow/internal/SsaImpl.qll | 2 +-
.../controlflow/graph/Condition.ql | 3 +-
.../codeql/ruby/controlflow/BasicBlocks.qll | 36 +++++-
.../ruby/controlflow/internal/Guards.qll | 2 +-
.../dataflow/internal/DataFlowPrivate.qll | 2 +-
.../ConditionalBypassCustomizations.qll | 2 +-
.../controlflow/graph/BasicBlocks.ql | 4 +-
.../dataflow/barrier-guards/barrier-guards.ql | 2 +-
.../codeql/rust/dataflow/internal/SsaImpl.qll | 2 +-
.../library-tests/controlflow/BasicBlocks.ql | 2 +-
.../codeql/controlflow/BasicBlock.qll | 103 +++++++++---------
shared/controlflow/codeql/controlflow/Cfg.qll | 2 +
.../codeql/swift/controlflow/BasicBlocks.qll | 17 ++-
.../security/PathInjectionExtensions.qll | 2 +-
16 files changed, 142 insertions(+), 75 deletions(-)
diff --git a/csharp/ql/lib/semmle/code/csharp/controlflow/BasicBlocks.qll b/csharp/ql/lib/semmle/code/csharp/controlflow/BasicBlocks.qll
index eb375dc4450..d37af2f57e5 100644
--- a/csharp/ql/lib/semmle/code/csharp/controlflow/BasicBlocks.qll
+++ b/csharp/ql/lib/semmle/code/csharp/controlflow/BasicBlocks.qll
@@ -202,6 +202,29 @@ final class BasicBlock extends BasicBlocksImpl::BasicBlock {
*/
BasicBlock getImmediateDominator() { result = super.getImmediateDominator() }
+ /**
+ * Holds if the edge with successor type `s` out of this basic block is a
+ * dominating edge for `dominated`.
+ *
+ * That is, all paths reaching `dominated` from the entry point basic
+ * block must go through the `s` edge out of this basic block.
+ *
+ * Edge dominance is similar to node dominance except it concerns edges
+ * instead of nodes: A basic block is dominated by a _basic block_ `bb` if it
+ * can only be reached through `bb` and dominated by an _edge_ `s` if it can
+ * only be reached through `s`.
+ *
+ * Note that where all basic blocks (except the entry basic block) are
+ * strictly dominated by at least one basic block, a basic block may not be
+ * dominated by any edge. If an edge dominates a basic block `bb`, then
+ * both endpoints of the edge dominates `bb`. The converse is not the case,
+ * as there may be multiple paths between the endpoints with none of them
+ * dominating.
+ */
+ predicate edgeDominates(BasicBlock dominated, ControlFlow::SuccessorType s) {
+ super.edgeDominates(dominated, s)
+ }
+
/**
* Holds if this basic block strictly post-dominates basic block `bb`.
*
@@ -296,11 +319,14 @@ final class JoinBlockPredecessor extends BasicBlock, BasicBlocksImpl::JoinPredec
* control flow.
*/
final class ConditionBlock extends BasicBlock, BasicBlocksImpl::ConditionBasicBlock {
- predicate immediatelyControls(BasicBlock succ, ConditionalSuccessor s) {
- super.immediatelyControls(succ, s)
+ /** DEPRECATED: Use `edgeDominates` instead. */
+ deprecated predicate immediatelyControls(BasicBlock succ, ConditionalSuccessor s) {
+ this.getASuccessor(s) = succ and
+ BasicBlocksImpl::dominatingEdge(this, succ)
}
- predicate controls(BasicBlock controlled, ConditionalSuccessor s) {
- super.controls(controlled, s)
+ /** DEPRECATED: Use `edgeDominates` instead. */
+ deprecated predicate controls(BasicBlock controlled, ConditionalSuccessor s) {
+ super.edgeDominates(controlled, s)
}
}
diff --git a/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowElement.qll b/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowElement.qll
index 9fb450cd56d..5649f43b881 100644
--- a/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowElement.qll
+++ b/csharp/ql/lib/semmle/code/csharp/controlflow/ControlFlowElement.qll
@@ -225,6 +225,6 @@ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element {
this.controlsBlockSplit(controlled, s, cb)
or
cb.getLastNode() = this.getAControlFlowNode() and
- cb.controls(controlled, s)
+ cb.edgeDominates(controlled, s)
}
}
diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImpl.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImpl.qll
index 00665d836e1..4921bf5b13a 100644
--- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImpl.qll
+++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImpl.qll
@@ -1119,7 +1119,7 @@ private module DataFlowIntegrationInput implements Impl::DataFlowIntegrationInpu
exists(ConditionBlock conditionBlock, ControlFlow::SuccessorTypes::ConditionalSuccessor s |
guard.getAControlFlowNode() = conditionBlock.getLastNode() and
s.getValue() = branch and
- conditionBlock.controls(bb, s)
+ conditionBlock.edgeDominates(bb, s)
)
}
diff --git a/csharp/ql/test/library-tests/controlflow/graph/Condition.ql b/csharp/ql/test/library-tests/controlflow/graph/Condition.ql
index 4cb815016c4..25de07e9d5b 100644
--- a/csharp/ql/test/library-tests/controlflow/graph/Condition.ql
+++ b/csharp/ql/test/library-tests/controlflow/graph/Condition.ql
@@ -4,7 +4,8 @@ import ControlFlow
query predicate conditionBlock(
BasicBlocks::ConditionBlock cb, BasicBlock controlled, boolean testIsTrue
) {
- cb.controls(controlled, any(SuccessorTypes::ConditionalSuccessor s | testIsTrue = s.getValue()))
+ cb.edgeDominates(controlled,
+ any(SuccessorTypes::ConditionalSuccessor s | testIsTrue = s.getValue()))
}
ControlFlow::Node successor(ControlFlow::Node node, boolean kind) {
diff --git a/ruby/ql/lib/codeql/ruby/controlflow/BasicBlocks.qll b/ruby/ql/lib/codeql/ruby/controlflow/BasicBlocks.qll
index 6ca386ce0e6..3ac95ab5bfc 100644
--- a/ruby/ql/lib/codeql/ruby/controlflow/BasicBlocks.qll
+++ b/ruby/ql/lib/codeql/ruby/controlflow/BasicBlocks.qll
@@ -168,6 +168,29 @@ final class BasicBlock extends BasicBlocksImpl::BasicBlock {
*/
BasicBlock getImmediateDominator() { result = super.getImmediateDominator() }
+ /**
+ * Holds if the edge with successor type `s` out of this basic block is a
+ * dominating edge for `dominated`.
+ *
+ * That is, all paths reaching `dominated` from the entry point basic
+ * block must go through the `s` edge out of this basic block.
+ *
+ * Edge dominance is similar to node dominance except it concerns edges
+ * instead of nodes: A basic block is dominated by a _basic block_ `bb` if it
+ * can only be reached through `bb` and dominated by an _edge_ `s` if it can
+ * only be reached through `s`.
+ *
+ * Note that where all basic blocks (except the entry basic block) are
+ * strictly dominated by at least one basic block, a basic block may not be
+ * dominated by any edge. If an edge dominates a basic block `bb`, then
+ * both endpoints of the edge dominates `bb`. The converse is not the case,
+ * as there may be multiple paths between the endpoints with none of them
+ * dominating.
+ */
+ predicate edgeDominates(BasicBlock dominated, SuccessorType s) {
+ super.edgeDominates(dominated, s)
+ }
+
/**
* Holds if this basic block strictly post-dominates basic block `bb`.
*
@@ -248,21 +271,26 @@ final class JoinBlockPredecessor extends BasicBlock, BasicBlocksImpl::JoinPredec
*/
final class ConditionBlock extends BasicBlock, BasicBlocksImpl::ConditionBasicBlock {
/**
+ * DEPRECATED: Use `edgeDominates` instead.
+ *
* Holds if basic block `succ` is immediately controlled by this basic
* block with conditional value `s`. That is, `succ` is an immediate
* successor of this block, and `succ` can only be reached from
* the callable entry point by going via the `s` edge out of this basic block.
*/
- predicate immediatelyControls(BasicBlock succ, ConditionalSuccessor s) {
- super.immediatelyControls(succ, s)
+ deprecated predicate immediatelyControls(BasicBlock succ, ConditionalSuccessor s) {
+ this.getASuccessor(s) = succ and
+ BasicBlocksImpl::dominatingEdge(this, succ)
}
/**
+ * DEPRECATED: Use `edgeDominates` instead.
+ *
* Holds if basic block `controlled` is controlled by this basic block with
* conditional value `s`. That is, `controlled` can only be reached from the
* callable entry point by going via the `s` edge out of this basic block.
*/
- predicate controls(BasicBlock controlled, ConditionalSuccessor s) {
- super.controls(controlled, s)
+ deprecated predicate controls(BasicBlock controlled, ConditionalSuccessor s) {
+ super.edgeDominates(controlled, s)
}
}
diff --git a/ruby/ql/lib/codeql/ruby/controlflow/internal/Guards.qll b/ruby/ql/lib/codeql/ruby/controlflow/internal/Guards.qll
index c240595161c..1161a061581 100644
--- a/ruby/ql/lib/codeql/ruby/controlflow/internal/Guards.qll
+++ b/ruby/ql/lib/codeql/ruby/controlflow/internal/Guards.qll
@@ -6,6 +6,6 @@ predicate guardControlsBlock(CfgNodes::AstCfgNode guard, BasicBlock bb, boolean
exists(ConditionBlock conditionBlock, SuccessorTypes::ConditionalSuccessor s |
guard = conditionBlock.getLastNode() and
s.getValue() = branch and
- conditionBlock.controls(bb, s)
+ conditionBlock.edgeDominates(bb, s)
)
}
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll
index 4c0adc95f25..ca5b661181b 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll
@@ -2387,7 +2387,7 @@ module TypeInference {
|
m = resolveConstantReadAccess(pattern.getExpr()) and
cb.getLastNode() = pattern and
- cb.controls(read.getBasicBlock(),
+ cb.edgeDominates(read.getBasicBlock(),
any(SuccessorTypes::MatchingSuccessor match | match.getValue() = true)) and
caseRead = def.getARead() and
read = def.getARead() and
diff --git a/ruby/ql/lib/codeql/ruby/security/ConditionalBypassCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/ConditionalBypassCustomizations.qll
index 45fa5794250..a64cd1039c1 100644
--- a/ruby/ql/lib/codeql/ruby/security/ConditionalBypassCustomizations.qll
+++ b/ruby/ql/lib/codeql/ruby/security/ConditionalBypassCustomizations.qll
@@ -47,7 +47,7 @@ module ConditionalBypass {
SensitiveActionGuardConditional() {
exists(ConditionBlock cb, BasicBlock controlled |
- cb.controls(controlled, _) and
+ cb.edgeDominates(controlled, _) and
controlled.getANode() = action.asExpr() and
cb.getLastNode() = this.asExpr()
)
diff --git a/ruby/ql/test/library-tests/controlflow/graph/BasicBlocks.ql b/ruby/ql/test/library-tests/controlflow/graph/BasicBlocks.ql
index fb265997ab3..e7879fa600a 100644
--- a/ruby/ql/test/library-tests/controlflow/graph/BasicBlocks.ql
+++ b/ruby/ql/test/library-tests/controlflow/graph/BasicBlocks.ql
@@ -10,8 +10,8 @@ query predicate immediateDominator(BasicBlock bb1, BasicBlock bb2) {
bb1.getImmediateDominator() = bb2
}
-query predicate controls(ConditionBlock bb1, BasicBlock bb2, SuccessorType t) {
- bb1.controls(bb2, t)
+query predicate controls(ConditionBlock bb1, BasicBlock bb2, SuccessorTypes::ConditionalSuccessor t) {
+ bb1.edgeDominates(bb2, t)
}
query predicate successor(ConditionBlock bb1, BasicBlock bb2, SuccessorType t) {
diff --git a/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.ql b/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.ql
index ee9b9df140c..1d0d6388e4f 100644
--- a/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.ql
+++ b/ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.ql
@@ -14,7 +14,7 @@ query predicate newStyleBarrierGuards(DataFlow::Node n) {
query predicate controls(CfgNode condition, BasicBlock bb, SuccessorTypes::ConditionalSuccessor s) {
exists(ConditionBlock cb |
- cb.controls(bb, s) and
+ cb.edgeDominates(bb, s) and
condition = cb.getLastNode()
)
}
diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/SsaImpl.qll b/rust/ql/lib/codeql/rust/dataflow/internal/SsaImpl.qll
index 389539d9d8e..3dfb0f951a4 100644
--- a/rust/ql/lib/codeql/rust/dataflow/internal/SsaImpl.qll
+++ b/rust/ql/lib/codeql/rust/dataflow/internal/SsaImpl.qll
@@ -491,7 +491,7 @@ private module DataFlowIntegrationInput implements Impl::DataFlowIntegrationInpu
exists(ConditionBasicBlock conditionBlock, ConditionalSuccessor s |
guard = conditionBlock.getLastNode() and
s.getValue() = branch and
- conditionBlock.controls(bb, s)
+ conditionBlock.edgeDominates(bb, s)
)
}
diff --git a/rust/ql/test/library-tests/controlflow/BasicBlocks.ql b/rust/ql/test/library-tests/controlflow/BasicBlocks.ql
index 28083ef1478..2d072fa5b7c 100644
--- a/rust/ql/test/library-tests/controlflow/BasicBlocks.ql
+++ b/rust/ql/test/library-tests/controlflow/BasicBlocks.ql
@@ -11,7 +11,7 @@ query predicate immediateDominator(BasicBlock bb1, BasicBlock bb2) {
}
query predicate controls(ConditionBasicBlock bb1, BasicBlock bb2, SuccessorType t) {
- bb1.controls(bb2, t)
+ bb1.edgeDominates(bb2, t)
}
query predicate successor(ConditionBasicBlock bb1, BasicBlock bb2, SuccessorType t) {
diff --git a/shared/controlflow/codeql/controlflow/BasicBlock.qll b/shared/controlflow/codeql/controlflow/BasicBlock.qll
index 57f6a310d18..1deb7cb4292 100644
--- a/shared/controlflow/codeql/controlflow/BasicBlock.qll
+++ b/shared/controlflow/codeql/controlflow/BasicBlock.qll
@@ -169,71 +169,38 @@ module Make Input> {
BasicBlock getImmediateDominator() { bbIDominates(result, this) }
/**
- * Holds if basic block `succ` is immediately controlled by this basic
- * block with successor type `s`.
+ * Holds if the edge with successor type `s` out of this basic block is a
+ * dominating edge for `dominated`.
*
- * That is, `succ` is an immediate successor of this block, and `succ` can
- * only be reached from the entry block by going via the `s` edge out of
- * this basic block.
- */
- pragma[nomagic]
- predicate immediatelyControls(BasicBlock succ, SuccessorType s) {
- succ = this.getASuccessor(s) and
- bbIDominates(this, succ) and
- // The above is not sufficient to ensure that `succ` can only be reached
- // through `s`. To see why, consider this example corresponding to an
- // `if` statement without an `else` block and whe `A` is the basic block
- // following the `if` statement:
- // ```
- // ... --> cond --[true]--> ... --> A
- // \ /
- // ----[false]-----------
- // ```
- // Here `A` is a direct successor of `cond` along the `false` edge and it
- // is immediately dominated by `cond`, but `A` is not controlled by the
- // `false` edge since it is also possible to reach `A` via the `true`
- // edge.
- //
- // Note that the first and third conjunct implies the second. But
- // explicitly including the second conjunct leads to a better join order.
- forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != this |
- succ.dominates(pred)
- )
- }
-
- /**
- * Holds if basic block `controlled` is controlled by this basic block with
- * successor type `s`.
- *
- * That is, all paths reaching `controlled` from the entry point basic
+ * That is, all paths reaching `dominated` from the entry point basic
* block must go through the `s` edge out of this basic block.
*
- * Control is similar to dominance except it concerns edges instead of
- * nodes: A basic block is _dominated_ by a _basic block_ `bb` if it can
- * only be reached through `bb` and _controlled_ by an _edge_ `s` if it can
- * only be reached through `s`.
+ * Edge dominance is similar to node dominance except it concerns edges
+ * instead of nodes: A basic block is dominated by a _basic block_ `bb` if
+ * it can only be reached through `bb` and dominated by an _edge_ `s` if it
+ * can only be reached through `s`.
*
* Note that where all basic blocks (except the entry basic block) are
* strictly dominated by at least one basic block, a basic block may not be
- * controlled by any edge. If an edge controls a basic block `bb`, then
+ * dominated by any edge. If an edge dominates a basic block `bb`, then
* both endpoints of the edge dominates `bb`. The converse is not the case,
* as there may be multiple paths between the endpoints with none of them
* dominating.
*/
- predicate controls(BasicBlock controlled, SuccessorType s) {
- // For this block to control the block `controlled` with `s` the following must be true:
- // 1/ Execution must have passed through the test i.e. `this` must strictly dominate `controlled`.
+ predicate edgeDominates(BasicBlock dominated, SuccessorType s) {
+ // For this block to control the block `dominated` with `s` the following must be true:
+ // 1/ Execution must have passed through the test i.e. `this` must strictly dominate `dominated`.
// 2/ Execution must have passed through the `s` edge leaving `this`.
//
- // Although "passed through the `s` edge" implies that `this.getASuccessor(s)` dominates `controlled`,
+ // Although "passed through the `s` edge" implies that `this.getASuccessor(s)` dominates `dominated`,
// the reverse is not true, as flow may have passed through another edge to get to `this.getASuccessor(s)`
- // so we need to assert that `this.getASuccessor(s)` dominates `controlled` *and* that
+ // so we need to assert that `this.getASuccessor(s)` dominates `dominated` *and* that
// all predecessors of `this.getASuccessor(s)` are either `this` or dominated by `this.getASuccessor(s)`.
//
// For example, in the following C# snippet:
// ```csharp
// if (x)
- // controlled;
+ // dominated;
// false_successor;
// uncontrolled;
// ```
@@ -241,18 +208,20 @@ module Make Input> {
// or dominated by itself. Whereas in the following code:
// ```csharp
// if (x)
- // while (controlled)
+ // while (dominated)
// also_controlled;
// false_successor;
// uncontrolled;
// ```
- // the block `while controlled` is controlled because all of its predecessors are `this` (`if (x)`)
+ // the block `while dominated` is dominated because all of its predecessors are `this` (`if (x)`)
// or (in the case of `also_controlled`) dominated by itself.
//
// The additional constraint on the predecessors of the test successor implies
- // that `this` strictly dominates `controlled` so that isn't necessary to check
+ // that `this` strictly dominates `dominated` so that isn't necessary to check
// directly.
- exists(BasicBlock succ | this.immediatelyControls(succ, s) | succ.dominates(controlled))
+ exists(BasicBlock succ |
+ succ = this.getASuccessor(s) and dominatingEdge(this, succ) and succ.dominates(dominated)
+ )
}
/**
@@ -282,6 +251,38 @@ module Make Input> {
string toString() { result = this.getFirstNode().toString() }
}
+ /**
+ * Holds if `bb1` has `bb2` as a direct successor and the edge between `bb1`
+ * and `bb2` is a dominating edge.
+ *
+ * An edge `(bb1, bb2)` is dominating if there exists a basic block that can
+ * only be reached from the entry block by going through `(bb1, bb2)`. This
+ * implies that `(bb1, bb2)` dominates its endpoint `bb2`. I.e., `bb2` can
+ * only be reached from the entry block by going via `(bb1, bb2)`.
+ */
+ pragma[nomagic]
+ predicate dominatingEdge(BasicBlock bb1, BasicBlock bb2) {
+ bb1.getASuccessor(_) = bb2 and
+ bbIDominates(bb1, bb2) and
+ // The above is not sufficient to ensure that `bb1` can only be reached
+ // through `(bb1, bb2)`. To see why, consider this example corresponding to
+ // an `if` statement without an `else` block and whe `A` is the basic block
+ // following the `if` statement:
+ // ```
+ // ... --> cond --[true]--> ... --> A
+ // \ /
+ // ----[false]-----------
+ // ```
+ // Here `A` is a direct successor of `cond` along the `false` edge and it
+ // is immediately dominated by `cond`, but `A` is not controlled by the
+ // `false` edge since it is also possible to reach `A` via the `true`
+ // edge.
+ //
+ // Note that the first and third conjunct implies the second. But
+ // explicitly including the second conjunct leads to a better join order.
+ forall(BasicBlock pred | pred = bb2.getAPredecessor() and pred != bb1 | bb2.dominates(pred))
+ }
+
cached
private module Cached {
/**
diff --git a/shared/controlflow/codeql/controlflow/Cfg.qll b/shared/controlflow/codeql/controlflow/Cfg.qll
index 3b9f00c669a..9b5922598c9 100644
--- a/shared/controlflow/codeql/controlflow/Cfg.qll
+++ b/shared/controlflow/codeql/controlflow/Cfg.qll
@@ -1594,6 +1594,8 @@ module MakeWithSplitting<
final class BasicBlock = BasicBlockImpl::BasicBlock;
+ predicate dominatingEdge = BasicBlockImpl::dominatingEdge/2;
+
/**
* An entry basic block, that is, a basic block whose first node is
* an entry node.
diff --git a/swift/ql/lib/codeql/swift/controlflow/BasicBlocks.qll b/swift/ql/lib/codeql/swift/controlflow/BasicBlocks.qll
index b0094045113..160903fd895 100644
--- a/swift/ql/lib/codeql/swift/controlflow/BasicBlocks.qll
+++ b/swift/ql/lib/codeql/swift/controlflow/BasicBlocks.qll
@@ -44,6 +44,10 @@ final class BasicBlock extends BasicBlocksImpl::BasicBlock {
BasicBlock getImmediateDominator() { result = super.getImmediateDominator() }
+ predicate edgeDominates(BasicBlock dominated, SuccessorType s) {
+ super.edgeDominates(dominated, s)
+ }
+
predicate strictlyPostDominates(BasicBlock bb) { super.strictlyPostDominates(bb) }
predicate postDominates(BasicBlock bb) { super.postDominates(bb) }
@@ -84,21 +88,26 @@ class JoinBlockPredecessor extends BasicBlock, BasicBlocksImpl::JoinPredecessorB
/** A basic block that terminates in a condition, splitting the subsequent control flow. */
final class ConditionBlock extends BasicBlock, BasicBlocksImpl::ConditionBasicBlock {
/**
+ * DEPRECATED: Use `edgeDominates` instead.
+ *
* Holds if basic block `succ` is immediately controlled by this basic
* block with conditional value `s`. That is, `succ` is an immediate
* successor of this block, and `succ` can only be reached from
* the callable entry point by going via the `s` edge out of this basic block.
*/
- predicate immediatelyControls(BasicBlock succ, ConditionalSuccessor s) {
- super.immediatelyControls(succ, s)
+ deprecated predicate immediatelyControls(BasicBlock succ, ConditionalSuccessor s) {
+ this.getASuccessor(s) = succ and
+ BasicBlocksImpl::dominatingEdge(this, succ)
}
/**
+ * DEPRECATED: Use `edgeDominates` instead.
+ *
* Holds if basic block `controlled` is controlled by this basic block with
* conditional value `s`. That is, `controlled` can only be reached from
* the callable entry point by going via the `s` edge out of this basic block.
*/
- predicate controls(BasicBlock controlled, ConditionalSuccessor s) {
- super.controls(controlled, s)
+ deprecated predicate controls(BasicBlock controlled, ConditionalSuccessor s) {
+ super.edgeDominates(controlled, s)
}
}
diff --git a/swift/ql/lib/codeql/swift/security/PathInjectionExtensions.qll b/swift/ql/lib/codeql/swift/security/PathInjectionExtensions.qll
index 541607aaa43..72608f50fe6 100644
--- a/swift/ql/lib/codeql/swift/security/PathInjectionExtensions.qll
+++ b/swift/ql/lib/codeql/swift/security/PathInjectionExtensions.qll
@@ -112,7 +112,7 @@ private class DefaultPathInjectionBarrier extends PathInjectionBarrier {
bb.getANode().getNode().asAstNode().(IfStmt).getACondition() = getImmediateParent*(starts) and
b.getValue() = true
|
- bb.controls(this.getCfgNode().getBasicBlock(), b)
+ bb.edgeDominates(this.getCfgNode().getBasicBlock(), b)
)
)
}
From 7d6abb4e0a87ff25bb9265dc9e18bf7319f1af48 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 6 Feb 2025 11:30:18 +0100
Subject: [PATCH 085/279] JS: Disable diff-informedness for full SSRF
Partial SSRF uses its result in a way that prevents diff-informedness
---
.../dataflow/ServerSideRequestForgeryQuery.qll | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll b/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll
index a9d6c6a99b0..e60afa470ec 100644
--- a/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll
@@ -30,12 +30,10 @@ private module FullServerSideRequestForgeryConfig implements DataFlow::ConfigSig
node instanceof FullUrlControlSanitizer
}
- predicate observeDiffInformedIncrementalMode() { any() }
-
- Location getASelectedSinkLocation(DataFlow::Node sink) {
- result = sink.(Sink).getLocation()
- or
- result = sink.(Sink).getRequest().getLocation()
+ predicate observeDiffInformedIncrementalMode() {
+ // The partial request forgery query depends on `fullyControlledRequest` to reject alerts about
+ // such full-controlled requests, regardless of the associated source.
+ none()
}
}
From d3b9d1d89d8ed2f05bae32d0c47c17fc2a6bac22 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 6 Feb 2025 11:30:32 +0100
Subject: [PATCH 086/279] JS: Partial SSRF does not select the sink location
---
.../python/security/dataflow/ServerSideRequestForgeryQuery.qll | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll b/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll
index e60afa470ec..b466d34b227 100644
--- a/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryQuery.qll
@@ -68,8 +68,7 @@ private module PartialServerSideRequestForgeryConfig implements DataFlow::Config
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
- result = sink.(Sink).getLocation()
- or
+ // Note: this query does not select the sink itself
result = sink.(Sink).getRequest().getLocation()
}
}
From 7af8fa75e6c14beedf0444ef316dc28a874d52df Mon Sep 17 00:00:00 2001
From: yoff
Date: Thu, 6 Feb 2025 15:45:28 +0100
Subject: [PATCH 087/279] Apply suggestions from code review
Co-authored-by: Aditya Sharad <6874315+adityasharad@users.noreply.github.com>
---
ruby/ql/src/queries/performance/CouldBeHoisted.qhelp | 9 +++++----
ruby/ql/src/queries/performance/CouldBeHoisted.ql | 2 +-
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/ruby/ql/src/queries/performance/CouldBeHoisted.qhelp b/ruby/ql/src/queries/performance/CouldBeHoisted.qhelp
index b476a2c7a00..a473fc9ce1f 100644
--- a/ruby/ql/src/queries/performance/CouldBeHoisted.qhelp
+++ b/ruby/ql/src/queries/performance/CouldBeHoisted.qhelp
@@ -5,18 +5,19 @@
-When a Rails ActiveRecord query is executed in a loop, it is potentially an n+1 problem.
-This query identifies situations where an ActiveRecord query execution could be pulled out of a loop.
+When a database query operation, for example a call to a query method in the Rails `ActiveRecord::Relation` class, is executed in a loop, this can lead to a performance issue known as an "n+1 query problem".
+The database query will be executed in each iteration of the loop.
+Performance can usually be improved by performing a single database query outside of a loop, which retrieves all the required objects in a single operation.
-
If possible, pull the query out of the loop, thus replacing the many calls with a single one.
+
If possible, pull the database query out of the loop and rewrite it to retrieve all the required objects. This replaces multiple database operations with a single one.
The following (suboptimal) example code queries the User object in each iteration of the loop:
-
To improve the performance, we instead query the User object once outside the loop, gathereing all necessary information:
+
To improve the performance, we instead query the User object once outside the loop, gathering all necessary information in a single query:
\ No newline at end of file
diff --git a/ruby/ql/src/queries/performance/CouldBeHoisted.ql b/ruby/ql/src/queries/performance/CouldBeHoisted.ql
index 4f5653a8e45..92ea9eac302 100644
--- a/ruby/ql/src/queries/performance/CouldBeHoisted.ql
+++ b/ruby/ql/src/queries/performance/CouldBeHoisted.ql
@@ -87,4 +87,4 @@ where
// Only report calls that are likely to be expensive
call instanceof ActiveRecordModelFinderCall and
not call.getMethodName() in ["new", "create"]
-select call, "This call happens inside $@, and could be hoisted.", loop, "this loop"
+select call, "This call to a database query operation happens inside $@, and could be hoisted to a single call outside the loop.", loop, "this loop"
From 8aa195d8383d33e895bf93427f437214cf38da6e Mon Sep 17 00:00:00 2001
From: yoff
Date: Thu, 6 Feb 2025 16:59:08 +0100
Subject: [PATCH 088/279] ruby: remove comment (we can create issues)
---
ruby/ql/src/queries/performance/CouldBeHoisted.ql | 13 +++----------
1 file changed, 3 insertions(+), 10 deletions(-)
diff --git a/ruby/ql/src/queries/performance/CouldBeHoisted.ql b/ruby/ql/src/queries/performance/CouldBeHoisted.ql
index 92ea9eac302..137faa3c659 100644
--- a/ruby/ql/src/queries/performance/CouldBeHoisted.ql
+++ b/ruby/ql/src/queries/performance/CouldBeHoisted.ql
@@ -8,15 +8,6 @@
* @tags performance
*/
-// Possible Improvements;
-// - Consider also Associations.
-// Associations are lazy-loading by default, so something like
-// in a loop over `article` do
-// `article.book`
-// if you have 1000 articles it will do a 1000 calls to `book`.
-// If you already did `article includes book`, there should be no problem.
-// - Consider instances of ActiveRecordInstanceMethodCall, for instance
-// calls to `pluck`.
import ruby
private import codeql.ruby.AST
import codeql.ruby.ast.internal.Constant
@@ -87,4 +78,6 @@ where
// Only report calls that are likely to be expensive
call instanceof ActiveRecordModelFinderCall and
not call.getMethodName() in ["new", "create"]
-select call, "This call to a database query operation happens inside $@, and could be hoisted to a single call outside the loop.", loop, "this loop"
+select call,
+ "This call to a database query operation happens inside $@, and could be hoisted to a single call outside the loop.",
+ loop, "this loop"
From d9d0d3c18b372e158bb8cdfc91b65933889d9b53 Mon Sep 17 00:00:00 2001
From: yoff
Date: Thu, 6 Feb 2025 16:59:23 +0100
Subject: [PATCH 089/279] ruby: add code block
---
ruby/ql/src/queries/performance/CouldBeHoisted.qhelp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ruby/ql/src/queries/performance/CouldBeHoisted.qhelp b/ruby/ql/src/queries/performance/CouldBeHoisted.qhelp
index a473fc9ce1f..493597ee0c9 100644
--- a/ruby/ql/src/queries/performance/CouldBeHoisted.qhelp
+++ b/ruby/ql/src/queries/performance/CouldBeHoisted.qhelp
@@ -15,7 +15,7 @@ Performance can usually be improved by performing a single database query outsid
-
The following (suboptimal) example code queries the User object in each iteration of the loop:
+
The following (suboptimal) example code queries the User object in each iteration of the loop:
To improve the performance, we instead query the User object once outside the loop, gathering all necessary information in a single query:
From 51a2d8c72f76c6a22f5e5ab06042326cc1f1fc93 Mon Sep 17 00:00:00 2001
From: yoff
Date: Thu, 6 Feb 2025 17:07:12 +0100
Subject: [PATCH 090/279] ruby: rename query
---
.../{CouldBeHoisted.qhelp => DatabaseQueryInLoop.qhelp} | 0
.../{CouldBeHoisted.ql => DatabaseQueryInLoop.ql} | 6 +++---
2 files changed, 3 insertions(+), 3 deletions(-)
rename ruby/ql/src/queries/performance/{CouldBeHoisted.qhelp => DatabaseQueryInLoop.qhelp} (100%)
rename ruby/ql/src/queries/performance/{CouldBeHoisted.ql => DatabaseQueryInLoop.ql} (93%)
diff --git a/ruby/ql/src/queries/performance/CouldBeHoisted.qhelp b/ruby/ql/src/queries/performance/DatabaseQueryInLoop.qhelp
similarity index 100%
rename from ruby/ql/src/queries/performance/CouldBeHoisted.qhelp
rename to ruby/ql/src/queries/performance/DatabaseQueryInLoop.qhelp
diff --git a/ruby/ql/src/queries/performance/CouldBeHoisted.ql b/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql
similarity index 93%
rename from ruby/ql/src/queries/performance/CouldBeHoisted.ql
rename to ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql
index 137faa3c659..e34ff491656 100644
--- a/ruby/ql/src/queries/performance/CouldBeHoisted.ql
+++ b/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql
@@ -1,10 +1,10 @@
/**
- * @name Could be hoisted
- * @description Hoist Rails `ActiveRecord::Relation` query calls out of loops.
+ * @name Database query in a loop
+ * @description Database queries in a loop can lead to an unnecessary amount of database calls and poor performance.
* @kind problem
* @problem.severity info
* @precision high
- * @id rb/could-be-hoisted
+ * @id rb/database-query-in-loop
* @tags performance
*/
From 74155a0214657e265a6fa90a545f211c180b2062 Mon Sep 17 00:00:00 2001
From: yoff
Date: Thu, 6 Feb 2025 18:09:38 +0100
Subject: [PATCH 091/279] ruby: start adding comments I apuse here, because the
code may be simplified
---
ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql b/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql
index e34ff491656..8ca2f860098 100644
--- a/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql
+++ b/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql
@@ -15,18 +15,20 @@ import codeql.ruby.Concepts
import codeql.ruby.frameworks.ActiveRecord
private import codeql.ruby.TaintTracking
-string loopMethodName() {
+/** Gets the name of a built-in method that involves a loop operation. */
+string getALoopMethodName() {
result in [
"each", "reverse_each", "map", "map!", "foreach", "flat_map", "in_batches", "one?", "all?",
"collect", "collect!", "select", "select!", "reject", "reject!"
]
}
+/** A call to a loop operation. */
class LoopingCall extends DataFlow::CallNode {
DataFlow::CallableNode loopBlock;
LoopingCall() {
- this.getMethodName() = loopMethodName() and loopBlock = this.getBlock().asCallable()
+ this.getMethodName() = getALoopMethodName() and loopBlock = this.getBlock().asCallable()
}
DataFlow::CallableNode getLoopBlock() { result = loopBlock }
From d7ffc3fc772770016150a6bf8dd2e0ee82e2dcac Mon Sep 17 00:00:00 2001
From: yoff
Date: Thu, 6 Feb 2025 18:10:06 +0100
Subject: [PATCH 092/279] Ruby: remove test code filtering
---
ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql | 2 --
1 file changed, 2 deletions(-)
diff --git a/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql b/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql
index 8ca2f860098..6d74443e12a 100644
--- a/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql
+++ b/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql
@@ -71,8 +71,6 @@ from LoopingCall loop, DataFlow::CallNode call
where
// Disregard loops over constants
not isArrayConstant(loop.getReceiver().asExpr(), _) and
- // Disregard tests
- not call.getLocation().getFile().getAbsolutePath().matches("%test%") and
// Disregard cases where the looping is influenced by the query result
not usedInLoopControlGuard(call, _) and
// Only report the inner most loop
From 3b87fb18a678aafc7b1228d71e7c1e91aec5eb8c Mon Sep 17 00:00:00 2001
From: Remco Vermeulen
Date: Thu, 6 Feb 2025 14:10:27 -0800
Subject: [PATCH 093/279] Add CCR suite to query list
---
misc/scripts/generate-code-scanning-query-list.py | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/misc/scripts/generate-code-scanning-query-list.py b/misc/scripts/generate-code-scanning-query-list.py
index 6fc83a00cda..7cfad6ae200 100755
--- a/misc/scripts/generate-code-scanning-query-list.py
+++ b/misc/scripts/generate-code-scanning-query-list.py
@@ -31,7 +31,7 @@ assert hasattr(arguments, "ignore_missing_query_packs")
# Define which languages and query packs to consider
languages = [ "actions", "cpp", "csharp", "go", "java", "javascript", "python", "ruby", "swift" ]
-packs = [ "code-scanning", "security-and-quality", "security-extended", "security-experimental" ]
+packs = [ "code-scanning", "security-and-quality", "security-extended", "security-experimental", "ccr"]
class CodeQL:
def __init__(self):
@@ -169,7 +169,7 @@ with CodeQL() as codeql:
for pack in packs:
# Get absolute paths to queries in this pack by using 'codeql resolve queries'
try:
- queries_subp = codeql.command(["resolve","queries","--search-path", codeql_search_path, "%s-%s.qls" % (lang, pack)])
+ queries_subp = codeql.command(["resolve","queries","--search-path", codeql_search_path, "%s-%s.qls" % (lang, pack)]).strip()
except Exception as e:
# Resolving queries might go wrong if the github/codeql repository is not
# on the search path.
@@ -183,8 +183,13 @@ with CodeQL() as codeql:
else:
sys.exit("You can use '--ignore-missing-query-packs' to ignore this error")
+ # Exception for the CCR suites, which might be empty, but must be resolvable.
+ if pack == 'ccr' and queries_subp == '':
+ print(f'Warning: skipping empty suite ccr', file=sys.stderr)
+ continue
+
# Investigate metadata for every query by using 'codeql resolve metadata'
- for queryfile in queries_subp.strip().split("\n"):
+ for queryfile in queries_subp.split("\n"):
query_metadata_json = codeql.command(["resolve","metadata",queryfile]).strip()
# Turn an absolute path to a query file into an nwo-prefixed path (e.g. github/codeql/java/ql/src/....)
From 75b5493c9bb458224afc20829c83503d0fc68327 Mon Sep 17 00:00:00 2001
From: Paolo Tranquilli
Date: Fri, 7 Feb 2025 10:43:10 +0100
Subject: [PATCH 094/279] Bazel: update `rules_rust`
---
MODULE.bazel | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/MODULE.bazel b/MODULE.bazel
index 8de37e006bb..cd1652c0c12 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -14,7 +14,7 @@ local_path_override(
# see https://registry.bazel.build/ for a list of available packages
-bazel_dep(name = "platforms", version = "0.0.10")
+bazel_dep(name = "platforms", version = "0.0.11")
bazel_dep(name = "rules_go", version = "0.50.1")
bazel_dep(name = "rules_pkg", version = "1.0.1")
bazel_dep(name = "rules_nodejs", version = "6.2.0-codeql.1")
@@ -28,7 +28,7 @@ bazel_dep(name = "rules_kotlin", version = "2.0.0-codeql.1")
bazel_dep(name = "gazelle", version = "0.40.0")
bazel_dep(name = "rules_dotnet", version = "0.17.4")
bazel_dep(name = "googletest", version = "1.14.0.bcr.1")
-bazel_dep(name = "rules_rust", version = "0.52.2")
+bazel_dep(name = "rules_rust", version = "0.57.1")
bazel_dep(name = "zstd", version = "1.5.5.bcr.1")
bazel_dep(name = "buildifier_prebuilt", version = "6.4.0", dev_dependency = True)
From f6301b8ea8d63f6fd31fa7e4bd566ba1db5e045b Mon Sep 17 00:00:00 2001
From: Paolo Tranquilli
Date: Fri, 7 Feb 2025 10:59:11 +0100
Subject: [PATCH 095/279] Rust: remove unneeded and now broken bazel workaround
---
rust/ast-generator/src/main.rs | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/rust/ast-generator/src/main.rs b/rust/ast-generator/src/main.rs
index 1a4557bd5e9..c778389cfdb 100644
--- a/rust/ast-generator/src/main.rs
+++ b/rust/ast-generator/src/main.rs
@@ -150,10 +150,7 @@ fn write_schema(
.map(|node| node_src_to_schema_class(node, &super_types)),
);
// the concat dance is currently required by bazel
- let template = mustache::compile_str(include_str!(concat!(
- env!("CARGO_MANIFEST_DIR"),
- "/src/templates/schema.mustache"
- )))?;
+ let template = mustache::compile_str(include_str!("templates/schema.mustache"))?;
let res = template.render_to_string(&schema)?;
Ok(fix_blank_lines(&res))
}
@@ -558,10 +555,7 @@ fn write_extractor(grammar: &AstSrc) -> mustache::Result {
nodes: grammar.nodes.iter().map(node_to_extractor_info).collect(),
};
// the concat dance is currently required by bazel
- let template = mustache::compile_str(include_str!(concat!(
- env!("CARGO_MANIFEST_DIR"),
- "/src/templates/extractor.mustache"
- )))?;
+ let template = mustache::compile_str(include_str!("templates/extractor.mustache"))?;
let res = template.render_to_string(&extractor_info)?;
Ok(fix_blank_lines(&res))
}
From a8fbb37569b1311d0363bd52657660837cfa2d41 Mon Sep 17 00:00:00 2001
From: Arthur Baars
Date: Fri, 7 Feb 2025 12:28:17 +0100
Subject: [PATCH 096/279] TreeSitter extractors: log fewer lines
Printing a line for every extracted file is too verbose and for large projects makes it impossible to view the log in the Actions UI.
---
shared/tree-sitter-extractor/src/extractor/mod.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/shared/tree-sitter-extractor/src/extractor/mod.rs b/shared/tree-sitter-extractor/src/extractor/mod.rs
index 00f940ff89c..f9360be1445 100644
--- a/shared/tree-sitter-extractor/src/extractor/mod.rs
+++ b/shared/tree-sitter-extractor/src/extractor/mod.rs
@@ -192,7 +192,7 @@ pub fn extract(
let _enter = span.enter();
- tracing::info!("extracting: {}", path_str);
+ tracing::debug!("extracting: {}", path_str);
let mut parser = Parser::new();
parser.set_language(language).unwrap();
From 11685a820f0584487a12ceaccb39ecf2c1186ffc Mon Sep 17 00:00:00 2001
From: Simon Friis Vindum
Date: Fri, 7 Feb 2025 13:47:58 +0100
Subject: [PATCH 097/279] Rust: Add flow tests involving references
---
.../library-tests/dataflow/global/main.rs | 25 +++
.../dataflow/global/viableCallable.expected | 26 ++-
.../dataflow/pointers/inline-flow.expected | 164 +++++++++---------
.../library-tests/dataflow/pointers/main.rs | 2 +
.../utils-tests/modelgenerator/summaries.rs | 11 ++
5 files changed, 138 insertions(+), 90 deletions(-)
diff --git a/rust/ql/test/library-tests/dataflow/global/main.rs b/rust/ql/test/library-tests/dataflow/global/main.rs
index 60347623fd2..2b6f60e831a 100644
--- a/rust/ql/test/library-tests/dataflow/global/main.rs
+++ b/rust/ql/test/library-tests/dataflow/global/main.rs
@@ -134,6 +134,29 @@ pub fn test_operator_overloading() {
sink(d.value); // $ MISSING: hasValueFlow=7
}
+// Flow out of mutable parameters.
+
+fn set_int(n: &mut i64, c: i64) {
+ *n = c;
+}
+
+fn mutates_argument_1() {
+ // Passing an already borrowed value to a function and then reading from the same borrow.
+ let mut n = 0;
+ let m = &mut n;
+ sink(*m);
+ set_int(m, source(37));
+ sink(*m); // $ MISSING: hasValueFlow=37
+}
+
+fn mutates_argument_2() {
+ // Borrowing at the call and then reading from the unborrowed variable.
+ let mut n = 0;
+ sink(n);
+ set_int(&mut n, source(88));
+ sink(n); // $ MISSING: hasValueFlow=88
+}
+
fn main() {
data_out_of_call();
data_in_to_call();
@@ -145,4 +168,6 @@ fn main() {
data_through_method();
test_operator_overloading();
+ mutates_argument_1();
+ mutates_argument_2();
}
diff --git a/rust/ql/test/library-tests/dataflow/global/viableCallable.expected b/rust/ql/test/library-tests/dataflow/global/viableCallable.expected
index 4ae1c8689b6..fcd1a29f974 100644
--- a/rust/ql/test/library-tests/dataflow/global/viableCallable.expected
+++ b/rust/ql/test/library-tests/dataflow/global/viableCallable.expected
@@ -29,11 +29,21 @@
| main.rs:131:28:131:36 | source(...) | main.rs:1:1:3:1 | fn source |
| main.rs:133:13:133:20 | a.add(...) | main.rs:114:5:117:5 | fn add |
| main.rs:134:5:134:17 | sink(...) | main.rs:5:1:7:1 | fn sink |
-| main.rs:138:5:138:22 | data_out_of_call(...) | main.rs:16:1:19:1 | fn data_out_of_call |
-| main.rs:139:5:139:21 | data_in_to_call(...) | main.rs:25:1:28:1 | fn data_in_to_call |
-| main.rs:140:5:140:23 | data_through_call(...) | main.rs:34:1:38:1 | fn data_through_call |
-| main.rs:141:5:141:34 | data_through_nested_function(...) | main.rs:48:1:57:1 | fn data_through_nested_function |
-| main.rs:143:5:143:24 | data_out_of_method(...) | main.rs:86:1:90:1 | fn data_out_of_method |
-| main.rs:144:5:144:28 | data_in_to_method_call(...) | main.rs:92:1:96:1 | fn data_in_to_method_call |
-| main.rs:145:5:145:25 | data_through_method(...) | main.rs:98:1:103:1 | fn data_through_method |
-| main.rs:147:5:147:31 | test_operator_overloading(...) | main.rs:120:1:135:1 | fn test_operator_overloading |
+| main.rs:147:5:147:12 | sink(...) | main.rs:5:1:7:1 | fn sink |
+| main.rs:148:5:148:26 | set_int(...) | main.rs:139:1:141:1 | fn set_int |
+| main.rs:148:16:148:25 | source(...) | main.rs:1:1:3:1 | fn source |
+| main.rs:149:5:149:12 | sink(...) | main.rs:5:1:7:1 | fn sink |
+| main.rs:155:5:155:11 | sink(...) | main.rs:5:1:7:1 | fn sink |
+| main.rs:156:5:156:31 | set_int(...) | main.rs:139:1:141:1 | fn set_int |
+| main.rs:156:21:156:30 | source(...) | main.rs:1:1:3:1 | fn source |
+| main.rs:157:5:157:11 | sink(...) | main.rs:5:1:7:1 | fn sink |
+| main.rs:161:5:161:22 | data_out_of_call(...) | main.rs:16:1:19:1 | fn data_out_of_call |
+| main.rs:162:5:162:21 | data_in_to_call(...) | main.rs:25:1:28:1 | fn data_in_to_call |
+| main.rs:163:5:163:23 | data_through_call(...) | main.rs:34:1:38:1 | fn data_through_call |
+| main.rs:164:5:164:34 | data_through_nested_function(...) | main.rs:48:1:57:1 | fn data_through_nested_function |
+| main.rs:166:5:166:24 | data_out_of_method(...) | main.rs:86:1:90:1 | fn data_out_of_method |
+| main.rs:167:5:167:28 | data_in_to_method_call(...) | main.rs:92:1:96:1 | fn data_in_to_method_call |
+| main.rs:168:5:168:25 | data_through_method(...) | main.rs:98:1:103:1 | fn data_through_method |
+| main.rs:170:5:170:31 | test_operator_overloading(...) | main.rs:120:1:135:1 | fn test_operator_overloading |
+| main.rs:171:5:171:24 | mutates_argument_1(...) | main.rs:143:1:150:1 | fn mutates_argument_1 |
+| main.rs:172:5:172:24 | mutates_argument_2(...) | main.rs:152:1:158:1 | fn mutates_argument_2 |
diff --git a/rust/ql/test/library-tests/dataflow/pointers/inline-flow.expected b/rust/ql/test/library-tests/dataflow/pointers/inline-flow.expected
index 181bf9ed5d1..de53d1855af 100644
--- a/rust/ql/test/library-tests/dataflow/pointers/inline-flow.expected
+++ b/rust/ql/test/library-tests/dataflow/pointers/inline-flow.expected
@@ -8,42 +8,42 @@ edges
| main.rs:15:9:15:9 | c | main.rs:16:10:16:10 | c | provenance | |
| main.rs:15:13:15:14 | * ... | main.rs:15:9:15:9 | c | provenance | |
| main.rs:15:14:15:14 | b [&ref] | main.rs:15:13:15:14 | * ... | provenance | |
-| main.rs:35:25:35:26 | &... [&ref] | main.rs:35:26:35:26 | n | provenance | |
-| main.rs:35:25:35:32 | ...: ... [&ref] | main.rs:35:25:35:26 | &... [&ref] | provenance | |
-| main.rs:35:26:35:26 | n | main.rs:36:10:36:10 | n | provenance | |
-| main.rs:40:9:40:11 | val | main.rs:41:27:41:29 | val | provenance | |
-| main.rs:40:15:40:24 | source(...) | main.rs:40:9:40:11 | val | provenance | |
-| main.rs:41:26:41:29 | &val [&ref] | main.rs:35:25:35:32 | ...: ... [&ref] | provenance | |
-| main.rs:41:27:41:29 | val | main.rs:41:26:41:29 | &val [&ref] | provenance | |
-| main.rs:49:18:49:21 | SelfParam [MyNumber] | main.rs:50:15:50:18 | self [MyNumber] | provenance | |
-| main.rs:50:15:50:18 | self [MyNumber] | main.rs:51:13:51:38 | ...::MyNumber(...) [MyNumber] | provenance | |
-| main.rs:51:13:51:38 | ...::MyNumber(...) [MyNumber] | main.rs:51:32:51:37 | number | provenance | |
-| main.rs:51:32:51:37 | number | main.rs:49:31:55:5 | { ... } | provenance | |
-| main.rs:57:19:57:23 | SelfParam [&ref, MyNumber] | main.rs:58:15:58:18 | self [&ref, MyNumber] | provenance | |
-| main.rs:58:15:58:18 | self [&ref, MyNumber] | main.rs:59:13:59:39 | &... [&ref, MyNumber] | provenance | |
-| main.rs:59:13:59:39 | &... [&ref, MyNumber] | main.rs:59:14:59:39 | ...::MyNumber(...) [MyNumber] | provenance | |
-| main.rs:59:14:59:39 | ...::MyNumber(...) [MyNumber] | main.rs:59:33:59:38 | number | provenance | |
-| main.rs:59:33:59:38 | number | main.rs:57:33:63:5 | { ... } | provenance | |
-| main.rs:67:9:67:17 | my_number [MyNumber] | main.rs:68:10:68:18 | my_number [MyNumber] | provenance | |
-| main.rs:67:21:67:50 | ...::MyNumber(...) [MyNumber] | main.rs:67:9:67:17 | my_number [MyNumber] | provenance | |
-| main.rs:67:40:67:49 | source(...) | main.rs:67:21:67:50 | ...::MyNumber(...) [MyNumber] | provenance | |
-| main.rs:68:10:68:18 | my_number [MyNumber] | main.rs:49:18:49:21 | SelfParam [MyNumber] | provenance | |
-| main.rs:68:10:68:18 | my_number [MyNumber] | main.rs:68:10:68:30 | my_number.to_number(...) | provenance | |
-| main.rs:77:9:77:17 | my_number [&ref, MyNumber] | main.rs:78:10:78:18 | my_number [&ref, MyNumber] | provenance | |
-| main.rs:77:21:77:51 | &... [&ref, MyNumber] | main.rs:77:9:77:17 | my_number [&ref, MyNumber] | provenance | |
-| main.rs:77:22:77:51 | ...::MyNumber(...) [MyNumber] | main.rs:77:21:77:51 | &... [&ref, MyNumber] | provenance | |
-| main.rs:77:41:77:50 | source(...) | main.rs:77:22:77:51 | ...::MyNumber(...) [MyNumber] | provenance | |
-| main.rs:78:10:78:18 | my_number [&ref, MyNumber] | main.rs:57:19:57:23 | SelfParam [&ref, MyNumber] | provenance | |
-| main.rs:78:10:78:18 | my_number [&ref, MyNumber] | main.rs:78:10:78:31 | my_number.get_number(...) | provenance | |
-| main.rs:82:9:82:9 | a [&ref, tuple.0] | main.rs:85:19:85:19 | a [&ref, tuple.0] | provenance | |
-| main.rs:82:13:82:28 | &... [&ref, tuple.0] | main.rs:82:9:82:9 | a [&ref, tuple.0] | provenance | |
-| main.rs:82:14:82:28 | TupleExpr [tuple.0] | main.rs:82:13:82:28 | &... [&ref, tuple.0] | provenance | |
-| main.rs:82:15:82:24 | source(...) | main.rs:82:14:82:28 | TupleExpr [tuple.0] | provenance | |
-| main.rs:85:9:85:9 | b | main.rs:88:10:88:10 | b | provenance | |
-| main.rs:85:19:85:19 | a [&ref, tuple.0] | main.rs:86:9:86:15 | &... [&ref, tuple.0] | provenance | |
-| main.rs:86:9:86:15 | &... [&ref, tuple.0] | main.rs:86:10:86:15 | TuplePat [tuple.0] | provenance | |
-| main.rs:86:10:86:15 | TuplePat [tuple.0] | main.rs:86:11:86:11 | n | provenance | |
-| main.rs:86:11:86:11 | n | main.rs:85:9:85:9 | b | provenance | |
+| main.rs:37:25:37:26 | &... [&ref] | main.rs:37:26:37:26 | n | provenance | |
+| main.rs:37:25:37:32 | ...: ... [&ref] | main.rs:37:25:37:26 | &... [&ref] | provenance | |
+| main.rs:37:26:37:26 | n | main.rs:38:10:38:10 | n | provenance | |
+| main.rs:42:9:42:11 | val | main.rs:43:27:43:29 | val | provenance | |
+| main.rs:42:15:42:24 | source(...) | main.rs:42:9:42:11 | val | provenance | |
+| main.rs:43:26:43:29 | &val [&ref] | main.rs:37:25:37:32 | ...: ... [&ref] | provenance | |
+| main.rs:43:27:43:29 | val | main.rs:43:26:43:29 | &val [&ref] | provenance | |
+| main.rs:51:18:51:21 | SelfParam [MyNumber] | main.rs:52:15:52:18 | self [MyNumber] | provenance | |
+| main.rs:52:15:52:18 | self [MyNumber] | main.rs:53:13:53:38 | ...::MyNumber(...) [MyNumber] | provenance | |
+| main.rs:53:13:53:38 | ...::MyNumber(...) [MyNumber] | main.rs:53:32:53:37 | number | provenance | |
+| main.rs:53:32:53:37 | number | main.rs:51:31:57:5 | { ... } | provenance | |
+| main.rs:59:19:59:23 | SelfParam [&ref, MyNumber] | main.rs:60:15:60:18 | self [&ref, MyNumber] | provenance | |
+| main.rs:60:15:60:18 | self [&ref, MyNumber] | main.rs:61:13:61:39 | &... [&ref, MyNumber] | provenance | |
+| main.rs:61:13:61:39 | &... [&ref, MyNumber] | main.rs:61:14:61:39 | ...::MyNumber(...) [MyNumber] | provenance | |
+| main.rs:61:14:61:39 | ...::MyNumber(...) [MyNumber] | main.rs:61:33:61:38 | number | provenance | |
+| main.rs:61:33:61:38 | number | main.rs:59:33:65:5 | { ... } | provenance | |
+| main.rs:69:9:69:17 | my_number [MyNumber] | main.rs:70:10:70:18 | my_number [MyNumber] | provenance | |
+| main.rs:69:21:69:50 | ...::MyNumber(...) [MyNumber] | main.rs:69:9:69:17 | my_number [MyNumber] | provenance | |
+| main.rs:69:40:69:49 | source(...) | main.rs:69:21:69:50 | ...::MyNumber(...) [MyNumber] | provenance | |
+| main.rs:70:10:70:18 | my_number [MyNumber] | main.rs:51:18:51:21 | SelfParam [MyNumber] | provenance | |
+| main.rs:70:10:70:18 | my_number [MyNumber] | main.rs:70:10:70:30 | my_number.to_number(...) | provenance | |
+| main.rs:79:9:79:17 | my_number [&ref, MyNumber] | main.rs:80:10:80:18 | my_number [&ref, MyNumber] | provenance | |
+| main.rs:79:21:79:51 | &... [&ref, MyNumber] | main.rs:79:9:79:17 | my_number [&ref, MyNumber] | provenance | |
+| main.rs:79:22:79:51 | ...::MyNumber(...) [MyNumber] | main.rs:79:21:79:51 | &... [&ref, MyNumber] | provenance | |
+| main.rs:79:41:79:50 | source(...) | main.rs:79:22:79:51 | ...::MyNumber(...) [MyNumber] | provenance | |
+| main.rs:80:10:80:18 | my_number [&ref, MyNumber] | main.rs:59:19:59:23 | SelfParam [&ref, MyNumber] | provenance | |
+| main.rs:80:10:80:18 | my_number [&ref, MyNumber] | main.rs:80:10:80:31 | my_number.get_number(...) | provenance | |
+| main.rs:84:9:84:9 | a [&ref, tuple.0] | main.rs:87:19:87:19 | a [&ref, tuple.0] | provenance | |
+| main.rs:84:13:84:28 | &... [&ref, tuple.0] | main.rs:84:9:84:9 | a [&ref, tuple.0] | provenance | |
+| main.rs:84:14:84:28 | TupleExpr [tuple.0] | main.rs:84:13:84:28 | &... [&ref, tuple.0] | provenance | |
+| main.rs:84:15:84:24 | source(...) | main.rs:84:14:84:28 | TupleExpr [tuple.0] | provenance | |
+| main.rs:87:9:87:9 | b | main.rs:90:10:90:10 | b | provenance | |
+| main.rs:87:19:87:19 | a [&ref, tuple.0] | main.rs:88:9:88:15 | &... [&ref, tuple.0] | provenance | |
+| main.rs:88:9:88:15 | &... [&ref, tuple.0] | main.rs:88:10:88:15 | TuplePat [tuple.0] | provenance | |
+| main.rs:88:10:88:15 | TuplePat [tuple.0] | main.rs:88:11:88:11 | n | provenance | |
+| main.rs:88:11:88:11 | n | main.rs:87:9:87:9 | b | provenance | |
nodes
| main.rs:13:9:13:9 | a | semmle.label | a |
| main.rs:13:13:13:22 | source(...) | semmle.label | source(...) |
@@ -54,53 +54,53 @@ nodes
| main.rs:15:13:15:14 | * ... | semmle.label | * ... |
| main.rs:15:14:15:14 | b [&ref] | semmle.label | b [&ref] |
| main.rs:16:10:16:10 | c | semmle.label | c |
-| main.rs:35:25:35:26 | &... [&ref] | semmle.label | &... [&ref] |
-| main.rs:35:25:35:32 | ...: ... [&ref] | semmle.label | ...: ... [&ref] |
-| main.rs:35:26:35:26 | n | semmle.label | n |
-| main.rs:36:10:36:10 | n | semmle.label | n |
-| main.rs:40:9:40:11 | val | semmle.label | val |
-| main.rs:40:15:40:24 | source(...) | semmle.label | source(...) |
-| main.rs:41:26:41:29 | &val [&ref] | semmle.label | &val [&ref] |
-| main.rs:41:27:41:29 | val | semmle.label | val |
-| main.rs:49:18:49:21 | SelfParam [MyNumber] | semmle.label | SelfParam [MyNumber] |
-| main.rs:49:31:55:5 | { ... } | semmle.label | { ... } |
-| main.rs:50:15:50:18 | self [MyNumber] | semmle.label | self [MyNumber] |
-| main.rs:51:13:51:38 | ...::MyNumber(...) [MyNumber] | semmle.label | ...::MyNumber(...) [MyNumber] |
-| main.rs:51:32:51:37 | number | semmle.label | number |
-| main.rs:57:19:57:23 | SelfParam [&ref, MyNumber] | semmle.label | SelfParam [&ref, MyNumber] |
-| main.rs:57:33:63:5 | { ... } | semmle.label | { ... } |
-| main.rs:58:15:58:18 | self [&ref, MyNumber] | semmle.label | self [&ref, MyNumber] |
-| main.rs:59:13:59:39 | &... [&ref, MyNumber] | semmle.label | &... [&ref, MyNumber] |
-| main.rs:59:14:59:39 | ...::MyNumber(...) [MyNumber] | semmle.label | ...::MyNumber(...) [MyNumber] |
-| main.rs:59:33:59:38 | number | semmle.label | number |
-| main.rs:67:9:67:17 | my_number [MyNumber] | semmle.label | my_number [MyNumber] |
-| main.rs:67:21:67:50 | ...::MyNumber(...) [MyNumber] | semmle.label | ...::MyNumber(...) [MyNumber] |
-| main.rs:67:40:67:49 | source(...) | semmle.label | source(...) |
-| main.rs:68:10:68:18 | my_number [MyNumber] | semmle.label | my_number [MyNumber] |
-| main.rs:68:10:68:30 | my_number.to_number(...) | semmle.label | my_number.to_number(...) |
-| main.rs:77:9:77:17 | my_number [&ref, MyNumber] | semmle.label | my_number [&ref, MyNumber] |
-| main.rs:77:21:77:51 | &... [&ref, MyNumber] | semmle.label | &... [&ref, MyNumber] |
-| main.rs:77:22:77:51 | ...::MyNumber(...) [MyNumber] | semmle.label | ...::MyNumber(...) [MyNumber] |
-| main.rs:77:41:77:50 | source(...) | semmle.label | source(...) |
-| main.rs:78:10:78:18 | my_number [&ref, MyNumber] | semmle.label | my_number [&ref, MyNumber] |
-| main.rs:78:10:78:31 | my_number.get_number(...) | semmle.label | my_number.get_number(...) |
-| main.rs:82:9:82:9 | a [&ref, tuple.0] | semmle.label | a [&ref, tuple.0] |
-| main.rs:82:13:82:28 | &... [&ref, tuple.0] | semmle.label | &... [&ref, tuple.0] |
-| main.rs:82:14:82:28 | TupleExpr [tuple.0] | semmle.label | TupleExpr [tuple.0] |
-| main.rs:82:15:82:24 | source(...) | semmle.label | source(...) |
-| main.rs:85:9:85:9 | b | semmle.label | b |
-| main.rs:85:19:85:19 | a [&ref, tuple.0] | semmle.label | a [&ref, tuple.0] |
-| main.rs:86:9:86:15 | &... [&ref, tuple.0] | semmle.label | &... [&ref, tuple.0] |
-| main.rs:86:10:86:15 | TuplePat [tuple.0] | semmle.label | TuplePat [tuple.0] |
-| main.rs:86:11:86:11 | n | semmle.label | n |
-| main.rs:88:10:88:10 | b | semmle.label | b |
+| main.rs:37:25:37:26 | &... [&ref] | semmle.label | &... [&ref] |
+| main.rs:37:25:37:32 | ...: ... [&ref] | semmle.label | ...: ... [&ref] |
+| main.rs:37:26:37:26 | n | semmle.label | n |
+| main.rs:38:10:38:10 | n | semmle.label | n |
+| main.rs:42:9:42:11 | val | semmle.label | val |
+| main.rs:42:15:42:24 | source(...) | semmle.label | source(...) |
+| main.rs:43:26:43:29 | &val [&ref] | semmle.label | &val [&ref] |
+| main.rs:43:27:43:29 | val | semmle.label | val |
+| main.rs:51:18:51:21 | SelfParam [MyNumber] | semmle.label | SelfParam [MyNumber] |
+| main.rs:51:31:57:5 | { ... } | semmle.label | { ... } |
+| main.rs:52:15:52:18 | self [MyNumber] | semmle.label | self [MyNumber] |
+| main.rs:53:13:53:38 | ...::MyNumber(...) [MyNumber] | semmle.label | ...::MyNumber(...) [MyNumber] |
+| main.rs:53:32:53:37 | number | semmle.label | number |
+| main.rs:59:19:59:23 | SelfParam [&ref, MyNumber] | semmle.label | SelfParam [&ref, MyNumber] |
+| main.rs:59:33:65:5 | { ... } | semmle.label | { ... } |
+| main.rs:60:15:60:18 | self [&ref, MyNumber] | semmle.label | self [&ref, MyNumber] |
+| main.rs:61:13:61:39 | &... [&ref, MyNumber] | semmle.label | &... [&ref, MyNumber] |
+| main.rs:61:14:61:39 | ...::MyNumber(...) [MyNumber] | semmle.label | ...::MyNumber(...) [MyNumber] |
+| main.rs:61:33:61:38 | number | semmle.label | number |
+| main.rs:69:9:69:17 | my_number [MyNumber] | semmle.label | my_number [MyNumber] |
+| main.rs:69:21:69:50 | ...::MyNumber(...) [MyNumber] | semmle.label | ...::MyNumber(...) [MyNumber] |
+| main.rs:69:40:69:49 | source(...) | semmle.label | source(...) |
+| main.rs:70:10:70:18 | my_number [MyNumber] | semmle.label | my_number [MyNumber] |
+| main.rs:70:10:70:30 | my_number.to_number(...) | semmle.label | my_number.to_number(...) |
+| main.rs:79:9:79:17 | my_number [&ref, MyNumber] | semmle.label | my_number [&ref, MyNumber] |
+| main.rs:79:21:79:51 | &... [&ref, MyNumber] | semmle.label | &... [&ref, MyNumber] |
+| main.rs:79:22:79:51 | ...::MyNumber(...) [MyNumber] | semmle.label | ...::MyNumber(...) [MyNumber] |
+| main.rs:79:41:79:50 | source(...) | semmle.label | source(...) |
+| main.rs:80:10:80:18 | my_number [&ref, MyNumber] | semmle.label | my_number [&ref, MyNumber] |
+| main.rs:80:10:80:31 | my_number.get_number(...) | semmle.label | my_number.get_number(...) |
+| main.rs:84:9:84:9 | a [&ref, tuple.0] | semmle.label | a [&ref, tuple.0] |
+| main.rs:84:13:84:28 | &... [&ref, tuple.0] | semmle.label | &... [&ref, tuple.0] |
+| main.rs:84:14:84:28 | TupleExpr [tuple.0] | semmle.label | TupleExpr [tuple.0] |
+| main.rs:84:15:84:24 | source(...) | semmle.label | source(...) |
+| main.rs:87:9:87:9 | b | semmle.label | b |
+| main.rs:87:19:87:19 | a [&ref, tuple.0] | semmle.label | a [&ref, tuple.0] |
+| main.rs:88:9:88:15 | &... [&ref, tuple.0] | semmle.label | &... [&ref, tuple.0] |
+| main.rs:88:10:88:15 | TuplePat [tuple.0] | semmle.label | TuplePat [tuple.0] |
+| main.rs:88:11:88:11 | n | semmle.label | n |
+| main.rs:90:10:90:10 | b | semmle.label | b |
subpaths
-| main.rs:68:10:68:18 | my_number [MyNumber] | main.rs:49:18:49:21 | SelfParam [MyNumber] | main.rs:49:31:55:5 | { ... } | main.rs:68:10:68:30 | my_number.to_number(...) |
-| main.rs:78:10:78:18 | my_number [&ref, MyNumber] | main.rs:57:19:57:23 | SelfParam [&ref, MyNumber] | main.rs:57:33:63:5 | { ... } | main.rs:78:10:78:31 | my_number.get_number(...) |
+| main.rs:70:10:70:18 | my_number [MyNumber] | main.rs:51:18:51:21 | SelfParam [MyNumber] | main.rs:51:31:57:5 | { ... } | main.rs:70:10:70:30 | my_number.to_number(...) |
+| main.rs:80:10:80:18 | my_number [&ref, MyNumber] | main.rs:59:19:59:23 | SelfParam [&ref, MyNumber] | main.rs:59:33:65:5 | { ... } | main.rs:80:10:80:31 | my_number.get_number(...) |
testFailures
#select
| main.rs:16:10:16:10 | c | main.rs:13:13:13:22 | source(...) | main.rs:16:10:16:10 | c | $@ | main.rs:13:13:13:22 | source(...) | source(...) |
-| main.rs:36:10:36:10 | n | main.rs:40:15:40:24 | source(...) | main.rs:36:10:36:10 | n | $@ | main.rs:40:15:40:24 | source(...) | source(...) |
-| main.rs:68:10:68:30 | my_number.to_number(...) | main.rs:67:40:67:49 | source(...) | main.rs:68:10:68:30 | my_number.to_number(...) | $@ | main.rs:67:40:67:49 | source(...) | source(...) |
-| main.rs:78:10:78:31 | my_number.get_number(...) | main.rs:77:41:77:50 | source(...) | main.rs:78:10:78:31 | my_number.get_number(...) | $@ | main.rs:77:41:77:50 | source(...) | source(...) |
-| main.rs:88:10:88:10 | b | main.rs:82:15:82:24 | source(...) | main.rs:88:10:88:10 | b | $@ | main.rs:82:15:82:24 | source(...) | source(...) |
+| main.rs:38:10:38:10 | n | main.rs:42:15:42:24 | source(...) | main.rs:38:10:38:10 | n | $@ | main.rs:42:15:42:24 | source(...) | source(...) |
+| main.rs:70:10:70:30 | my_number.to_number(...) | main.rs:69:40:69:49 | source(...) | main.rs:70:10:70:30 | my_number.to_number(...) | $@ | main.rs:69:40:69:49 | source(...) | source(...) |
+| main.rs:80:10:80:31 | my_number.get_number(...) | main.rs:79:41:79:50 | source(...) | main.rs:80:10:80:31 | my_number.get_number(...) | $@ | main.rs:79:41:79:50 | source(...) | source(...) |
+| main.rs:90:10:90:10 | b | main.rs:84:15:84:24 | source(...) | main.rs:90:10:90:10 | b | $@ | main.rs:84:15:84:24 | source(...) | source(...) |
diff --git a/rust/ql/test/library-tests/dataflow/pointers/main.rs b/rust/ql/test/library-tests/dataflow/pointers/main.rs
index b7cdb44e402..7ff8fefc6c6 100644
--- a/rust/ql/test/library-tests/dataflow/pointers/main.rs
+++ b/rust/ql/test/library-tests/dataflow/pointers/main.rs
@@ -30,6 +30,8 @@ fn write_and_read_through_borrow() {
sink(*b);
*b = source(37);
sink(*b); // $ MISSING: hasValueFlow=37
+ *b = 0;
+ sink(*b); // now cleared
}
fn takes_borrowed_value(&n: &i64) {
diff --git a/rust/ql/test/utils-tests/modelgenerator/summaries.rs b/rust/ql/test/utils-tests/modelgenerator/summaries.rs
index 38b441f1d71..db1158640c2 100644
--- a/rust/ql/test/utils-tests/modelgenerator/summaries.rs
+++ b/rust/ql/test/utils-tests/modelgenerator/summaries.rs
@@ -76,3 +76,14 @@ impl MyStruct {
pub fn apply(n: i64, f: F) -> i64 where F : FnOnce(i64) -> i64 {
f(n)
}
+
+// Flow out of mutated arguments
+
+pub fn set_int(n: &mut i64, c: i64) {
+ *n = c;
+}
+
+// summary=repo::test;crate::summaries::read_int;Argument[0];ReturnValue;value;dfc-generated
+pub fn read_int(n: &mut i64) -> i64 {
+ *n
+}
From 58fb592822c4744979351a7cf87e7a04ee2d674c Mon Sep 17 00:00:00 2001
From: yoff
Date: Fri, 7 Feb 2025 13:50:27 +0100
Subject: [PATCH 098/279] ruby: add tests
---
.../DatabaseQueryInLoop.expected | 3 ++
.../DatabaseQueryInLoop.qlref | 1 +
.../DatabaseQueryInLoop.rb | 53 +++++++++++++++++++
3 files changed, 57 insertions(+)
create mode 100644 ruby/ql/test/query-tests/performance/DatabaseQueryInLoop/DatabaseQueryInLoop.expected
create mode 100644 ruby/ql/test/query-tests/performance/DatabaseQueryInLoop/DatabaseQueryInLoop.qlref
create mode 100644 ruby/ql/test/query-tests/performance/DatabaseQueryInLoop/DatabaseQueryInLoop.rb
diff --git a/ruby/ql/test/query-tests/performance/DatabaseQueryInLoop/DatabaseQueryInLoop.expected b/ruby/ql/test/query-tests/performance/DatabaseQueryInLoop/DatabaseQueryInLoop.expected
new file mode 100644
index 00000000000..b4cbf277380
--- /dev/null
+++ b/ruby/ql/test/query-tests/performance/DatabaseQueryInLoop/DatabaseQueryInLoop.expected
@@ -0,0 +1,3 @@
+| DatabaseQueryInLoop.rb:11:13:11:52 | call to first | This call to a database query operation happens inside $@, and could be hoisted to a single call outside the loop. | DatabaseQueryInLoop.rb:10:9:12:11 | call to map | this loop |
+| DatabaseQueryInLoop.rb:16:20:16:59 | call to first | This call to a database query operation happens inside $@, and could be hoisted to a single call outside the loop. | DatabaseQueryInLoop.rb:15:9:21:11 | call to map | this loop |
+| DatabaseQueryInLoop.rb:19:17:19:56 | call to first | This call to a database query operation happens inside $@, and could be hoisted to a single call outside the loop. | DatabaseQueryInLoop.rb:18:13:20:15 | call to map | this loop |
diff --git a/ruby/ql/test/query-tests/performance/DatabaseQueryInLoop/DatabaseQueryInLoop.qlref b/ruby/ql/test/query-tests/performance/DatabaseQueryInLoop/DatabaseQueryInLoop.qlref
new file mode 100644
index 00000000000..f1302ca1ff4
--- /dev/null
+++ b/ruby/ql/test/query-tests/performance/DatabaseQueryInLoop/DatabaseQueryInLoop.qlref
@@ -0,0 +1 @@
+queries/performance/DatabaseQueryInLoop.ql
diff --git a/ruby/ql/test/query-tests/performance/DatabaseQueryInLoop/DatabaseQueryInLoop.rb b/ruby/ql/test/query-tests/performance/DatabaseQueryInLoop/DatabaseQueryInLoop.rb
new file mode 100644
index 00000000000..57c27217a2e
--- /dev/null
+++ b/ruby/ql/test/query-tests/performance/DatabaseQueryInLoop/DatabaseQueryInLoop.rb
@@ -0,0 +1,53 @@
+
+class User < ActiveRecord::Base
+end
+
+class DatabaseQueryInLoopTest
+ def test
+ ### These are bad
+
+ # simple query in loop
+ names.map do |name|
+ User.where(login: name).pluck(:id).first
+ end
+
+ # nested loop
+ names.map do |name|
+ user = User.where(login: name).pluck(:id).first
+
+ ids.map do |user_id|
+ User.where(id: user_id).pluck(:id).first
+ end
+ end
+
+ ### These are OK
+
+ # Not in loop
+ User.where(login: owner_slug).pluck(:id).first
+
+ # Loops over constant array
+ %w(first-name second-name).map { |name| User.where(login: name).pluck(:id).first }
+
+ constant_names = [first-name, second-name]
+ constant_names.each do |name|
+ User.where(login: name).pluck(:id).first
+ end
+
+ # Loop traversal is influenced by query result
+ # raising an exception if the user is not found
+ names.map do |name|
+ user = User.where(login: name).pluck(:id).first
+ unless user
+ raise Error.new("User '#{name}' not found")
+ end
+ end
+
+ # skipping through the loop when users are not relevant
+ names.map do |name|
+ user = User.where(login: name).pluck(:id).first
+ if not isRelevant(user)
+ next
+ end
+ end
+ end
+end
From 11055760a442deaf583d7be8a1bf512be0c23821 Mon Sep 17 00:00:00 2001
From: Simon Friis Vindum
Date: Fri, 7 Feb 2025 13:53:17 +0100
Subject: [PATCH 099/279] Rust: Handle writes to references and add encoding of
reference content
---
.../rust/dataflow/internal/DataFlowImpl.qll | 15 +++++++++
.../dataflow/internal/FlowSummaryImpl.qll | 4 +++
.../modelgenerator/internal/CaptureModels.qll | 5 ++-
.../dataflow/global/inline-flow.expected | 17 ++++++++++
.../library-tests/dataflow/global/main.rs | 2 +-
.../dataflow/pointers/inline-flow.expected | 8 +++++
.../library-tests/dataflow/pointers/main.rs | 2 +-
.../test/utils-tests/modelgenerator/option.rs | 32 +++++++++++--------
.../utils-tests/modelgenerator/summaries.rs | 3 +-
9 files changed, 69 insertions(+), 19 deletions(-)
diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll
index 6c539e9642d..70736818cad 100644
--- a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll
+++ b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll
@@ -1211,6 +1211,17 @@ module RustDataFlow implements InputSig {
)
}
+ pragma[nomagic]
+ private predicate referenceAssignment(Node node1, Node node2, ReferenceContent c) {
+ exists(AssignmentExprCfgNode assignment, PrefixExprCfgNode deref |
+ assignment.getLhs() = deref and
+ deref.getOperatorName() = "*" and
+ node1.asExpr() = assignment.getRhs() and
+ node2.asExpr() = deref.getExpr() and
+ exists(c)
+ )
+ }
+
pragma[nomagic]
private predicate storeContentStep(Node node1, Content c, Node node2) {
exists(CallExprCfgNode call, int pos |
@@ -1242,6 +1253,8 @@ module RustDataFlow implements InputSig {
or
fieldAssignment(node1, node2.(PostUpdateNode).getPreUpdateNode(), c)
or
+ referenceAssignment(node1, node2.(PostUpdateNode).getPreUpdateNode(), c)
+ or
exists(AssignmentExprCfgNode assignment, IndexExprCfgNode index |
c instanceof ElementContent and
assignment.getLhs() = index and
@@ -1285,6 +1298,8 @@ module RustDataFlow implements InputSig {
predicate clearsContent(Node n, ContentSet cs) {
fieldAssignment(_, n, cs.(SingletonContentSet).getContent())
or
+ referenceAssignment(_, n, cs.(SingletonContentSet).getContent())
+ or
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(Node::FlowSummaryNode).getSummaryNode(),
cs)
or
diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll b/rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll
index b716e81b129..8ef1473b62e 100644
--- a/rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll
+++ b/rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll
@@ -89,6 +89,10 @@ module Input implements InputSig {
arg = v.getExtendedCanonicalPath() + "(" + v.getPosition() + ")"
)
or
+ result = "Reference" and
+ c = TReferenceContent() and
+ arg = ""
+ or
result = "Element" and
c = TElementContent() and
arg = ""
diff --git a/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll b/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll
index 21269cacec2..a04367055f6 100644
--- a/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll
+++ b/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll
@@ -81,9 +81,8 @@ module ModelGeneratorInput implements ModelGeneratorInputSig MyOption {
}
}
- // summary=repo::test;::as_ref;Argument[self].Variant[crate::option::MyOption::MySome(0)];ReturnValue.Variant[crate::option::MyOption::MySome(0)];value;dfc-generated
+ // NOTE: The returned value inside the variant should be inside a `Reference`, requires handling
+ // `ref` in patterns.
+ // summary=repo::test;::as_ref;Argument[self].Reference.Variant[crate::option::MyOption::MySome(0)];ReturnValue.Variant[crate::option::MyOption::MySome(0)];value;dfc-generated
pub fn as_ref(&self) -> MyOption<&T> {
match *self {
MySome(ref x) => MySome(x),
@@ -52,7 +54,7 @@ impl MyOption {
}
}
- // summary=repo::test;::as_mut;Argument[self].Variant[crate::option::MyOption::MySome(0)];ReturnValue.Variant[crate::option::MyOption::MySome(0)];value;dfc-generated
+ // summary=repo::test;::as_mut;Argument[self].Reference.Variant[crate::option::MyOption::MySome(0)];ReturnValue.Variant[crate::option::MyOption::MySome(0)];value;dfc-generated
pub fn as_mut(&mut self) -> MyOption<&mut T> {
match *self {
MySome(ref mut x) => MySome(x),
@@ -285,8 +287,11 @@ impl MyOption {
}
}
- // MISSING: summary=repo::test;::insert;Argument[0];ReturnValue;value;dfc-generated
- // SPURIOUS-summary=repo::test;::insert;Argument[self].Variant[crate::option::MyOption::MySome(0)];ReturnValue;value;dfc-generated
+ // summary=repo::test;::insert;Argument[0];Argument[self].Reference.Variant[crate::option::MyOption::MySome(0)];value;dfc-generated
+ // The below should be `ReturnValue.Reference` and not just `ReturnValue`.
+ // SPURIOUS-summary=repo::test;::insert;Argument[0];ReturnValue;value;dfc-generated
+ // The content of `self` is overwritten so it does not flow to the return value.
+ // SPURIOUS-summary=repo::test;::insert;Argument[self].Reference.Variant[crate::option::MyOption::MySome(0)];ReturnValue;value;dfc-generated
pub fn insert(&mut self, value: T) -> &mut T {
*self = MySome(value);
@@ -294,13 +299,14 @@ impl MyOption {
unsafe { self.as_mut().unwrap_unchecked() }
}
- // summary=repo::test;::get_or_insert;Argument[self].Variant[crate::option::MyOption::MySome(0)];ReturnValue;value;dfc-generated
- // MISSING: repo::test;::get_or_insert;Argument[0];ReturnValue;value;dfc-generated
+ // summary=repo::test;::get_or_insert;Argument[0];Argument[self].Reference.Variant[crate::option::MyOption::MySome(0)];value;dfc-generated
+ // summary=repo::test;::get_or_insert;Argument[0];ReturnValue;value;dfc-generated
+ // summary=repo::test;::get_or_insert;Argument[self].Reference.Variant[crate::option::MyOption::MySome(0)];ReturnValue;value;dfc-generated
pub fn get_or_insert(&mut self, value: T) -> &mut T {
self.get_or_insert_with(|| value)
}
- // summary=repo::test;::get_or_insert_default;Argument[self].Variant[crate::option::MyOption::MySome(0)];ReturnValue;value;dfc-generated
+ // summary=repo::test;::get_or_insert_default;Argument[self].Reference.Variant[crate::option::MyOption::MySome(0)];ReturnValue;value;dfc-generated
pub fn get_or_insert_default(&mut self) -> &mut T
where
T: Default,
@@ -308,7 +314,7 @@ impl MyOption {
self.get_or_insert_with(T::default)
}
- // summary=repo::test;::get_or_insert_with;Argument[self].Variant[crate::option::MyOption::MySome(0)];ReturnValue;value;dfc-generated
+ // summary=repo::test;::get_or_insert_with;Argument[self].Reference.Variant[crate::option::MyOption::MySome(0)];ReturnValue;value;dfc-generated
// MISSING: Mutating `self` parameter.
pub fn get_or_insert_with(&mut self, f: F) -> &mut T
where
@@ -329,7 +335,7 @@ impl MyOption {
mem::replace(self, MyNone)
}
- // summary=repo::test;::take_if;Argument[self].Variant[crate::option::MyOption::MySome(0)];Argument[0].Parameter[0];value;dfc-generated
+ // summary=repo::test;::take_if;Argument[self].Reference.Variant[crate::option::MyOption::MySome(0)];Argument[0].Parameter[0];value;dfc-generated
// MISSING: Uses `take` which doesn't have flow
pub fn take_if