From 8808f0f8249c10790d35be1785368ac0ee8c5ee5 Mon Sep 17 00:00:00 2001 From: Taus Date: Wed, 8 Jan 2025 15:56:17 +0000 Subject: [PATCH 01/66] Misc: Add script for calculating MRVA totals Use this script if you want to quickly calculate the totals of some query across all the queries in a MRVA run. For an example of such a query, see e.g. `python/ql/src/Metrics/Internal/TypeAnnotations.ql` The script expects the query to produce an output table of the form ``` | header0 | header1 | header2 | header3 | ... |----------|----------|----------|----------|---- | message1 | value11 | value12 | value13 | ... | message2 | value21 | value22 | value23 | ... ... ``` where all of the `values` are numbers. For each `(message, header)` pair, it then calculates the total of all the values in that cell, across all of the repos in the MRVA run. To use the script, simply pass it the URL of the exported Gist of the MRVA run. After calculating the totals, the script will then (optionally, but by default) add the totals to the `_summary.md` file, and push these changes to the Gist. --- misc/scripts/calculate_mrva_totals.py | 131 ++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 misc/scripts/calculate_mrva_totals.py diff --git a/misc/scripts/calculate_mrva_totals.py b/misc/scripts/calculate_mrva_totals.py new file mode 100644 index 00000000000..cec1b8b54bf --- /dev/null +++ b/misc/scripts/calculate_mrva_totals.py @@ -0,0 +1,131 @@ +import os +import subprocess +import tempfile +import argparse +from collections import defaultdict + +help_text = """ +To use this script, pass the URL of a GitHub Gist as an argument. The Gist should contain the +exported MarkDown output of a MRVA run. + +The script expects the query to produce an output table of the form +``` +| header0 | header1 | header2 | header3 | ... +|----------|----------|----------|----------|---- +| message1 | value11 | value12 | value13 | ... +| message2 | value21 | value22 | value23 | ... +... +``` +The script will calculate the totals for each message and header, and put a table containing these +totals in the `_summary.md` file in the Gist. By default it will then commit and push these changes +to the Gist (having first displayed a diff of the changes). +""" + +first_header = "" + +def split_line(line): + return [item.strip() for item in line.strip('|').split('|')] + +def parse_markdown_table(stream): + global first_header + iterator = (line.strip() for line in stream) + + # Skip irrelevant lines until we find the header line + for line in iterator: + if line.startswith('|'): + first_header, *headers = split_line(line) + break + + # Skip the separator line + next(iterator) + + data_dict = {} + + # Process the remaining lines + for line in iterator: + if line.startswith('|'): + message, *values = [value.strip('`') for value in split_line(line)] + data_dict[message] = { + headers[i]: int(value) if value.isdigit() else value + for i, value in enumerate(values) + } + + return data_dict + +def clone_gist(gist_url, repo_dir): + try: + subprocess.run(["gh", "gist", "clone", gist_url, repo_dir], check=True) + except subprocess.CalledProcessError: + print(f"Failed to clone the gist from {gist_url}") + subprocess.run(["rm", "-rf", repo_dir]) + exit(1) + +def process_gist_files(repo_dir): + total_data = defaultdict(lambda: defaultdict(int)) + + for filename in os.listdir(repo_dir): + if filename.endswith(".md") and filename != "_summary.md": + with open(os.path.join(repo_dir, filename), "r") as file: + data_dict = parse_markdown_table(file) + + for message, values in data_dict.items(): + for header, value in values.items(): + if isinstance(value, int): + total_data[message][header] += value + + return total_data + +def append_totals_to_summary(total_data, repo_dir): + global first_header + summary_path = os.path.join(repo_dir, "_summary.md") + with open(summary_path, "r") as summary_file: + content = summary_file.read() + + totals_table = "\n\n### Totals\n\n" + headers = [first_header] + list(next(iter(total_data.values())).keys()) + totals_table += "| " + " | ".join(headers) + " |\n" + totals_table += "| " + "|".join(["---"] + ["---:"] * (len(headers) - 1)) + " |\n" # Right align all but the first column + for message, values in total_data.items(): + row = [message] + [f"{values[header]:,}" for header in headers[1:]] + totals_table += "| " + " | ".join(row) + " |\n" + + new_content = content.replace("### Summary", totals_table + "\n### Summary") + + with open(summary_path, "w") as summary_file: + summary_file.write(new_content) + +def commit_and_push_changes(repo_dir): + subprocess.run(["git", "add", "_summary.md"], cwd=repo_dir, check=True) + subprocess.run(["git", "commit", "-m", "Update summary with totals"], cwd=repo_dir, check=True) + subprocess.run(["git", "push"], cwd=repo_dir, check=True) + +def show_git_diff(repo_dir): + subprocess.run(["git", "diff", "_summary.md"], cwd=repo_dir, check=True) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Calculate MRVA totals from a GitHub Gist", epilog=help_text, formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument("gist_url", nargs='?', help="URL of the GitHub Gist") + parser.add_argument("--keep-dir", action="store_true", help="Keep the temporary directory") + + args = parser.parse_args() + + if not args.gist_url: + parser.print_help() + exit(1) + + repo_dir = tempfile.mkdtemp(dir=".") + clone_gist(args.gist_url, repo_dir) + + total_data = process_gist_files(repo_dir) + + append_totals_to_summary(total_data, repo_dir) + + show_git_diff(repo_dir) + + if input("Do you want to push the changes to the gist? (Y/n): ").strip().lower() in ['y', '']: + commit_and_push_changes(repo_dir) + + if args.keep_dir: + print(f"Temporary directory retained at: {repo_dir}") + else: + subprocess.run(["rm", "-rf", repo_dir]) From f1a3293f4c81bb870add0eb4ea083dc34f6e2aa6 Mon Sep 17 00:00:00 2001 From: Napalys Date: Mon, 14 Apr 2025 11:04:56 +0200 Subject: [PATCH 02/66] Added change note --- javascript/ql/lib/change-notes/2025-04-14-fastify-addhook.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 javascript/ql/lib/change-notes/2025-04-14-fastify-addhook.md diff --git a/javascript/ql/lib/change-notes/2025-04-14-fastify-addhook.md b/javascript/ql/lib/change-notes/2025-04-14-fastify-addhook.md new file mode 100644 index 00000000000..a9e754bd56e --- /dev/null +++ b/javascript/ql/lib/change-notes/2025-04-14-fastify-addhook.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added support for the `fastify` `addHook` method. From c1750816982a41ed13caf1e65ecc7912198220b5 Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 15 Apr 2025 09:33:41 +0200 Subject: [PATCH 03/66] Added test cases for `fastify.addHook` --- .../CodeInjection/CodeInjection.expected | 13 +++ .../HeuristicSourceCodeInjection.expected | 9 ++ .../Security/CWE-094/CodeInjection/fastify.js | 103 ++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected index a81b9dbcce0..5fc5856814c 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected @@ -27,6 +27,10 @@ | express.js:20:34:20:38 | taint | express.js:19:17:19:35 | req.param("wobble") | express.js:20:34:20:38 | taint | This code execution depends on a $@. | express.js:19:17:19:35 | req.param("wobble") | user-provided value | | express.js:36:15:36:19 | taint | express.js:27:17:27:35 | req.param("wobble") | express.js:36:15:36:19 | taint | This code execution depends on a $@. | express.js:27:17:27:35 | req.param("wobble") | user-provided value | | express.js:43:10:43:12 | msg | express.js:42:30:42:32 | msg | express.js:43:10:43:12 | msg | This code execution depends on a $@. | express.js:42:30:42:32 | msg | user-provided value | +| fastify.js:58:44:58:52 | userInput | fastify.js:57:21:57:33 | request.query | fastify.js:58:44:58:52 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:33 | request.query | user-provided value | +| fastify.js:58:44:58:52 | userInput | fastify.js:57:21:57:39 | request.query.input | fastify.js:58:44:58:52 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:39 | request.query.input | user-provided value | +| fastify.js:59:23:59:31 | userInput | fastify.js:57:21:57:33 | request.query | fastify.js:59:23:59:31 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:33 | request.query | user-provided value | +| fastify.js:59:23:59:31 | userInput | fastify.js:57:21:57:39 | request.query.input | fastify.js:59:23:59:31 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:39 | request.query.input | user-provided value | | module.js:9:16:9:29 | req.query.code | module.js:9:16:9:29 | req.query.code | module.js:9:16:9:29 | req.query.code | This code execution depends on a $@. | module.js:9:16:9:29 | req.query.code | user-provided value | | module.js:11:17:11:30 | req.query.code | module.js:11:17:11:30 | req.query.code | module.js:11:17:11:30 | req.query.code | This code execution depends on a $@. | module.js:11:17:11:30 | req.query.code | user-provided value | | react-native.js:8:32:8:38 | tainted | react-native.js:7:17:7:33 | req.param("code") | react-native.js:8:32:8:38 | tainted | This code execution depends on a $@. | react-native.js:7:17:7:33 | req.param("code") | user-provided value | @@ -75,6 +79,10 @@ edges | express.js:27:9:27:35 | taint | express.js:36:15:36:19 | taint | provenance | | | express.js:27:17:27:35 | req.param("wobble") | express.js:27:9:27:35 | taint | provenance | | | express.js:42:30:42:32 | msg | express.js:43:10:43:12 | msg | provenance | | +| fastify.js:57:9:57:39 | userInput | fastify.js:58:44:58:52 | userInput | provenance | | +| fastify.js:57:9:57:39 | userInput | fastify.js:59:23:59:31 | userInput | provenance | | +| fastify.js:57:21:57:33 | request.query | fastify.js:57:9:57:39 | userInput | provenance | | +| fastify.js:57:21:57:39 | request.query.input | fastify.js:57:9:57:39 | userInput | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:8:32:8:38 | tainted | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:10:23:10:29 | tainted | provenance | | | react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted | provenance | | @@ -144,6 +152,11 @@ nodes | express.js:36:15:36:19 | taint | semmle.label | taint | | express.js:42:30:42:32 | msg | semmle.label | msg | | express.js:43:10:43:12 | msg | semmle.label | msg | +| fastify.js:57:9:57:39 | userInput | semmle.label | userInput | +| fastify.js:57:21:57:33 | request.query | semmle.label | request.query | +| fastify.js:57:21:57:39 | request.query.input | semmle.label | request.query.input | +| fastify.js:58:44:58:52 | userInput | semmle.label | userInput | +| fastify.js:59:23:59:31 | userInput | semmle.label | userInput | | module.js:9:16:9:29 | req.query.code | semmle.label | req.query.code | | module.js:11:17:11:30 | req.query.code | semmle.label | req.query.code | | react-native.js:7:7:7:33 | tainted | semmle.label | tainted | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected index ba973943e12..b56f28a7917 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected @@ -11,6 +11,10 @@ edges | express.js:27:9:27:35 | taint | express.js:36:15:36:19 | taint | provenance | | | express.js:27:17:27:35 | req.param("wobble") | express.js:27:9:27:35 | taint | provenance | | | express.js:42:30:42:32 | msg | express.js:43:10:43:12 | msg | provenance | | +| fastify.js:57:9:57:39 | userInput | fastify.js:58:44:58:52 | userInput | provenance | | +| fastify.js:57:9:57:39 | userInput | fastify.js:59:23:59:31 | userInput | provenance | | +| fastify.js:57:21:57:33 | request.query | fastify.js:57:9:57:39 | userInput | provenance | | +| fastify.js:57:21:57:39 | request.query.input | fastify.js:57:9:57:39 | userInput | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:8:32:8:38 | tainted | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:10:23:10:29 | tainted | provenance | | | react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted | provenance | | @@ -82,6 +86,11 @@ nodes | express.js:36:15:36:19 | taint | semmle.label | taint | | express.js:42:30:42:32 | msg | semmle.label | msg | | express.js:43:10:43:12 | msg | semmle.label | msg | +| fastify.js:57:9:57:39 | userInput | semmle.label | userInput | +| fastify.js:57:21:57:33 | request.query | semmle.label | request.query | +| fastify.js:57:21:57:39 | request.query.input | semmle.label | request.query.input | +| fastify.js:58:44:58:52 | userInput | semmle.label | userInput | +| fastify.js:59:23:59:31 | userInput | semmle.label | userInput | | module.js:9:16:9:29 | req.query.code | semmle.label | req.query.code | | module.js:11:17:11:30 | req.query.code | semmle.label | req.query.code | | react-native.js:7:7:7:33 | tainted | semmle.label | tainted | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js new file mode 100644 index 00000000000..b8878cdab41 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js @@ -0,0 +1,103 @@ +const fastify = require('fastify')({ logger: true }); + +fastify.addHook('onRequest', async (request, reply) => { + const userInput = request.query.onRequest; // $ MISSING: Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] +}); + +fastify.addHook('onSend', async (request, reply, payload) => { + const userInput = request.query.onSend; // $ MISSING: Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] + return JSON.stringify({ ...JSON.parse(payload), onSend: request.evalResult }); +}); + +fastify.addHook('preParsing', async (request, reply, payload) => { + const userInput = request.query.preParsing; // $ MISSING: Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] + return payload; +}); + +fastify.addHook('preValidation', async (request, reply) => { + const userInput = request.query.preValidation; // $ MISSING: Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] +}); + +fastify.addHook('preHandler', async (request, reply) => { + const userInput = request.query.preHandler; // $ MISSING: Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] +}); + +fastify.addHook('preSerialization', async (request, reply, payload) => { + const userInput = request.query.preSerialization; // $ MISSING: Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] + return payload; +}); + +fastify.addHook('onResponse', async (request, reply) => { + const userInput = request.query.onResponse; // $ MISSING: Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] +}); + +fastify.addHook('onError', async (request, reply, error) => { + const userInput = request.query.onError; // $ MISSING: Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] +}); + +fastify.addHook('onTimeout', async (request, reply) => { + const userInput = request.query.onTimeout; // $ MISSING: Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] +}); + +fastify.addHook('onRequestAbort', (request, done) => { + const userInput = request.query.onRequestAbort; // $ MISSING: Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] +}); + +fastify.get('/dangerous', async (request, reply) => { + const userInput = request.query.input; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] + const result = eval(userInput); // $ Alert[js/code-injection] + return { result }; +}); + + +// Store user input in request object +fastify.addHook('preHandler', async (request, reply) => { + request.storedCode = request.query.storedCode; +}); +fastify.get('/flow-through-request', async (request, reply) => { + // Use the stored code from previous hook + if (request.storedCode) { + const evaluatedResult = eval(request.storedCode); // $ MISSING: Alert[js/code-injection] + return { result: evaluatedResult }; + } + return { result: null }; +}); + +// Store user input in reply object +fastify.addHook('onRequest', async (request, reply) => { + reply.userCode = request.query.replyCode; +}); +fastify.get('/flow-through-reply', async (request, reply) => { + // Use the code stored in reply object + if (reply.userCode) { + const replyResult = eval(reply.userCode); // $ MISSING: Alert[js/code-injection] + return { result: replyResult }; + } + return { result: null }; +}); + + +// Store user input in reply object +fastify.addHook('onRequest', async (request, reply) => { + reply.locals = reply.locals || {}; + reply.locals.nestedCode = request.query.replyCode; +}); +fastify.get('/flow-through-reply', async (request, reply) => { + // Use the code stored in reply object + if (reply.locals && reply.locals.nestedCode) { + const replyResult = eval(reply.locals.nestedCode); // $ MISSING: Alert[js/code-injection] + return { result: replyResult }; + } + return { result: null }; +}); From 9b194ea613c653a7bf6e16ca4633827b8e8d2a41 Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 15 Apr 2025 09:37:13 +0200 Subject: [PATCH 04/66] Added `addHook` to `RouteSetup` thus now it is recognized now as rouute handler --- .../semmle/javascript/frameworks/Fastify.qll | 2 +- .../CodeInjection/CodeInjection.expected | 90 +++++++++++++++++++ .../HeuristicSourceCodeInjection.expected | 70 +++++++++++++++ .../Security/CWE-094/CodeInjection/fastify.js | 40 ++++----- 4 files changed, 181 insertions(+), 21 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll index 2b8d6287d78..5b2485c7679 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll @@ -138,7 +138,7 @@ module Fastify { RouteSetup() { this = server(server).getAMethodCall(methodName) and - methodName = ["route", "get", "head", "post", "put", "delete", "options", "patch"] + methodName = ["route", "get", "head", "post", "put", "delete", "options", "patch", "addHook"] } override DataFlow::SourceNode getARouteHandler() { diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected index 5fc5856814c..34487a43e63 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected @@ -27,6 +27,26 @@ | express.js:20:34:20:38 | taint | express.js:19:17:19:35 | req.param("wobble") | express.js:20:34:20:38 | taint | This code execution depends on a $@. | express.js:19:17:19:35 | req.param("wobble") | user-provided value | | express.js:36:15:36:19 | taint | express.js:27:17:27:35 | req.param("wobble") | express.js:36:15:36:19 | taint | This code execution depends on a $@. | express.js:27:17:27:35 | req.param("wobble") | user-provided value | | express.js:43:10:43:12 | msg | express.js:42:30:42:32 | msg | express.js:43:10:43:12 | msg | This code execution depends on a $@. | express.js:42:30:42:32 | msg | user-provided value | +| fastify.js:5:44:5:52 | userInput | fastify.js:4:21:4:33 | request.query | fastify.js:5:44:5:52 | userInput | This code execution depends on a $@. | fastify.js:4:21:4:33 | request.query | user-provided value | +| fastify.js:5:44:5:52 | userInput | fastify.js:4:21:4:43 | request ... Request | fastify.js:5:44:5:52 | userInput | This code execution depends on a $@. | fastify.js:4:21:4:43 | request ... Request | user-provided value | +| fastify.js:10:44:10:52 | userInput | fastify.js:9:21:9:33 | request.query | fastify.js:10:44:10:52 | userInput | This code execution depends on a $@. | fastify.js:9:21:9:33 | request.query | user-provided value | +| fastify.js:10:44:10:52 | userInput | fastify.js:9:21:9:40 | request.query.onSend | fastify.js:10:44:10:52 | userInput | This code execution depends on a $@. | fastify.js:9:21:9:40 | request.query.onSend | user-provided value | +| fastify.js:16:44:16:52 | userInput | fastify.js:15:21:15:33 | request.query | fastify.js:16:44:16:52 | userInput | This code execution depends on a $@. | fastify.js:15:21:15:33 | request.query | user-provided value | +| fastify.js:16:44:16:52 | userInput | fastify.js:15:21:15:44 | request ... Parsing | fastify.js:16:44:16:52 | userInput | This code execution depends on a $@. | fastify.js:15:21:15:44 | request ... Parsing | user-provided value | +| fastify.js:22:44:22:52 | userInput | fastify.js:21:21:21:33 | request.query | fastify.js:22:44:22:52 | userInput | This code execution depends on a $@. | fastify.js:21:21:21:33 | request.query | user-provided value | +| fastify.js:22:44:22:52 | userInput | fastify.js:21:21:21:47 | request ... idation | fastify.js:22:44:22:52 | userInput | This code execution depends on a $@. | fastify.js:21:21:21:47 | request ... idation | user-provided value | +| fastify.js:27:44:27:52 | userInput | fastify.js:26:21:26:33 | request.query | fastify.js:27:44:27:52 | userInput | This code execution depends on a $@. | fastify.js:26:21:26:33 | request.query | user-provided value | +| fastify.js:27:44:27:52 | userInput | fastify.js:26:21:26:44 | request ... Handler | fastify.js:27:44:27:52 | userInput | This code execution depends on a $@. | fastify.js:26:21:26:44 | request ... Handler | user-provided value | +| fastify.js:32:44:32:52 | userInput | fastify.js:31:21:31:33 | request.query | fastify.js:32:44:32:52 | userInput | This code execution depends on a $@. | fastify.js:31:21:31:33 | request.query | user-provided value | +| fastify.js:32:44:32:52 | userInput | fastify.js:31:21:31:50 | request ... ization | fastify.js:32:44:32:52 | userInput | This code execution depends on a $@. | fastify.js:31:21:31:50 | request ... ization | user-provided value | +| fastify.js:38:44:38:52 | userInput | fastify.js:37:21:37:33 | request.query | fastify.js:38:44:38:52 | userInput | This code execution depends on a $@. | fastify.js:37:21:37:33 | request.query | user-provided value | +| fastify.js:38:44:38:52 | userInput | fastify.js:37:21:37:44 | request ... esponse | fastify.js:38:44:38:52 | userInput | This code execution depends on a $@. | fastify.js:37:21:37:44 | request ... esponse | user-provided value | +| fastify.js:43:44:43:52 | userInput | fastify.js:42:21:42:33 | request.query | fastify.js:43:44:43:52 | userInput | This code execution depends on a $@. | fastify.js:42:21:42:33 | request.query | user-provided value | +| fastify.js:43:44:43:52 | userInput | fastify.js:42:21:42:41 | request ... onError | fastify.js:43:44:43:52 | userInput | This code execution depends on a $@. | fastify.js:42:21:42:41 | request ... onError | user-provided value | +| fastify.js:48:44:48:52 | userInput | fastify.js:47:21:47:33 | request.query | fastify.js:48:44:48:52 | userInput | This code execution depends on a $@. | fastify.js:47:21:47:33 | request.query | user-provided value | +| fastify.js:48:44:48:52 | userInput | fastify.js:47:21:47:43 | request ... Timeout | fastify.js:48:44:48:52 | userInput | This code execution depends on a $@. | fastify.js:47:21:47:43 | request ... Timeout | user-provided value | +| fastify.js:53:46:53:54 | userInput | fastify.js:52:23:52:35 | request.query | fastify.js:53:46:53:54 | userInput | This code execution depends on a $@. | fastify.js:52:23:52:35 | request.query | user-provided value | +| fastify.js:53:46:53:54 | userInput | fastify.js:52:23:52:50 | request ... stAbort | fastify.js:53:46:53:54 | userInput | This code execution depends on a $@. | fastify.js:52:23:52:50 | request ... stAbort | user-provided value | | fastify.js:58:44:58:52 | userInput | fastify.js:57:21:57:33 | request.query | fastify.js:58:44:58:52 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:33 | request.query | user-provided value | | fastify.js:58:44:58:52 | userInput | fastify.js:57:21:57:39 | request.query.input | fastify.js:58:44:58:52 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:39 | request.query.input | user-provided value | | fastify.js:59:23:59:31 | userInput | fastify.js:57:21:57:33 | request.query | fastify.js:59:23:59:31 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:33 | request.query | user-provided value | @@ -79,6 +99,36 @@ edges | express.js:27:9:27:35 | taint | express.js:36:15:36:19 | taint | provenance | | | express.js:27:17:27:35 | req.param("wobble") | express.js:27:9:27:35 | taint | provenance | | | express.js:42:30:42:32 | msg | express.js:43:10:43:12 | msg | provenance | | +| fastify.js:4:9:4:43 | userInput | fastify.js:5:44:5:52 | userInput | provenance | | +| fastify.js:4:21:4:33 | request.query | fastify.js:4:9:4:43 | userInput | provenance | | +| fastify.js:4:21:4:43 | request ... Request | fastify.js:4:9:4:43 | userInput | provenance | | +| fastify.js:9:9:9:40 | userInput | fastify.js:10:44:10:52 | userInput | provenance | | +| fastify.js:9:21:9:33 | request.query | fastify.js:9:9:9:40 | userInput | provenance | | +| fastify.js:9:21:9:40 | request.query.onSend | fastify.js:9:9:9:40 | userInput | provenance | | +| fastify.js:15:9:15:44 | userInput | fastify.js:16:44:16:52 | userInput | provenance | | +| fastify.js:15:21:15:33 | request.query | fastify.js:15:9:15:44 | userInput | provenance | | +| fastify.js:15:21:15:44 | request ... Parsing | fastify.js:15:9:15:44 | userInput | provenance | | +| fastify.js:21:9:21:47 | userInput | fastify.js:22:44:22:52 | userInput | provenance | | +| fastify.js:21:21:21:33 | request.query | fastify.js:21:9:21:47 | userInput | provenance | | +| fastify.js:21:21:21:47 | request ... idation | fastify.js:21:9:21:47 | userInput | provenance | | +| fastify.js:26:9:26:44 | userInput | fastify.js:27:44:27:52 | userInput | provenance | | +| fastify.js:26:21:26:33 | request.query | fastify.js:26:9:26:44 | userInput | provenance | | +| fastify.js:26:21:26:44 | request ... Handler | fastify.js:26:9:26:44 | userInput | provenance | | +| fastify.js:31:9:31:50 | userInput | fastify.js:32:44:32:52 | userInput | provenance | | +| fastify.js:31:21:31:33 | request.query | fastify.js:31:9:31:50 | userInput | provenance | | +| fastify.js:31:21:31:50 | request ... ization | fastify.js:31:9:31:50 | userInput | provenance | | +| fastify.js:37:9:37:44 | userInput | fastify.js:38:44:38:52 | userInput | provenance | | +| fastify.js:37:21:37:33 | request.query | fastify.js:37:9:37:44 | userInput | provenance | | +| fastify.js:37:21:37:44 | request ... esponse | fastify.js:37:9:37:44 | userInput | provenance | | +| fastify.js:42:9:42:41 | userInput | fastify.js:43:44:43:52 | userInput | provenance | | +| fastify.js:42:21:42:33 | request.query | fastify.js:42:9:42:41 | userInput | provenance | | +| fastify.js:42:21:42:41 | request ... onError | fastify.js:42:9:42:41 | userInput | provenance | | +| fastify.js:47:9:47:43 | userInput | fastify.js:48:44:48:52 | userInput | provenance | | +| fastify.js:47:21:47:33 | request.query | fastify.js:47:9:47:43 | userInput | provenance | | +| fastify.js:47:21:47:43 | request ... Timeout | fastify.js:47:9:47:43 | userInput | provenance | | +| fastify.js:52:11:52:50 | userInput | fastify.js:53:46:53:54 | userInput | provenance | | +| fastify.js:52:23:52:35 | request.query | fastify.js:52:11:52:50 | userInput | provenance | | +| fastify.js:52:23:52:50 | request ... stAbort | fastify.js:52:11:52:50 | userInput | provenance | | | fastify.js:57:9:57:39 | userInput | fastify.js:58:44:58:52 | userInput | provenance | | | fastify.js:57:9:57:39 | userInput | fastify.js:59:23:59:31 | userInput | provenance | | | fastify.js:57:21:57:33 | request.query | fastify.js:57:9:57:39 | userInput | provenance | | @@ -152,6 +202,46 @@ nodes | express.js:36:15:36:19 | taint | semmle.label | taint | | express.js:42:30:42:32 | msg | semmle.label | msg | | express.js:43:10:43:12 | msg | semmle.label | msg | +| fastify.js:4:9:4:43 | userInput | semmle.label | userInput | +| fastify.js:4:21:4:33 | request.query | semmle.label | request.query | +| fastify.js:4:21:4:43 | request ... Request | semmle.label | request ... Request | +| fastify.js:5:44:5:52 | userInput | semmle.label | userInput | +| fastify.js:9:9:9:40 | userInput | semmle.label | userInput | +| fastify.js:9:21:9:33 | request.query | semmle.label | request.query | +| fastify.js:9:21:9:40 | request.query.onSend | semmle.label | request.query.onSend | +| fastify.js:10:44:10:52 | userInput | semmle.label | userInput | +| fastify.js:15:9:15:44 | userInput | semmle.label | userInput | +| fastify.js:15:21:15:33 | request.query | semmle.label | request.query | +| fastify.js:15:21:15:44 | request ... Parsing | semmle.label | request ... Parsing | +| fastify.js:16:44:16:52 | userInput | semmle.label | userInput | +| fastify.js:21:9:21:47 | userInput | semmle.label | userInput | +| fastify.js:21:21:21:33 | request.query | semmle.label | request.query | +| fastify.js:21:21:21:47 | request ... idation | semmle.label | request ... idation | +| fastify.js:22:44:22:52 | userInput | semmle.label | userInput | +| fastify.js:26:9:26:44 | userInput | semmle.label | userInput | +| fastify.js:26:21:26:33 | request.query | semmle.label | request.query | +| fastify.js:26:21:26:44 | request ... Handler | semmle.label | request ... Handler | +| fastify.js:27:44:27:52 | userInput | semmle.label | userInput | +| fastify.js:31:9:31:50 | userInput | semmle.label | userInput | +| fastify.js:31:21:31:33 | request.query | semmle.label | request.query | +| fastify.js:31:21:31:50 | request ... ization | semmle.label | request ... ization | +| fastify.js:32:44:32:52 | userInput | semmle.label | userInput | +| fastify.js:37:9:37:44 | userInput | semmle.label | userInput | +| fastify.js:37:21:37:33 | request.query | semmle.label | request.query | +| fastify.js:37:21:37:44 | request ... esponse | semmle.label | request ... esponse | +| fastify.js:38:44:38:52 | userInput | semmle.label | userInput | +| fastify.js:42:9:42:41 | userInput | semmle.label | userInput | +| fastify.js:42:21:42:33 | request.query | semmle.label | request.query | +| fastify.js:42:21:42:41 | request ... onError | semmle.label | request ... onError | +| fastify.js:43:44:43:52 | userInput | semmle.label | userInput | +| fastify.js:47:9:47:43 | userInput | semmle.label | userInput | +| fastify.js:47:21:47:33 | request.query | semmle.label | request.query | +| fastify.js:47:21:47:43 | request ... Timeout | semmle.label | request ... Timeout | +| fastify.js:48:44:48:52 | userInput | semmle.label | userInput | +| fastify.js:52:11:52:50 | userInput | semmle.label | userInput | +| fastify.js:52:23:52:35 | request.query | semmle.label | request.query | +| fastify.js:52:23:52:50 | request ... stAbort | semmle.label | request ... stAbort | +| fastify.js:53:46:53:54 | userInput | semmle.label | userInput | | fastify.js:57:9:57:39 | userInput | semmle.label | userInput | | fastify.js:57:21:57:33 | request.query | semmle.label | request.query | | fastify.js:57:21:57:39 | request.query.input | semmle.label | request.query.input | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected index b56f28a7917..41550a22c9f 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected @@ -11,6 +11,36 @@ edges | express.js:27:9:27:35 | taint | express.js:36:15:36:19 | taint | provenance | | | express.js:27:17:27:35 | req.param("wobble") | express.js:27:9:27:35 | taint | provenance | | | express.js:42:30:42:32 | msg | express.js:43:10:43:12 | msg | provenance | | +| fastify.js:4:9:4:43 | userInput | fastify.js:5:44:5:52 | userInput | provenance | | +| fastify.js:4:21:4:33 | request.query | fastify.js:4:9:4:43 | userInput | provenance | | +| fastify.js:4:21:4:43 | request ... Request | fastify.js:4:9:4:43 | userInput | provenance | | +| fastify.js:9:9:9:40 | userInput | fastify.js:10:44:10:52 | userInput | provenance | | +| fastify.js:9:21:9:33 | request.query | fastify.js:9:9:9:40 | userInput | provenance | | +| fastify.js:9:21:9:40 | request.query.onSend | fastify.js:9:9:9:40 | userInput | provenance | | +| fastify.js:15:9:15:44 | userInput | fastify.js:16:44:16:52 | userInput | provenance | | +| fastify.js:15:21:15:33 | request.query | fastify.js:15:9:15:44 | userInput | provenance | | +| fastify.js:15:21:15:44 | request ... Parsing | fastify.js:15:9:15:44 | userInput | provenance | | +| fastify.js:21:9:21:47 | userInput | fastify.js:22:44:22:52 | userInput | provenance | | +| fastify.js:21:21:21:33 | request.query | fastify.js:21:9:21:47 | userInput | provenance | | +| fastify.js:21:21:21:47 | request ... idation | fastify.js:21:9:21:47 | userInput | provenance | | +| fastify.js:26:9:26:44 | userInput | fastify.js:27:44:27:52 | userInput | provenance | | +| fastify.js:26:21:26:33 | request.query | fastify.js:26:9:26:44 | userInput | provenance | | +| fastify.js:26:21:26:44 | request ... Handler | fastify.js:26:9:26:44 | userInput | provenance | | +| fastify.js:31:9:31:50 | userInput | fastify.js:32:44:32:52 | userInput | provenance | | +| fastify.js:31:21:31:33 | request.query | fastify.js:31:9:31:50 | userInput | provenance | | +| fastify.js:31:21:31:50 | request ... ization | fastify.js:31:9:31:50 | userInput | provenance | | +| fastify.js:37:9:37:44 | userInput | fastify.js:38:44:38:52 | userInput | provenance | | +| fastify.js:37:21:37:33 | request.query | fastify.js:37:9:37:44 | userInput | provenance | | +| fastify.js:37:21:37:44 | request ... esponse | fastify.js:37:9:37:44 | userInput | provenance | | +| fastify.js:42:9:42:41 | userInput | fastify.js:43:44:43:52 | userInput | provenance | | +| fastify.js:42:21:42:33 | request.query | fastify.js:42:9:42:41 | userInput | provenance | | +| fastify.js:42:21:42:41 | request ... onError | fastify.js:42:9:42:41 | userInput | provenance | | +| fastify.js:47:9:47:43 | userInput | fastify.js:48:44:48:52 | userInput | provenance | | +| fastify.js:47:21:47:33 | request.query | fastify.js:47:9:47:43 | userInput | provenance | | +| fastify.js:47:21:47:43 | request ... Timeout | fastify.js:47:9:47:43 | userInput | provenance | | +| fastify.js:52:11:52:50 | userInput | fastify.js:53:46:53:54 | userInput | provenance | | +| fastify.js:52:23:52:35 | request.query | fastify.js:52:11:52:50 | userInput | provenance | | +| fastify.js:52:23:52:50 | request ... stAbort | fastify.js:52:11:52:50 | userInput | provenance | | | fastify.js:57:9:57:39 | userInput | fastify.js:58:44:58:52 | userInput | provenance | | | fastify.js:57:9:57:39 | userInput | fastify.js:59:23:59:31 | userInput | provenance | | | fastify.js:57:21:57:33 | request.query | fastify.js:57:9:57:39 | userInput | provenance | | @@ -86,6 +116,46 @@ nodes | express.js:36:15:36:19 | taint | semmle.label | taint | | express.js:42:30:42:32 | msg | semmle.label | msg | | express.js:43:10:43:12 | msg | semmle.label | msg | +| fastify.js:4:9:4:43 | userInput | semmle.label | userInput | +| fastify.js:4:21:4:33 | request.query | semmle.label | request.query | +| fastify.js:4:21:4:43 | request ... Request | semmle.label | request ... Request | +| fastify.js:5:44:5:52 | userInput | semmle.label | userInput | +| fastify.js:9:9:9:40 | userInput | semmle.label | userInput | +| fastify.js:9:21:9:33 | request.query | semmle.label | request.query | +| fastify.js:9:21:9:40 | request.query.onSend | semmle.label | request.query.onSend | +| fastify.js:10:44:10:52 | userInput | semmle.label | userInput | +| fastify.js:15:9:15:44 | userInput | semmle.label | userInput | +| fastify.js:15:21:15:33 | request.query | semmle.label | request.query | +| fastify.js:15:21:15:44 | request ... Parsing | semmle.label | request ... Parsing | +| fastify.js:16:44:16:52 | userInput | semmle.label | userInput | +| fastify.js:21:9:21:47 | userInput | semmle.label | userInput | +| fastify.js:21:21:21:33 | request.query | semmle.label | request.query | +| fastify.js:21:21:21:47 | request ... idation | semmle.label | request ... idation | +| fastify.js:22:44:22:52 | userInput | semmle.label | userInput | +| fastify.js:26:9:26:44 | userInput | semmle.label | userInput | +| fastify.js:26:21:26:33 | request.query | semmle.label | request.query | +| fastify.js:26:21:26:44 | request ... Handler | semmle.label | request ... Handler | +| fastify.js:27:44:27:52 | userInput | semmle.label | userInput | +| fastify.js:31:9:31:50 | userInput | semmle.label | userInput | +| fastify.js:31:21:31:33 | request.query | semmle.label | request.query | +| fastify.js:31:21:31:50 | request ... ization | semmle.label | request ... ization | +| fastify.js:32:44:32:52 | userInput | semmle.label | userInput | +| fastify.js:37:9:37:44 | userInput | semmle.label | userInput | +| fastify.js:37:21:37:33 | request.query | semmle.label | request.query | +| fastify.js:37:21:37:44 | request ... esponse | semmle.label | request ... esponse | +| fastify.js:38:44:38:52 | userInput | semmle.label | userInput | +| fastify.js:42:9:42:41 | userInput | semmle.label | userInput | +| fastify.js:42:21:42:33 | request.query | semmle.label | request.query | +| fastify.js:42:21:42:41 | request ... onError | semmle.label | request ... onError | +| fastify.js:43:44:43:52 | userInput | semmle.label | userInput | +| fastify.js:47:9:47:43 | userInput | semmle.label | userInput | +| fastify.js:47:21:47:33 | request.query | semmle.label | request.query | +| fastify.js:47:21:47:43 | request ... Timeout | semmle.label | request ... Timeout | +| fastify.js:48:44:48:52 | userInput | semmle.label | userInput | +| fastify.js:52:11:52:50 | userInput | semmle.label | userInput | +| fastify.js:52:23:52:35 | request.query | semmle.label | request.query | +| fastify.js:52:23:52:50 | request ... stAbort | semmle.label | request ... stAbort | +| fastify.js:53:46:53:54 | userInput | semmle.label | userInput | | fastify.js:57:9:57:39 | userInput | semmle.label | userInput | | fastify.js:57:21:57:33 | request.query | semmle.label | request.query | | fastify.js:57:21:57:39 | request.query.input | semmle.label | request.query.input | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js index b8878cdab41..082061bba3f 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js @@ -1,56 +1,56 @@ const fastify = require('fastify')({ logger: true }); fastify.addHook('onRequest', async (request, reply) => { - const userInput = request.query.onRequest; // $ MISSING: Source[js/code-injection] - if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] + const userInput = request.query.onRequest; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] }); fastify.addHook('onSend', async (request, reply, payload) => { - const userInput = request.query.onSend; // $ MISSING: Source[js/code-injection] - if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] + const userInput = request.query.onSend; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] return JSON.stringify({ ...JSON.parse(payload), onSend: request.evalResult }); }); fastify.addHook('preParsing', async (request, reply, payload) => { - const userInput = request.query.preParsing; // $ MISSING: Source[js/code-injection] - if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] + const userInput = request.query.preParsing; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] return payload; }); fastify.addHook('preValidation', async (request, reply) => { - const userInput = request.query.preValidation; // $ MISSING: Source[js/code-injection] - if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] + const userInput = request.query.preValidation; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] }); fastify.addHook('preHandler', async (request, reply) => { - const userInput = request.query.preHandler; // $ MISSING: Source[js/code-injection] - if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] + const userInput = request.query.preHandler; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] }); fastify.addHook('preSerialization', async (request, reply, payload) => { - const userInput = request.query.preSerialization; // $ MISSING: Source[js/code-injection] - if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] + const userInput = request.query.preSerialization; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] return payload; }); fastify.addHook('onResponse', async (request, reply) => { - const userInput = request.query.onResponse; // $ MISSING: Source[js/code-injection] - if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] + const userInput = request.query.onResponse; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] }); fastify.addHook('onError', async (request, reply, error) => { - const userInput = request.query.onError; // $ MISSING: Source[js/code-injection] - if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] + const userInput = request.query.onError; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] }); fastify.addHook('onTimeout', async (request, reply) => { - const userInput = request.query.onTimeout; // $ MISSING: Source[js/code-injection] - if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] + const userInput = request.query.onTimeout; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] }); fastify.addHook('onRequestAbort', (request, done) => { - const userInput = request.query.onRequestAbort; // $ MISSING: Source[js/code-injection] - if (userInput) request.evalResult = eval(userInput); // $ MISSING: Alert[js/code-injection] + const userInput = request.query.onRequestAbort; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] }); fastify.get('/dangerous', async (request, reply) => { From 5c3556da66d7d28d287ec475d51b51dd043aa9b7 Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 15 Apr 2025 09:41:52 +0200 Subject: [PATCH 05/66] Add user-controlled property tracking and update code injection alerts in Fastify hooks --- .../semmle/javascript/frameworks/Fastify.qll | 28 +++++++++++++++++++ .../CodeInjection/CodeInjection.expected | 6 ++++ .../HeuristicSourceCodeInjection.expected | 3 ++ .../Security/CWE-094/CodeInjection/fastify.js | 6 ++-- 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll index 5b2485c7679..53c92e7f7b3 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll @@ -238,6 +238,20 @@ module Fastify { } } + /** + * Gets the property name where user-controlled input is written to a request or response object + * in a route handler. This is used to track taint flow through request and response object properties. + */ + private string getUserControlledPropertyName() { + exists(DataFlow::PropWrite write, DataFlow::Node source, RouteHandler rh | + write.getBase*() = + [rh.getARequestSource().ref().getALocalUse(), rh.getAResponseSource().ref().getALocalUse()] and + write.getPropertyName() = result and + write.getRhs() = source and + source = any(Http::RequestInputAccess ria).getASuccessor*() + ) + } + /** * An access to a user-controlled Fastify request input. */ @@ -252,6 +266,20 @@ module Fastify { or kind = "body" and name = "body" + or + kind = "stored" and + name = getUserControlledPropertyName() + ) + or + // Handle reading from reply object with user input stored on it + exists(string name | + ( + this = rh.getAResponseSource().ref().getAPropertyRead(name) + or + this = rh.getAResponseSource().ref().getAPropertyRead+().getAPropertyRead(name) + ) and + kind = "stored" and + name = getUserControlledPropertyName() ) } diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected index 34487a43e63..9a81aa80a2c 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected @@ -51,6 +51,9 @@ | fastify.js:58:44:58:52 | userInput | fastify.js:57:21:57:39 | request.query.input | fastify.js:58:44:58:52 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:39 | request.query.input | user-provided value | | fastify.js:59:23:59:31 | userInput | fastify.js:57:21:57:33 | request.query | fastify.js:59:23:59:31 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:33 | request.query | user-provided value | | fastify.js:59:23:59:31 | userInput | fastify.js:57:21:57:39 | request.query.input | fastify.js:59:23:59:31 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:39 | request.query.input | user-provided value | +| fastify.js:71:34:71:51 | request.storedCode | fastify.js:71:34:71:51 | request.storedCode | fastify.js:71:34:71:51 | request.storedCode | This code execution depends on a $@. | fastify.js:71:34:71:51 | request.storedCode | user-provided value | +| fastify.js:84:30:84:43 | reply.userCode | fastify.js:84:30:84:43 | reply.userCode | fastify.js:84:30:84:43 | reply.userCode | This code execution depends on a $@. | fastify.js:84:30:84:43 | reply.userCode | user-provided value | +| fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:99:30:99:52 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:99:30:99:52 | reply.l ... tedCode | user-provided value | | module.js:9:16:9:29 | req.query.code | module.js:9:16:9:29 | req.query.code | module.js:9:16:9:29 | req.query.code | This code execution depends on a $@. | module.js:9:16:9:29 | req.query.code | user-provided value | | module.js:11:17:11:30 | req.query.code | module.js:11:17:11:30 | req.query.code | module.js:11:17:11:30 | req.query.code | This code execution depends on a $@. | module.js:11:17:11:30 | req.query.code | user-provided value | | react-native.js:8:32:8:38 | tainted | react-native.js:7:17:7:33 | req.param("code") | react-native.js:8:32:8:38 | tainted | This code execution depends on a $@. | react-native.js:7:17:7:33 | req.param("code") | user-provided value | @@ -247,6 +250,9 @@ nodes | fastify.js:57:21:57:39 | request.query.input | semmle.label | request.query.input | | fastify.js:58:44:58:52 | userInput | semmle.label | userInput | | fastify.js:59:23:59:31 | userInput | semmle.label | userInput | +| fastify.js:71:34:71:51 | request.storedCode | semmle.label | request.storedCode | +| fastify.js:84:30:84:43 | reply.userCode | semmle.label | reply.userCode | +| fastify.js:99:30:99:52 | reply.l ... tedCode | semmle.label | reply.l ... tedCode | | module.js:9:16:9:29 | req.query.code | semmle.label | req.query.code | | module.js:11:17:11:30 | req.query.code | semmle.label | req.query.code | | react-native.js:7:7:7:33 | tainted | semmle.label | tainted | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected index 41550a22c9f..0f52acfa6cd 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected @@ -161,6 +161,9 @@ nodes | fastify.js:57:21:57:39 | request.query.input | semmle.label | request.query.input | | fastify.js:58:44:58:52 | userInput | semmle.label | userInput | | fastify.js:59:23:59:31 | userInput | semmle.label | userInput | +| fastify.js:71:34:71:51 | request.storedCode | semmle.label | request.storedCode | +| fastify.js:84:30:84:43 | reply.userCode | semmle.label | reply.userCode | +| fastify.js:99:30:99:52 | reply.l ... tedCode | semmle.label | reply.l ... tedCode | | module.js:9:16:9:29 | req.query.code | semmle.label | req.query.code | | module.js:11:17:11:30 | req.query.code | semmle.label | req.query.code | | react-native.js:7:7:7:33 | tainted | semmle.label | tainted | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js index 082061bba3f..bb5577c7acc 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js @@ -68,7 +68,7 @@ fastify.addHook('preHandler', async (request, reply) => { fastify.get('/flow-through-request', async (request, reply) => { // Use the stored code from previous hook if (request.storedCode) { - const evaluatedResult = eval(request.storedCode); // $ MISSING: Alert[js/code-injection] + const evaluatedResult = eval(request.storedCode); // $ Alert[js/code-injection] return { result: evaluatedResult }; } return { result: null }; @@ -81,7 +81,7 @@ fastify.addHook('onRequest', async (request, reply) => { fastify.get('/flow-through-reply', async (request, reply) => { // Use the code stored in reply object if (reply.userCode) { - const replyResult = eval(reply.userCode); // $ MISSING: Alert[js/code-injection] + const replyResult = eval(reply.userCode); // $ Alert[js/code-injection] return { result: replyResult }; } return { result: null }; @@ -96,7 +96,7 @@ fastify.addHook('onRequest', async (request, reply) => { fastify.get('/flow-through-reply', async (request, reply) => { // Use the code stored in reply object if (reply.locals && reply.locals.nestedCode) { - const replyResult = eval(reply.locals.nestedCode); // $ MISSING: Alert[js/code-injection] + const replyResult = eval(reply.locals.nestedCode); // $ Alert[js/code-injection] return { result: replyResult }; } return { result: null }; From 00661b62dc489e08716e6c713ce2ca7dac66a07c Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 22 Apr 2025 12:00:02 +0200 Subject: [PATCH 06/66] JS: Add isMiddlewareSetup() hook to Routing model --- .../ql/lib/semmle/javascript/Routing.qll | 39 ++++++++++++++++++- .../semmle/javascript/frameworks/Fastify.qll | 8 +++- .../CodeInjection/CodeInjection.expected | 18 +++++++++ .../HeuristicSourceCodeInjection.expected | 12 ++++++ .../Security/CWE-094/CodeInjection/fastify.js | 6 +-- 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/Routing.qll b/javascript/ql/lib/semmle/javascript/Routing.qll index 29700a255d6..530322a2d2c 100644 --- a/javascript/ql/lib/semmle/javascript/Routing.qll +++ b/javascript/ql/lib/semmle/javascript/Routing.qll @@ -139,6 +139,8 @@ module Routing { predicate mayResumeDispatch() { this.getLastChild().mayResumeDispatch() or + isInMiddlewareSetup(this) + or exists(this.(RouteHandler).getAContinuationInvocation()) or // Leaf nodes that aren't functions are assumed to invoke their continuation @@ -155,6 +157,8 @@ module Routing { predicate definitelyResumesDispatch() { this.getLastChild().definitelyResumesDispatch() or + isInMiddlewareSetup(this) + or exists(this.(RouteHandler).getAContinuationInvocation()) or this instanceof MkRouter @@ -325,6 +329,19 @@ module Routing { DataFlow::Node getValueImplicitlyStoredInAccessPath(int n, string path) { none() } } + /** + * Holds if `node` is installed at a route handler that is declared to be a middleware setup, + * and is therefore assume to resume dispatch. + */ + private predicate isInMiddlewareSetup(Node node) { + exists(RouteSetup::Range range | + node = getRouteSetupNode(range) and + range.isMiddlewareSetup() + ) + or + isInMiddlewareSetup(node.getParent()) + } + /** Holds if `pred` and `succ` are adjacent siblings and `succ` is installed after `pred`. */ private predicate areSiblings(Node pred, Node succ) { exists(ValueNode::Range base, int n | @@ -612,6 +629,20 @@ module Routing { * Holds if this route setup targets `router` and occurs at the given `cfgNode`. */ abstract predicate isInstalledAt(Router::Range router, ControlFlowNode cfgNode); + + /** + * Holds if this is a middleware setup, meaning dispatch will resume after the + * route handlers in this route setup have completed (usually meaning that they have returned a promise, which has resolved). + * + * This should only be overridden when the route setup itself determines whether subsequent + * route handlers are invoked afterwards. + * - For Express-like libraries, the route _handler_ determines whether to resume dispatch, + * based on whether the `next` callback is invoked. For such libraries, do not override `isMiddlewareSetup`. + * - For Fastify-like libraries, the route _setup_ determines whether to resume dispatch. + * For example, `.addHook()` will resume dispatch whereas `.get()` will not. `isMiddlewareSetup()` should thus + * hold for `.addHook()` but not for `.get()` calls. + */ + predicate isMiddlewareSetup() { none() } } /** @@ -892,10 +923,14 @@ module Routing { * based on `Node::Range::getValueAtAccessPath`. */ private DataFlow::Node getAnAccessPathRhs(Node base, int n, string path) { - // Assigned in the body of a route handler function, whi + // Assigned in the body of a route handler function, which is a middleware exists(RouteHandler handler | base = handler | result = AccessPath::getAnAssignmentTo(handler.getParameter(n).ref(), path) and - exists(handler.getAContinuationInvocation()) + ( + exists(handler.getAContinuationInvocation()) + or + isInMiddlewareSetup(handler) + ) ) or // Implicit assignment contributed by framework model diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll index 53c92e7f7b3..2c2f0f6fc11 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll @@ -164,13 +164,19 @@ module Fastify { private class ShorthandRoutingTreeSetup extends Routing::RouteSetup::MethodCall instanceof RouteSetup { - ShorthandRoutingTreeSetup() { not this.getMethodName() = "route" } + ShorthandRoutingTreeSetup() { not this.getMethodName() = ["route", "addHook"] } override string getRelativePath() { result = this.getArgument(0).getStringValue() } override Http::RequestMethodName getHttpMethod() { result = this.getMethodName().toUpperCase() } } + private class AddHookRouteSetup extends Routing::RouteSetup::MethodCall instanceof RouteSetup { + AddHookRouteSetup() { this.getMethodName() = "addHook" } + + override predicate isMiddlewareSetup() { any() } + } + /** Gets the name of the `n`th handler function that can be installed a route setup, in order of execution. */ private string getNthHandlerName(int n) { result = diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected index 9a81aa80a2c..a63cc195ecb 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected @@ -51,8 +51,14 @@ | fastify.js:58:44:58:52 | userInput | fastify.js:57:21:57:39 | request.query.input | fastify.js:58:44:58:52 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:39 | request.query.input | user-provided value | | fastify.js:59:23:59:31 | userInput | fastify.js:57:21:57:33 | request.query | fastify.js:59:23:59:31 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:33 | request.query | user-provided value | | fastify.js:59:23:59:31 | userInput | fastify.js:57:21:57:39 | request.query.input | fastify.js:59:23:59:31 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:39 | request.query.input | user-provided value | +| fastify.js:71:34:71:51 | request.storedCode | fastify.js:66:24:66:36 | request.query | fastify.js:71:34:71:51 | request.storedCode | This code execution depends on a $@. | fastify.js:66:24:66:36 | request.query | user-provided value | +| fastify.js:71:34:71:51 | request.storedCode | fastify.js:66:24:66:47 | request ... redCode | fastify.js:71:34:71:51 | request.storedCode | This code execution depends on a $@. | fastify.js:66:24:66:47 | request ... redCode | user-provided value | | fastify.js:71:34:71:51 | request.storedCode | fastify.js:71:34:71:51 | request.storedCode | fastify.js:71:34:71:51 | request.storedCode | This code execution depends on a $@. | fastify.js:71:34:71:51 | request.storedCode | user-provided value | +| fastify.js:84:30:84:43 | reply.userCode | fastify.js:79:20:79:32 | request.query | fastify.js:84:30:84:43 | reply.userCode | This code execution depends on a $@. | fastify.js:79:20:79:32 | request.query | user-provided value | +| fastify.js:84:30:84:43 | reply.userCode | fastify.js:79:20:79:42 | request ... plyCode | fastify.js:84:30:84:43 | reply.userCode | This code execution depends on a $@. | fastify.js:79:20:79:42 | request ... plyCode | user-provided value | | fastify.js:84:30:84:43 | reply.userCode | fastify.js:84:30:84:43 | reply.userCode | fastify.js:84:30:84:43 | reply.userCode | This code execution depends on a $@. | fastify.js:84:30:84:43 | reply.userCode | user-provided value | +| fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:94:29:94:41 | request.query | fastify.js:99:30:99:52 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:94:29:94:41 | request.query | user-provided value | +| fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:94:29:94:51 | request ... plyCode | fastify.js:99:30:99:52 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:94:29:94:51 | request ... plyCode | user-provided value | | fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:99:30:99:52 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:99:30:99:52 | reply.l ... tedCode | user-provided value | | module.js:9:16:9:29 | req.query.code | module.js:9:16:9:29 | req.query.code | module.js:9:16:9:29 | req.query.code | This code execution depends on a $@. | module.js:9:16:9:29 | req.query.code | user-provided value | | module.js:11:17:11:30 | req.query.code | module.js:11:17:11:30 | req.query.code | module.js:11:17:11:30 | req.query.code | This code execution depends on a $@. | module.js:11:17:11:30 | req.query.code | user-provided value | @@ -136,6 +142,12 @@ edges | fastify.js:57:9:57:39 | userInput | fastify.js:59:23:59:31 | userInput | provenance | | | fastify.js:57:21:57:33 | request.query | fastify.js:57:9:57:39 | userInput | provenance | | | fastify.js:57:21:57:39 | request.query.input | fastify.js:57:9:57:39 | userInput | provenance | | +| fastify.js:66:24:66:36 | request.query | fastify.js:66:24:66:47 | request ... redCode | provenance | | +| fastify.js:66:24:66:47 | request ... redCode | fastify.js:71:34:71:51 | request.storedCode | provenance | | +| fastify.js:79:20:79:32 | request.query | fastify.js:79:20:79:42 | request ... plyCode | provenance | | +| fastify.js:79:20:79:42 | request ... plyCode | fastify.js:84:30:84:43 | reply.userCode | provenance | | +| fastify.js:94:29:94:41 | request.query | fastify.js:94:29:94:51 | request ... plyCode | provenance | | +| fastify.js:94:29:94:51 | request ... plyCode | fastify.js:99:30:99:52 | reply.l ... tedCode | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:8:32:8:38 | tainted | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:10:23:10:29 | tainted | provenance | | | react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted | provenance | | @@ -250,8 +262,14 @@ nodes | fastify.js:57:21:57:39 | request.query.input | semmle.label | request.query.input | | fastify.js:58:44:58:52 | userInput | semmle.label | userInput | | fastify.js:59:23:59:31 | userInput | semmle.label | userInput | +| fastify.js:66:24:66:36 | request.query | semmle.label | request.query | +| fastify.js:66:24:66:47 | request ... redCode | semmle.label | request ... redCode | | fastify.js:71:34:71:51 | request.storedCode | semmle.label | request.storedCode | +| fastify.js:79:20:79:32 | request.query | semmle.label | request.query | +| fastify.js:79:20:79:42 | request ... plyCode | semmle.label | request ... plyCode | | fastify.js:84:30:84:43 | reply.userCode | semmle.label | reply.userCode | +| fastify.js:94:29:94:41 | request.query | semmle.label | request.query | +| fastify.js:94:29:94:51 | request ... plyCode | semmle.label | request ... plyCode | | fastify.js:99:30:99:52 | reply.l ... tedCode | semmle.label | reply.l ... tedCode | | module.js:9:16:9:29 | req.query.code | semmle.label | req.query.code | | module.js:11:17:11:30 | req.query.code | semmle.label | req.query.code | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected index 0f52acfa6cd..aa23d7a6d5a 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected @@ -45,6 +45,12 @@ edges | fastify.js:57:9:57:39 | userInput | fastify.js:59:23:59:31 | userInput | provenance | | | fastify.js:57:21:57:33 | request.query | fastify.js:57:9:57:39 | userInput | provenance | | | fastify.js:57:21:57:39 | request.query.input | fastify.js:57:9:57:39 | userInput | provenance | | +| fastify.js:66:24:66:36 | request.query | fastify.js:66:24:66:47 | request ... redCode | provenance | | +| fastify.js:66:24:66:47 | request ... redCode | fastify.js:71:34:71:51 | request.storedCode | provenance | | +| fastify.js:79:20:79:32 | request.query | fastify.js:79:20:79:42 | request ... plyCode | provenance | | +| fastify.js:79:20:79:42 | request ... plyCode | fastify.js:84:30:84:43 | reply.userCode | provenance | | +| fastify.js:94:29:94:41 | request.query | fastify.js:94:29:94:51 | request ... plyCode | provenance | | +| fastify.js:94:29:94:51 | request ... plyCode | fastify.js:99:30:99:52 | reply.l ... tedCode | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:8:32:8:38 | tainted | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:10:23:10:29 | tainted | provenance | | | react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted | provenance | | @@ -161,8 +167,14 @@ nodes | fastify.js:57:21:57:39 | request.query.input | semmle.label | request.query.input | | fastify.js:58:44:58:52 | userInput | semmle.label | userInput | | fastify.js:59:23:59:31 | userInput | semmle.label | userInput | +| fastify.js:66:24:66:36 | request.query | semmle.label | request.query | +| fastify.js:66:24:66:47 | request ... redCode | semmle.label | request ... redCode | | fastify.js:71:34:71:51 | request.storedCode | semmle.label | request.storedCode | +| fastify.js:79:20:79:32 | request.query | semmle.label | request.query | +| fastify.js:79:20:79:42 | request ... plyCode | semmle.label | request ... plyCode | | fastify.js:84:30:84:43 | reply.userCode | semmle.label | reply.userCode | +| fastify.js:94:29:94:41 | request.query | semmle.label | request.query | +| fastify.js:94:29:94:51 | request ... plyCode | semmle.label | request ... plyCode | | fastify.js:99:30:99:52 | reply.l ... tedCode | semmle.label | reply.l ... tedCode | | module.js:9:16:9:29 | req.query.code | semmle.label | req.query.code | | module.js:11:17:11:30 | req.query.code | semmle.label | req.query.code | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js index bb5577c7acc..e1cba0d277c 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js @@ -63,7 +63,7 @@ fastify.get('/dangerous', async (request, reply) => { // Store user input in request object fastify.addHook('preHandler', async (request, reply) => { - request.storedCode = request.query.storedCode; + request.storedCode = request.query.storedCode; // $ Source[js/code-injection] }); fastify.get('/flow-through-request', async (request, reply) => { // Use the stored code from previous hook @@ -76,7 +76,7 @@ fastify.get('/flow-through-request', async (request, reply) => { // Store user input in reply object fastify.addHook('onRequest', async (request, reply) => { - reply.userCode = request.query.replyCode; + reply.userCode = request.query.replyCode; // $ Source[js/code-injection] }); fastify.get('/flow-through-reply', async (request, reply) => { // Use the code stored in reply object @@ -91,7 +91,7 @@ fastify.get('/flow-through-reply', async (request, reply) => { // Store user input in reply object fastify.addHook('onRequest', async (request, reply) => { reply.locals = reply.locals || {}; - reply.locals.nestedCode = request.query.replyCode; + reply.locals.nestedCode = request.query.replyCode; // $ Source[js/code-injection] }); fastify.get('/flow-through-reply', async (request, reply) => { // Use the code stored in reply object From fdfdcc0d9302ef2fe500aa125f3fc3d43aa9f3d6 Mon Sep 17 00:00:00 2001 From: Napalys Date: Tue, 22 Apr 2025 14:16:45 +0200 Subject: [PATCH 07/66] Undo unnecessary name tracking for request, response objects --- .../semmle/javascript/frameworks/Fastify.qll | 28 ------------------- .../CodeInjection/CodeInjection.expected | 3 -- 2 files changed, 31 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll index 2c2f0f6fc11..aaf1dbfdd77 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll @@ -244,20 +244,6 @@ module Fastify { } } - /** - * Gets the property name where user-controlled input is written to a request or response object - * in a route handler. This is used to track taint flow through request and response object properties. - */ - private string getUserControlledPropertyName() { - exists(DataFlow::PropWrite write, DataFlow::Node source, RouteHandler rh | - write.getBase*() = - [rh.getARequestSource().ref().getALocalUse(), rh.getAResponseSource().ref().getALocalUse()] and - write.getPropertyName() = result and - write.getRhs() = source and - source = any(Http::RequestInputAccess ria).getASuccessor*() - ) - } - /** * An access to a user-controlled Fastify request input. */ @@ -272,20 +258,6 @@ module Fastify { or kind = "body" and name = "body" - or - kind = "stored" and - name = getUserControlledPropertyName() - ) - or - // Handle reading from reply object with user input stored on it - exists(string name | - ( - this = rh.getAResponseSource().ref().getAPropertyRead(name) - or - this = rh.getAResponseSource().ref().getAPropertyRead+().getAPropertyRead(name) - ) and - kind = "stored" and - name = getUserControlledPropertyName() ) } diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected index a63cc195ecb..9ad04af3a2c 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected @@ -53,13 +53,10 @@ | fastify.js:59:23:59:31 | userInput | fastify.js:57:21:57:39 | request.query.input | fastify.js:59:23:59:31 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:39 | request.query.input | user-provided value | | fastify.js:71:34:71:51 | request.storedCode | fastify.js:66:24:66:36 | request.query | fastify.js:71:34:71:51 | request.storedCode | This code execution depends on a $@. | fastify.js:66:24:66:36 | request.query | user-provided value | | fastify.js:71:34:71:51 | request.storedCode | fastify.js:66:24:66:47 | request ... redCode | fastify.js:71:34:71:51 | request.storedCode | This code execution depends on a $@. | fastify.js:66:24:66:47 | request ... redCode | user-provided value | -| fastify.js:71:34:71:51 | request.storedCode | fastify.js:71:34:71:51 | request.storedCode | fastify.js:71:34:71:51 | request.storedCode | This code execution depends on a $@. | fastify.js:71:34:71:51 | request.storedCode | user-provided value | | fastify.js:84:30:84:43 | reply.userCode | fastify.js:79:20:79:32 | request.query | fastify.js:84:30:84:43 | reply.userCode | This code execution depends on a $@. | fastify.js:79:20:79:32 | request.query | user-provided value | | fastify.js:84:30:84:43 | reply.userCode | fastify.js:79:20:79:42 | request ... plyCode | fastify.js:84:30:84:43 | reply.userCode | This code execution depends on a $@. | fastify.js:79:20:79:42 | request ... plyCode | user-provided value | -| fastify.js:84:30:84:43 | reply.userCode | fastify.js:84:30:84:43 | reply.userCode | fastify.js:84:30:84:43 | reply.userCode | This code execution depends on a $@. | fastify.js:84:30:84:43 | reply.userCode | user-provided value | | fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:94:29:94:41 | request.query | fastify.js:99:30:99:52 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:94:29:94:41 | request.query | user-provided value | | fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:94:29:94:51 | request ... plyCode | fastify.js:99:30:99:52 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:94:29:94:51 | request ... plyCode | user-provided value | -| fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:99:30:99:52 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:99:30:99:52 | reply.l ... tedCode | user-provided value | | module.js:9:16:9:29 | req.query.code | module.js:9:16:9:29 | req.query.code | module.js:9:16:9:29 | req.query.code | This code execution depends on a $@. | module.js:9:16:9:29 | req.query.code | user-provided value | | module.js:11:17:11:30 | req.query.code | module.js:11:17:11:30 | req.query.code | module.js:11:17:11:30 | req.query.code | This code execution depends on a $@. | module.js:11:17:11:30 | req.query.code | user-provided value | | react-native.js:8:32:8:38 | tainted | react-native.js:7:17:7:33 | req.param("code") | react-native.js:8:32:8:38 | tainted | This code execution depends on a $@. | react-native.js:7:17:7:33 | req.param("code") | user-provided value | From 6df5a1ef8036b7d50ebafc03569d243ba9194231 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 24 Apr 2025 14:14:21 +0200 Subject: [PATCH 08/66] Rust: Extract `SelfParam`s from crate graph --- rust/extractor/src/crate_graph.rs | 45 +++++++++++++++---- .../dataflow/modeled/inline-flow.expected | 18 ++++++++ .../type-inference/type-inference.ql | 3 +- .../PathResolutionConsistency.expected | 13 ++++++ 4 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 rust/ql/test/query-tests/security/CWE-089/CONSISTENCY/PathResolutionConsistency.expected diff --git a/rust/extractor/src/crate_graph.rs b/rust/extractor/src/crate_graph.rs index 0203e8adc47..8122248aba3 100644 --- a/rust/extractor/src/crate_graph.rs +++ b/rust/extractor/src/crate_graph.rs @@ -383,25 +383,54 @@ fn emit_function( assert_eq!(sig.binders.len(Interner), parameters.len()); let sig = sig.skip_binders(); let ty_vars = &[parameters]; + let function_data = db.function_data(function); + let mut self_param = None; let params = sig .params() .iter() - .map(|p| { + .enumerate() + .filter_map(|(idx, p)| { let type_repr = emit_hir_ty(trap, db, ty_vars, p); - trap.emit(generated::Param { - id: trap::TrapId::Star, - attrs: vec![], - type_repr, - pat: None, - }) + + if idx == 0 && function_data.has_self_param() { + // Check if the self parameter is a reference + let (is_ref, is_mut) = match p.kind(Interner) { + chalk_ir::TyKind::Ref(mutability, _, _) => { + (true, matches!(mutability, chalk_ir::Mutability::Mut)) + } + chalk_ir::TyKind::Raw(mutability, _) => { + (false, matches!(mutability, chalk_ir::Mutability::Mut)) + } + _ => (false, false), + }; + + self_param = Some(trap.emit(generated::SelfParam { + id: trap::TrapId::Star, + attrs: vec![], + type_repr, + is_ref, + is_mut, + lifetime: None, + name: None, + })); + None + } else { + Some(trap.emit(generated::Param { + id: trap::TrapId::Star, + attrs: vec![], + type_repr, + pat: None, + })) + } }) .collect(); let ret_type = emit_hir_ty(trap, db, ty_vars, sig.ret()); + let param_list = trap.emit(generated::ParamList { id: trap::TrapId::Star, params, - self_param: None, + self_param, }); let ret_type = ret_type.map(|ret_type| { trap.emit(generated::RetTypeRepr { diff --git a/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected b/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected index a7d350ee5ac..ff86fd568dd 100644 --- a/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected +++ b/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected @@ -8,19 +8,29 @@ models | 7 | Summary: lang:core; crate::ptr::write; Argument[1]; Argument[0].Reference; value | edges | main.rs:12:9:12:9 | a [Some] | main.rs:13:10:13:19 | a.unwrap() | provenance | MaD:2 | +| main.rs:12:9:12:9 | a [Some] | main.rs:14:13:14:13 | a [Some] | provenance | | | main.rs:12:9:12:9 | a [Some] | main.rs:14:13:14:21 | a.clone() [Some] | provenance | MaD:1 | | main.rs:12:9:12:9 | a [Some] | main.rs:14:13:14:21 | a.clone() [Some] | provenance | generated | | main.rs:12:13:12:28 | Some(...) [Some] | main.rs:12:9:12:9 | a [Some] | provenance | | | main.rs:12:18:12:27 | source(...) | main.rs:12:13:12:28 | Some(...) [Some] | provenance | | +| main.rs:14:9:14:9 | b [&ref, Some] | main.rs:15:10:15:10 | b [&ref, Some] | provenance | | | main.rs:14:9:14:9 | b [Some] | main.rs:15:10:15:19 | b.unwrap() | provenance | MaD:2 | +| main.rs:14:13:14:13 | a [Some] | main.rs:14:13:14:21 | a.clone() [&ref, Some] | provenance | generated | +| main.rs:14:13:14:21 | a.clone() [&ref, Some] | main.rs:14:9:14:9 | b [&ref, Some] | provenance | | | main.rs:14:13:14:21 | a.clone() [Some] | main.rs:14:9:14:9 | b [Some] | provenance | | +| main.rs:15:10:15:10 | b [&ref, Some] | main.rs:15:10:15:19 | b.unwrap() | provenance | MaD:2 | | main.rs:19:9:19:9 | a [Ok] | main.rs:20:10:20:19 | a.unwrap() | provenance | MaD:5 | +| main.rs:19:9:19:9 | a [Ok] | main.rs:21:13:21:13 | a [Ok] | provenance | | | main.rs:19:9:19:9 | a [Ok] | main.rs:21:13:21:21 | a.clone() [Ok] | provenance | MaD:4 | | main.rs:19:9:19:9 | a [Ok] | main.rs:21:13:21:21 | a.clone() [Ok] | provenance | generated | | main.rs:19:31:19:44 | Ok(...) [Ok] | main.rs:19:9:19:9 | a [Ok] | provenance | | | main.rs:19:34:19:43 | source(...) | main.rs:19:31:19:44 | Ok(...) [Ok] | provenance | | +| main.rs:21:9:21:9 | b [&ref, Ok] | main.rs:22:10:22:10 | b [&ref, Ok] | provenance | | | main.rs:21:9:21:9 | b [Ok] | main.rs:22:10:22:19 | b.unwrap() | provenance | MaD:5 | +| main.rs:21:13:21:13 | a [Ok] | main.rs:21:13:21:21 | a.clone() [&ref, Ok] | provenance | generated | +| main.rs:21:13:21:21 | a.clone() [&ref, Ok] | main.rs:21:9:21:9 | b [&ref, Ok] | provenance | | | main.rs:21:13:21:21 | a.clone() [Ok] | main.rs:21:9:21:9 | b [Ok] | provenance | | +| main.rs:22:10:22:10 | b [&ref, Ok] | main.rs:22:10:22:19 | b.unwrap() | provenance | MaD:5 | | main.rs:26:9:26:9 | a | main.rs:27:10:27:10 | a | provenance | | | main.rs:26:9:26:9 | a | main.rs:28:13:28:21 | a.clone() | provenance | generated | | main.rs:26:13:26:22 | source(...) | main.rs:26:9:26:9 | a | provenance | | @@ -56,15 +66,23 @@ nodes | main.rs:12:13:12:28 | Some(...) [Some] | semmle.label | Some(...) [Some] | | main.rs:12:18:12:27 | source(...) | semmle.label | source(...) | | main.rs:13:10:13:19 | a.unwrap() | semmle.label | a.unwrap() | +| main.rs:14:9:14:9 | b [&ref, Some] | semmle.label | b [&ref, Some] | | main.rs:14:9:14:9 | b [Some] | semmle.label | b [Some] | +| main.rs:14:13:14:13 | a [Some] | semmle.label | a [Some] | +| main.rs:14:13:14:21 | a.clone() [&ref, Some] | semmle.label | a.clone() [&ref, Some] | | main.rs:14:13:14:21 | a.clone() [Some] | semmle.label | a.clone() [Some] | +| main.rs:15:10:15:10 | b [&ref, Some] | semmle.label | b [&ref, Some] | | main.rs:15:10:15:19 | b.unwrap() | semmle.label | b.unwrap() | | main.rs:19:9:19:9 | a [Ok] | semmle.label | a [Ok] | | main.rs:19:31:19:44 | Ok(...) [Ok] | semmle.label | Ok(...) [Ok] | | main.rs:19:34:19:43 | source(...) | semmle.label | source(...) | | main.rs:20:10:20:19 | a.unwrap() | semmle.label | a.unwrap() | +| main.rs:21:9:21:9 | b [&ref, Ok] | semmle.label | b [&ref, Ok] | | main.rs:21:9:21:9 | b [Ok] | semmle.label | b [Ok] | +| main.rs:21:13:21:13 | a [Ok] | semmle.label | a [Ok] | +| main.rs:21:13:21:21 | a.clone() [&ref, Ok] | semmle.label | a.clone() [&ref, Ok] | | main.rs:21:13:21:21 | a.clone() [Ok] | semmle.label | a.clone() [Ok] | +| main.rs:22:10:22:10 | b [&ref, Ok] | semmle.label | b [&ref, Ok] | | main.rs:22:10:22:19 | b.unwrap() | semmle.label | b.unwrap() | | main.rs:26:9:26:9 | a | semmle.label | a | | main.rs:26:13:26:22 | source(...) | semmle.label | source(...) | diff --git a/rust/ql/test/library-tests/type-inference/type-inference.ql b/rust/ql/test/library-tests/type-inference/type-inference.ql index d83900e5840..2652699558f 100644 --- a/rust/ql/test/library-tests/type-inference/type-inference.ql +++ b/rust/ql/test/library-tests/type-inference/type-inference.ql @@ -4,7 +4,8 @@ import codeql.rust.internal.TypeInference as TypeInference import TypeInference query predicate inferType(AstNode n, TypePath path, Type t) { - t = TypeInference::inferType(n, path) + t = TypeInference::inferType(n, path) and + n.fromSource() } module ResolveTest implements TestSig { diff --git a/rust/ql/test/query-tests/security/CWE-089/CONSISTENCY/PathResolutionConsistency.expected b/rust/ql/test/query-tests/security/CWE-089/CONSISTENCY/PathResolutionConsistency.expected new file mode 100644 index 00000000000..9567c4ea517 --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-089/CONSISTENCY/PathResolutionConsistency.expected @@ -0,0 +1,13 @@ +multipleMethodCallTargets +| sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | From 7e205366ab370d3d7bf532483dafb23278f924f1 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 25 Apr 2025 08:49:02 +0200 Subject: [PATCH 09/66] Rust: Adjust `clone` modeling --- .../lib/codeql/rust/frameworks/stdlib/Clone.qll | 4 +++- .../dataflow/modeled/inline-flow.expected | 16 ++-------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/rust/ql/lib/codeql/rust/frameworks/stdlib/Clone.qll b/rust/ql/lib/codeql/rust/frameworks/stdlib/Clone.qll index 0798343837e..514c3781355 100644 --- a/rust/ql/lib/codeql/rust/frameworks/stdlib/Clone.qll +++ b/rust/ql/lib/codeql/rust/frameworks/stdlib/Clone.qll @@ -18,7 +18,9 @@ final class CloneCallable extends SummarizedCallable::Range { final override predicate propagatesFlow( string input, string output, boolean preservesValue, string model ) { - input = "Argument[self]" and + // The `clone` method takes a `&self` parameter and dereferences it; + // make sure to not clone the reference itself + input = ["Argument[self].Reference", "Argument[self].WithoutReference"] and output = "ReturnValue" and preservesValue = true and model = "generated" diff --git a/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected b/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected index ff86fd568dd..b5e19855cfa 100644 --- a/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected +++ b/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected @@ -13,24 +13,18 @@ edges | main.rs:12:9:12:9 | a [Some] | main.rs:14:13:14:21 | a.clone() [Some] | provenance | generated | | main.rs:12:13:12:28 | Some(...) [Some] | main.rs:12:9:12:9 | a [Some] | provenance | | | main.rs:12:18:12:27 | source(...) | main.rs:12:13:12:28 | Some(...) [Some] | provenance | | -| main.rs:14:9:14:9 | b [&ref, Some] | main.rs:15:10:15:10 | b [&ref, Some] | provenance | | | main.rs:14:9:14:9 | b [Some] | main.rs:15:10:15:19 | b.unwrap() | provenance | MaD:2 | -| main.rs:14:13:14:13 | a [Some] | main.rs:14:13:14:21 | a.clone() [&ref, Some] | provenance | generated | -| main.rs:14:13:14:21 | a.clone() [&ref, Some] | main.rs:14:9:14:9 | b [&ref, Some] | provenance | | +| main.rs:14:13:14:13 | a [Some] | main.rs:14:13:14:21 | a.clone() [Some] | provenance | generated | | main.rs:14:13:14:21 | a.clone() [Some] | main.rs:14:9:14:9 | b [Some] | provenance | | -| main.rs:15:10:15:10 | b [&ref, Some] | main.rs:15:10:15:19 | b.unwrap() | provenance | MaD:2 | | main.rs:19:9:19:9 | a [Ok] | main.rs:20:10:20:19 | a.unwrap() | provenance | MaD:5 | | main.rs:19:9:19:9 | a [Ok] | main.rs:21:13:21:13 | a [Ok] | provenance | | | main.rs:19:9:19:9 | a [Ok] | main.rs:21:13:21:21 | a.clone() [Ok] | provenance | MaD:4 | | main.rs:19:9:19:9 | a [Ok] | main.rs:21:13:21:21 | a.clone() [Ok] | provenance | generated | | main.rs:19:31:19:44 | Ok(...) [Ok] | main.rs:19:9:19:9 | a [Ok] | provenance | | | main.rs:19:34:19:43 | source(...) | main.rs:19:31:19:44 | Ok(...) [Ok] | provenance | | -| main.rs:21:9:21:9 | b [&ref, Ok] | main.rs:22:10:22:10 | b [&ref, Ok] | provenance | | | main.rs:21:9:21:9 | b [Ok] | main.rs:22:10:22:19 | b.unwrap() | provenance | MaD:5 | -| main.rs:21:13:21:13 | a [Ok] | main.rs:21:13:21:21 | a.clone() [&ref, Ok] | provenance | generated | -| main.rs:21:13:21:21 | a.clone() [&ref, Ok] | main.rs:21:9:21:9 | b [&ref, Ok] | provenance | | +| main.rs:21:13:21:13 | a [Ok] | main.rs:21:13:21:21 | a.clone() [Ok] | provenance | generated | | main.rs:21:13:21:21 | a.clone() [Ok] | main.rs:21:9:21:9 | b [Ok] | provenance | | -| main.rs:22:10:22:10 | b [&ref, Ok] | main.rs:22:10:22:19 | b.unwrap() | provenance | MaD:5 | | main.rs:26:9:26:9 | a | main.rs:27:10:27:10 | a | provenance | | | main.rs:26:9:26:9 | a | main.rs:28:13:28:21 | a.clone() | provenance | generated | | main.rs:26:13:26:22 | source(...) | main.rs:26:9:26:9 | a | provenance | | @@ -66,23 +60,17 @@ nodes | main.rs:12:13:12:28 | Some(...) [Some] | semmle.label | Some(...) [Some] | | main.rs:12:18:12:27 | source(...) | semmle.label | source(...) | | main.rs:13:10:13:19 | a.unwrap() | semmle.label | a.unwrap() | -| main.rs:14:9:14:9 | b [&ref, Some] | semmle.label | b [&ref, Some] | | main.rs:14:9:14:9 | b [Some] | semmle.label | b [Some] | | main.rs:14:13:14:13 | a [Some] | semmle.label | a [Some] | -| main.rs:14:13:14:21 | a.clone() [&ref, Some] | semmle.label | a.clone() [&ref, Some] | | main.rs:14:13:14:21 | a.clone() [Some] | semmle.label | a.clone() [Some] | -| main.rs:15:10:15:10 | b [&ref, Some] | semmle.label | b [&ref, Some] | | main.rs:15:10:15:19 | b.unwrap() | semmle.label | b.unwrap() | | main.rs:19:9:19:9 | a [Ok] | semmle.label | a [Ok] | | main.rs:19:31:19:44 | Ok(...) [Ok] | semmle.label | Ok(...) [Ok] | | main.rs:19:34:19:43 | source(...) | semmle.label | source(...) | | main.rs:20:10:20:19 | a.unwrap() | semmle.label | a.unwrap() | -| main.rs:21:9:21:9 | b [&ref, Ok] | semmle.label | b [&ref, Ok] | | main.rs:21:9:21:9 | b [Ok] | semmle.label | b [Ok] | | main.rs:21:13:21:13 | a [Ok] | semmle.label | a [Ok] | -| main.rs:21:13:21:21 | a.clone() [&ref, Ok] | semmle.label | a.clone() [&ref, Ok] | | main.rs:21:13:21:21 | a.clone() [Ok] | semmle.label | a.clone() [Ok] | -| main.rs:22:10:22:10 | b [&ref, Ok] | semmle.label | b [&ref, Ok] | | main.rs:22:10:22:19 | b.unwrap() | semmle.label | b.unwrap() | | main.rs:26:9:26:9 | a | semmle.label | a | | main.rs:26:13:26:22 | source(...) | semmle.label | source(...) | From ac3c3ae13a11b2a3a1db1e2ddd758b46401e1399 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Thu, 10 Apr 2025 11:45:34 +0200 Subject: [PATCH 10/66] C++: Do not limit second level scopes to the top-level --- .../code/cpp/ir/dataflow/internal/DataFlowPrivate.qll | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll index c5024d07dcb..39975d8883c 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll @@ -1652,8 +1652,6 @@ predicate validParameterAliasStep(Node node1, Node node2) { ) } -private predicate isTopLevel(Cpp::Stmt s) { any(Function f).getBlock().getAStmt() = s } - private Cpp::Stmt getAChainedBranch(Cpp::IfStmt s) { result = s.getThen() or @@ -1684,11 +1682,9 @@ private Instruction getAnInstruction(Node n) { } private newtype TDataFlowSecondLevelScope = - TTopLevelIfBranch(Cpp::Stmt s) { - exists(Cpp::IfStmt ifstmt | s = getAChainedBranch(ifstmt) and isTopLevel(ifstmt)) - } or + TTopLevelIfBranch(Cpp::Stmt s) { s = getAChainedBranch(_) } or TTopLevelSwitchCase(Cpp::SwitchCase s) { - exists(Cpp::SwitchStmt switchstmt | s = switchstmt.getASwitchCase() and isTopLevel(switchstmt)) + exists(Cpp::SwitchStmt switchstmt | s = switchstmt.getASwitchCase()) } /** From 9396f0ee5599abe047211dc7381bda0642a2fa74 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Thu, 24 Apr 2025 20:19:29 +0200 Subject: [PATCH 11/66] C++: Set `defaultFieldFlowBranchLimit` to 3 --- .../code/cpp/ir/dataflow/internal/DataFlowImplSpecific.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplSpecific.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplSpecific.qll index dfd207ed7e5..1b23cf0b9ae 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplSpecific.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplSpecific.qll @@ -31,4 +31,6 @@ module CppDataFlow implements InputSig { predicate viableImplInCallContext = Private::viableImplInCallContext/2; predicate neverSkipInPathGraph = Private::neverSkipInPathGraph/1; + + int defaultFieldFlowBranchLimit() { result = 3 } } From 89a9ae8bf4eb18198a3cfd31ec43c289b3846397 Mon Sep 17 00:00:00 2001 From: Paolo Tranquilli Date: Fri, 25 Apr 2025 17:18:09 +0200 Subject: [PATCH 12/66] Bazel: update `rules_kotlin` to 2.1.3 --- MODULE.bazel | 2 +- .../rules_kotlin/2.1.3-codeql.1/MODULE.bazel | 35 +++++++++++++++++++ .../codeql_add_language_version_option.patch | 32 +++++++++++++++++ .../patches/codeql_do_not_emit_jdeps.patch | 16 +++++++++ .../rules_kotlin/2.1.3-codeql.1/source.json | 9 +++++ .../modules/rules_kotlin/metadata.json | 1 + 6 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/MODULE.bazel create mode 100644 misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/patches/codeql_add_language_version_option.patch create mode 100644 misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/patches/codeql_do_not_emit_jdeps.patch create mode 100644 misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/source.json diff --git a/MODULE.bazel b/MODULE.bazel index ae00bca4390..7e8e36b5309 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -24,7 +24,7 @@ bazel_dep(name = "bazel_skylib", version = "1.7.1") bazel_dep(name = "abseil-cpp", version = "20240116.1", repo_name = "absl") bazel_dep(name = "nlohmann_json", version = "3.11.3", repo_name = "json") bazel_dep(name = "fmt", version = "10.0.0") -bazel_dep(name = "rules_kotlin", version = "2.0.0-codeql.1") +bazel_dep(name = "rules_kotlin", version = "2.1.3-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") diff --git a/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/MODULE.bazel b/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/MODULE.bazel new file mode 100644 index 00000000000..41bef52cf9f --- /dev/null +++ b/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/MODULE.bazel @@ -0,0 +1,35 @@ +module( + name = "rules_kotlin", + version = "2.1.3-codeql.1", + compatibility_level = 1, + repo_name = "rules_kotlin", +) + +bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "bazel_skylib", version = "1.7.1") +bazel_dep(name = "rules_java", version = "7.2.0") +bazel_dep(name = "rules_python", version = "0.23.1") +bazel_dep(name = "rules_cc", version = "0.0.8") +bazel_dep(name = "rules_android", version = "0.1.1") + +rules_kotlin_extensions = use_extension( + "//src/main/starlark/core/repositories:bzlmod_setup.bzl", + "rules_kotlin_extensions", +) +use_repo( + rules_kotlin_extensions, + "com_github_google_ksp", + "com_github_jetbrains_kotlin", + "com_github_pinterest_ktlint", + "kotlinx_serialization_core_jvm", + "kotlinx_serialization_json", + "kotlinx_serialization_json_jvm", +) + +register_toolchains("//kotlin/internal:default_toolchain") + +# TODO(bencodes) We should be able to remove this once rules_android has rolled out official Bzlmod support +remote_android_extensions = use_extension("@bazel_tools//tools/android:android_extensions.bzl", "remote_android_tools_extensions") +use_repo(remote_android_extensions, "android_gmaven_r8", "android_tools") + +bazel_dep(name = "rules_proto", version = "5.3.0-21.7") diff --git a/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/patches/codeql_add_language_version_option.patch b/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/patches/codeql_add_language_version_option.patch new file mode 100644 index 00000000000..b0bf85d4fae --- /dev/null +++ b/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/patches/codeql_add_language_version_option.patch @@ -0,0 +1,32 @@ +diff --git a/src/main/starlark/core/options/opts.kotlinc.bzl b/src/main/starlark/core/options/opts.kotlinc.bzl +index 76df826..ef2d6ca 100644 +--- a/src/main/starlark/core/options/opts.kotlinc.bzl ++++ b/src/main/starlark/core/options/opts.kotlinc.bzl +@@ -33,6 +33,11 @@ def _map_jdk_release_to_flag(version): + return None + return ["-Xjdk-release=%s" % version] + ++def _map_language_version_to_flag(version): ++ if not version: ++ return None ++ return ["-language-version=%s" % version, "-api-version=%s" % version] ++ + _KOPTS_ALL = { + "warn": struct( + args = dict( +@@ -429,6 +434,15 @@ _KOPTS_ALL = { + value_to_flag = None, + map_value_to_flag = _map_jdk_release_to_flag, + ), ++ "language_version": struct( ++ args = dict( ++ default = "1.9", ++ doc = "-language-version", ++ ), ++ type = attr.string, ++ value_to_flag = None, ++ map_value_to_flag = _map_language_version_to_flag, ++ ), + } + + def _merge(key, rule_defined): diff --git a/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/patches/codeql_do_not_emit_jdeps.patch b/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/patches/codeql_do_not_emit_jdeps.patch new file mode 100644 index 00000000000..380c837d06a --- /dev/null +++ b/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/patches/codeql_do_not_emit_jdeps.patch @@ -0,0 +1,16 @@ +Emitting jdeps is broken for the 2.0.0 kotlin extractor, and we don't need those files. +Patching it here rather than passing `--@rules_kotlin//kotlin/settings:jvm_emit_jdeps=false` +allows us to not have to specify that option (and therefore pull in `rules_kotlin`) in `semmle-code`. +diff --git a/kotlin/settings/BUILD.bazel b/kotlin/settings/BUILD.bazel +index 2c93c11..f352b80 100644 +--- a/kotlin/settings/BUILD.bazel ++++ b/kotlin/settings/BUILD.bazel +@@ -25,7 +25,7 @@ release_archive( + # Flag that controls the emission of jdeps files during kotlin jvm compilation. + bool_flag( + name = "jvm_emit_jdeps", +- build_setting_default = True, # Upstream default behavior ++ build_setting_default = False, + visibility = ["//visibility:public"], + ) + diff --git a/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/source.json b/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/source.json new file mode 100644 index 00000000000..8abac8eb4bd --- /dev/null +++ b/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/source.json @@ -0,0 +1,9 @@ +{ + "integrity": "sha256-4USKVrJGJAeyaI3qht9cN1s2oJkb1HjC3dlMlxaBJeI=", + "url": "https://github.com/bazelbuild/rules_kotlin/releases/download/v2.1.3/rules_kotlin-v2.1.3.tar.gz", + "patches": { + "codeql_do_not_emit_jdeps.patch": "sha256-1ir4Aio1SICxnj1wafQ0GefT/m7bwn2n+SQwq19V3A8=", + "codeql_add_language_version_option.patch": "sha256-F7RthnrO6kJlCNcQ76L1Utqll2OwyeFZ/HmT82NwgB4=" + }, + "patch_strip": 1 +} diff --git a/misc/bazel/registry/modules/rules_kotlin/metadata.json b/misc/bazel/registry/modules/rules_kotlin/metadata.json index ac259b2e729..3e11b7df820 100644 --- a/misc/bazel/registry/modules/rules_kotlin/metadata.json +++ b/misc/bazel/registry/modules/rules_kotlin/metadata.json @@ -21,6 +21,7 @@ "github:bazelbuild/rules_kotlin" ], "versions": [ + "2.1.3-codeql.1", "2.0.0-codeql.1" ], "yanked_versions": {} From 4ac104060ec07b47dc7a752cc22d73432c9b1f17 Mon Sep 17 00:00:00 2001 From: Paolo Tranquilli Date: Mon, 28 Apr 2025 10:22:11 +0200 Subject: [PATCH 13/66] Bazel: remove `2.0.0` `rules_kotlin` patching --- .../rules_kotlin/2.0.0-codeql.1/MODULE.bazel | 35 ------------------- .../codeql_add_language_version_option.patch | 34 ------------------ .../patches/codeql_do_not_emit_jdeps.patch | 16 --------- .../rules_kotlin/2.0.0-codeql.1/source.json | 9 ----- .../modules/rules_kotlin/metadata.json | 3 +- 5 files changed, 1 insertion(+), 96 deletions(-) delete mode 100644 misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/MODULE.bazel delete mode 100644 misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/patches/codeql_add_language_version_option.patch delete mode 100644 misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/patches/codeql_do_not_emit_jdeps.patch delete mode 100644 misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/source.json diff --git a/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/MODULE.bazel b/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/MODULE.bazel deleted file mode 100644 index 6c11301e234..00000000000 --- a/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/MODULE.bazel +++ /dev/null @@ -1,35 +0,0 @@ -module( - name = "rules_kotlin", - version = "2.0.0-codeql.1", - compatibility_level = 1, - repo_name = "rules_kotlin", -) - -bazel_dep(name = "platforms", version = "0.0.10") -bazel_dep(name = "bazel_skylib", version = "1.7.1") -bazel_dep(name = "rules_java", version = "7.2.0") -bazel_dep(name = "rules_python", version = "0.23.1") -bazel_dep(name = "rules_cc", version = "0.0.8") -bazel_dep(name = "rules_android", version = "0.1.1") - -rules_kotlin_extensions = use_extension( - "//src/main/starlark/core/repositories:bzlmod_setup.bzl", - "rules_kotlin_extensions", -) -use_repo( - rules_kotlin_extensions, - "com_github_google_ksp", - "com_github_jetbrains_kotlin", - "com_github_pinterest_ktlint", - "kotlinx_serialization_core_jvm", - "kotlinx_serialization_json", - "kotlinx_serialization_json_jvm", -) - -register_toolchains("//kotlin/internal:default_toolchain") - -# TODO(bencodes) We should be able to remove this once rules_android has rolled out official Bzlmod support -remote_android_extensions = use_extension("@bazel_tools//tools/android:android_extensions.bzl", "remote_android_tools_extensions") -use_repo(remote_android_extensions, "android_gmaven_r8", "android_tools") - -bazel_dep(name = "rules_proto", version = "5.3.0-21.7") diff --git a/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/patches/codeql_add_language_version_option.patch b/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/patches/codeql_add_language_version_option.patch deleted file mode 100644 index d5716daba07..00000000000 --- a/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/patches/codeql_add_language_version_option.patch +++ /dev/null @@ -1,34 +0,0 @@ -We need to build different extractor variants with different -language-version options, which is not allowed -in current kotlin_rules -diff --git a/src/main/starlark/core/options/opts.kotlinc.bzl b/src/main/starlark/core/options/opts.kotlinc.bzl -index 5e1461b..b93e6aa 100644 ---- a/src/main/starlark/core/options/opts.kotlinc.bzl -+++ b/src/main/starlark/core/options/opts.kotlinc.bzl -@@ -33,6 +33,11 @@ def _map_jdk_release_to_flag(version): - return None - return ["-Xjdk-release=%s" % version] - -+def _map_language_version_to_flag(version): -+ if not version: -+ return None -+ return ["-language-version=%s" % version, "-api-version=%s" % version] -+ - _KOPTS_ALL = { - "warn": struct( - args = dict( -@@ -417,6 +422,15 @@ _KOPTS_ALL = { - value_to_flag = None, - map_value_to_flag = _map_jdk_release_to_flag, - ), -+ "language_version": struct( -+ args = dict( -+ default = "1.9", -+ doc = "-language-version", -+ ), -+ type = attr.string, -+ value_to_flag = None, -+ map_value_to_flag = _map_language_version_to_flag, -+ ), - } - - # Filters out options that are not available in current compiler release \ No newline at end of file diff --git a/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/patches/codeql_do_not_emit_jdeps.patch b/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/patches/codeql_do_not_emit_jdeps.patch deleted file mode 100644 index 380c837d06a..00000000000 --- a/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/patches/codeql_do_not_emit_jdeps.patch +++ /dev/null @@ -1,16 +0,0 @@ -Emitting jdeps is broken for the 2.0.0 kotlin extractor, and we don't need those files. -Patching it here rather than passing `--@rules_kotlin//kotlin/settings:jvm_emit_jdeps=false` -allows us to not have to specify that option (and therefore pull in `rules_kotlin`) in `semmle-code`. -diff --git a/kotlin/settings/BUILD.bazel b/kotlin/settings/BUILD.bazel -index 2c93c11..f352b80 100644 ---- a/kotlin/settings/BUILD.bazel -+++ b/kotlin/settings/BUILD.bazel -@@ -25,7 +25,7 @@ release_archive( - # Flag that controls the emission of jdeps files during kotlin jvm compilation. - bool_flag( - name = "jvm_emit_jdeps", -- build_setting_default = True, # Upstream default behavior -+ build_setting_default = False, - visibility = ["//visibility:public"], - ) - diff --git a/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/source.json b/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/source.json deleted file mode 100644 index 96d828e3455..00000000000 --- a/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/source.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "integrity": "sha256-2JcjzJ67t72y66yhr30jg+B0YVZDz5ejZrdYp2t9xEM=", - "url": "https://github.com/bazelbuild/rules_kotlin/releases/download/v2.0.0/rules_kotlin-v2.0.0.tar.gz", - "patches": { - "codeql_do_not_emit_jdeps.patch": "sha256-1ir4Aio1SICxnj1wafQ0GefT/m7bwn2n+SQwq19V3A8=", - "codeql_add_language_version_option.patch": "sha256-t8Fm0bYZ4Q4vTqcoXZjyK4WPEoAafjE4whJLNnrnRbg=" - }, - "patch_strip": 1 -} diff --git a/misc/bazel/registry/modules/rules_kotlin/metadata.json b/misc/bazel/registry/modules/rules_kotlin/metadata.json index 3e11b7df820..dace87c72d1 100644 --- a/misc/bazel/registry/modules/rules_kotlin/metadata.json +++ b/misc/bazel/registry/modules/rules_kotlin/metadata.json @@ -21,8 +21,7 @@ "github:bazelbuild/rules_kotlin" ], "versions": [ - "2.1.3-codeql.1", - "2.0.0-codeql.1" + "2.1.3-codeql.1" ], "yanked_versions": {} } From 8b53f8f2a67cd456c48420c2ab950e506fa67c67 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Mon, 28 Apr 2025 12:21:21 +0200 Subject: [PATCH 14/66] Fix, prevent addHook return values from being treated as XSS sinks --- javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll index aaf1dbfdd77..cf8dd76b75e 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll @@ -328,7 +328,11 @@ module Fastify { ResponseSendArgument() { this = rh.getAResponseSource().ref().getAMethodCall("send").getArgument(0) or - this = rh.(DataFlow::FunctionNode).getAReturn() + exists(RouteSetup setup | + rh = setup.getARouteHandler() and + this = rh.(DataFlow::FunctionNode).getAReturn() and + setup.getMethodName() != "addHook" + ) } override RouteHandler getRouteHandler() { result = rh } From acb9c20a7624c0817eaeb962cda1c4d30af44dbe Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Tue, 29 Apr 2025 13:22:11 +0100 Subject: [PATCH 15/66] Use explicit fastTC --- .../Resources/FileNotAlwaysClosedQuery.qll | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/python/ql/src/Resources/FileNotAlwaysClosedQuery.qll b/python/ql/src/Resources/FileNotAlwaysClosedQuery.qll index fe1d6578e11..af31ec6ea4f 100644 --- a/python/ql/src/Resources/FileNotAlwaysClosedQuery.qll +++ b/python/ql/src/Resources/FileNotAlwaysClosedQuery.qll @@ -52,14 +52,29 @@ class FileWrapperCall extends DataFlow::CallCfgNode { abstract class FileClose extends DataFlow::CfgNode { /** Holds if this file close will occur if an exception is thrown at `raises`. */ predicate guardsExceptions(DataFlow::CfgNode raises) { - this.asCfgNode() = raises.asCfgNode().getAnExceptionalSuccessor().getASuccessor*() + cfgGetASuccessorStar(raises.asCfgNode().getAnExceptionalSuccessor(), this.asCfgNode()) or // The expression is after the close call. // This also covers the body of a `with` statement. - raises.asCfgNode() = this.asCfgNode().getASuccessor*() + cfgGetASuccessorStar(this.asCfgNode(), raises.asCfgNode()) } } +private predicate cfgGetASuccessor(ControlFlowNode src, ControlFlowNode sink) { + sink = src.getASuccessor() +} + +pragma[inline] +private predicate cfgGetASuccessorPlus(ControlFlowNode src, ControlFlowNode sink) = + fastTC(cfgGetASuccessor/2)(src, sink) + +pragma[inline] +private predicate cfgGetASuccessorStar(ControlFlowNode src, ControlFlowNode sink) { + src = sink + or + cfgGetASuccessorPlus(src, sink) +} + /** A call to the `.close()` method of a file object. */ class FileCloseCall extends FileClose { FileCloseCall() { exists(DataFlow::MethodCallNode mc | mc.calls(this, "close")) } From 7f9020282dae18959a3ee34c1f21bdf3c744496c Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Tue, 29 Apr 2025 13:16:40 +0200 Subject: [PATCH 16/66] C++: Turn header variant tests that use PCH files into integration tests These tests currently test a code path in the extractor that only exists for these tests. By turning them into integration tests, we actually use the code path that normal database creation uses. --- .../header-variant-tests/clang-pch/a.c | 2 ++ .../header-variant-tests/clang-pch/a.h | 0 .../header-variant-tests/clang-pch/b.c | 0 .../header-variant-tests/clang-pch/b.h | 0 .../header-variant-tests/clang-pch/c.c | 3 +++ .../clang-pch/clang-pch.expected | 0 .../header-variant-tests/clang-pch/clang-pch.ql | 0 .../header-variant-tests/clang-pch/d.c | 1 + .../header-variant-tests/clang-pch/d.h | 0 .../header-variant-tests/clang-pch/e.c | 3 +++ .../header-variant-tests/clang-pch/f.c | 5 +++++ .../header-variant-tests/clang-pch/g.c | 5 +++++ .../header-variant-tests/clang-pch/h.c | 4 ++++ .../header-variant-tests/clang-pch/h1.h | 0 .../header-variant-tests/clang-pch/h2.h | 0 .../header-variant-tests/clang-pch/i.c | 1 - .../header-variant-tests/clang-pch/test.py | 16 ++++++++++++++++ .../header-variant-tests/microsoft-pch/a.c | 1 + .../header-variant-tests/microsoft-pch/a.h | 0 .../header-variant-tests/microsoft-pch/b.c | 6 ++++++ .../header-variant-tests/microsoft-pch/c.c | 6 ++++++ .../microsoft-pch/microsoft-pch.expected | 0 .../microsoft-pch/microsoft-pch.ql | 0 .../header-variant-tests/microsoft-pch/test.py | 10 ++++++++++ cpp/ql/test/header-variant-tests/clang-pch/_.c | 3 --- cpp/ql/test/header-variant-tests/clang-pch/a.c | 3 --- cpp/ql/test/header-variant-tests/clang-pch/c.c | 4 ---- cpp/ql/test/header-variant-tests/clang-pch/d.c | 2 -- cpp/ql/test/header-variant-tests/clang-pch/e.c | 4 ---- cpp/ql/test/header-variant-tests/clang-pch/f.c | 6 ------ cpp/ql/test/header-variant-tests/clang-pch/g.c | 6 ------ cpp/ql/test/header-variant-tests/clang-pch/h.c | 5 ----- .../test/header-variant-tests/microsoft-pch/_.c | 3 --- .../test/header-variant-tests/microsoft-pch/a.c | 2 -- .../test/header-variant-tests/microsoft-pch/b.c | 7 ------- .../test/header-variant-tests/microsoft-pch/c.c | 7 ------- 36 files changed, 62 insertions(+), 53 deletions(-) create mode 100644 cpp/ql/integration-tests/header-variant-tests/clang-pch/a.c rename cpp/ql/{test => integration-tests}/header-variant-tests/clang-pch/a.h (100%) rename cpp/ql/{test => integration-tests}/header-variant-tests/clang-pch/b.c (100%) rename cpp/ql/{test => integration-tests}/header-variant-tests/clang-pch/b.h (100%) create mode 100644 cpp/ql/integration-tests/header-variant-tests/clang-pch/c.c rename cpp/ql/{test => integration-tests}/header-variant-tests/clang-pch/clang-pch.expected (100%) rename cpp/ql/{test => integration-tests}/header-variant-tests/clang-pch/clang-pch.ql (100%) create mode 100644 cpp/ql/integration-tests/header-variant-tests/clang-pch/d.c rename cpp/ql/{test => integration-tests}/header-variant-tests/clang-pch/d.h (100%) create mode 100644 cpp/ql/integration-tests/header-variant-tests/clang-pch/e.c create mode 100644 cpp/ql/integration-tests/header-variant-tests/clang-pch/f.c create mode 100644 cpp/ql/integration-tests/header-variant-tests/clang-pch/g.c create mode 100644 cpp/ql/integration-tests/header-variant-tests/clang-pch/h.c rename cpp/ql/{test => integration-tests}/header-variant-tests/clang-pch/h1.h (100%) rename cpp/ql/{test => integration-tests}/header-variant-tests/clang-pch/h2.h (100%) rename cpp/ql/{test => integration-tests}/header-variant-tests/clang-pch/i.c (64%) create mode 100644 cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py create mode 100644 cpp/ql/integration-tests/header-variant-tests/microsoft-pch/a.c rename cpp/ql/{test => integration-tests}/header-variant-tests/microsoft-pch/a.h (100%) create mode 100644 cpp/ql/integration-tests/header-variant-tests/microsoft-pch/b.c create mode 100644 cpp/ql/integration-tests/header-variant-tests/microsoft-pch/c.c rename cpp/ql/{test => integration-tests}/header-variant-tests/microsoft-pch/microsoft-pch.expected (100%) rename cpp/ql/{test => integration-tests}/header-variant-tests/microsoft-pch/microsoft-pch.ql (100%) create mode 100644 cpp/ql/integration-tests/header-variant-tests/microsoft-pch/test.py delete mode 100644 cpp/ql/test/header-variant-tests/clang-pch/_.c delete mode 100644 cpp/ql/test/header-variant-tests/clang-pch/a.c delete mode 100644 cpp/ql/test/header-variant-tests/clang-pch/c.c delete mode 100644 cpp/ql/test/header-variant-tests/clang-pch/d.c delete mode 100644 cpp/ql/test/header-variant-tests/clang-pch/e.c delete mode 100644 cpp/ql/test/header-variant-tests/clang-pch/f.c delete mode 100644 cpp/ql/test/header-variant-tests/clang-pch/g.c delete mode 100644 cpp/ql/test/header-variant-tests/clang-pch/h.c delete mode 100644 cpp/ql/test/header-variant-tests/microsoft-pch/_.c delete mode 100644 cpp/ql/test/header-variant-tests/microsoft-pch/a.c delete mode 100644 cpp/ql/test/header-variant-tests/microsoft-pch/b.c delete mode 100644 cpp/ql/test/header-variant-tests/microsoft-pch/c.c diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/a.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/a.c new file mode 100644 index 00000000000..f80dfe40160 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/a.c @@ -0,0 +1,2 @@ +#include "a.h" +#define FOUR 4 diff --git a/cpp/ql/test/header-variant-tests/clang-pch/a.h b/cpp/ql/integration-tests/header-variant-tests/clang-pch/a.h similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/a.h rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/a.h diff --git a/cpp/ql/test/header-variant-tests/clang-pch/b.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/b.c similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/b.c rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/b.c diff --git a/cpp/ql/test/header-variant-tests/clang-pch/b.h b/cpp/ql/integration-tests/header-variant-tests/clang-pch/b.h similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/b.h rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/b.h diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/c.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/c.c new file mode 100644 index 00000000000..e8c42c9c4b2 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/c.c @@ -0,0 +1,3 @@ +int main() { + return ONE + FOUR; +} diff --git a/cpp/ql/test/header-variant-tests/clang-pch/clang-pch.expected b/cpp/ql/integration-tests/header-variant-tests/clang-pch/clang-pch.expected similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/clang-pch.expected rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/clang-pch.expected diff --git a/cpp/ql/test/header-variant-tests/clang-pch/clang-pch.ql b/cpp/ql/integration-tests/header-variant-tests/clang-pch/clang-pch.ql similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/clang-pch.ql rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/clang-pch.ql diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/d.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/d.c new file mode 100644 index 00000000000..35e2312fd89 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/d.c @@ -0,0 +1 @@ +#import "d.h" diff --git a/cpp/ql/test/header-variant-tests/clang-pch/d.h b/cpp/ql/integration-tests/header-variant-tests/clang-pch/d.h similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/d.h rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/d.h diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/e.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/e.c new file mode 100644 index 00000000000..571efb6f271 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/e.c @@ -0,0 +1,3 @@ +int main() { + return SEVENTEEN; +} diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/f.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/f.c new file mode 100644 index 00000000000..613824dc783 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/f.c @@ -0,0 +1,5 @@ +#if 1 +#pragma hdrstop +extern int x; +#define SEEN_F +#endif diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/g.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/g.c new file mode 100644 index 00000000000..bf105c29e95 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/g.c @@ -0,0 +1,5 @@ +#ifdef SEEN_F +static int g() { + return 20; +} +#endif diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/h.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/h.c new file mode 100644 index 00000000000..6f183a7070f --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/h.c @@ -0,0 +1,4 @@ +#include "h1.h" +#pragma hdrstop +#include "h2.h" +#define SEEN_H diff --git a/cpp/ql/test/header-variant-tests/clang-pch/h1.h b/cpp/ql/integration-tests/header-variant-tests/clang-pch/h1.h similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/h1.h rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/h1.h diff --git a/cpp/ql/test/header-variant-tests/clang-pch/h2.h b/cpp/ql/integration-tests/header-variant-tests/clang-pch/h2.h similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/h2.h rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/h2.h diff --git a/cpp/ql/test/header-variant-tests/clang-pch/i.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/i.c similarity index 64% rename from cpp/ql/test/header-variant-tests/clang-pch/i.c rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/i.c index 05aa74b3047..1274d675000 100644 --- a/cpp/ql/test/header-variant-tests/clang-pch/i.c +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/i.c @@ -13,4 +13,3 @@ static int h2() { return 32; } #endif -// semmle-extractor-options: --clang -include-pch ${testdir}/clang-pch.testproj/h.pch diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py b/cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py new file mode 100644 index 00000000000..3cf2d42f764 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py @@ -0,0 +1,16 @@ +import os + + +def test(codeql, cpp): + os.mkdir("pch") + codeql.database.create(command=[ + f'"{cpp.get_tool("extractor")}" --mimic-clang -emit-pch -o pch/a.pch a.c', + f'"{cpp.get_tool("extractor")}" --mimic-clang -include-pch pch/a.pch -Iextra_dummy_path b.c', + f'"{cpp.get_tool("extractor")}" --mimic-clang -include pch/a -Iextra_dummy_path c.c', + f'"{cpp.get_tool("extractor")}" --mimic-clang -emit-pch -o pch/d.pch d.c', + f'"{cpp.get_tool("extractor")}" --mimic-clang -include-pch pch/d.pch e.c', + f'"{cpp.get_tool("extractor")}" --mimic-clang -emit-pch -o pch/f.pch f.c', + f'"{cpp.get_tool("extractor")}" --mimic-clang -include-pch pch/f.pch g.c', + f'"{cpp.get_tool("extractor")}" --mimic-clang -emit-pch -o pch/h.pch h.c', + f'"{cpp.get_tool("extractor")}" --mimic-clang -include-pch pch/h.pch i.c', + ]) diff --git a/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/a.c b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/a.c new file mode 100644 index 00000000000..2243de1baf9 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/a.c @@ -0,0 +1 @@ +#include "a.h" diff --git a/cpp/ql/test/header-variant-tests/microsoft-pch/a.h b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/a.h similarity index 100% rename from cpp/ql/test/header-variant-tests/microsoft-pch/a.h rename to cpp/ql/integration-tests/header-variant-tests/microsoft-pch/a.h diff --git a/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/b.c b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/b.c new file mode 100644 index 00000000000..ef8f5d3ca55 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/b.c @@ -0,0 +1,6 @@ +#pragma hdrstop +#include "b.h" + +int b() { + return A; +} diff --git a/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/c.c b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/c.c new file mode 100644 index 00000000000..b270ddd6478 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/c.c @@ -0,0 +1,6 @@ +#include "d.h" +#include "c.h" + +int c() { + return A; +} diff --git a/cpp/ql/test/header-variant-tests/microsoft-pch/microsoft-pch.expected b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/microsoft-pch.expected similarity index 100% rename from cpp/ql/test/header-variant-tests/microsoft-pch/microsoft-pch.expected rename to cpp/ql/integration-tests/header-variant-tests/microsoft-pch/microsoft-pch.expected diff --git a/cpp/ql/test/header-variant-tests/microsoft-pch/microsoft-pch.ql b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/microsoft-pch.ql similarity index 100% rename from cpp/ql/test/header-variant-tests/microsoft-pch/microsoft-pch.ql rename to cpp/ql/integration-tests/header-variant-tests/microsoft-pch/microsoft-pch.ql diff --git a/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/test.py b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/test.py new file mode 100644 index 00000000000..eaa0fdb8786 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/test.py @@ -0,0 +1,10 @@ +import os + + +def test(codeql, cpp): + os.mkdir("pch") + codeql.database.create(command=[ + f'"{cpp.get_tool("extractor")}" --mimic-cl /Yca.h /Fppch/a.pch a.c', + f'"{cpp.get_tool("extractor")}" --mimic-cl /Yub.h /Fppch/a.pch b.c', + f'"{cpp.get_tool("extractor")}" --mimic-cl /Yuc.h /Fppch/a.pch c.c', + ]) diff --git a/cpp/ql/test/header-variant-tests/clang-pch/_.c b/cpp/ql/test/header-variant-tests/clang-pch/_.c deleted file mode 100644 index 6186edd586f..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/_.c +++ /dev/null @@ -1,3 +0,0 @@ -// This file exists to ensure that the output subdirectory exists prior to -// a.c being indexed, as said directory needs to exist for the PCH file to -// be created, and will be created by running the extractor. diff --git a/cpp/ql/test/header-variant-tests/clang-pch/a.c b/cpp/ql/test/header-variant-tests/clang-pch/a.c deleted file mode 100644 index 9164cdc19a5..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/a.c +++ /dev/null @@ -1,3 +0,0 @@ -#include "a.h" -#define FOUR 4 -// semmle-extractor-options: --clang -emit-pch -o ${testdir}/clang-pch.testproj/a.pch diff --git a/cpp/ql/test/header-variant-tests/clang-pch/c.c b/cpp/ql/test/header-variant-tests/clang-pch/c.c deleted file mode 100644 index 0a8b9766713..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/c.c +++ /dev/null @@ -1,4 +0,0 @@ -int main() { - return ONE + FOUR; -} -// semmle-extractor-options: --clang -include ${testdir}/clang-pch.testproj/a -Iextra_dummy_path diff --git a/cpp/ql/test/header-variant-tests/clang-pch/d.c b/cpp/ql/test/header-variant-tests/clang-pch/d.c deleted file mode 100644 index f81af3d1b63..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/d.c +++ /dev/null @@ -1,2 +0,0 @@ -#import "d.h" -// semmle-extractor-options: --clang -emit-pch -o ${testdir}/clang-pch.testproj/d.pch diff --git a/cpp/ql/test/header-variant-tests/clang-pch/e.c b/cpp/ql/test/header-variant-tests/clang-pch/e.c deleted file mode 100644 index abdb5900200..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/e.c +++ /dev/null @@ -1,4 +0,0 @@ -int main() { - return SEVENTEEN; -} -// semmle-extractor-options: --clang -include-pch ${testdir}/clang-pch.testproj/d.pch diff --git a/cpp/ql/test/header-variant-tests/clang-pch/f.c b/cpp/ql/test/header-variant-tests/clang-pch/f.c deleted file mode 100644 index cca56931acd..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/f.c +++ /dev/null @@ -1,6 +0,0 @@ -#if 1 -#pragma hdrstop -extern int x; -#define SEEN_F -#endif -// semmle-extractor-options: --clang -emit-pch -o ${testdir}/clang-pch.testproj/f.pch diff --git a/cpp/ql/test/header-variant-tests/clang-pch/g.c b/cpp/ql/test/header-variant-tests/clang-pch/g.c deleted file mode 100644 index b16ca33ab36..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/g.c +++ /dev/null @@ -1,6 +0,0 @@ -#ifdef SEEN_F -static int g() { - return 20; -} -#endif -// semmle-extractor-options: --clang -include-pch ${testdir}/clang-pch.testproj/f.pch diff --git a/cpp/ql/test/header-variant-tests/clang-pch/h.c b/cpp/ql/test/header-variant-tests/clang-pch/h.c deleted file mode 100644 index 7ffc9b6133c..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/h.c +++ /dev/null @@ -1,5 +0,0 @@ -#include "h1.h" -#pragma hdrstop -#include "h2.h" -#define SEEN_H -// semmle-extractor-options: --clang -emit-pch -o ${testdir}/clang-pch.testproj/h.pch diff --git a/cpp/ql/test/header-variant-tests/microsoft-pch/_.c b/cpp/ql/test/header-variant-tests/microsoft-pch/_.c deleted file mode 100644 index 6186edd586f..00000000000 --- a/cpp/ql/test/header-variant-tests/microsoft-pch/_.c +++ /dev/null @@ -1,3 +0,0 @@ -// This file exists to ensure that the output subdirectory exists prior to -// a.c being indexed, as said directory needs to exist for the PCH file to -// be created, and will be created by running the extractor. diff --git a/cpp/ql/test/header-variant-tests/microsoft-pch/a.c b/cpp/ql/test/header-variant-tests/microsoft-pch/a.c deleted file mode 100644 index ba41bafcaad..00000000000 --- a/cpp/ql/test/header-variant-tests/microsoft-pch/a.c +++ /dev/null @@ -1,2 +0,0 @@ -#include "a.h" -// semmle-extractor-options: --microsoft /Yca.h /Fp${testdir}/microsoft-pch.testproj/a.pch diff --git a/cpp/ql/test/header-variant-tests/microsoft-pch/b.c b/cpp/ql/test/header-variant-tests/microsoft-pch/b.c deleted file mode 100644 index b6ea5085fd1..00000000000 --- a/cpp/ql/test/header-variant-tests/microsoft-pch/b.c +++ /dev/null @@ -1,7 +0,0 @@ -#pragma hdrstop -#include "b.h" - -int b() { - return A; -} -// semmle-extractor-options: --microsoft /Yub.h /Fp${testdir}/microsoft-pch.testproj/a.pch diff --git a/cpp/ql/test/header-variant-tests/microsoft-pch/c.c b/cpp/ql/test/header-variant-tests/microsoft-pch/c.c deleted file mode 100644 index aaddbae8688..00000000000 --- a/cpp/ql/test/header-variant-tests/microsoft-pch/c.c +++ /dev/null @@ -1,7 +0,0 @@ -#include "d.h" -#include "c.h" - -int c() { - return A; -} -// semmle-extractor-options: --microsoft /Yuc.h /Fp${testdir}/microsoft-pch.testproj/a.pch From e40b93b8a353b3fcdd87d4ee4e4e10d08ac866c6 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 29 Apr 2025 14:50:57 +0200 Subject: [PATCH 17/66] JS: Add type-tracking step through simple Promise.all() calls --- .../internal/flow_summaries/Promises.qll | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll index 33299a3f5c0..1122c38320a 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll @@ -4,6 +4,7 @@ private import javascript private import semmle.javascript.dataflow.FlowSummary +private import semmle.javascript.dataflow.TypeTracking private import FlowSummaryUtil DataFlow::SourceNode promiseConstructorRef() { @@ -211,12 +212,57 @@ private class PromiseReject extends SummarizedCallable { } } +/** + * A call to `Promise.all()`. + */ +class PromiseAllCall extends DataFlow::CallNode { + PromiseAllCall() { this = promiseConstructorRef().getAMemberCall("all") } + + /** Gets the source of the input array */ + DataFlow::ArrayCreationNode getInputArray() { result = this.getArgument(0).getALocalSource() } + + /** Gets the `n`th element of the input array */ + DataFlow::Node getNthInput(int n) { result = this.getInputArray().getElement(n) } + + /** Gets a reference to the output array. */ + DataFlow::SourceNode getOutputArray() { + exists(AwaitExpr await | + this.flowsToExpr(await.getOperand()) and + result = await.flow() + ) + or + result = this.getAMethodCall("then").getCallback(0).getParameter(0) + } + + /** Gets the `n`th output */ + DataFlow::SourceNode getNthOutput(int n) { + exists(string prop | + result = this.getOutputArray().getAPropertyRead(prop) and + n = prop.toInt() + ) + } +} + +/** + * Helps type-tracking simple uses of `Promise.all()` such as `const [a, b] = await Promise.all([x, y])`. + * + * Due to limited access path depth, type tracking can't track things that are in a promise and an array + * at once. This generates a step directly from the input array to the output array. + */ +private class PromiseAllStep extends SharedTypeTrackingStep { + override predicate loadStep(DataFlow::Node node1, DataFlow::Node node2, string prop) { + exists(PromiseAllCall call, int n | + node1 = call.getNthInput(n) and + node2 = call.getNthOutput(n) and + prop = Promises::valueProp() + ) + } +} + private class PromiseAll extends SummarizedCallable { PromiseAll() { this = "Promise.all()" } - override DataFlow::InvokeNode getACallSimple() { - result = promiseConstructorRef().getAMemberCall("all") - } + override DataFlow::InvokeNode getACallSimple() { result instanceof PromiseAllCall } override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and From eae1e1cb0225bf8cfa93549d4df7eceea8f38129 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 29 Apr 2025 14:51:17 +0200 Subject: [PATCH 18/66] JS: Make API graphs rely on type-tracking steps in general --- javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 974fdd7c0cb..276fe5a0169 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -850,10 +850,10 @@ module API { ) or lbl = Label::promised() and - PromiseFlow::storeStep(rhs, pred, Promises::valueProp()) + SharedTypeTrackingStep::storeStep(rhs, pred, Promises::valueProp()) or lbl = Label::promisedError() and - PromiseFlow::storeStep(rhs, pred, Promises::errorProp()) + SharedTypeTrackingStep::storeStep(rhs, pred, Promises::errorProp()) or // The return-value of a getter G counts as a definition of property G // (Ordinary methods and properties are handled as PropWrite nodes) @@ -1008,11 +1008,11 @@ module API { propDesc = "" ) or - PromiseFlow::loadStep(pred.getALocalUse(), ref, Promises::valueProp()) and + SharedTypeTrackingStep::loadStep(pred.getALocalUse(), ref, Promises::valueProp()) and lbl = Label::promised() and (propDesc = Promises::valueProp() or propDesc = "") or - PromiseFlow::loadStep(pred.getALocalUse(), ref, Promises::errorProp()) and + SharedTypeTrackingStep::loadStep(pred.getALocalUse(), ref, Promises::errorProp()) and lbl = Label::promisedError() and (propDesc = Promises::errorProp() or propDesc = "") } From b5c596b2ce2450e1969ca751fa3ff3ce47155bf9 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 24 Apr 2025 15:32:12 +0200 Subject: [PATCH 19/66] Shared: Split model printing of summaries and sources/sinks into separate param modules. --- .../CaptureTypeBasedSummaryModels.qll | 6 ++-- .../CaptureTypeBasedSummaryModels.qll | 6 ++-- .../internal/ModelGeneratorImpl.qll | 31 +++++++++++-------- .../modelgenerator/internal/ModelPrinting.qll | 28 ++++++++++++----- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/csharp/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll b/csharp/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll index f7b0633ddd3..62286c27888 100644 --- a/csharp/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll +++ b/csharp/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll @@ -177,15 +177,13 @@ private predicate output(Callable callable, TypeParameter tp, string output) { delegateSink(callable, tp, output) } -private module ModelPrintingInput implements ModelPrintingSig { +private module ModelPrintingInput implements ModelPrintingSummarySig { class SummaryApi = TypeBasedFlowTargetApi; - class SourceOrSinkApi = TypeBasedFlowTargetApi; - string getProvenance() { result = "tb-generated" } } -private module Printing = ModelPrinting; +private module Printing = ModelPrintingSummary; /** * A class of callables that are relevant generating summaries for based diff --git a/java/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll b/java/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll index 36aec805319..9145a077907 100644 --- a/java/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll +++ b/java/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll @@ -284,15 +284,13 @@ private predicate output(Callable callable, TypeVariable tv, string output) { functionalSink(callable, tv, output) } -module ModelPrintingInput implements ModelPrintingSig { +module ModelPrintingInput implements ModelPrintingSummarySig { class SummaryApi = TypeBasedFlowTargetApi; - class SourceOrSinkApi = ModelGeneratorInput::SourceOrSinkTargetApi; - string getProvenance() { result = "tb-generated" } } -private module Printing = ModelPrinting; +private module Printing = ModelPrintingSummary; /** * A class of callables that are relevant generating summaries for based diff --git a/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll b/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll index b9592964f93..59f53c2b1c9 100644 --- a/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll +++ b/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll @@ -370,20 +370,27 @@ module MakeModelGenerator< * based on heuristic data flow. */ module Heuristic { - private module ModelPrintingInput implements Printing::ModelPrintingSig { + private module ModelPrintingSummaryInput implements Printing::ModelPrintingSummarySig { class SummaryApi = DataFlowSummaryTargetApi; + string getProvenance() { result = "df-generated" } + } + + module ModelPrintingSummary = Printing::ModelPrintingSummary; + + private module ModelPrintingSourceOrSinkInput implements Printing::ModelPrintingSourceOrSinkSig { class SourceOrSinkApi = SourceOrSinkTargetApi; string getProvenance() { result = "df-generated" } } - module ModelPrinting = Printing::ModelPrinting; - private string getOutput(ReturnNodeExt node) { result = PrintReturnNodeExt::getOutput(node) } + private module ModelPrintingSourceOrSink = + Printing::ModelPrintingSourceOrSink; + /** * Holds if data can flow from `node1` to `node2` either via a read or a write of an intermediate field `f`. */ @@ -419,7 +426,7 @@ module MakeModelGenerator< api = returnNodeEnclosingCallable(ret) and isOwnInstanceAccessNode(ret) ) and - result = ModelPrinting::asLiftedValueModel(api, qualifierString(), "ReturnValue") + result = ModelPrintingSummary::asLiftedValueModel(api, qualifierString(), "ReturnValue") } private int accessPathLimit0() { result = 2 } @@ -539,7 +546,7 @@ module MakeModelGenerator< input = parameterNodeAsInput(p) and output = getOutput(returnNodeExt) and input != output and - result = ModelPrinting::asLiftedTaintModel(api, input, output) + result = ModelPrintingSummary::asLiftedTaintModel(api, input, output) ) } @@ -572,7 +579,7 @@ module MakeModelGenerator< exists(captureFlow(api0)) and api0.lift() = api.lift() ) and api.isRelevant() and - result = ModelPrinting::asNeutralSummaryModel(api) + result = ModelPrintingSummary::asNeutralSummaryModel(api) } /** @@ -617,7 +624,7 @@ module MakeModelGenerator< sourceNode(source, kind) and api = getEnclosingCallable(sink) and not irrelevantSourceSinkApi(getEnclosingCallable(source), api) and - result = ModelPrinting::asSourceModel(api, getOutput(sink), kind) + result = ModelPrintingSourceOrSink::asSourceModel(api, getOutput(sink), kind) ) } @@ -663,7 +670,7 @@ module MakeModelGenerator< PropagateToSink::flow(src, sink) and sinkNode(sink, kind) and api = getEnclosingCallable(src) and - result = ModelPrinting::asSinkModel(api, asInputArgument(src), kind) + result = ModelPrintingSourceOrSink::asSinkModel(api, asInputArgument(src), kind) ) } } @@ -703,15 +710,13 @@ module MakeModelGenerator< private module PropagateContentFlow = ContentDataFlow::Global; - private module ContentModelPrintingInput implements Printing::ModelPrintingSig { + private module ContentModelPrintingInput implements Printing::ModelPrintingSummarySig { class SummaryApi = DataFlowSummaryTargetApi; - class SourceOrSinkApi = SourceOrSinkTargetApi; - string getProvenance() { result = "dfc-generated" } } - private module ContentModelPrinting = Printing::ModelPrinting; + private module ContentModelPrinting = Printing::ModelPrintingSummary; private string getContentOutput(ReturnNodeExt node) { result = PrintReturnNodeExt::getOutput(node) @@ -1075,6 +1080,6 @@ module MakeModelGenerator< ) ) and api.isRelevant() and - result = Heuristic::ModelPrinting::asNeutralSummaryModel(api) + result = Heuristic::ModelPrintingSummary::asNeutralSummaryModel(api) } } diff --git a/shared/mad/codeql/mad/modelgenerator/internal/ModelPrinting.qll b/shared/mad/codeql/mad/modelgenerator/internal/ModelPrinting.qll index 0ab92f7032b..fc1e0113d1d 100644 --- a/shared/mad/codeql/mad/modelgenerator/internal/ModelPrinting.qll +++ b/shared/mad/codeql/mad/modelgenerator/internal/ModelPrinting.qll @@ -16,7 +16,7 @@ signature module ModelPrintingLangSig { } module ModelPrintingImpl { - signature module ModelPrintingSig { + signature module ModelPrintingSummarySig { /** * The class of APIs relevant for model generation. */ @@ -24,6 +24,16 @@ module ModelPrintingImpl { Lang::Callable lift(); } + /** + * Gets the string representation of the provenance of the models. + */ + string getProvenance(); + } + + signature module ModelPrintingSourceOrSinkSig { + /** + * The class of APIs relevant for model generation. + */ class SourceOrSinkApi extends Lang::Callable; /** @@ -32,14 +42,14 @@ module ModelPrintingImpl { string getProvenance(); } - module ModelPrinting { - /** - * Computes the first columns for MaD rows used for summaries, sources and sinks. - */ - private string asPartialModel(Lang::Callable api) { - result = strictconcat(int i | | Lang::partialModelRow(api, i), ";" order by i) + ";" - } + /** + * Computes the first columns for MaD rows used for summaries, sources and sinks. + */ + private string asPartialModel(Lang::Callable api) { + result = strictconcat(int i | | Lang::partialModelRow(api, i), ";" order by i) + ";" + } + module ModelPrintingSummary { /** * Computes the first columns for neutral MaD rows. */ @@ -106,7 +116,9 @@ module ModelPrintingImpl { preservesValue = false and result = asSummaryModel(api, input, output, "taint", lift) } + } + module ModelPrintingSourceOrSink { /** * Gets the sink model for `api` with `input` and `kind`. */ From bb6530fcf85e47c696c13f02a3b1f00652d3ff04 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 24 Apr 2025 16:12:01 +0200 Subject: [PATCH 20/66] Shared: Make the summary, source and sink model generation a parameterized module. --- .../internal/ModelGeneratorImpl.qll | 1721 +++++++++-------- 1 file changed, 893 insertions(+), 828 deletions(-) diff --git a/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll b/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll index 59f53c2b1c9..d981ef151fd 100644 --- a/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll +++ b/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll @@ -15,7 +15,7 @@ private import ModelPrinting /** * Provides language-specific model generator parameters. */ -signature module ModelGeneratorInputSig Lang> { +signature module ModelGeneratorCommonInputSig Lang> { /** * A Type. */ @@ -49,52 +49,6 @@ signature module ModelGeneratorInputSig /** Gets the enclosing callable of `node`. */ Callable getEnclosingCallable(NodeExtended node); - /** - * Gets the enclosing callable of `node`, when considered as an expression. - */ - Callable getAsExprEnclosingCallable(NodeExtended node); - - /** Gets the parameter corresponding to this node, if any. */ - Parameter asParameter(NodeExtended n); - - /** - * A class of callables that are potentially relevant for generating summary or - * neutral models. - * - * In the Standard library and 3rd party libraries it is the callables (or callables that have a - * super implementation) that can be called from outside the library itself. - */ - class SummaryTargetApi extends Callable { - /** - * Gets the callable that a model will be lifted to. - * - * The lifted callable is relevant in terms of model - * generation (this is ensured by `liftedImpl`). - */ - Callable lift(); - - /** - * Holds if `this` is relevant in terms of model generation. - */ - predicate isRelevant(); - } - - /** - * A class of callables that are potentially relevant for generating source or - * sink models. - */ - class SourceOrSinkTargetApi extends Callable; - - /** - * A class of callables that are potentially relevant for generating source models. - */ - class SourceTargetApi extends SourceOrSinkTargetApi; - - /** - * A class of callables that are potentially relevant for generating sink models. - */ - class SinkTargetApi extends SourceOrSinkTargetApi; - /** * An instance parameter node. */ @@ -113,22 +67,6 @@ signature module ModelGeneratorInputSig */ Type getUnderlyingContentType(Lang::ContentSet c); - /** - * Gets the MaD string representation of the qualifier. - */ - string qualifierString(); - - /** - * Gets the MaD string representation of the parameter `p`. - */ - string parameterAccess(Parameter p); - - /** - * Gets the MaD string representation of the parameter `p` - * when used in content flow. - */ - string parameterContentAccess(Parameter p); - /** * Gets the MaD string representation of return through parameter at position * `pos` of callable `c`. @@ -154,69 +92,26 @@ signature module ModelGeneratorInputSig predicate isOwnInstanceAccessNode(Lang::ReturnNode node); /** - * Holds if `node` is a sanitizer for sink model construction. + * Gets the MaD string representation of the parameter `p`. */ - predicate sinkModelSanitizer(Lang::Node node); + string parameterAccess(Parameter p); /** - * Holds if `source` is an api entrypoint relevant for creating sink models. + * Gets the MaD string representation of the parameter `p` + * when used in content flow. */ - predicate apiSource(Lang::Node source); + string parameterContentAccess(Parameter p); /** - * Gets the MaD input string representation of `source`. + * Gets the MaD string representation of the qualifier. */ - string getInputArgument(Lang::Node source); - - /** - * Holds if it is not relevant to generate a source model for `api`, even - * if flow is detected from a node within `source` to a sink within `api`. - */ - bindingset[sourceEnclosing, api] - predicate irrelevantSourceSinkApi(Callable sourceEnclosing, SourceTargetApi api); - - /** - * Holds if `kind` is a relevant sink kind for creating sink models. - */ - bindingset[kind] - predicate isRelevantSinkKind(string kind); - - /** - * Holds if `kind` is a relevant source kind for creating source models. - */ - bindingset[kind] - predicate isRelevantSourceKind(string kind); + string qualifierString(); /** * Holds if the the content `c` is a container. */ predicate containerContent(Lang::ContentSet c); - /** - * Holds if there is a taint step from `node1` to `node2` in content flow. - */ - predicate isAdditionalContentFlowStep(Lang::Node nodeFrom, Lang::Node nodeTo); - - /** - * Holds if the content set `c` is field like. - */ - predicate isField(Lang::ContentSet c); - - /** - * Holds if the content set `c` is callback like. - */ - predicate isCallback(Lang::ContentSet c); - - /** - * Gets the MaD synthetic name string representation for the content set `c`, if any. - */ - string getSyntheticName(Lang::ContentSet c); - - /** - * Gets the MaD string representation of the content set `c`. - */ - string printContent(Lang::ContentSet c); - /** * Gets the parameter position of the return kind, if any. */ @@ -230,22 +125,6 @@ signature module ModelGeneratorInputSig */ default string getReturnValueString(Lang::ReturnKind kind) { result = "ReturnValue" } - /** - * Holds if it is irrelevant to generate models for `api` based on data flow analysis. - * - * This serves as an extra filter for the `relevant` predicate. - */ - predicate isUninterestingForDataFlowModels(Callable api); - - /** - * Holds if it is irrelevant to generate models for `api` based on the heuristic - * (non-content) flow analysis. - * - * This serves as an extra filter for the `relevant` - * and `isUninterestingForDataFlowModels` predicates. - */ - predicate isUninterestingForHeuristicDataFlowModels(Callable api); - /** * Gets the string representation for the `i`th column in the MaD row for `api`. */ @@ -255,23 +134,14 @@ signature module ModelGeneratorInputSig * Gets the string representation for the `i`th column in the neutral MaD row for `api`. */ string partialNeutralModelRow(Callable api, int i); - - /** - * Holds if `node` is specified as a source with the given kind in a MaD flow - * model. - */ - predicate sourceNode(Lang::Node node, string kind); - - /** - * Holds if `node` is specified as a sink with the given kind in a MaD flow - * model. - */ - predicate sinkNode(Lang::Node node, string kind); } -module MakeModelGenerator< +/** + * Make a factory for constructing different model generators. + */ +module MakeModelGeneratorFactory< LocationSig Location, InputSig Lang, Tt::InputSig TaintLang, - ModelGeneratorInputSig ModelGeneratorInput> + ModelGeneratorCommonInputSig ModelGeneratorInput> { private module DataFlow { import Lang @@ -338,16 +208,6 @@ module MakeModelGenerator< } } - final private class SummaryTargetApiFinal = SummaryTargetApi; - - class DataFlowSummaryTargetApi extends SummaryTargetApiFinal { - DataFlowSummaryTargetApi() { not isUninterestingForDataFlowModels(this) } - } - - class DataFlowSourceTargetApi = SourceTargetApi; - - class DataFlowSinkTargetApi = SinkTargetApi; - /** * Holds if `c` is a relevant content kind, where the underlying type is relevant. */ @@ -365,721 +225,926 @@ module MakeModelGenerator< containerContent(c) } + private string getOutput(ReturnNodeExt node) { + result = PrintReturnNodeExt::getOutput(node) + } + /** - * Provides classes and predicates related to capturing models - * based on heuristic data flow. + * Provides language-specific summary model generator parameters. */ - module Heuristic { - private module ModelPrintingSummaryInput implements Printing::ModelPrintingSummarySig { - class SummaryApi = DataFlowSummaryTargetApi; - - string getProvenance() { result = "df-generated" } - } - - module ModelPrintingSummary = Printing::ModelPrintingSummary; - - private module ModelPrintingSourceOrSinkInput implements Printing::ModelPrintingSourceOrSinkSig { - class SourceOrSinkApi = SourceOrSinkTargetApi; - - string getProvenance() { result = "df-generated" } - } - - private string getOutput(ReturnNodeExt node) { - result = PrintReturnNodeExt::getOutput(node) - } - - private module ModelPrintingSourceOrSink = - Printing::ModelPrintingSourceOrSink; - + signature module SummaryModelGeneratorInputSig { /** - * Holds if data can flow from `node1` to `node2` either via a read or a write of an intermediate field `f`. - */ - private predicate isRelevantTaintStep(DataFlow::Node node1, DataFlow::Node node2) { - exists(DataFlow::ContentSet f | - DataFlow::readStep(node1, f, node2) and - // Partially restrict the content types used for intermediate steps. - (not exists(getUnderlyingContentType(f)) or isRelevantTypeInContent(f)) - ) - or - exists(DataFlow::ContentSet f | DataFlow::storeStep(node1, f, node2) | containerContent(f)) - } - - /** - * Gets the MaD string representation of the parameter node `p`. - */ - string parameterNodeAsInput(DataFlow::ParameterNode p) { - result = parameterAccess(asParameter(p)) - or - result = qualifierString() and p instanceof InstanceParameterNode - } - - /** - * Gets the MaD input string representation of `source`. - */ - private string asInputArgument(NodeExtended source) { result = getInputArgument(source) } - - /** - * Gets the summary model of `api`, if it follows the `fluent` programming pattern (returns `this`). - */ - private string captureQualifierFlow(DataFlowSummaryTargetApi api) { - exists(ReturnNodeExt ret | - api = returnNodeEnclosingCallable(ret) and - isOwnInstanceAccessNode(ret) - ) and - result = ModelPrintingSummary::asLiftedValueModel(api, qualifierString(), "ReturnValue") - } - - private int accessPathLimit0() { result = 2 } - - private newtype TTaintState = - TTaintRead(int n) { n in [0 .. accessPathLimit0()] } or - TTaintStore(int n) { n in [1 .. accessPathLimit0()] } - - abstract private class TaintState extends TTaintState { - abstract string toString(); - } - - /** - * A FlowState representing a tainted read. - */ - private class TaintRead extends TaintState, TTaintRead { - private int step; - - TaintRead() { this = TTaintRead(step) } - - /** - * Gets the flow state step number. - */ - int getStep() { result = step } - - override string toString() { result = "TaintRead(" + step + ")" } - } - - /** - * A FlowState representing a tainted write. - */ - private class TaintStore extends TaintState, TTaintStore { - private int step; - - TaintStore() { this = TTaintStore(step) } - - /** - * Gets the flow state step number. - */ - int getStep() { result = step } - - override string toString() { result = "TaintStore(" + step + ")" } - } - - /** - * A data flow configuration for tracking flow through APIs. - * The sources are the parameters of an API and the sinks are the return values (excluding `this`) and parameters. + * A class of callables that are potentially relevant for generating summary or + * neutral models. * - * This can be used to generate Flow summaries for APIs from parameter to return. + * In the Standard library and 3rd party libraries it is the callables (or callables that have a + * super implementation) that can be called from outside the library itself. */ - private module PropagateFlowConfig implements DataFlow::StateConfigSig { - class FlowState = TaintState; + class SummaryTargetApi extends Callable { + /** + * Gets the callable that a model will be lifted to. + * + * The lifted callable is relevant in terms of model + * generation (this is ensured by `liftedImpl`). + */ + Callable lift(); - predicate isSource(DataFlow::Node source, FlowState state) { - source instanceof DataFlow::ParameterNode and - exists(Callable c | - c = getEnclosingCallable(source) and - c instanceof DataFlowSummaryTargetApi and - not isUninterestingForHeuristicDataFlowModels(c) + /** + * Holds if `this` is relevant in terms of model generation. + */ + predicate isRelevant(); + } + + /** + * Gets the enclosing callable of `node`, when considered as an expression. + */ + Callable getAsExprEnclosingCallable(NodeExtended node); + + /** + * Gets the parameter corresponding to this node, if any. + */ + Parameter asParameter(NodeExtended n); + + /** + * Holds if there is a taint step from `node1` to `node2` in content flow. + */ + predicate isAdditionalContentFlowStep(Lang::Node nodeFrom, Lang::Node nodeTo); + + /** + * Holds if the content set `c` is field like. + */ + predicate isField(Lang::ContentSet c); + + /** + * Holds if the content set `c` is callback like. + */ + predicate isCallback(Lang::ContentSet c); + + /** + * Gets the MaD synthetic name string representation for the content set `c`, if any. + */ + string getSyntheticName(Lang::ContentSet c); + + /** + * Gets the MaD string representation of the content set `c`. + */ + string printContent(Lang::ContentSet c); + + /** + * Holds if it is irrelevant to generate models for `api` based on data flow analysis. + * + * This serves as an extra filter for the `relevant` predicate. + */ + predicate isUninterestingForDataFlowModels(Callable api); + + /** + * Holds if it is irrelevant to generate models for `api` based on the heuristic + * (non-content) flow analysis. + * + * This serves as an extra filter for the `relevant` + * and `isUninterestingForDataFlowModels` predicates. + */ + predicate isUninterestingForHeuristicDataFlowModels(Callable api); + } + + /** + * Make a summary model generator. + */ + module MakeSummaryModelGenerator { + private import SummaryModelGeneratorInput + + final private class SummaryTargetApiFinal = SummaryTargetApi; + + class DataFlowSummaryTargetApi extends SummaryTargetApiFinal { + DataFlowSummaryTargetApi() { not isUninterestingForDataFlowModels(this) } + } + + /** + * Provides classes and predicates related to capturing summary models + * based on heuristic data flow. + */ + module Heuristic { + private module ModelPrintingSummaryInput implements Printing::ModelPrintingSummarySig { + class SummaryApi = DataFlowSummaryTargetApi; + + string getProvenance() { result = "df-generated" } + } + + module ModelPrintingSummary = Printing::ModelPrintingSummary; + + /** + * Gets the MaD string representation of the parameter node `p`. + */ + string parameterNodeAsInput(DataFlow::ParameterNode p) { + result = parameterAccess(asParameter(p)) + or + result = qualifierString() and p instanceof InstanceParameterNode + } + + /** + * Gets the summary model of `api`, if it follows the `fluent` programming pattern (returns `this`). + */ + private string captureQualifierFlow(DataFlowSummaryTargetApi api) { + exists(ReturnNodeExt ret | + api = returnNodeEnclosingCallable(ret) and + isOwnInstanceAccessNode(ret) ) and - state.(TaintRead).getStep() = 0 + result = ModelPrintingSummary::asLiftedValueModel(api, qualifierString(), "ReturnValue") } - predicate isSink(DataFlow::Node sink, FlowState state) { - // Sinks are provided by `isSink/1` - none() + private int accessPathLimit0() { result = 2 } + + private newtype TTaintState = + TTaintRead(int n) { n in [0 .. accessPathLimit0()] } or + TTaintStore(int n) { n in [1 .. accessPathLimit0()] } + + abstract private class TaintState extends TTaintState { + abstract string toString(); } - predicate isSink(DataFlow::Node sink) { - sink instanceof ReturnNodeExt and - not isOwnInstanceAccessNode(sink) and - not exists(captureQualifierFlow(getAsExprEnclosingCallable(sink))) + /** + * A FlowState representing a tainted read. + */ + private class TaintRead extends TaintState, TTaintRead { + private int step; + + TaintRead() { this = TTaintRead(step) } + + /** + * Gets the flow state step number. + */ + int getStep() { result = step } + + override string toString() { result = "TaintRead(" + step + ")" } } - predicate isAdditionalFlowStep( - DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2 + /** + * A FlowState representing a tainted write. + */ + private class TaintStore extends TaintState, TTaintStore { + private int step; + + TaintStore() { this = TTaintStore(step) } + + /** + * Gets the flow state step number. + */ + int getStep() { result = step } + + override string toString() { result = "TaintStore(" + step + ")" } + } + + /** + * A data flow configuration for tracking flow through APIs. + * The sources are the parameters of an API and the sinks are the return values (excluding `this`) and parameters. + * + * This can be used to generate Flow summaries for APIs from parameter to return. + */ + private module PropagateFlowConfig implements DataFlow::StateConfigSig { + class FlowState = TaintState; + + predicate isSource(DataFlow::Node source, FlowState state) { + source instanceof DataFlow::ParameterNode and + exists(Callable c | + c = getEnclosingCallable(source) and + c instanceof DataFlowSummaryTargetApi and + not isUninterestingForHeuristicDataFlowModels(c) + ) and + state.(TaintRead).getStep() = 0 + } + + predicate isSink(DataFlow::Node sink, FlowState state) { + // Sinks are provided by `isSink/1` + none() + } + + predicate isSink(DataFlow::Node sink) { + sink instanceof ReturnNodeExt and + not isOwnInstanceAccessNode(sink) and + not exists(captureQualifierFlow(getAsExprEnclosingCallable(sink))) + } + + predicate isAdditionalFlowStep( + DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2 + ) { + exists(DataFlow::NodeEx n1, DataFlow::NodeEx n2, DataFlow::ContentSet c | + node1 = n1.asNode() and + node2 = n2.asNode() and + DataFlow::storeEx(n1, c.getAStoreContent(), n2, _, _) and + isRelevantContent0(c) and + ( + state1 instanceof TaintRead and state2.(TaintStore).getStep() = 1 + or + state1.(TaintStore).getStep() + 1 = state2.(TaintStore).getStep() + ) + ) + or + exists(DataFlow::ContentSet c | + DataFlow::readStep(node1, c, node2) and + isRelevantContent0(c) and + state1.(TaintRead).getStep() + 1 = state2.(TaintRead).getStep() + ) + } + + predicate isBarrier(DataFlow::Node n) { + exists(Type t | t = n.(NodeExtended).getType() and not isRelevantType(t)) + } + + DataFlow::FlowFeature getAFeature() { + result instanceof DataFlow::FeatureEqualSourceSinkCallContext + } + } + + module PropagateFlow = TaintTracking::GlobalWithState; + + /** + * Gets the summary model(s) of `api`, if there is flow from parameters to return value or parameter. + */ + string captureThroughFlow0( + DataFlowSummaryTargetApi api, DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt ) { - exists(DataFlow::NodeEx n1, DataFlow::NodeEx n2, DataFlow::ContentSet c | - node1 = n1.asNode() and - node2 = n2.asNode() and - DataFlow::storeEx(n1, c.getAStoreContent(), n2, _, _) and - isRelevantContent0(c) and - ( - state1 instanceof TaintRead and state2.(TaintStore).getStep() = 1 + exists(string input, string output | + getEnclosingCallable(p) = api and + getEnclosingCallable(returnNodeExt) = api and + input = parameterNodeAsInput(p) and + output = getOutput(returnNodeExt) and + input != output and + result = ModelPrintingSummary::asLiftedTaintModel(api, input, output) + ) + } + + /** + * Gets the summary model(s) of `api`, if there is flow from parameters to return value or parameter. + */ + private string captureThroughFlow(DataFlowSummaryTargetApi api) { + exists(DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt | + PropagateFlow::flow(p, returnNodeExt) and + result = captureThroughFlow0(api, p, returnNodeExt) + ) + } + + /** + * Gets the summary model(s) of `api`, if there is flow from parameters to the + * return value or parameter or if `api` is a fluent API. + */ + string captureFlow(DataFlowSummaryTargetApi api) { + result = captureQualifierFlow(api) or + result = captureThroughFlow(api) + } + + /** + * Gets the neutral summary model for `api`, if any. + * A neutral summary model is generated, if we are not generating + * a summary model that applies to `api`. + */ + string captureNoFlow(DataFlowSummaryTargetApi api) { + not exists(DataFlowSummaryTargetApi api0 | + exists(captureFlow(api0)) and api0.lift() = api.lift() + ) and + api.isRelevant() and + result = ModelPrintingSummary::asNeutralSummaryModel(api) + } + } + + /** + * Provides classes and predicates related to capturing summary models + * based on content data flow. + */ + module ContentSensitive { + private import MakeImplContentDataFlow as ContentDataFlow + + private module PropagateContentFlowConfig implements ContentDataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source instanceof DataFlow::ParameterNode and + getEnclosingCallable(source) instanceof DataFlowSummaryTargetApi + } + + predicate isSink(DataFlow::Node sink) { + sink instanceof ReturnNodeExt and + getEnclosingCallable(sink) instanceof DataFlowSummaryTargetApi + } + + predicate isAdditionalFlowStep = isAdditionalContentFlowStep/2; + + predicate isBarrier(DataFlow::Node n) { + exists(Type t | t = n.(NodeExtended).getType() and not isRelevantType(t)) + } + + int accessPathLimit() { result = 2 } + + predicate isRelevantContent(DataFlow::ContentSet s) { isRelevantContent0(s) } + + DataFlow::FlowFeature getAFeature() { + result instanceof DataFlow::FeatureEqualSourceSinkCallContext + } + } + + private module PropagateContentFlow = ContentDataFlow::Global; + + private module ContentModelPrintingInput implements Printing::ModelPrintingSummarySig { + class SummaryApi = DataFlowSummaryTargetApi; + + string getProvenance() { result = "dfc-generated" } + } + + private module ContentModelPrinting = + Printing::ModelPrintingSummary; + + private string getContentOutput(ReturnNodeExt node) { + result = PrintReturnNodeExt::getOutput(node) + } + + /** + * Gets the MaD string representation of the parameter `p` + * when used in content flow. + */ + private string parameterNodeAsContentInput(DataFlow::ParameterNode p) { + result = parameterContentAccess(asParameter(p)) + or + result = qualifierString() and p instanceof InstanceParameterNode + } + + private string getContent(PropagateContentFlow::AccessPath ap, int i) { + result = "." + printContent(ap.getAtIndex(i)) + } + + /** + * Gets the MaD string representation of a store step access path. + */ + private string printStoreAccessPath(PropagateContentFlow::AccessPath ap) { + result = concat(int i | | getContent(ap, i), "" order by i) + } + + /** + * Gets the MaD string representation of a read step access path. + */ + private string printReadAccessPath(PropagateContentFlow::AccessPath ap) { + result = concat(int i | | getContent(ap, i), "" order by i desc) + } + + /** + * Holds if the access path `ap` contains a field or synthetic field access. + */ + private predicate mentionsField(PropagateContentFlow::AccessPath ap) { + isField(ap.getAtIndex(_)) + } + + /** + * Holds if this access path `ap` mentions a callback. + */ + private predicate mentionsCallback(PropagateContentFlow::AccessPath ap) { + isCallback(ap.getAtIndex(_)) + } + + /** + * Holds if the access path `ap` is not a parameter or returnvalue of a callback + * stored in a field. + * + * That is, we currently don't include summaries that rely on parameters or return values + * of callbacks stored in fields. + */ + private predicate validateAccessPath(PropagateContentFlow::AccessPath ap) { + not (mentionsField(ap) and mentionsCallback(ap)) + } + + private predicate apiFlow( + DataFlowSummaryTargetApi api, DataFlow::ParameterNode p, + PropagateContentFlow::AccessPath reads, ReturnNodeExt returnNodeExt, + PropagateContentFlow::AccessPath stores, boolean preservesValue + ) { + PropagateContentFlow::flow(p, reads, returnNodeExt, stores, preservesValue) and + getEnclosingCallable(returnNodeExt) = api and + getEnclosingCallable(p) = api + } + + /** + * A class of APIs relevant for modeling using content flow. + * The following heuristic is applied: + * Content flow is only relevant for an API on a parameter, if + * #content flow from parameter <= 3 + * If an API produces more content flow on a parameter, it is likely that + * 1. Types are not sufficiently constrained on the parameter leading to a combinatorial + * explosion in dispatch and thus in the generated summaries. + * 2. It is a reasonable approximation to use the heuristic based flow + * detection instead, as reads and stores would use a significant + * part of an objects internal state. + */ + private class ContentDataFlowSummaryTargetApi extends DataFlowSummaryTargetApi { + private DataFlow::ParameterNode parameter; + + ContentDataFlowSummaryTargetApi() { + strictcount(string input, string output | + exists( + PropagateContentFlow::AccessPath reads, ReturnNodeExt returnNodeExt, + PropagateContentFlow::AccessPath stores + | + apiFlow(this, parameter, reads, returnNodeExt, stores, _) and + input = parameterNodeAsContentInput(parameter) + printReadAccessPath(reads) and + output = getContentOutput(returnNodeExt) + printStoreAccessPath(stores) + ) + ) <= 3 + } + + /** + * Gets a parameter node of `this` api, where there are less than 3 possible models, if any. + */ + DataFlow::ParameterNode getARelevantParameterNode() { result = parameter } + } + + pragma[nomagic] + private predicate apiContentFlow( + ContentDataFlowSummaryTargetApi api, DataFlow::ParameterNode p, + PropagateContentFlow::AccessPath reads, ReturnNodeExt returnNodeExt, + PropagateContentFlow::AccessPath stores, boolean preservesValue + ) { + PropagateContentFlow::flow(p, reads, returnNodeExt, stores, preservesValue) and + getEnclosingCallable(returnNodeExt) = api and + getEnclosingCallable(p) = api and + p = api.getARelevantParameterNode() + } + + /** + * Holds if any of the content sets in `path` translates into a synthetic field. + */ + private predicate hasSyntheticContent(PropagateContentFlow::AccessPath path) { + exists(getSyntheticName(path.getAtIndex(_))) + } + + private string getHashAtIndex(PropagateContentFlow::AccessPath ap, int i) { + result = getSyntheticName(ap.getAtIndex(i)) + } + + private string getReversedHash(PropagateContentFlow::AccessPath ap) { + result = strictconcat(int i | | getHashAtIndex(ap, i), "." order by i desc) + } + + private string getHash(PropagateContentFlow::AccessPath ap) { + result = strictconcat(int i | | getHashAtIndex(ap, i), "." order by i) + } + + /** + * Gets all access paths that contain the synthetic fields + * from `ap` in reverse order (if `ap` contains at least one synthetic field). + * These are the possible candidates for synthetic path continuations. + */ + private PropagateContentFlow::AccessPath getSyntheticPathCandidate( + PropagateContentFlow::AccessPath ap + ) { + getHash(ap) = getReversedHash(result) + } + + /** + * A module containing predicates for validating access paths containing content sets + * that translates into synthetic fields, when used for generated summary models. + */ + private module AccessPathSyntheticValidation { + /** + * Holds if there exists an API that has content flow from `read` (on type `t1`) + * to `store` (on type `t2`). + */ + private predicate step( + Type t1, PropagateContentFlow::AccessPath read, Type t2, + PropagateContentFlow::AccessPath store + ) { + exists(DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt | + p.(NodeExtended).getType() = t1 and + returnNodeExt.getType() = t2 and + apiContentFlow(_, p, read, returnNodeExt, store, _) + ) + } + + /** + * Holds if there exists an API that has content flow from `read` (on type `t1`) + * to `store` (on type `t2`), where `read` does not have synthetic content and `store` does. + * + * Step A -> Synth. + */ + private predicate synthPathEntry( + Type t1, PropagateContentFlow::AccessPath read, Type t2, + PropagateContentFlow::AccessPath store + ) { + not hasSyntheticContent(read) and + hasSyntheticContent(store) and + step(t1, read, t2, store) + } + + /** + * Holds if there exists an API that has content flow from `read` (on type `t1`) + * to `store` (on type `t2`), where `read` has synthetic content + * and `store` does not. + * + * Step Synth -> A. + */ + private predicate synthPathExit( + Type t1, PropagateContentFlow::AccessPath read, Type t2, + PropagateContentFlow::AccessPath store + ) { + hasSyntheticContent(read) and + not hasSyntheticContent(store) and + step(t1, read, t2, store) + } + + /** + * Holds if there exists a path of steps from `read` to an exit. + * + * read ->* Synth -> A + */ + private predicate reachesSynthExit(Type t, PropagateContentFlow::AccessPath read) { + synthPathExit(t, read, _, _) + or + hasSyntheticContent(read) and + exists(PropagateContentFlow::AccessPath mid, Type midType | + hasSyntheticContent(mid) and + step(t, read, midType, mid) and + reachesSynthExit(midType, getSyntheticPathCandidate(mid)) + ) + } + + /** + * Holds if there exists a path of steps from an entry to `store`. + * + * A -> Synth ->* store + */ + private predicate synthEntryReaches(Type t, PropagateContentFlow::AccessPath store) { + synthPathEntry(_, _, t, store) + or + hasSyntheticContent(store) and + exists(PropagateContentFlow::AccessPath mid, Type midType | + hasSyntheticContent(mid) and + step(midType, mid, t, store) and + synthEntryReaches(midType, getSyntheticPathCandidate(mid)) + ) + } + + /** + * Holds if at least one of the access paths `read` (on type `t1`) and `store` (on type `t2`) + * contain content that will be translated into a synthetic field, when being used in + * a MaD summary model, and if there is a range of APIs, such that + * when chaining their flow access paths, there exists access paths `A` and `B` where + * A ->* read -> store ->* B and where `A` and `B` do not contain content that will + * be translated into a synthetic field. + * + * This is needed because we don't want to include summaries that reads from or + * stores into an "internal" synthetic field. + * + * Example: + * Assume we have a type `t` (in this case `t1` = `t2`) with methods `getX` and + * `setX`, which gets and sets a private field `X` on `t`. + * This would lead to the following content flows + * getX : Argument[this].SyntheticField[t.X] -> ReturnValue. + * setX : Argument[0] -> Argument[this].SyntheticField[t.X] + * As the reads and stores are on synthetic fields we should only make summaries + * if both of these methods exist. + */ + pragma[nomagic] + predicate acceptReadStore( + Type t1, PropagateContentFlow::AccessPath read, Type t2, + PropagateContentFlow::AccessPath store + ) { + synthPathEntry(t1, read, t2, store) and + reachesSynthExit(t2, getSyntheticPathCandidate(store)) + or + exists(PropagateContentFlow::AccessPath store0 | + getSyntheticPathCandidate(store0) = read + | + synthEntryReaches(t1, store0) and synthPathExit(t1, read, t2, store) or - state1.(TaintStore).getStep() + 1 = state2.(TaintStore).getStep() + synthEntryReaches(t1, store0) and + step(t1, read, t2, store) and + reachesSynthExit(t2, getSyntheticPathCandidate(store)) + ) + } + } + + /** + * Holds, if the API `api` has relevant flow from `read` on `p` to `store` on `returnNodeExt`. + * Flow is considered relevant, + * 1. If `read` or `store` do not contain a content set that translates into a synthetic field. + * 2. If `read` or `store` contain a content set that translates into a synthetic field, and if + * the synthetic content is "live" on the relevant declaring type. + */ + private predicate apiRelevantContentFlow( + ContentDataFlowSummaryTargetApi api, DataFlow::ParameterNode p, + PropagateContentFlow::AccessPath read, ReturnNodeExt returnNodeExt, + PropagateContentFlow::AccessPath store, boolean preservesValue + ) { + apiContentFlow(api, p, read, returnNodeExt, store, preservesValue) and + ( + not hasSyntheticContent(read) and not hasSyntheticContent(store) + or + AccessPathSyntheticValidation::acceptReadStore(p.(NodeExtended).getType(), read, + returnNodeExt.getType(), store) + ) + } + + pragma[nomagic] + private predicate captureFlow0( + ContentDataFlowSummaryTargetApi api, string input, string output, boolean preservesValue, + boolean lift + ) { + exists( + DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt, + PropagateContentFlow::AccessPath reads, PropagateContentFlow::AccessPath stores + | + apiRelevantContentFlow(api, p, reads, returnNodeExt, stores, preservesValue) and + input = parameterNodeAsContentInput(p) + printReadAccessPath(reads) and + output = getContentOutput(returnNodeExt) + printStoreAccessPath(stores) and + input != output and + validateAccessPath(reads) and + validateAccessPath(stores) and + ( + if mentionsField(reads) or mentionsField(stores) + then lift = false and api.isRelevant() + else lift = true ) ) - or - exists(DataFlow::ContentSet c | - DataFlow::readStep(node1, c, node2) and - isRelevantContent0(c) and - state1.(TaintRead).getStep() + 1 = state2.(TaintRead).getStep() + } + + /** + * Gets the content based summary model(s) of the API `api` (if there is flow from a parameter to + * the return value or a parameter). `lift` is true, if the model should be lifted, otherwise false. + * + * Models are lifted to the best type in case the read and store access paths do not + * contain a field or synthetic field access. + */ + string captureFlow(ContentDataFlowSummaryTargetApi api, boolean lift) { + exists(string input, string output, boolean preservesValue | + captureFlow0(api, input, output, _, lift) and + preservesValue = max(boolean p | captureFlow0(api, input, output, p, lift)) and + result = ContentModelPrinting::asModel(api, input, output, preservesValue, lift) ) } - - predicate isBarrier(DataFlow::Node n) { - exists(Type t | t = n.(NodeExtended).getType() and not isRelevantType(t)) - } - - DataFlow::FlowFeature getAFeature() { - result instanceof DataFlow::FeatureEqualSourceSinkCallContext - } - } - - module PropagateFlow = TaintTracking::GlobalWithState; - - /** - * Gets the summary model(s) of `api`, if there is flow from parameters to return value or parameter. - */ - string captureThroughFlow0( - DataFlowSummaryTargetApi api, DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt - ) { - exists(string input, string output | - getEnclosingCallable(p) = api and - getEnclosingCallable(returnNodeExt) = api and - input = parameterNodeAsInput(p) and - output = getOutput(returnNodeExt) and - input != output and - result = ModelPrintingSummary::asLiftedTaintModel(api, input, output) - ) } /** - * Gets the summary model(s) of `api`, if there is flow from parameters to return value or parameter. + * Gets the summary model(s) for `api`, if any. `lift` is true if the model is lifted + * otherwise false. + * The following heuristic is applied: + * 1. If content based flow yields at lease one summary for an API, then we use that. + * 2. If content based flow does not yield any summary for an API, then we try and + * generate flow summaries using the heuristic based summary generator. */ - private string captureThroughFlow(DataFlowSummaryTargetApi api) { - exists(DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt | - PropagateFlow::flow(p, returnNodeExt) and - result = captureThroughFlow0(api, p, returnNodeExt) - ) - } - - /** - * Gets the summary model(s) of `api`, if there is flow from parameters to the - * return value or parameter or if `api` is a fluent API. - */ - string captureFlow(DataFlowSummaryTargetApi api) { - result = captureQualifierFlow(api) or - result = captureThroughFlow(api) + string captureFlow(DataFlowSummaryTargetApi api, boolean lift) { + result = ContentSensitive::captureFlow(api, lift) + or + not exists(DataFlowSummaryTargetApi api0 | + (api0 = api or api.lift() = api0) and + exists(ContentSensitive::captureFlow(api0, false)) + or + api0.lift() = api.lift() and + exists(ContentSensitive::captureFlow(api0, true)) + ) and + result = Heuristic::captureFlow(api) and + lift = true } /** * Gets the neutral summary model for `api`, if any. * A neutral summary model is generated, if we are not generating - * a summary model that applies to `api`. + * a mixed summary model that applies to `api`. */ - string captureNoFlow(DataFlowSummaryTargetApi api) { - not exists(DataFlowSummaryTargetApi api0 | - exists(captureFlow(api0)) and api0.lift() = api.lift() + string captureNeutral(DataFlowSummaryTargetApi api) { + not exists(DataFlowSummaryTargetApi api0, boolean lift | + exists(captureFlow(api0, lift)) and + ( + lift = false and + (api0 = api or api0 = api.lift()) + or + lift = true and api0.lift() = api.lift() + ) ) and api.isRelevant() and - result = ModelPrintingSummary::asNeutralSummaryModel(api) - } - - /** - * A data flow configuration used for finding new sources. - * The sources are the already known existing sources and the sinks are the API return nodes. - * - * This can be used to generate Source summaries for an API, if the API expose an already known source - * via its return (then the API itself becomes a source). - */ - module PropagateFromSourceConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { - exists(string kind | - isRelevantSourceKind(kind) and - sourceNode(source, kind) - ) - } - - predicate isSink(DataFlow::Node sink) { - sink instanceof ReturnNodeExt and - getEnclosingCallable(sink) instanceof DataFlowSourceTargetApi - } - - DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSinkCallContext } - - predicate isBarrier(DataFlow::Node n) { - exists(Type t | t = n.(NodeExtended).getType() and not isRelevantType(t)) - } - - predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { - isRelevantTaintStep(node1, node2) - } - } - - private module PropagateFromSource = TaintTracking::Global; - - /** - * Gets the source model(s) of `api`, if there is flow from an existing known source to the return of `api`. - */ - string captureSource(DataFlowSourceTargetApi api) { - exists(NodeExtended source, ReturnNodeExt sink, string kind | - PropagateFromSource::flow(source, sink) and - sourceNode(source, kind) and - api = getEnclosingCallable(sink) and - not irrelevantSourceSinkApi(getEnclosingCallable(source), api) and - result = ModelPrintingSourceOrSink::asSourceModel(api, getOutput(sink), kind) - ) - } - - /** - * A data flow configuration used for finding new sinks. - * The sources are the parameters of the API and the fields of the enclosing type. - * - * This can be used to generate Sink summaries for APIs, if the API propagates a parameter (or enclosing type field) - * into an existing known sink (then the API itself becomes a sink). - */ - module PropagateToSinkConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { - apiSource(source) and - getEnclosingCallable(source) instanceof DataFlowSinkTargetApi - } - - predicate isSink(DataFlow::Node sink) { - exists(string kind | isRelevantSinkKind(kind) and sinkNode(sink, kind)) - } - - predicate isBarrier(DataFlow::Node node) { - exists(Type t | t = node.(NodeExtended).getType() and not isRelevantType(t)) - or - sinkModelSanitizer(node) - } - - DataFlow::FlowFeature getAFeature() { - result instanceof DataFlow::FeatureHasSourceCallContext - } - - predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { - isRelevantTaintStep(node1, node2) - } - } - - private module PropagateToSink = TaintTracking::Global; - - /** - * Gets the sink model(s) of `api`, if there is flow from a parameter to an existing known sink. - */ - string captureSink(DataFlowSinkTargetApi api) { - exists(NodeExtended src, NodeExtended sink, string kind | - PropagateToSink::flow(src, sink) and - sinkNode(sink, kind) and - api = getEnclosingCallable(src) and - result = ModelPrintingSourceOrSink::asSinkModel(api, asInputArgument(src), kind) - ) + result = Heuristic::ModelPrintingSummary::asNeutralSummaryModel(api) } } /** - * Provides classes and predicates related to capturing summary models - * based on content data flow. + * Holds if data can flow from `node1` to `node2` either via a read or a write of an intermediate field `f`. */ - module ContentSensitive { - private import MakeImplContentDataFlow as ContentDataFlow - - private module PropagateContentFlowConfig implements ContentDataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { - source instanceof DataFlow::ParameterNode and - getEnclosingCallable(source) instanceof DataFlowSummaryTargetApi - } - - predicate isSink(DataFlow::Node sink) { - sink instanceof ReturnNodeExt and - getEnclosingCallable(sink) instanceof DataFlowSummaryTargetApi - } - - predicate isAdditionalFlowStep = isAdditionalContentFlowStep/2; - - predicate isBarrier(DataFlow::Node n) { - exists(Type t | t = n.(NodeExtended).getType() and not isRelevantType(t)) - } - - int accessPathLimit() { result = 2 } - - predicate isRelevantContent(DataFlow::ContentSet s) { isRelevantContent0(s) } - - DataFlow::FlowFeature getAFeature() { - result instanceof DataFlow::FeatureEqualSourceSinkCallContext - } - } - - private module PropagateContentFlow = ContentDataFlow::Global; - - private module ContentModelPrintingInput implements Printing::ModelPrintingSummarySig { - class SummaryApi = DataFlowSummaryTargetApi; - - string getProvenance() { result = "dfc-generated" } - } - - private module ContentModelPrinting = Printing::ModelPrintingSummary; - - private string getContentOutput(ReturnNodeExt node) { - result = PrintReturnNodeExt::getOutput(node) - } - - /** - * Gets the MaD string representation of the parameter `p` - * when used in content flow. - */ - private string parameterNodeAsContentInput(DataFlow::ParameterNode p) { - result = parameterContentAccess(asParameter(p)) - or - result = qualifierString() and p instanceof InstanceParameterNode - } - - private string getContent(PropagateContentFlow::AccessPath ap, int i) { - result = "." + printContent(ap.getAtIndex(i)) - } - - /** - * Gets the MaD string representation of a store step access path. - */ - private string printStoreAccessPath(PropagateContentFlow::AccessPath ap) { - result = concat(int i | | getContent(ap, i), "" order by i) - } - - /** - * Gets the MaD string representation of a read step access path. - */ - private string printReadAccessPath(PropagateContentFlow::AccessPath ap) { - result = concat(int i | | getContent(ap, i), "" order by i desc) - } - - /** - * Holds if the access path `ap` contains a field or synthetic field access. - */ - private predicate mentionsField(PropagateContentFlow::AccessPath ap) { - isField(ap.getAtIndex(_)) - } - - /** - * Holds if this access path `ap` mentions a callback. - */ - private predicate mentionsCallback(PropagateContentFlow::AccessPath ap) { - isCallback(ap.getAtIndex(_)) - } - - /** - * Holds if the access path `ap` is not a parameter or returnvalue of a callback - * stored in a field. - * - * That is, we currently don't include summaries that rely on parameters or return values - * of callbacks stored in fields. - */ - private predicate validateAccessPath(PropagateContentFlow::AccessPath ap) { - not (mentionsField(ap) and mentionsCallback(ap)) - } - - private predicate apiFlow( - DataFlowSummaryTargetApi api, DataFlow::ParameterNode p, - PropagateContentFlow::AccessPath reads, ReturnNodeExt returnNodeExt, - PropagateContentFlow::AccessPath stores, boolean preservesValue - ) { - PropagateContentFlow::flow(p, reads, returnNodeExt, stores, preservesValue) and - getEnclosingCallable(returnNodeExt) = api and - getEnclosingCallable(p) = api - } - - /** - * A class of APIs relevant for modeling using content flow. - * The following heuristic is applied: - * Content flow is only relevant for an API on a parameter, if - * #content flow from parameter <= 3 - * If an API produces more content flow on a parameter, it is likely that - * 1. Types are not sufficiently constrained on the parameter leading to a combinatorial - * explosion in dispatch and thus in the generated summaries. - * 2. It is a reasonable approximation to use the heuristic based flow - * detection instead, as reads and stores would use a significant - * part of an objects internal state. - */ - private class ContentDataFlowSummaryTargetApi extends DataFlowSummaryTargetApi { - private DataFlow::ParameterNode parameter; - - ContentDataFlowSummaryTargetApi() { - strictcount(string input, string output | - exists( - PropagateContentFlow::AccessPath reads, ReturnNodeExt returnNodeExt, - PropagateContentFlow::AccessPath stores - | - apiFlow(this, parameter, reads, returnNodeExt, stores, _) and - input = parameterNodeAsContentInput(parameter) + printReadAccessPath(reads) and - output = getContentOutput(returnNodeExt) + printStoreAccessPath(stores) - ) - ) <= 3 - } - - /** - * Gets a parameter node of `this` api, where there are less than 3 possible models, if any. - */ - DataFlow::ParameterNode getARelevantParameterNode() { result = parameter } - } - - pragma[nomagic] - private predicate apiContentFlow( - ContentDataFlowSummaryTargetApi api, DataFlow::ParameterNode p, - PropagateContentFlow::AccessPath reads, ReturnNodeExt returnNodeExt, - PropagateContentFlow::AccessPath stores, boolean preservesValue - ) { - PropagateContentFlow::flow(p, reads, returnNodeExt, stores, preservesValue) and - getEnclosingCallable(returnNodeExt) = api and - getEnclosingCallable(p) = api and - p = api.getARelevantParameterNode() - } - - /** - * Holds if any of the content sets in `path` translates into a synthetic field. - */ - private predicate hasSyntheticContent(PropagateContentFlow::AccessPath path) { - exists(getSyntheticName(path.getAtIndex(_))) - } - - private string getHashAtIndex(PropagateContentFlow::AccessPath ap, int i) { - result = getSyntheticName(ap.getAtIndex(i)) - } - - private string getReversedHash(PropagateContentFlow::AccessPath ap) { - result = strictconcat(int i | | getHashAtIndex(ap, i), "." order by i desc) - } - - private string getHash(PropagateContentFlow::AccessPath ap) { - result = strictconcat(int i | | getHashAtIndex(ap, i), "." order by i) - } - - /** - * Gets all access paths that contain the synthetic fields - * from `ap` in reverse order (if `ap` contains at least one synthetic field). - * These are the possible candidates for synthetic path continuations. - */ - private PropagateContentFlow::AccessPath getSyntheticPathCandidate( - PropagateContentFlow::AccessPath ap - ) { - getHash(ap) = getReversedHash(result) - } - - /** - * A module containing predicates for validating access paths containing content sets - * that translates into synthetic fields, when used for generated summary models. - */ - private module AccessPathSyntheticValidation { - /** - * Holds if there exists an API that has content flow from `read` (on type `t1`) - * to `store` (on type `t2`). - */ - private predicate step( - Type t1, PropagateContentFlow::AccessPath read, Type t2, - PropagateContentFlow::AccessPath store - ) { - exists(DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt | - p.(NodeExtended).getType() = t1 and - returnNodeExt.getType() = t2 and - apiContentFlow(_, p, read, returnNodeExt, store, _) - ) - } - - /** - * Holds if there exists an API that has content flow from `read` (on type `t1`) - * to `store` (on type `t2`), where `read` does not have synthetic content and `store` does. - * - * Step A -> Synth. - */ - private predicate synthPathEntry( - Type t1, PropagateContentFlow::AccessPath read, Type t2, - PropagateContentFlow::AccessPath store - ) { - not hasSyntheticContent(read) and - hasSyntheticContent(store) and - step(t1, read, t2, store) - } - - /** - * Holds if there exists an API that has content flow from `read` (on type `t1`) - * to `store` (on type `t2`), where `read` has synthetic content - * and `store` does not. - * - * Step Synth -> A. - */ - private predicate synthPathExit( - Type t1, PropagateContentFlow::AccessPath read, Type t2, - PropagateContentFlow::AccessPath store - ) { - hasSyntheticContent(read) and - not hasSyntheticContent(store) and - step(t1, read, t2, store) - } - - /** - * Holds if there exists a path of steps from `read` to an exit. - * - * read ->* Synth -> A - */ - private predicate reachesSynthExit(Type t, PropagateContentFlow::AccessPath read) { - synthPathExit(t, read, _, _) - or - hasSyntheticContent(read) and - exists(PropagateContentFlow::AccessPath mid, Type midType | - hasSyntheticContent(mid) and - step(t, read, midType, mid) and - reachesSynthExit(midType, getSyntheticPathCandidate(mid)) - ) - } - - /** - * Holds if there exists a path of steps from an entry to `store`. - * - * A -> Synth ->* store - */ - private predicate synthEntryReaches(Type t, PropagateContentFlow::AccessPath store) { - synthPathEntry(_, _, t, store) - or - hasSyntheticContent(store) and - exists(PropagateContentFlow::AccessPath mid, Type midType | - hasSyntheticContent(mid) and - step(midType, mid, t, store) and - synthEntryReaches(midType, getSyntheticPathCandidate(mid)) - ) - } - - /** - * Holds if at least one of the access paths `read` (on type `t1`) and `store` (on type `t2`) - * contain content that will be translated into a synthetic field, when being used in - * a MaD summary model, and if there is a range of APIs, such that - * when chaining their flow access paths, there exists access paths `A` and `B` where - * A ->* read -> store ->* B and where `A` and `B` do not contain content that will - * be translated into a synthetic field. - * - * This is needed because we don't want to include summaries that reads from or - * stores into an "internal" synthetic field. - * - * Example: - * Assume we have a type `t` (in this case `t1` = `t2`) with methods `getX` and - * `setX`, which gets and sets a private field `X` on `t`. - * This would lead to the following content flows - * getX : Argument[this].SyntheticField[t.X] -> ReturnValue. - * setX : Argument[0] -> Argument[this].SyntheticField[t.X] - * As the reads and stores are on synthetic fields we should only make summaries - * if both of these methods exist. - */ - pragma[nomagic] - predicate acceptReadStore( - Type t1, PropagateContentFlow::AccessPath read, Type t2, - PropagateContentFlow::AccessPath store - ) { - synthPathEntry(t1, read, t2, store) and - reachesSynthExit(t2, getSyntheticPathCandidate(store)) - or - exists(PropagateContentFlow::AccessPath store0 | getSyntheticPathCandidate(store0) = read | - synthEntryReaches(t1, store0) and synthPathExit(t1, read, t2, store) - or - synthEntryReaches(t1, store0) and - step(t1, read, t2, store) and - reachesSynthExit(t2, getSyntheticPathCandidate(store)) - ) - } - } - - /** - * Holds, if the API `api` has relevant flow from `read` on `p` to `store` on `returnNodeExt`. - * Flow is considered relevant, - * 1. If `read` or `store` do not contain a content set that translates into a synthetic field. - * 2. If `read` or `store` contain a content set that translates into a synthetic field, and if - * the synthetic content is "live" on the relevant declaring type. - */ - private predicate apiRelevantContentFlow( - ContentDataFlowSummaryTargetApi api, DataFlow::ParameterNode p, - PropagateContentFlow::AccessPath read, ReturnNodeExt returnNodeExt, - PropagateContentFlow::AccessPath store, boolean preservesValue - ) { - apiContentFlow(api, p, read, returnNodeExt, store, preservesValue) and - ( - not hasSyntheticContent(read) and not hasSyntheticContent(store) - or - AccessPathSyntheticValidation::acceptReadStore(p.(NodeExtended).getType(), read, - returnNodeExt.getType(), store) - ) - } - - pragma[nomagic] - private predicate captureFlow0( - ContentDataFlowSummaryTargetApi api, string input, string output, boolean preservesValue, - boolean lift - ) { - exists( - DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt, - PropagateContentFlow::AccessPath reads, PropagateContentFlow::AccessPath stores - | - apiRelevantContentFlow(api, p, reads, returnNodeExt, stores, preservesValue) and - input = parameterNodeAsContentInput(p) + printReadAccessPath(reads) and - output = getContentOutput(returnNodeExt) + printStoreAccessPath(stores) and - input != output and - validateAccessPath(reads) and - validateAccessPath(stores) and - ( - if mentionsField(reads) or mentionsField(stores) - then lift = false and api.isRelevant() - else lift = true - ) - ) - } - - /** - * Gets the content based summary model(s) of the API `api` (if there is flow from a parameter to - * the return value or a parameter). `lift` is true, if the model should be lifted, otherwise false. - * - * Models are lifted to the best type in case the read and store access paths do not - * contain a field or synthetic field access. - */ - string captureFlow(ContentDataFlowSummaryTargetApi api, boolean lift) { - exists(string input, string output, boolean preservesValue | - captureFlow0(api, input, output, _, lift) and - preservesValue = max(boolean p | captureFlow0(api, input, output, p, lift)) and - result = ContentModelPrinting::asModel(api, input, output, preservesValue, lift) - ) - } - } - - /** - * Gets the summary model(s) for `api`, if any. `lift` is true if the model is lifted - * otherwise false. - * The following heuristic is applied: - * 1. If content based flow yields at lease one summary for an API, then we use that. - * 2. If content based flow does not yield any summary for an API, then we try and - * generate flow summaries using the heuristic based summary generator. - */ - string captureFlow(DataFlowSummaryTargetApi api, boolean lift) { - result = ContentSensitive::captureFlow(api, lift) + private predicate isRelevantTaintStep(DataFlow::Node node1, DataFlow::Node node2) { + exists(DataFlow::ContentSet f | + DataFlow::readStep(node1, f, node2) and + // Partially restrict the content types used for intermediate steps. + (not exists(getUnderlyingContentType(f)) or isRelevantTypeInContent(f)) + ) or - not exists(DataFlowSummaryTargetApi api0 | - (api0 = api or api.lift() = api0) and - exists(ContentSensitive::captureFlow(api0, false)) - or - api0.lift() = api.lift() and - exists(ContentSensitive::captureFlow(api0, true)) - ) and - result = Heuristic::captureFlow(api) and - lift = true + exists(DataFlow::ContentSet f | DataFlow::storeStep(node1, f, node2) | containerContent(f)) } /** - * Gets the neutral summary model for `api`, if any. - * A neutral summary model is generated, if we are not generating - * a mixed summary model that applies to `api`. + * Provides language-specific source model generator parameters. */ - string captureNeutral(DataFlowSummaryTargetApi api) { - not exists(DataFlowSummaryTargetApi api0, boolean lift | - exists(captureFlow(api0, lift)) and - ( - lift = false and - (api0 = api or api0 = api.lift()) - or - lift = true and api0.lift() = api.lift() - ) - ) and - api.isRelevant() and - result = Heuristic::ModelPrintingSummary::asNeutralSummaryModel(api) + signature module SourceModelGeneratorInputSig { + /** + * A class of callables that are potentially relevant for generating source models. + */ + class SourceTargetApi extends Callable; + + /** + * Holds if it is not relevant to generate a source model for `api`, even + * if flow is detected from a node within `source` to a sink within `api`. + */ + bindingset[sourceEnclosing, api] + predicate irrelevantSourceSinkApi(Callable sourceEnclosing, SourceTargetApi api); + + /** + * Holds if `kind` is a relevant source kind for creating source models. + */ + bindingset[kind] + predicate isRelevantSourceKind(string kind); + + /** + * Holds if `node` is specified as a source with the given kind in a MaD flow + * model. + */ + predicate sourceNode(Lang::Node node, string kind); + } + + /** + * Provides language-specific sink model generator parameters. + */ + signature module SinkModelGeneratorInputSig { + /** + * A class of callables that are potentially relevant for generating sink models. + */ + class SinkTargetApi extends Callable; + + /** + * Gets the MaD input string representation of `source`. + */ + string getInputArgument(Lang::Node source); + + /** + * Holds if `node` is a sanitizer for sink model construction. + */ + predicate sinkModelSanitizer(Lang::Node node); + + /** + * Holds if `source` is an api entrypoint relevant for creating sink models. + */ + predicate apiSource(Lang::Node source); + + /** + * Holds if `kind` is a relevant sink kind for creating sink models. + */ + bindingset[kind] + predicate isRelevantSinkKind(string kind); + + /** + * Holds if `node` is specified as a sink with the given kind in a MaD flow + * model. + */ + predicate sinkNode(Lang::Node node, string kind); + } + + /** + * Make a source model generator. + */ + module MakeSourceModelGenerator { + private import SourceModelGeneratorInput + + class DataFlowSourceTargetApi = SourceTargetApi; + + /** + * Provides classes and predicates related to capturing source models + * based on heuristic data flow. + */ + module Heuristic { + private module ModelPrintingSourceOrSinkInput implements + Printing::ModelPrintingSourceOrSinkSig + { + class SourceOrSinkApi = DataFlowSourceTargetApi; + + string getProvenance() { result = "df-generated" } + } + + private module ModelPrintingSourceOrSink = + Printing::ModelPrintingSourceOrSink; + + /** + * A data flow configuration used for finding new sources. + * The sources are the already known existing sources and the sinks are the API return nodes. + * + * This can be used to generate Source summaries for an API, if the API expose an already known source + * via its return (then the API itself becomes a source). + */ + module PropagateFromSourceConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + exists(string kind | + isRelevantSourceKind(kind) and + sourceNode(source, kind) + ) + } + + predicate isSink(DataFlow::Node sink) { + sink instanceof ReturnNodeExt and + getEnclosingCallable(sink) instanceof DataFlowSourceTargetApi + } + + DataFlow::FlowFeature getAFeature() { + result instanceof DataFlow::FeatureHasSinkCallContext + } + + predicate isBarrier(DataFlow::Node n) { + exists(Type t | t = n.(NodeExtended).getType() and not isRelevantType(t)) + } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + isRelevantTaintStep(node1, node2) + } + } + + private module PropagateFromSource = TaintTracking::Global; + + /** + * Gets the source model(s) of `api`, if there is flow from an existing known source to the return of `api`. + */ + string captureSource(DataFlowSourceTargetApi api) { + exists(NodeExtended source, ReturnNodeExt sink, string kind | + PropagateFromSource::flow(source, sink) and + sourceNode(source, kind) and + api = getEnclosingCallable(sink) and + not irrelevantSourceSinkApi(getEnclosingCallable(source), api) and + result = ModelPrintingSourceOrSink::asSourceModel(api, getOutput(sink), kind) + ) + } + } + } + + /** + * Make a sink model generator. + */ + module MakeSinkModelGenerator { + private import SinkModelGeneratorInput + + class DataFlowSinkTargetApi = SinkTargetApi; + + /** + * Provides classes and predicates related to capturing sink models + * based on heuristic data flow. + */ + module Heuristic { + private module ModelPrintingSourceOrSinkInput implements + Printing::ModelPrintingSourceOrSinkSig + { + class SourceOrSinkApi = DataFlowSinkTargetApi; + + string getProvenance() { result = "df-generated" } + } + + private module ModelPrintingSourceOrSink = + Printing::ModelPrintingSourceOrSink; + + /** + * Gets the MaD input string representation of `source`. + */ + private string asInputArgument(NodeExtended source) { result = getInputArgument(source) } + + /** + * A data flow configuration used for finding new sinks. + * The sources are the parameters of the API and the fields of the enclosing type. + * + * This can be used to generate Sink summaries for APIs, if the API propagates a parameter (or enclosing type field) + * into an existing known sink (then the API itself becomes a sink). + */ + module PropagateToSinkConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + apiSource(source) and + getEnclosingCallable(source) instanceof DataFlowSinkTargetApi + } + + predicate isSink(DataFlow::Node sink) { + exists(string kind | isRelevantSinkKind(kind) and sinkNode(sink, kind)) + } + + predicate isBarrier(DataFlow::Node node) { + exists(Type t | t = node.(NodeExtended).getType() and not isRelevantType(t)) + or + sinkModelSanitizer(node) + } + + DataFlow::FlowFeature getAFeature() { + result instanceof DataFlow::FeatureHasSourceCallContext + } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + isRelevantTaintStep(node1, node2) + } + } + + private module PropagateToSink = TaintTracking::Global; + + /** + * Gets the sink model(s) of `api`, if there is flow from a parameter to an existing known sink. + */ + string captureSink(DataFlowSinkTargetApi api) { + exists(NodeExtended src, NodeExtended sink, string kind | + PropagateToSink::flow(src, sink) and + sinkNode(sink, kind) and + api = getEnclosingCallable(src) and + result = ModelPrintingSourceOrSink::asSinkModel(api, asInputArgument(src), kind) + ) + } + } } } From 0016fbfa213c4bc04d9239cab76c6233e7ac5982 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 25 Apr 2025 09:34:33 +0200 Subject: [PATCH 21/66] C#: Re-factor implementation to use the new model generator interface. --- .../CaptureContentSummaryModels.ql | 1 + .../modelgenerator/CaptureNeutralModels.ql | 1 + .../utils/modelgenerator/CaptureSinkModels.ql | 1 + .../modelgenerator/CaptureSourceModels.ql | 1 + .../modelgenerator/CaptureSummaryModels.ql | 1 + .../debug/CaptureSummaryModelsPartialPath.ql | 1 + .../debug/CaptureSummaryModelsPath.ql | 1 + .../modelgenerator/internal/CaptureModels.qll | 404 +++++++++--------- .../internal/CaptureModelsPrinting.qll | 2 +- .../CaptureTypeBasedSummaryModels.qll | 5 +- .../dataflow/CaptureContentSummaryModels.ql | 1 + .../dataflow/CaptureHeuristicSummaryModels.ql | 1 + .../dataflow/CaptureNeutralModels.ql | 1 + .../dataflow/CaptureSinkModels.ql | 1 + .../dataflow/CaptureSourceModels.ql | 1 + 15 files changed, 222 insertions(+), 201 deletions(-) diff --git a/csharp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql b/csharp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql index 039c96a9a0b..4d56c922a39 100644 --- a/csharp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql +++ b/csharp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string flow where flow = ContentSensitive::captureFlow(api, _) diff --git a/csharp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql b/csharp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql index 2afb0ea0284..c74240bedea 100644 --- a/csharp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql +++ b/csharp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string noflow where noflow = captureNeutral(api) diff --git a/csharp/ql/src/utils/modelgenerator/CaptureSinkModels.ql b/csharp/ql/src/utils/modelgenerator/CaptureSinkModels.ql index f4c9405c96a..f0d3294cd8a 100644 --- a/csharp/ql/src/utils/modelgenerator/CaptureSinkModels.ql +++ b/csharp/ql/src/utils/modelgenerator/CaptureSinkModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SinkModels from DataFlowSinkTargetApi api, string sink where sink = Heuristic::captureSink(api) diff --git a/csharp/ql/src/utils/modelgenerator/CaptureSourceModels.ql b/csharp/ql/src/utils/modelgenerator/CaptureSourceModels.ql index 70f853b35a9..97688d6eb42 100644 --- a/csharp/ql/src/utils/modelgenerator/CaptureSourceModels.ql +++ b/csharp/ql/src/utils/modelgenerator/CaptureSourceModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SourceModels from DataFlowSourceTargetApi api, string source where source = Heuristic::captureSource(api) diff --git a/csharp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql b/csharp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql index a0193397eb2..61656c200ad 100644 --- a/csharp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql +++ b/csharp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string flow where flow = captureFlow(api, _) diff --git a/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql b/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql index beb14cd8e62..60d7b42a46d 100644 --- a/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql +++ b/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql @@ -10,6 +10,7 @@ import csharp import utils.modelgenerator.internal.CaptureModels +import SummaryModels import PartialFlow::PartialPathGraph int explorationLimit() { result = 3 } diff --git a/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql b/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql index e3de78767ea..a53958ad0e6 100644 --- a/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql +++ b/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql @@ -10,6 +10,7 @@ import csharp import utils.modelgenerator.internal.CaptureModels +import SummaryModels import Heuristic import PropagateFlow::PathGraph diff --git a/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll b/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll index ce83369df07..7bf8a248a6f 100644 --- a/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll +++ b/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll @@ -15,7 +15,41 @@ private import semmle.code.csharp.frameworks.System private import semmle.code.csharp.Location private import codeql.mad.modelgenerator.internal.ModelGeneratorImpl -module ModelGeneratorInput implements ModelGeneratorInputSig { +private predicate irrelevantAccessor(CS::Accessor a) { + a.getDeclaration().(CS::Property).isReadWrite() +} + +private predicate isUninterestingForModels(Callable api) { + api.getDeclaringType().getNamespace().getFullName() = "" + or + api instanceof CS::ConversionOperator + or + api instanceof Util::MainMethod + or + api instanceof CS::Destructor + or + api instanceof CS::AnonymousFunctionExpr + or + api.(CS::Constructor).isParameterless() + or + exists(Type decl | decl = api.getDeclaringType() | + decl instanceof SystemObjectClass or + decl instanceof SystemValueTypeClass + ) + or + // Disregard properties that have both a get and a set accessor, + // which implicitly means auto implemented properties. + irrelevantAccessor(api) +} + +private predicate relevant(Callable api) { + [api.(CS::Modifiable), api.(CS::Accessor).getDeclaration()].isEffectivelyPublic() and + api.fromSource() and + api.isUnboundDeclaration() and + not isUninterestingForModels(api) +} + +module ModelGeneratorCommonInput implements ModelGeneratorCommonInputSig { class Type = CS::Type; class Parameter = CS::Parameter; @@ -24,127 +58,8 @@ module ModelGeneratorInput implements ModelGeneratorInputSig`. - */ - private predicate isHigherOrder(Callable api) { - exists(Type t | t = api.getAParameter().getType().getUnboundDeclaration() | - t instanceof SystemLinqExpressions::DelegateExtType - ) - } - - private predicate irrelevantAccessor(CS::Accessor a) { - a.getDeclaration().(CS::Property).isReadWrite() - } - - private predicate isUninterestingForModels(Callable api) { - api.getDeclaringType().getNamespace().getFullName() = "" - or - api instanceof CS::ConversionOperator - or - api instanceof Util::MainMethod - or - api instanceof CS::Destructor - or - api instanceof CS::AnonymousFunctionExpr - or - api.(CS::Constructor).isParameterless() - or - exists(Type decl | decl = api.getDeclaringType() | - decl instanceof SystemObjectClass or - decl instanceof SystemValueTypeClass - ) - or - // Disregard properties that have both a get and a set accessor, - // which implicitly means auto implemented properties. - irrelevantAccessor(api) - } - - private predicate relevant(Callable api) { - [api.(CS::Modifiable), api.(CS::Accessor).getDeclaration()].isEffectivelyPublic() and - api.fromSource() and - api.isUnboundDeclaration() and - not isUninterestingForModels(api) - } - - private Callable getARelevantOverrideeOrImplementee(Overridable m) { - m.overridesOrImplements(result) and relevant(result) - } - - /** - * Gets the super implementation of `api` if it is relevant. - * If such a super implementation does not exist, returns `api` if it is relevant. - */ - private Callable liftedImpl(Callable api) { - ( - result = getARelevantOverrideeOrImplementee(api) - or - result = api and relevant(api) - ) and - not exists(getARelevantOverrideeOrImplementee(result)) - } - - private predicate hasManualSummaryModel(Callable api) { - api = any(FlowSummaryImpl::Public::SummarizedCallable sc | sc.applyManualModel()) or - api = any(FlowSummaryImpl::Public::NeutralSummaryCallable sc | sc.hasManualModel()) - } - - private predicate hasManualSourceModel(Callable api) { - api = any(ExternalFlow::SourceCallable sc | sc.hasManualModel()) or - api = any(FlowSummaryImpl::Public::NeutralSourceCallable sc | sc.hasManualModel()) - } - - private predicate hasManualSinkModel(Callable api) { - api = any(ExternalFlow::SinkCallable sc | sc.hasManualModel()) or - api = any(FlowSummaryImpl::Public::NeutralSinkCallable sc | sc.hasManualModel()) - } - - predicate isUninterestingForDataFlowModels(Callable api) { none() } - - predicate isUninterestingForHeuristicDataFlowModels(Callable api) { isHigherOrder(api) } - - class SourceOrSinkTargetApi extends Callable { - SourceOrSinkTargetApi() { relevant(this) } - } - - class SinkTargetApi extends SourceOrSinkTargetApi { - SinkTargetApi() { not hasManualSinkModel(this) } - } - - class SourceTargetApi extends SourceOrSinkTargetApi { - SourceTargetApi() { - not hasManualSourceModel(this) and - // Do not generate source models for overridable callables - // as virtual dispatch implies that too many methods - // will be considered sources. - not this.(Overridable).overridesOrImplements(_) - } - } - - class SummaryTargetApi extends Callable { - private Callable lift; - - SummaryTargetApi() { - lift = liftedImpl(this) and - not hasManualSummaryModel(lift) - } - - Callable lift() { result = lift } - - predicate isRelevant() { - relevant(this) and - not hasManualSummaryModel(this) - } - } - /** * Holds if `t` is a type that is generally used for bulk data in collection types. * Eg. char[] is roughly equivalent to string and thus a highly @@ -205,6 +120,8 @@ module ModelGeneratorInput implements ModelGeneratorInputSig { @@ -251,63 +166,94 @@ module ModelGeneratorInput implements ModelGeneratorInputSig + +module SummaryModelGeneratorInput implements SummaryModelGeneratorInputSig { + Callable getAsExprEnclosingCallable(NodeExtended node) { + result = node.asExpr().getEnclosingCallable() + } + + Parameter asParameter(NodeExtended node) { result = node.asParameter() } + + /** + * Holds if any of the parameters of `api` are `System.Func<>`. + */ + private predicate isHigherOrder(Callable api) { + exists(Type t | t = api.getAParameter().getType().getUnboundDeclaration() | + t instanceof SystemLinqExpressions::DelegateExtType + ) + } + + private Callable getARelevantOverrideeOrImplementee(Overridable m) { + m.overridesOrImplements(result) and relevant(result) + } + + /** + * Gets the super implementation of `api` if it is relevant. + * If such a super implementation does not exist, returns `api` if it is relevant. + */ + private Callable liftedImpl(Callable api) { + ( + result = getARelevantOverrideeOrImplementee(api) + or + result = api and relevant(api) + ) and + not exists(getARelevantOverrideeOrImplementee(result)) + } + + private predicate hasManualSummaryModel(Callable api) { + api = any(FlowSummaryImpl::Public::SummarizedCallable sc | sc.applyManualModel()) or + api = any(FlowSummaryImpl::Public::NeutralSummaryCallable sc | sc.hasManualModel()) + } + + predicate isUninterestingForDataFlowModels(Callable api) { none() } + + predicate isUninterestingForHeuristicDataFlowModels(Callable api) { isHigherOrder(api) } + + class SummaryTargetApi extends Callable { + private Callable lift; + + SummaryTargetApi() { + lift = liftedImpl(this) and + not hasManualSummaryModel(lift) + } + + Callable lift() { result = lift } + + predicate isRelevant() { + relevant(this) and + not hasManualSummaryModel(this) + } + } + predicate isAdditionalContentFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { TaintTrackingPrivate::defaultAdditionalTaintStep(nodeFrom, nodeTo, _) and not nodeTo.asExpr() instanceof CS::ElementAccess and @@ -370,34 +316,96 @@ module ModelGeneratorInput implements ModelGeneratorInputSig +import MakeSummaryModelGenerator as SummaryModels +import MakeSourceModelGenerator as SourceModels +import MakeSinkModelGenerator as SinkModels diff --git a/csharp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll b/csharp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll index 97e58d439f0..52611279b27 100644 --- a/csharp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll +++ b/csharp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll @@ -1,6 +1,6 @@ private import csharp as CS private import codeql.mad.modelgenerator.internal.ModelPrinting -private import CaptureModels::ModelGeneratorInput as ModelGeneratorInput +private import CaptureModels::ModelGeneratorCommonInput as ModelGeneratorInput private module ModelPrintingLang implements ModelPrintingLangSig { class Callable = CS::Callable; diff --git a/csharp/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll b/csharp/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll index 62286c27888..baba462c8a2 100644 --- a/csharp/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll +++ b/csharp/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll @@ -2,7 +2,8 @@ private import csharp private import semmle.code.csharp.frameworks.system.collections.Generic as GenericCollections private import semmle.code.csharp.dataflow.internal.DataFlowPrivate private import semmle.code.csharp.frameworks.system.linq.Expressions -private import CaptureModels::ModelGeneratorInput as ModelGeneratorInput +private import CaptureModels::ModelGeneratorCommonInput as ModelGeneratorInput +private import CaptureModels::SummaryModelGeneratorInput as SummaryModelGeneratorInput private import CaptureModelsPrinting /** @@ -189,7 +190,7 @@ private module Printing = ModelPrintingSummary; * A class of callables that are relevant generating summaries for based * on the Theorems for Free approach. */ -class TypeBasedFlowTargetApi extends ModelGeneratorInput::SummaryTargetApi { +class TypeBasedFlowTargetApi extends SummaryModelGeneratorInput::SummaryTargetApi { /** * Gets the string representation of all type based summaries for `this` * inspired by the Theorems for Free approach. diff --git a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql index 0d9e4cd52d9..0c8134546d2 100644 --- a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql +++ b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql @@ -1,5 +1,6 @@ import csharp import utils.modelgenerator.internal.CaptureModels +import SummaryModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql index 24cb66e427e..b5a3b31a035 100644 --- a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql +++ b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql @@ -1,5 +1,6 @@ import csharp import utils.modelgenerator.internal.CaptureModels +import SummaryModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql index d5aa685bfe3..e79cab74560 100644 --- a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql +++ b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql @@ -1,5 +1,6 @@ import csharp import utils.modelgenerator.internal.CaptureModels +import SummaryModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql index cc84ede4235..0cc8dd6d08d 100644 --- a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql +++ b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql @@ -1,5 +1,6 @@ import csharp import utils.modelgenerator.internal.CaptureModels +import SinkModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql index 4c10362960a..2a54abf9b72 100644 --- a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql +++ b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql @@ -1,5 +1,6 @@ import csharp import utils.modelgenerator.internal.CaptureModels +import SourceModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { From 2535055de054b8fa0d92510dfa56c813efe4ae30 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 25 Apr 2025 11:36:03 +0200 Subject: [PATCH 22/66] Java: Re-factor implementation to use the new model generator interface. --- .../CaptureContentSummaryModels.ql | 1 + .../modelgenerator/CaptureNeutralModels.ql | 1 + .../utils/modelgenerator/CaptureSinkModels.ql | 1 + .../modelgenerator/CaptureSourceModels.ql | 1 + .../modelgenerator/CaptureSummaryModels.ql | 1 + .../debug/CaptureSummaryModelsPartialPath.ql | 1 + .../debug/CaptureSummaryModelsPath.ql | 1 + .../modelgenerator/internal/CaptureModels.qll | 311 +++++++++--------- .../internal/CaptureModelsPrinting.qll | 2 +- .../CaptureTypeBasedSummaryModels.qll | 5 +- .../dataflow/CaptureContentSummaryModels.ql | 1 + .../dataflow/CaptureHeuristicSummaryModels.ql | 1 + .../dataflow/CaptureNeutralModels.ql | 1 + .../dataflow/CaptureSinkModels.ql | 1 + .../dataflow/CaptureSourceModels.ql | 1 + 15 files changed, 175 insertions(+), 155 deletions(-) diff --git a/java/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql b/java/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql index b1340e2c0d3..1fe70bae0b5 100644 --- a/java/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql +++ b/java/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string flow where flow = ContentSensitive::captureFlow(api, _) diff --git a/java/ql/src/utils/modelgenerator/CaptureNeutralModels.ql b/java/ql/src/utils/modelgenerator/CaptureNeutralModels.ql index d17c11d4a7b..6008c3bfb8c 100644 --- a/java/ql/src/utils/modelgenerator/CaptureNeutralModels.ql +++ b/java/ql/src/utils/modelgenerator/CaptureNeutralModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string noflow where noflow = captureNeutral(api) diff --git a/java/ql/src/utils/modelgenerator/CaptureSinkModels.ql b/java/ql/src/utils/modelgenerator/CaptureSinkModels.ql index 7c316a02b09..7fcba8850d6 100644 --- a/java/ql/src/utils/modelgenerator/CaptureSinkModels.ql +++ b/java/ql/src/utils/modelgenerator/CaptureSinkModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SinkModels from DataFlowSinkTargetApi api, string sink where sink = Heuristic::captureSink(api) diff --git a/java/ql/src/utils/modelgenerator/CaptureSourceModels.ql b/java/ql/src/utils/modelgenerator/CaptureSourceModels.ql index 4a955d4614b..c623645820b 100644 --- a/java/ql/src/utils/modelgenerator/CaptureSourceModels.ql +++ b/java/ql/src/utils/modelgenerator/CaptureSourceModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SourceModels from DataFlowSourceTargetApi api, string source where source = Heuristic::captureSource(api) diff --git a/java/ql/src/utils/modelgenerator/CaptureSummaryModels.ql b/java/ql/src/utils/modelgenerator/CaptureSummaryModels.ql index 34b6521e7b2..3bc49c31df2 100644 --- a/java/ql/src/utils/modelgenerator/CaptureSummaryModels.ql +++ b/java/ql/src/utils/modelgenerator/CaptureSummaryModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string flow where flow = captureFlow(api, _) diff --git a/java/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql b/java/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql index 8895fdaefbb..b9dc9ea236a 100644 --- a/java/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql +++ b/java/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql @@ -11,6 +11,7 @@ import java import semmle.code.java.dataflow.DataFlow import utils.modelgenerator.internal.CaptureModels +import SummaryModels import PartialFlow::PartialPathGraph int explorationLimit() { result = 3 } diff --git a/java/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql b/java/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql index 8f6bf1c1f53..5925364800c 100644 --- a/java/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql +++ b/java/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql @@ -11,6 +11,7 @@ import java import semmle.code.java.dataflow.DataFlow import utils.modelgenerator.internal.CaptureModels +import SummaryModels import Heuristic import PropagateFlow::PathGraph diff --git a/java/ql/src/utils/modelgenerator/internal/CaptureModels.qll b/java/ql/src/utils/modelgenerator/internal/CaptureModels.qll index 9dd317b3006..cff4e97d8d3 100644 --- a/java/ql/src/utils/modelgenerator/internal/CaptureModels.qll +++ b/java/ql/src/utils/modelgenerator/internal/CaptureModels.qll @@ -25,7 +25,20 @@ predicate isPrimitiveTypeUsedForBulkData(J::Type t) { t.hasName(["byte", "char", "Byte", "Character"]) } -module ModelGeneratorInput implements ModelGeneratorInputSig { +private predicate isInfrequentlyUsed(J::CompilationUnit cu) { + cu.getPackage().getName().matches("javax.swing%") or + cu.getPackage().getName().matches("java.awt%") +} + +private predicate relevant(Callable api) { + api.isPublic() and + api.getDeclaringType().isPublic() and + api.fromSource() and + not isUninterestingForModels(api) and + not isInfrequentlyUsed(api.getCompilationUnit()) +} + +module ModelGeneratorCommonInput implements ModelGeneratorCommonInputSig { class Type = J::Type; class Parameter = J::Parameter; @@ -34,96 +47,8 @@ module ModelGeneratorInput implements ModelGeneratorInputSig= 0 then result = "Argument[" + pos + "]" else result = qualifierString() - ) - or - source.asExpr() instanceof J::FieldAccess and - result = qualifierString() - } - - bindingset[kind] - predicate isRelevantSinkKind(string kind) { - not kind = "log-injection" and - not kind.matches("regex-use%") and - not kind = "file-content-store" - } - - bindingset[kind] - predicate isRelevantSourceKind(string kind) { any() } - predicate containerContent = DataFlowPrivate::containerContent/1; + string partialModelRow(Callable api, int i) { + i = 0 and qualifiedName(api, result, _) // package + or + i = 1 and qualifiedName(api, _, result) // type + or + i = 2 and result = isExtensible(api) // extensible + or + i = 3 and result = api.getName() // name + or + i = 4 and result = ExternalFlow::paramsString(api) // parameters + or + i = 5 and result = "" and exists(api) // ext + } + + string partialNeutralModelRow(Callable api, int i) { + i = 0 and qualifiedName(api, result, _) // package + or + i = 1 and qualifiedName(api, _, result) // type + or + i = 2 and result = api.getName() // name + or + i = 3 and result = ExternalFlow::paramsString(api) // parameters + } +} + +private import ModelGeneratorCommonInput +private import MakeModelGeneratorFactory + +module SummaryModelGeneratorInput implements SummaryModelGeneratorInputSig { + Callable getAsExprEnclosingCallable(NodeExtended node) { + result = node.asExpr().getEnclosingCallable() + } + + Parameter asParameter(NodeExtended node) { result = node.asParameter() } + + private J::Method getARelevantOverride(J::Method m) { + result = m.getAnOverride() and + relevant(result) and + // Other exclusions for overrides. + not m instanceof J::ToStringMethod + } + + /** + * Gets the super implementation of `m` if it is relevant. + * If such a super implementations does not exist, returns `m` if it is relevant. + */ + private J::Callable liftedImpl(J::Callable m) { + ( + result = getARelevantOverride(m) + or + result = m and relevant(m) + ) and + not exists(getARelevantOverride(result)) + } + + private predicate hasManualSummaryModel(Callable api) { + api = any(FlowSummaryImpl::Public::SummarizedCallable sc | sc.applyManualModel()).asCallable() or + api = any(FlowSummaryImpl::Public::NeutralSummaryCallable sc | sc.hasManualModel()).asCallable() + } + + class SummaryTargetApi extends Callable { + private Callable lift; + + SummaryTargetApi() { + lift = liftedImpl(this) and + not hasManualSummaryModel(lift) + } + + Callable lift() { result = lift } + + predicate isRelevant() { + relevant(this) and + not hasManualSummaryModel(this) + } + } + + predicate isUninterestingForDataFlowModels(Callable api) { + api.getDeclaringType() instanceof J::Interface and not exists(api.getBody()) + } + + predicate isUninterestingForHeuristicDataFlowModels(Callable api) { none() } + predicate isAdditionalContentFlowStep(DataFlow::Node node1, DataFlow::Node node2) { TaintTracking::defaultAdditionalTaintStep(node1, node2, _) and not exists(DataFlow::Content f | @@ -287,34 +252,76 @@ module ModelGeneratorInput implements ModelGeneratorInputSig= 0 then result = "Argument[" + pos + "]" else result = qualifierString() + ) + or + source.asExpr() instanceof J::FieldAccess and + result = qualifierString() + } + + bindingset[kind] + predicate isRelevantSinkKind(string kind) { + not kind = "log-injection" and + not kind.matches("regex-use%") and + not kind = "file-content-store" + } predicate sinkNode = ExternalFlow::sinkNode/2; } -import MakeModelGenerator +import MakeSummaryModelGenerator as SummaryModels +import MakeSourceModelGenerator as SourceModels +import MakeSinkModelGenerator as SinkModels diff --git a/java/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll b/java/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll index 4b376654afb..0d26f36c690 100644 --- a/java/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll +++ b/java/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll @@ -1,6 +1,6 @@ private import java as J private import codeql.mad.modelgenerator.internal.ModelPrinting -private import CaptureModels::ModelGeneratorInput as ModelGeneratorInput +private import CaptureModels::ModelGeneratorCommonInput as ModelGeneratorInput private module ModelPrintingLang implements ModelPrintingLangSig { class Callable = J::Callable; diff --git a/java/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll b/java/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll index 9145a077907..00c8c686c2d 100644 --- a/java/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll +++ b/java/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll @@ -2,7 +2,8 @@ private import java private import semmle.code.java.Collections private import semmle.code.java.dataflow.internal.ContainerFlow private import CaptureModels as CaptureModels -private import CaptureModels::ModelGeneratorInput as ModelGeneratorInput +private import CaptureModels::ModelGeneratorCommonInput as ModelGeneratorInput +private import CaptureModels::SummaryModelGeneratorInput as SummaryModelGeneratorInput private import CaptureModelsPrinting /** @@ -296,7 +297,7 @@ private module Printing = ModelPrintingSummary; * A class of callables that are relevant generating summaries for based * on the Theorems for Free approach. */ -class TypeBasedFlowTargetApi extends ModelGeneratorInput::SummaryTargetApi { +class TypeBasedFlowTargetApi extends SummaryModelGeneratorInput::SummaryTargetApi { /** * Gets the string representation of all type based summaries for `this` * inspired by the Theorems for Free approach. diff --git a/java/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql b/java/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql index 8dd23714fb7..b3d9101633b 100644 --- a/java/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql +++ b/java/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql @@ -1,5 +1,6 @@ import java import utils.modelgenerator.internal.CaptureModels +import SummaryModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/java/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql b/java/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql index 45485a8009a..8d6021ab42e 100644 --- a/java/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql +++ b/java/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql @@ -1,5 +1,6 @@ import java import utils.modelgenerator.internal.CaptureModels +import SummaryModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/java/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql b/java/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql index 3578153ddb8..ad567051922 100644 --- a/java/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql +++ b/java/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql @@ -1,5 +1,6 @@ import java import utils.modelgenerator.internal.CaptureModels +import SummaryModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/java/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql b/java/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql index 027670316c3..3d21b2e4f7d 100644 --- a/java/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql +++ b/java/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql @@ -1,5 +1,6 @@ import java import utils.modelgenerator.internal.CaptureModels +import SinkModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/java/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql b/java/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql index d8346f0e3dc..bc95ecf3f2d 100644 --- a/java/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql +++ b/java/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql @@ -1,5 +1,6 @@ import java import utils.modelgenerator.internal.CaptureModels +import SourceModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { From c16d913f8a586daa995449a00651427d475466c0 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 25 Apr 2025 12:49:05 +0200 Subject: [PATCH 23/66] C++: Re-factor implementation to use the new model generator interface. --- .../CaptureContentSummaryModels.ql | 1 + .../modelgenerator/CaptureNeutralModels.ql | 1 + .../utils/modelgenerator/CaptureSinkModels.ql | 4 +- .../modelgenerator/CaptureSourceModels.ql | 4 +- .../modelgenerator/CaptureSummaryModels.ql | 1 + .../modelgenerator/internal/CaptureModels.qll | 317 +++++++++--------- .../internal/CaptureModelsPrinting.qll | 2 +- .../dataflow/CaptureContentSummaryModels.ql | 1 + .../dataflow/CaptureHeuristicSummaryModels.ql | 1 + 9 files changed, 176 insertions(+), 156 deletions(-) diff --git a/cpp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql b/cpp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql index 8dc0c3d7f6b..be0cf443431 100644 --- a/cpp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql +++ b/cpp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string flow where flow = ContentSensitive::captureFlow(api, _) diff --git a/cpp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql b/cpp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql index e9a5ea24dec..813eece65b9 100644 --- a/cpp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql +++ b/cpp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string noflow where noflow = captureNeutral(api) diff --git a/cpp/ql/src/utils/modelgenerator/CaptureSinkModels.ql b/cpp/ql/src/utils/modelgenerator/CaptureSinkModels.ql index 5485a2645bc..1ed9b032070 100644 --- a/cpp/ql/src/utils/modelgenerator/CaptureSinkModels.ql +++ b/cpp/ql/src/utils/modelgenerator/CaptureSinkModels.ql @@ -7,8 +7,8 @@ */ import internal.CaptureModels -import Heuristic +import SinkModels from DataFlowSinkTargetApi api, string sink -where sink = captureSink(api) +where sink = Heuristic::captureSink(api) select sink order by sink diff --git a/cpp/ql/src/utils/modelgenerator/CaptureSourceModels.ql b/cpp/ql/src/utils/modelgenerator/CaptureSourceModels.ql index c2240c64688..4f86fb6b6fb 100644 --- a/cpp/ql/src/utils/modelgenerator/CaptureSourceModels.ql +++ b/cpp/ql/src/utils/modelgenerator/CaptureSourceModels.ql @@ -7,8 +7,8 @@ */ import internal.CaptureModels -import Heuristic +import SourceModels from DataFlowSourceTargetApi api, string source -where source = captureSource(api) +where source = Heuristic::captureSource(api) select source order by source diff --git a/cpp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql b/cpp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql index 60341abc0b5..a023afabd31 100644 --- a/cpp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql +++ b/cpp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string flow where flow = captureFlow(api, _) diff --git a/cpp/ql/src/utils/modelgenerator/internal/CaptureModels.qll b/cpp/ql/src/utils/modelgenerator/internal/CaptureModels.qll index 58acfa01118..5b3beac3453 100644 --- a/cpp/ql/src/utils/modelgenerator/internal/CaptureModels.qll +++ b/cpp/ql/src/utils/modelgenerator/internal/CaptureModels.qll @@ -2,7 +2,7 @@ * Provides predicates related to capturing summary models of the Standard or a 3rd party library. */ -private import cpp +private import cpp as Cpp private import semmle.code.cpp.ir.IR private import semmle.code.cpp.dataflow.ExternalFlow as ExternalFlow private import semmle.code.cpp.ir.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon @@ -10,113 +10,67 @@ private import semmle.code.cpp.ir.dataflow.internal.DataFlowImplSpecific private import semmle.code.cpp.ir.dataflow.internal.DataFlowPrivate as DataFlowPrivate private import semmle.code.cpp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl private import semmle.code.cpp.ir.dataflow.internal.TaintTrackingImplSpecific -private import semmle.code.cpp.dataflow.new.TaintTracking +private import semmle.code.cpp.dataflow.new.TaintTracking as Tt +private import semmle.code.cpp.dataflow.new.DataFlow as Df private import codeql.mad.modelgenerator.internal.ModelGeneratorImpl -module ModelGeneratorInput implements ModelGeneratorInputSig { +/** + * Holds if `f` is a "private" function. + * + * A "private" function does not contribute any models as it is assumed + * to be an implementation detail of some other "public" function for which + * we will generate a summary. + */ +private predicate isPrivateOrProtected(Cpp::Function f) { + f.getNamespace().getParentNamespace*().isAnonymous() + or + exists(Cpp::MemberFunction mf | mf = f | + mf.isPrivate() + or + mf.isProtected() + ) + or + f.isStatic() +} + +private predicate isUninterestingForModels(Callable api) { + // Note: This also makes all global/static-local variables + // not relevant (which is good!) + not api.(Cpp::Function).hasDefinition() + or + isPrivateOrProtected(api) + or + api instanceof Cpp::Destructor + or + api = any(Cpp::LambdaExpression lambda).getLambdaFunction() + or + api.isFromUninstantiatedTemplate(_) +} + +private predicate relevant(Callable api) { + api.fromSource() and + not isUninterestingForModels(api) +} + +module ModelGeneratorCommonInput implements ModelGeneratorCommonInputSig +{ + private module DataFlow = Df::DataFlow; + class Type = DataFlowPrivate::DataFlowType; // Note: This also includes `this` class Parameter = DataFlow::ParameterNode; - class Callable = Declaration; + class Callable = Cpp::Declaration; class NodeExtended extends DataFlow::Node { Callable getAsExprEnclosingCallable() { result = this.asExpr().getEnclosingDeclaration() } } - Parameter asParameter(NodeExtended n) { result = n } - Callable getEnclosingCallable(NodeExtended n) { result = n.getEnclosingCallable().asSourceCallable() } - Callable getAsExprEnclosingCallable(NodeExtended n) { - result = n.asExpr().getEnclosingDeclaration() - } - - /** Gets `api` if it is relevant. */ - private Callable liftedImpl(Callable api) { result = api and relevant(api) } - - private predicate hasManualSummaryModel(Callable api) { - api = any(FlowSummaryImpl::Public::SummarizedCallable sc | sc.applyManualModel()) or - api = any(FlowSummaryImpl::Public::NeutralSummaryCallable sc | sc.hasManualModel()) - } - - private predicate hasManualSourceModel(Callable api) { - api = any(FlowSummaryImpl::Public::NeutralSourceCallable sc | sc.hasManualModel()) - } - - private predicate hasManualSinkModel(Callable api) { - api = any(FlowSummaryImpl::Public::NeutralSinkCallable sc | sc.hasManualModel()) - } - - /** - * Holds if `f` is a "private" function. - * - * A "private" function does not contribute any models as it is assumed - * to be an implementation detail of some other "public" function for which - * we will generate a summary. - */ - private predicate isPrivateOrProtected(Function f) { - f.getNamespace().getParentNamespace*().isAnonymous() - or - exists(MemberFunction mf | mf = f | - mf.isPrivate() - or - mf.isProtected() - ) - or - f.isStatic() - } - - private predicate isUninterestingForModels(Callable api) { - // Note: This also makes all global/static-local variables - // not relevant (which is good!) - not api.(Function).hasDefinition() - or - isPrivateOrProtected(api) - or - api instanceof Destructor - or - api = any(LambdaExpression lambda).getLambdaFunction() - or - api.isFromUninstantiatedTemplate(_) - } - - private predicate relevant(Callable api) { - api.fromSource() and - not isUninterestingForModels(api) - } - - class SummaryTargetApi extends Callable { - private Callable lift; - - SummaryTargetApi() { - lift = liftedImpl(this) and - not hasManualSummaryModel(lift) - } - - Callable lift() { result = lift } - - predicate isRelevant() { - relevant(this) and - not hasManualSummaryModel(this) - } - } - - class SourceOrSinkTargetApi extends Callable { - SourceOrSinkTargetApi() { relevant(this) } - } - - class SinkTargetApi extends SourceOrSinkTargetApi { - SinkTargetApi() { not hasManualSinkModel(this) } - } - - class SourceTargetApi extends SourceOrSinkTargetApi { - SourceTargetApi() { not hasManualSourceModel(this) } - } - class InstanceParameterNode extends DataFlow::ParameterNode { InstanceParameterNode() { DataFlowPrivate::nodeHasInstruction(this, @@ -124,7 +78,7 @@ module ModelGeneratorInput implements ModelGeneratorInputSig" @@ -166,7 +120,7 @@ module ModelGeneratorInput implements ModelGeneratorInputSig + +private module SummaryModelGeneratorInput implements SummaryModelGeneratorInputSig { + private module DataFlow = Df::DataFlow; + + Parameter asParameter(NodeExtended n) { result = n } + + Callable getAsExprEnclosingCallable(NodeExtended n) { + result = n.asExpr().getEnclosingDeclaration() + } + + private predicate hasManualSummaryModel(Callable api) { + api = any(FlowSummaryImpl::Public::SummarizedCallable sc | sc.applyManualModel()) or + api = any(FlowSummaryImpl::Public::NeutralSummaryCallable sc | sc.hasManualModel()) + } + + /** Gets `api` if it is relevant. */ + private Callable liftedImpl(Callable api) { result = api and relevant(api) } + + class SummaryTargetApi extends Callable { + private Callable lift; + + SummaryTargetApi() { + lift = liftedImpl(this) and + not hasManualSummaryModel(lift) + } + + Callable lift() { result = lift } + + predicate isRelevant() { + relevant(this) and + not hasManualSummaryModel(this) + } + } + predicate isAdditionalContentFlowStep(DataFlow::Node node1, DataFlow::Node node2) { - TaintTracking::defaultAdditionalTaintStep(node1, node2, _) and + Tt::TaintTracking::defaultAdditionalTaintStep(node1, node2, _) and not exists(DataFlow::Content f | DataFlowPrivate::readStep(node1, f, node2) and containerContent(f) ) @@ -341,7 +328,7 @@ module ModelGeneratorInput implements ModelGeneratorInputSig +import MakeSummaryModelGenerator as SummaryModels +import MakeSourceModelGenerator as SourceModels +import MakeSinkModelGenerator as SinkModels diff --git a/cpp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll b/cpp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll index 7841f8ed1a4..43342aa671e 100644 --- a/cpp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll +++ b/cpp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll @@ -1,6 +1,6 @@ private import cpp as Cpp private import codeql.mad.modelgenerator.internal.ModelPrinting -private import CaptureModels::ModelGeneratorInput as ModelGeneratorInput +private import CaptureModels::ModelGeneratorCommonInput as ModelGeneratorInput private module ModelPrintingLang implements ModelPrintingLangSig { class Callable = Cpp::Declaration; diff --git a/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureContentSummaryModels.ql b/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureContentSummaryModels.ql index 0156eaaeb98..8196f6329cc 100644 --- a/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureContentSummaryModels.ql +++ b/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureContentSummaryModels.ql @@ -1,5 +1,6 @@ import cpp import utils.modelgenerator.internal.CaptureModels +import SummaryModels import InlineModelsAsDataTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql b/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql index 3ab1dc6c471..fc05c4fe434 100644 --- a/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql +++ b/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql @@ -1,5 +1,6 @@ import cpp import utils.modelgenerator.internal.CaptureModels +import SummaryModels import InlineModelsAsDataTest module InlineMadTestConfig implements InlineMadTestConfigSig { From a6b5645b139350a5527959492eeb7aa784b9cfc7 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 25 Apr 2025 13:09:13 +0200 Subject: [PATCH 24/66] Rust: Re-factor implementation to use the new model generator interface. --- .../CaptureContentSummaryModels.ql | 1 + .../modelgenerator/CaptureNeutralModels.ql | 1 + .../utils/modelgenerator/CaptureSinkModels.ql | 1 + .../modelgenerator/CaptureSourceModels.ql | 1 + .../modelgenerator/CaptureSummaryModels.ql | 1 + .../debug/CaptureSummaryModelsPartialPath.ql | 1 + .../debug/CaptureSummaryModelsPath.ql | 1 + .../modelgenerator/internal/CaptureModels.qll | 211 ++++++++++-------- .../internal/CaptureModelsPrinting.qll | 2 +- .../modelgenerator/CaptureSinkModels.ql | 1 + .../modelgenerator/CaptureSourceModels.ql | 1 + .../modelgenerator/CaptureSummaryModels.ql | 1 + 12 files changed, 124 insertions(+), 99 deletions(-) diff --git a/rust/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql b/rust/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql index da90465197e..4b672e74da5 100644 --- a/rust/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql +++ b/rust/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string flow where flow = ContentSensitive::captureFlow(api, _) diff --git a/rust/ql/src/utils/modelgenerator/CaptureNeutralModels.ql b/rust/ql/src/utils/modelgenerator/CaptureNeutralModels.ql index 8efc8a485e1..556d1624f39 100644 --- a/rust/ql/src/utils/modelgenerator/CaptureNeutralModels.ql +++ b/rust/ql/src/utils/modelgenerator/CaptureNeutralModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string noflow where noflow = Heuristic::captureNoFlow(api) diff --git a/rust/ql/src/utils/modelgenerator/CaptureSinkModels.ql b/rust/ql/src/utils/modelgenerator/CaptureSinkModels.ql index 36b1b813297..989637a867e 100644 --- a/rust/ql/src/utils/modelgenerator/CaptureSinkModels.ql +++ b/rust/ql/src/utils/modelgenerator/CaptureSinkModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SinkModels from DataFlowSinkTargetApi api, string sink where sink = Heuristic::captureSink(api) diff --git a/rust/ql/src/utils/modelgenerator/CaptureSourceModels.ql b/rust/ql/src/utils/modelgenerator/CaptureSourceModels.ql index 7086f719b2d..2ffc4894b18 100644 --- a/rust/ql/src/utils/modelgenerator/CaptureSourceModels.ql +++ b/rust/ql/src/utils/modelgenerator/CaptureSourceModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SourceModels from DataFlowSourceTargetApi api, string source where source = Heuristic::captureSource(api) diff --git a/rust/ql/src/utils/modelgenerator/CaptureSummaryModels.ql b/rust/ql/src/utils/modelgenerator/CaptureSummaryModels.ql index 8947dd01531..d0b3152c9be 100644 --- a/rust/ql/src/utils/modelgenerator/CaptureSummaryModels.ql +++ b/rust/ql/src/utils/modelgenerator/CaptureSummaryModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string flow where flow = captureFlow(api, _) diff --git a/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql b/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql index eb0cd638b53..b8855b94bf2 100644 --- a/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql +++ b/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql @@ -10,6 +10,7 @@ private import codeql.rust.dataflow.DataFlow import utils.modelgenerator.internal.CaptureModels +import SummaryModels import PartialFlow::PartialPathGraph int explorationLimit() { result = 3 } diff --git a/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql b/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql index 1ddec1ff618..ac25306ceee 100644 --- a/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql +++ b/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql @@ -10,6 +10,7 @@ private import codeql.rust.dataflow.DataFlow import utils.modelgenerator.internal.CaptureModels +import SummaryModels import Heuristic import PropagateFlow::PathGraph diff --git a/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll b/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll index 237da46750b..6aeae06bbdb 100644 --- a/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll +++ b/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll @@ -2,7 +2,7 @@ private import codeql.util.Unit private import rust private import rust as R private import codeql.rust.dataflow.DataFlow -private import codeql.rust.dataflow.internal.DataFlowImpl +private import codeql.rust.dataflow.internal.DataFlowImpl as DataFlowImpl private import codeql.rust.dataflow.internal.Node as Node private import codeql.rust.dataflow.internal.Content private import codeql.rust.dataflow.FlowSource as FlowSource @@ -11,7 +11,25 @@ private import codeql.rust.dataflow.internal.TaintTrackingImpl private import codeql.mad.modelgenerator.internal.ModelGeneratorImpl private import codeql.rust.dataflow.internal.FlowSummaryImpl as FlowSummary -module ModelGeneratorInput implements ModelGeneratorInputSig { +private predicate relevant(Function api) { + // Only include functions that have a resolved path. + api.hasCrateOrigin() and + api.hasExtendedCanonicalPath() and + ( + // This excludes closures (these are not exported API endpoints) and + // functions without a `pub` visiblity. A function can be `pub` without + // ultimately being exported by a crate, so this is an overapproximation. + api.hasVisibility() + or + // If a method implements a public trait it is exposed through the trait. + // We overapproximate this by including all trait method implementations. + exists(Impl impl | impl.hasTrait() and impl.getAssocItemList().getAssocItem(_) = api) + ) +} + +module ModelGeneratorCommonInput implements + ModelGeneratorCommonInputSig +{ // NOTE: We are not using type information for now. class Type = Unit; @@ -23,42 +41,71 @@ module ModelGeneratorInput implements ModelGeneratorInputSig + +private module SummaryModelGeneratorInput implements SummaryModelGeneratorInputSig { class SummaryTargetApi extends Callable { private Callable lift; @@ -72,74 +119,13 @@ module ModelGeneratorInput implements ModelGeneratorInputSig +import MakeSummaryModelGenerator as SummaryModels +import MakeSourceModelGenerator as SourceModels +import MakeSinkModelGenerator as SinkModels diff --git a/rust/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll b/rust/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll index 4c2da918f73..789113f7580 100644 --- a/rust/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll +++ b/rust/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll @@ -1,6 +1,6 @@ private import rust as R private import codeql.mad.modelgenerator.internal.ModelPrinting -private import CaptureModels::ModelGeneratorInput as ModelGeneratorInput +private import CaptureModels::ModelGeneratorCommonInput as ModelGeneratorInput private module ModelPrintingLang implements ModelPrintingLangSig { class Callable = R::Callable; diff --git a/rust/ql/test/utils-tests/modelgenerator/CaptureSinkModels.ql b/rust/ql/test/utils-tests/modelgenerator/CaptureSinkModels.ql index 14edea3af8a..4b8041fb444 100644 --- a/rust/ql/test/utils-tests/modelgenerator/CaptureSinkModels.ql +++ b/rust/ql/test/utils-tests/modelgenerator/CaptureSinkModels.ql @@ -1,5 +1,6 @@ import rust import utils.modelgenerator.internal.CaptureModels +import SinkModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/rust/ql/test/utils-tests/modelgenerator/CaptureSourceModels.ql b/rust/ql/test/utils-tests/modelgenerator/CaptureSourceModels.ql index 66f0780448c..c535ce78704 100644 --- a/rust/ql/test/utils-tests/modelgenerator/CaptureSourceModels.ql +++ b/rust/ql/test/utils-tests/modelgenerator/CaptureSourceModels.ql @@ -1,5 +1,6 @@ import rust import utils.modelgenerator.internal.CaptureModels +import SourceModels import utils.test.InlineMadTest import codeql.rust.dataflow.internal.ModelsAsData diff --git a/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.ql b/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.ql index 002689a2039..e0788253f43 100644 --- a/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.ql +++ b/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.ql @@ -1,5 +1,6 @@ import rust import utils.modelgenerator.internal.CaptureModels +import SummaryModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { From c9d01bc6079e4e698e90c82ba1099d11a8df5881 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 25 Apr 2025 14:06:04 +0200 Subject: [PATCH 25/66] Shared: Sprinkle some predicate defaults and clean up. --- .../modelgenerator/internal/CaptureModels.qll | 16 ------ .../modelgenerator/internal/CaptureModels.qll | 10 ---- .../modelgenerator/internal/CaptureModels.qll | 7 --- .../modelgenerator/internal/CaptureModels.qll | 15 ----- .../internal/ModelGeneratorImpl.qll | 57 ++++++++----------- 5 files changed, 24 insertions(+), 81 deletions(-) diff --git a/cpp/ql/src/utils/modelgenerator/internal/CaptureModels.qll b/cpp/ql/src/utils/modelgenerator/internal/CaptureModels.qll index 5b3beac3453..93abe205f1a 100644 --- a/cpp/ql/src/utils/modelgenerator/internal/CaptureModels.qll +++ b/cpp/ql/src/utils/modelgenerator/internal/CaptureModels.qll @@ -360,12 +360,6 @@ private module SummaryModelGeneratorInput implements SummaryModelGeneratorInputS result = "Element[" + ec.getIndirectionIndex() + "]" ) } - - predicate isUninterestingForDataFlowModels(Callable api) { none() } - - predicate isUninterestingForHeuristicDataFlowModels(Callable api) { - isUninterestingForDataFlowModels(api) - } } private module SourceModelGeneratorInput implements SourceModelGeneratorInputSig { @@ -377,11 +371,6 @@ private module SourceModelGeneratorInput implements SourceModelGeneratorInputSig SourceTargetApi() { relevant(this) and not hasManualSourceModel(this) } } - predicate irrelevantSourceSinkApi(Callable source, SourceTargetApi api) { none() } - - bindingset[kind] - predicate isRelevantSourceKind(string kind) { any() } - predicate sourceNode = ExternalFlow::sourceNode/2; } @@ -396,8 +385,6 @@ private module SinkModelGeneratorInput implements SinkModelGeneratorInputSig { SinkTargetApi() { relevant(this) and not hasManualSinkModel(this) } } - predicate sinkModelSanitizer(DataFlow::Node node) { none() } - predicate apiSource(DataFlow::Node source) { DataFlowPrivate::nodeHasOperand(source, any(DataFlow::FieldAddress fa), 1) or @@ -416,9 +403,6 @@ private module SinkModelGeneratorInput implements SinkModelGeneratorInputSig { result = qualifierString() } - bindingset[kind] - predicate isRelevantSinkKind(string kind) { any() } - predicate sinkNode = ExternalFlow::sinkNode/2; } diff --git a/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll b/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll index 7bf8a248a6f..b0300e4a87f 100644 --- a/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll +++ b/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll @@ -234,8 +234,6 @@ module SummaryModelGeneratorInput implements SummaryModelGeneratorInputSig { api = any(FlowSummaryImpl::Public::NeutralSummaryCallable sc | sc.hasManualModel()) } - predicate isUninterestingForDataFlowModels(Callable api) { none() } - predicate isUninterestingForHeuristicDataFlowModels(Callable api) { isHigherOrder(api) } class SummaryTargetApi extends Callable { @@ -356,9 +354,6 @@ private module SourceModelGeneratorInput implements SourceModelGeneratorInputSig ) } - bindingset[kind] - predicate isRelevantSourceKind(string kind) { any() } - predicate sourceNode = ExternalFlow::sourceNode/2; } @@ -372,8 +367,6 @@ private module SinkModelGeneratorInput implements SinkModelGeneratorInputSig { SinkTargetApi() { relevant(this) and not hasManualSinkModel(this) } } - predicate sinkModelSanitizer(DataFlow::Node node) { none() } - private predicate isRelevantMemberAccess(DataFlow::Node node) { exists(CS::MemberAccess access | access = node.asExpr() | access.hasThisQualifier() and @@ -400,9 +393,6 @@ private module SinkModelGeneratorInput implements SinkModelGeneratorInputSig { result = qualifierString() } - bindingset[kind] - predicate isRelevantSinkKind(string kind) { any() } - predicate sinkNode = ExternalFlow::sinkNode/2; } diff --git a/java/ql/src/utils/modelgenerator/internal/CaptureModels.qll b/java/ql/src/utils/modelgenerator/internal/CaptureModels.qll index cff4e97d8d3..09223d23b1c 100644 --- a/java/ql/src/utils/modelgenerator/internal/CaptureModels.qll +++ b/java/ql/src/utils/modelgenerator/internal/CaptureModels.qll @@ -211,8 +211,6 @@ module SummaryModelGeneratorInput implements SummaryModelGeneratorInputSig { api.getDeclaringType() instanceof J::Interface and not exists(api.getBody()) } - predicate isUninterestingForHeuristicDataFlowModels(Callable api) { none() } - predicate isAdditionalContentFlowStep(DataFlow::Node node1, DataFlow::Node node2) { TaintTracking::defaultAdditionalTaintStep(node1, node2, _) and not exists(DataFlow::Content f | @@ -264,11 +262,6 @@ private module SourceModelGeneratorInput implements SourceModelGeneratorInputSig SourceTargetApi() { relevant(this) and not hasManualSourceModel(this) } } - predicate irrelevantSourceSinkApi(Callable source, SourceTargetApi api) { none() } - - bindingset[kind] - predicate isRelevantSourceKind(string kind) { any() } - predicate sourceNode = ExternalFlow::sourceNode/2; } diff --git a/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll b/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll index 6aeae06bbdb..99e1c527b54 100644 --- a/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll +++ b/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll @@ -123,10 +123,6 @@ private module SummaryModelGeneratorInput implements SummaryModelGeneratorInputS Parameter asParameter(NodeExtended node) { result = node.asParameter() } - predicate isUninterestingForDataFlowModels(Callable api) { none() } - - predicate isUninterestingForHeuristicDataFlowModels(Callable api) { none() } - predicate isAdditionalContentFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { none() } predicate isField(DataFlow::ContentSet c) { @@ -169,12 +165,6 @@ private module SourceModelGeneratorInput implements SourceModelGeneratorInputSig SourceTargetApi() { relevant(this) } } - bindingset[sourceEnclosing, api] - predicate irrelevantSourceSinkApi(Callable sourceEnclosing, SourceTargetApi api) { none() } - - bindingset[kind] - predicate isRelevantSourceKind(string kind) { any() } - predicate sourceNode(DataFlow::Node node, string kind) { FlowSource::sourceNode(node, kind) } } @@ -183,8 +173,6 @@ private module SinkModelGeneratorInput implements SinkModelGeneratorInputSig { SinkTargetApi() { relevant(this) } } - predicate sinkModelSanitizer(DataFlow::Node node) { none() } - /** * Holds if `source` is an API entrypoint, i.e., a source of input where data * can flow in to a library. This is used for creating sink models, as we @@ -197,9 +185,6 @@ private module SinkModelGeneratorInput implements SinkModelGeneratorInputSig { result = "Argument[" + source.(Node::SourceParameterNode).getPosition().toString() + "]" } - bindingset[kind] - predicate isRelevantSinkKind(string kind) { any() } - predicate sinkNode(DataFlow::Node node, string kind) { FlowSink::sinkNode(node, kind) } } diff --git a/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll b/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll index d981ef151fd..dabd687b52b 100644 --- a/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll +++ b/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll @@ -295,7 +295,7 @@ module MakeModelGeneratorFactory< * * This serves as an extra filter for the `relevant` predicate. */ - predicate isUninterestingForDataFlowModels(Callable api); + default predicate isUninterestingForDataFlowModels(Callable api) { none() } /** * Holds if it is irrelevant to generate models for `api` based on the heuristic @@ -304,7 +304,7 @@ module MakeModelGeneratorFactory< * This serves as an extra filter for the `relevant` * and `isUninterestingForDataFlowModels` predicates. */ - predicate isUninterestingForHeuristicDataFlowModels(Callable api); + default predicate isUninterestingForHeuristicDataFlowModels(Callable api) { none() } } /** @@ -940,24 +940,20 @@ module MakeModelGeneratorFactory< */ class SourceTargetApi extends Callable; - /** - * Holds if it is not relevant to generate a source model for `api`, even - * if flow is detected from a node within `source` to a sink within `api`. - */ - bindingset[sourceEnclosing, api] - predicate irrelevantSourceSinkApi(Callable sourceEnclosing, SourceTargetApi api); - - /** - * Holds if `kind` is a relevant source kind for creating source models. - */ - bindingset[kind] - predicate isRelevantSourceKind(string kind); - /** * Holds if `node` is specified as a source with the given kind in a MaD flow * model. */ predicate sourceNode(Lang::Node node, string kind); + + /** + * Holds if it is not relevant to generate a source model for `api`, even + * if flow is detected from a node within `source` to a sink within `api`. + */ + bindingset[sourceEnclosing, api] + default predicate irrelevantSourceSinkApi(Callable sourceEnclosing, SourceTargetApi api) { + none() + } } /** @@ -969,32 +965,32 @@ module MakeModelGeneratorFactory< */ class SinkTargetApi extends Callable; + /** + * Holds if `node` is specified as a sink with the given kind in a MaD flow + * model. + */ + predicate sinkNode(Lang::Node node, string kind); + /** * Gets the MaD input string representation of `source`. */ string getInputArgument(Lang::Node source); - /** - * Holds if `node` is a sanitizer for sink model construction. - */ - predicate sinkModelSanitizer(Lang::Node node); - /** * Holds if `source` is an api entrypoint relevant for creating sink models. */ predicate apiSource(Lang::Node source); + /** + * Holds if `node` is a sanitizer for sink model construction. + */ + default predicate sinkModelSanitizer(Lang::Node node) { none() } + /** * Holds if `kind` is a relevant sink kind for creating sink models. */ bindingset[kind] - predicate isRelevantSinkKind(string kind); - - /** - * Holds if `node` is specified as a sink with the given kind in a MaD flow - * model. - */ - predicate sinkNode(Lang::Node node, string kind); + default predicate isRelevantSinkKind(string kind) { any() } } /** @@ -1029,12 +1025,7 @@ module MakeModelGeneratorFactory< * via its return (then the API itself becomes a source). */ module PropagateFromSourceConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { - exists(string kind | - isRelevantSourceKind(kind) and - sourceNode(source, kind) - ) - } + predicate isSource(DataFlow::Node source) { sourceNode(source, _) } predicate isSink(DataFlow::Node sink) { sink instanceof ReturnNodeExt and From e3a66811825fa6d588494ac8486ba3913685725b Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Tue, 29 Apr 2025 16:02:21 +0200 Subject: [PATCH 26/66] Add code quality suite selector --- misc/suite-helpers/code-quality-selectors.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 misc/suite-helpers/code-quality-selectors.yml diff --git a/misc/suite-helpers/code-quality-selectors.yml b/misc/suite-helpers/code-quality-selectors.yml new file mode 100644 index 00000000000..ddaf3366599 --- /dev/null +++ b/misc/suite-helpers/code-quality-selectors.yml @@ -0,0 +1,10 @@ +- description: Selectors for selecting the Code-Quality-relevant queries for a language +- include: + kind: + - problem + - path-problem + precision: + - high + - very-high + tags contain: + - quality From ea9b95790bb099fa756bbaf54e797bc0394085df Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Tue, 29 Apr 2025 16:02:46 +0200 Subject: [PATCH 27/66] Use code-quality-selectors in C# suite --- csharp/ql/src/API Abuse/FormatInvalid.ql | 1 + .../NoDisposeCallOnLocalIDisposable.ql | 1 + .../Control-Flow/ConstantCondition.ql | 1 + csharp/ql/src/Dead Code/DeadStoreOfLocal.ql | 1 + .../Collections/ContainerLengthCmpOffByOne.ql | 1 + .../Collections/ContainerSizeCmpZero.ql | 1 + .../DangerousNonShortCircuitLogic.ql | 1 + .../src/Likely Bugs/EqualityCheckOnFloats.ql | 1 + .../Likely Bugs/ReferenceEqualsOnValueTypes.ql | 1 + csharp/ql/src/Likely Bugs/SelfAssignment.ql | 1 + .../src/Likely Bugs/UncheckedCastInEquals.ql | 1 + csharp/ql/src/Performance/UseTryGetValue.ql | 4 +++- csharp/ql/src/Useless code/DefaultToString.ql | 1 + csharp/ql/src/Useless code/IntGetHashCode.ql | 1 + .../src/codeql-suites/csharp-code-quality.qls | 18 ++---------------- 15 files changed, 18 insertions(+), 17 deletions(-) diff --git a/csharp/ql/src/API Abuse/FormatInvalid.ql b/csharp/ql/src/API Abuse/FormatInvalid.ql index a2b8ef5e222..056730a577d 100644 --- a/csharp/ql/src/API Abuse/FormatInvalid.ql +++ b/csharp/ql/src/API Abuse/FormatInvalid.ql @@ -8,6 +8,7 @@ * @id cs/invalid-string-formatting * @tags reliability * maintainability + * quality */ import csharp diff --git a/csharp/ql/src/API Abuse/NoDisposeCallOnLocalIDisposable.ql b/csharp/ql/src/API Abuse/NoDisposeCallOnLocalIDisposable.ql index e5826c42342..f02b0d49b54 100644 --- a/csharp/ql/src/API Abuse/NoDisposeCallOnLocalIDisposable.ql +++ b/csharp/ql/src/API Abuse/NoDisposeCallOnLocalIDisposable.ql @@ -8,6 +8,7 @@ * @id cs/local-not-disposed * @tags efficiency * maintainability + * quality * external/cwe/cwe-404 * external/cwe/cwe-459 * external/cwe/cwe-460 diff --git a/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql b/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql index 88d938e399f..7bfdea1e906 100644 --- a/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql +++ b/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql @@ -9,6 +9,7 @@ * @id cs/constant-condition * @tags maintainability * readability + * quality * external/cwe/cwe-835 */ diff --git a/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql b/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql index 1e2eaad1aa1..5be820ee74c 100644 --- a/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql +++ b/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql @@ -6,6 +6,7 @@ * @problem.severity warning * @id cs/useless-assignment-to-local * @tags maintainability + * quality * external/cwe/cwe-563 * @precision very-high */ diff --git a/csharp/ql/src/Likely Bugs/Collections/ContainerLengthCmpOffByOne.ql b/csharp/ql/src/Likely Bugs/Collections/ContainerLengthCmpOffByOne.ql index 615f0634f16..754ed8ad773 100644 --- a/csharp/ql/src/Likely Bugs/Collections/ContainerLengthCmpOffByOne.ql +++ b/csharp/ql/src/Likely Bugs/Collections/ContainerLengthCmpOffByOne.ql @@ -9,6 +9,7 @@ * @tags reliability * correctness * logic + * quality * external/cwe/cwe-193 */ diff --git a/csharp/ql/src/Likely Bugs/Collections/ContainerSizeCmpZero.ql b/csharp/ql/src/Likely Bugs/Collections/ContainerSizeCmpZero.ql index 6ba109713ae..90f5b981e24 100644 --- a/csharp/ql/src/Likely Bugs/Collections/ContainerSizeCmpZero.ql +++ b/csharp/ql/src/Likely Bugs/Collections/ContainerSizeCmpZero.ql @@ -8,6 +8,7 @@ * @tags reliability * correctness * logic + * quality */ import csharp diff --git a/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql b/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql index 6091b0f79a3..f3defa47a43 100644 --- a/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql +++ b/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql @@ -9,6 +9,7 @@ * @tags reliability * correctness * logic + * quality * external/cwe/cwe-480 * external/cwe/cwe-691 */ diff --git a/csharp/ql/src/Likely Bugs/EqualityCheckOnFloats.ql b/csharp/ql/src/Likely Bugs/EqualityCheckOnFloats.ql index 1109201fbe1..108e6f45f94 100644 --- a/csharp/ql/src/Likely Bugs/EqualityCheckOnFloats.ql +++ b/csharp/ql/src/Likely Bugs/EqualityCheckOnFloats.ql @@ -9,6 +9,7 @@ * @id cs/equality-on-floats * @tags reliability * correctness + * quality */ import csharp diff --git a/csharp/ql/src/Likely Bugs/ReferenceEqualsOnValueTypes.ql b/csharp/ql/src/Likely Bugs/ReferenceEqualsOnValueTypes.ql index f038117aff3..de29f6109ad 100644 --- a/csharp/ql/src/Likely Bugs/ReferenceEqualsOnValueTypes.ql +++ b/csharp/ql/src/Likely Bugs/ReferenceEqualsOnValueTypes.ql @@ -7,6 +7,7 @@ * @id cs/reference-equality-on-valuetypes * @tags reliability * correctness + * quality * external/cwe/cwe-595 */ diff --git a/csharp/ql/src/Likely Bugs/SelfAssignment.ql b/csharp/ql/src/Likely Bugs/SelfAssignment.ql index dd63ba87627..e010d18292c 100644 --- a/csharp/ql/src/Likely Bugs/SelfAssignment.ql +++ b/csharp/ql/src/Likely Bugs/SelfAssignment.ql @@ -8,6 +8,7 @@ * @tags reliability * correctness * logic + * quality */ import csharp diff --git a/csharp/ql/src/Likely Bugs/UncheckedCastInEquals.ql b/csharp/ql/src/Likely Bugs/UncheckedCastInEquals.ql index aa244147e3e..d1e2dbdf056 100644 --- a/csharp/ql/src/Likely Bugs/UncheckedCastInEquals.ql +++ b/csharp/ql/src/Likely Bugs/UncheckedCastInEquals.ql @@ -7,6 +7,7 @@ * @id cs/unchecked-cast-in-equals * @tags reliability * maintainability + * quality */ import csharp diff --git a/csharp/ql/src/Performance/UseTryGetValue.ql b/csharp/ql/src/Performance/UseTryGetValue.ql index 67b57e90347..c33c0d1e8d7 100644 --- a/csharp/ql/src/Performance/UseTryGetValue.ql +++ b/csharp/ql/src/Performance/UseTryGetValue.ql @@ -6,7 +6,9 @@ * @problem.severity recommendation * @precision high * @id cs/inefficient-containskey - * @tags maintainability efficiency + * @tags maintainability + * efficiency + * quality */ import csharp diff --git a/csharp/ql/src/Useless code/DefaultToString.ql b/csharp/ql/src/Useless code/DefaultToString.ql index 544347576fe..ea33fe7874b 100644 --- a/csharp/ql/src/Useless code/DefaultToString.ql +++ b/csharp/ql/src/Useless code/DefaultToString.ql @@ -8,6 +8,7 @@ * @id cs/call-to-object-tostring * @tags reliability * maintainability + * quality */ import DefaultToStringQuery diff --git a/csharp/ql/src/Useless code/IntGetHashCode.ql b/csharp/ql/src/Useless code/IntGetHashCode.ql index 85d0f56aae0..847443d018e 100644 --- a/csharp/ql/src/Useless code/IntGetHashCode.ql +++ b/csharp/ql/src/Useless code/IntGetHashCode.ql @@ -8,6 +8,7 @@ * @id cs/useless-gethashcode-call * @tags readability * useless-code + * quality */ import csharp diff --git a/csharp/ql/src/codeql-suites/csharp-code-quality.qls b/csharp/ql/src/codeql-suites/csharp-code-quality.qls index 85bbe4db683..2074f9378cf 100644 --- a/csharp/ql/src/codeql-suites/csharp-code-quality.qls +++ b/csharp/ql/src/codeql-suites/csharp-code-quality.qls @@ -1,17 +1,3 @@ - queries: . -- include: - id: - - cs/index-out-of-bounds - - cs/test-for-negative-container-size - - cs/unchecked-cast-in-equals - - cs/reference-equality-on-valuetypes - - cs/self-assignment - - cs/inefficient-containskey - - cs/call-to-object-tostring - - cs/local-not-disposed - - cs/constant-condition - - cs/useless-gethashcode-call - - cs/non-short-circuit - - cs/useless-assignment-to-local - - cs/invalid-string-formatting - - cs/equality-on-floats +- apply: code-quality-selectors.yml + from: codeql/suite-helpers From 425e020d6c2269bfd3db5d4260adfae38d32ab02 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Tue, 29 Apr 2025 16:09:51 +0200 Subject: [PATCH 28/66] Use code-quality-selectors in Java suite --- .../query-suite/java-code-quality.qls.expected | 1 - .../Language Abuse/TypeVariableHidesType.ql | 1 + .../Likely Bugs/Arithmetic/IntMultToLong.ql | 1 + .../Collections/WriteOnlyContainer.ql | 1 + .../Comparison/IncomparableEquals.ql | 1 + .../Comparison/InconsistentEqualsHashCode.ql | 1 + .../Comparison/MissingInstanceofInEquals.ql | 1 + .../src/Likely Bugs/Comparison/RefEqBoxed.ql | 1 + .../Likely Typos/ContradictoryTypeChecks.ql | 1 + .../Likely Typos/SuspiciousDateFormat.ql | 1 + .../Likely Bugs/Resource Leaks/CloseReader.ql | 1 + .../Likely Bugs/Resource Leaks/CloseWriter.ql | 1 + .../ql/src/codeql-suites/java-code-quality.qls | 18 ++---------------- 13 files changed, 13 insertions(+), 17 deletions(-) diff --git a/java/ql/integration-tests/java/query-suite/java-code-quality.qls.expected b/java/ql/integration-tests/java/query-suite/java-code-quality.qls.expected index 0934d4cf996..4af6a4dd5db 100644 --- a/java/ql/integration-tests/java/query-suite/java-code-quality.qls.expected +++ b/java/ql/integration-tests/java/query-suite/java-code-quality.qls.expected @@ -1,4 +1,3 @@ -ql/java/ql/src/Language Abuse/TypeVariableHidesType.ql ql/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql ql/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql ql/java/ql/src/Likely Bugs/Comparison/IncomparableEquals.ql diff --git a/java/ql/src/Language Abuse/TypeVariableHidesType.ql b/java/ql/src/Language Abuse/TypeVariableHidesType.ql index d411c3848e2..81da0e9703e 100644 --- a/java/ql/src/Language Abuse/TypeVariableHidesType.ql +++ b/java/ql/src/Language Abuse/TypeVariableHidesType.ql @@ -9,6 +9,7 @@ * @tags reliability * readability * types + * quality */ import java diff --git a/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql b/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql index 31a1d8a20a1..026096b63a7 100644 --- a/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql +++ b/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql @@ -9,6 +9,7 @@ * @tags reliability * correctness * types + * quality * external/cwe/cwe-190 * external/cwe/cwe-192 * external/cwe/cwe-197 diff --git a/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql b/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql index 8c8cb6105b3..1f4ebb3403d 100644 --- a/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql +++ b/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql @@ -7,6 +7,7 @@ * @id java/unused-container * @tags maintainability * useless-code + * quality * external/cwe/cwe-561 */ diff --git a/java/ql/src/Likely Bugs/Comparison/IncomparableEquals.ql b/java/ql/src/Likely Bugs/Comparison/IncomparableEquals.ql index 2326ae5dd3d..f5019373b65 100644 --- a/java/ql/src/Likely Bugs/Comparison/IncomparableEquals.ql +++ b/java/ql/src/Likely Bugs/Comparison/IncomparableEquals.ql @@ -8,6 +8,7 @@ * @id java/equals-on-unrelated-types * @tags reliability * correctness + * quality */ import java diff --git a/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.ql b/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.ql index b788caec0e1..2fce3397346 100644 --- a/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.ql +++ b/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.ql @@ -8,6 +8,7 @@ * @id java/inconsistent-equals-and-hashcode * @tags reliability * correctness + * quality * external/cwe/cwe-581 */ diff --git a/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql b/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql index 455ca22d6e3..413a88bb008 100644 --- a/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql +++ b/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql @@ -8,6 +8,7 @@ * @id java/unchecked-cast-in-equals * @tags reliability * correctness + * quality */ import java diff --git a/java/ql/src/Likely Bugs/Comparison/RefEqBoxed.ql b/java/ql/src/Likely Bugs/Comparison/RefEqBoxed.ql index b5c679f67e7..811edd9e2d7 100644 --- a/java/ql/src/Likely Bugs/Comparison/RefEqBoxed.ql +++ b/java/ql/src/Likely Bugs/Comparison/RefEqBoxed.ql @@ -8,6 +8,7 @@ * @id java/reference-equality-of-boxed-types * @tags reliability * correctness + * quality * external/cwe/cwe-595 */ diff --git a/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql b/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql index 9608ec93767..a8ff61f481d 100644 --- a/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql +++ b/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql @@ -9,6 +9,7 @@ * @id java/contradictory-type-checks * @tags correctness * logic + * quality */ import java diff --git a/java/ql/src/Likely Bugs/Likely Typos/SuspiciousDateFormat.ql b/java/ql/src/Likely Bugs/Likely Typos/SuspiciousDateFormat.ql index b39da1aa870..9e24a3b7b4e 100644 --- a/java/ql/src/Likely Bugs/Likely Typos/SuspiciousDateFormat.ql +++ b/java/ql/src/Likely Bugs/Likely Typos/SuspiciousDateFormat.ql @@ -6,6 +6,7 @@ * @precision high * @id java/suspicious-date-format * @tags correctness + * quality */ import java diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.ql b/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.ql index 92da62633d8..d210581d20c 100644 --- a/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.ql +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.ql @@ -9,6 +9,7 @@ * @tags efficiency * correctness * resources + * quality * external/cwe/cwe-404 * external/cwe/cwe-772 */ diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.ql b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.ql index fa04de220bf..fe23286b2e0 100644 --- a/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.ql +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.ql @@ -9,6 +9,7 @@ * @tags efficiency * correctness * resources + * quality * external/cwe/cwe-404 * external/cwe/cwe-772 */ diff --git a/java/ql/src/codeql-suites/java-code-quality.qls b/java/ql/src/codeql-suites/java-code-quality.qls index 847864606ca..2074f9378cf 100644 --- a/java/ql/src/codeql-suites/java-code-quality.qls +++ b/java/ql/src/codeql-suites/java-code-quality.qls @@ -1,17 +1,3 @@ - queries: . -- include: - id: - - java/contradictory-type-checks - - java/do-not-call-finalize - - java/equals-on-unrelated-types - - java/inconsistent-equals-and-hashcode - - java/input-resource-leak - - java/integer-multiplication-cast-to-long - - java/junit5-missing-nested-annotation - - java/output-resource-leak - - java/reference-equality-of-boxed-types - - java/string-replace-all-with-non-regex - - java/suspicious-date-format - - java/type-variable-hides-type - - java/unchecked-cast-in-equals - - java/unused-container \ No newline at end of file +- apply: code-quality-selectors.yml + from: codeql/suite-helpers From bb46ca7a6423669ccfe27fc35fe3af3ca8029fae Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Tue, 29 Apr 2025 16:22:37 +0200 Subject: [PATCH 29/66] Modify quality query suite selector for actions, cpp, rust and swift --- actions/ql/src/codeql-suites/actions-code-quality.qls | 4 +++- cpp/ql/src/codeql-suites/cpp-code-quality.qls | 4 +++- rust/ql/src/codeql-suites/rust-code-quality.qls | 4 +++- swift/ql/src/codeql-suites/swift-code-quality.qls | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/actions/ql/src/codeql-suites/actions-code-quality.qls b/actions/ql/src/codeql-suites/actions-code-quality.qls index 0637a088a01..2074f9378cf 100644 --- a/actions/ql/src/codeql-suites/actions-code-quality.qls +++ b/actions/ql/src/codeql-suites/actions-code-quality.qls @@ -1 +1,3 @@ -[] \ No newline at end of file +- queries: . +- apply: code-quality-selectors.yml + from: codeql/suite-helpers diff --git a/cpp/ql/src/codeql-suites/cpp-code-quality.qls b/cpp/ql/src/codeql-suites/cpp-code-quality.qls index 0637a088a01..2074f9378cf 100644 --- a/cpp/ql/src/codeql-suites/cpp-code-quality.qls +++ b/cpp/ql/src/codeql-suites/cpp-code-quality.qls @@ -1 +1,3 @@ -[] \ No newline at end of file +- queries: . +- apply: code-quality-selectors.yml + from: codeql/suite-helpers diff --git a/rust/ql/src/codeql-suites/rust-code-quality.qls b/rust/ql/src/codeql-suites/rust-code-quality.qls index 0637a088a01..2074f9378cf 100644 --- a/rust/ql/src/codeql-suites/rust-code-quality.qls +++ b/rust/ql/src/codeql-suites/rust-code-quality.qls @@ -1 +1,3 @@ -[] \ No newline at end of file +- queries: . +- apply: code-quality-selectors.yml + from: codeql/suite-helpers diff --git a/swift/ql/src/codeql-suites/swift-code-quality.qls b/swift/ql/src/codeql-suites/swift-code-quality.qls index 0637a088a01..2074f9378cf 100644 --- a/swift/ql/src/codeql-suites/swift-code-quality.qls +++ b/swift/ql/src/codeql-suites/swift-code-quality.qls @@ -1 +1,3 @@ -[] \ No newline at end of file +- queries: . +- apply: code-quality-selectors.yml + from: codeql/suite-helpers From 2515b06b2afef5c61245406401fd13dc6ed354c2 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Tue, 29 Apr 2025 16:22:57 +0200 Subject: [PATCH 30/66] Use code-quality-selectors in Go suite --- go/ql/src/InconsistentCode/LengthComparisonOffByOne.ql | 1 + go/ql/src/InconsistentCode/MissingErrorCheck.ql | 1 + .../InconsistentCode/UnhandledCloseWritableHandle.ql | 1 + go/ql/src/InconsistentCode/WrappedErrorAlwaysNil.ql | 1 + go/ql/src/RedundantCode/NegativeLengthCheck.ql | 1 + go/ql/src/RedundantCode/RedundantRecover.ql | 1 + go/ql/src/codeql-suites/go-code-quality.qls | 10 ++-------- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go/ql/src/InconsistentCode/LengthComparisonOffByOne.ql b/go/ql/src/InconsistentCode/LengthComparisonOffByOne.ql index 05a468b8517..436eb8a8fe5 100644 --- a/go/ql/src/InconsistentCode/LengthComparisonOffByOne.ql +++ b/go/ql/src/InconsistentCode/LengthComparisonOffByOne.ql @@ -8,6 +8,7 @@ * @tags reliability * correctness * logic + * quality * external/cwe/cwe-193 * @precision high */ diff --git a/go/ql/src/InconsistentCode/MissingErrorCheck.ql b/go/ql/src/InconsistentCode/MissingErrorCheck.ql index d2021517267..9acd7e13602 100644 --- a/go/ql/src/InconsistentCode/MissingErrorCheck.ql +++ b/go/ql/src/InconsistentCode/MissingErrorCheck.ql @@ -8,6 +8,7 @@ * @tags reliability * correctness * logic + * quality * @precision high */ diff --git a/go/ql/src/InconsistentCode/UnhandledCloseWritableHandle.ql b/go/ql/src/InconsistentCode/UnhandledCloseWritableHandle.ql index 211cf4b3985..051e4644cc7 100644 --- a/go/ql/src/InconsistentCode/UnhandledCloseWritableHandle.ql +++ b/go/ql/src/InconsistentCode/UnhandledCloseWritableHandle.ql @@ -11,6 +11,7 @@ * correctness * call * defer + * quality */ import go diff --git a/go/ql/src/InconsistentCode/WrappedErrorAlwaysNil.ql b/go/ql/src/InconsistentCode/WrappedErrorAlwaysNil.ql index 48df6a9297d..fac236c7f03 100644 --- a/go/ql/src/InconsistentCode/WrappedErrorAlwaysNil.ql +++ b/go/ql/src/InconsistentCode/WrappedErrorAlwaysNil.ql @@ -7,6 +7,7 @@ * @tags reliability * correctness * logic + * quality * @precision high */ diff --git a/go/ql/src/RedundantCode/NegativeLengthCheck.ql b/go/ql/src/RedundantCode/NegativeLengthCheck.ql index f1ae5409c2d..adac6fe78d9 100644 --- a/go/ql/src/RedundantCode/NegativeLengthCheck.ql +++ b/go/ql/src/RedundantCode/NegativeLengthCheck.ql @@ -9,6 +9,7 @@ * @precision very-high * @id go/negative-length-check * @tags correctness + * quality */ import go diff --git a/go/ql/src/RedundantCode/RedundantRecover.ql b/go/ql/src/RedundantCode/RedundantRecover.ql index d2138068515..08fc06727e5 100644 --- a/go/ql/src/RedundantCode/RedundantRecover.ql +++ b/go/ql/src/RedundantCode/RedundantRecover.ql @@ -8,6 +8,7 @@ * @id go/redundant-recover * @tags maintainability * correctness + * quality * @precision high */ diff --git a/go/ql/src/codeql-suites/go-code-quality.qls b/go/ql/src/codeql-suites/go-code-quality.qls index 84c0fe6b35b..2074f9378cf 100644 --- a/go/ql/src/codeql-suites/go-code-quality.qls +++ b/go/ql/src/codeql-suites/go-code-quality.qls @@ -1,9 +1,3 @@ - queries: . -- include: - id: - - go/unhandled-writable-file-close - - go/unexpected-nil-value - - go/negative-length-check - - go/redundant-recover - - go/missing-error-check - - go/index-out-of-bounds \ No newline at end of file +- apply: code-quality-selectors.yml + from: codeql/suite-helpers From d56c5225f6c0dc4c8df6560c0f293de24d8a2499 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Tue, 29 Apr 2025 16:23:08 +0200 Subject: [PATCH 31/66] Use code-quality-selectors in JS suite --- .../ql/src/Declarations/IneffectiveParameterType.ql | 1 + javascript/ql/src/Expressions/MissingAwait.ql | 1 + javascript/ql/src/LanguageFeatures/SpuriousArguments.ql | 1 + javascript/ql/src/RegExp/RegExpAlwaysMatches.ql | 1 + .../ql/src/codeql-suites/javascript-code-quality.qls | 9 ++------- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/javascript/ql/src/Declarations/IneffectiveParameterType.ql b/javascript/ql/src/Declarations/IneffectiveParameterType.ql index da8d610c974..18899bd0c4e 100644 --- a/javascript/ql/src/Declarations/IneffectiveParameterType.ql +++ b/javascript/ql/src/Declarations/IneffectiveParameterType.ql @@ -7,6 +7,7 @@ * @precision high * @tags correctness * typescript + * quality */ import javascript diff --git a/javascript/ql/src/Expressions/MissingAwait.ql b/javascript/ql/src/Expressions/MissingAwait.ql index 08b6c7af0bb..d97c006a7bd 100644 --- a/javascript/ql/src/Expressions/MissingAwait.ql +++ b/javascript/ql/src/Expressions/MissingAwait.ql @@ -5,6 +5,7 @@ * @problem.severity warning * @id js/missing-await * @tags correctness + * quality * @precision high */ diff --git a/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql b/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql index fd3914c9023..fd493a247a4 100644 --- a/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql +++ b/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql @@ -7,6 +7,7 @@ * @tags maintainability * correctness * language-features + * quality * external/cwe/cwe-685 * @precision very-high */ diff --git a/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql b/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql index 04756158f55..1d063534903 100644 --- a/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql +++ b/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql @@ -6,6 +6,7 @@ * @id js/regex/always-matches * @tags correctness * regular-expressions + * quality * @precision high */ diff --git a/javascript/ql/src/codeql-suites/javascript-code-quality.qls b/javascript/ql/src/codeql-suites/javascript-code-quality.qls index 5570d91c4f7..2074f9378cf 100644 --- a/javascript/ql/src/codeql-suites/javascript-code-quality.qls +++ b/javascript/ql/src/codeql-suites/javascript-code-quality.qls @@ -1,8 +1,3 @@ - queries: . -- include: - id: - - js/missing-await - - js/regex/always-matches - - js/superfluous-trailing-arguments - - js/useless-expression - - js/ineffective-parameter-type \ No newline at end of file +- apply: code-quality-selectors.yml + from: codeql/suite-helpers From fdeac95714f4db728fe64c23e53ff6b8aee4d66f Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Tue, 29 Apr 2025 16:23:22 +0200 Subject: [PATCH 32/66] Use code-quality-selectors in Python suite --- .../query-suite/python-code-quality.qls.expected | 2 ++ python/ql/src/Functions/NonCls.ql | 1 + python/ql/src/Functions/NonSelf.ql | 1 + python/ql/src/codeql-suites/python-code-quality.qls | 8 ++------ 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/python/ql/integration-tests/query-suite/python-code-quality.qls.expected b/python/ql/integration-tests/query-suite/python-code-quality.qls.expected index 47643f6a319..b81d300d024 100644 --- a/python/ql/integration-tests/query-suite/python-code-quality.qls.expected +++ b/python/ql/integration-tests/query-suite/python-code-quality.qls.expected @@ -1,4 +1,6 @@ ql/python/ql/src/Functions/NonCls.ql ql/python/ql/src/Functions/NonSelf.ql +ql/python/ql/src/Functions/ReturnConsistentTupleSizes.ql ql/python/ql/src/Functions/SignatureSpecialMethods.ql ql/python/ql/src/Resources/FileNotAlwaysClosed.ql +ql/python/ql/src/Variables/LoopVariableCapture/LoopVariableCapture.ql diff --git a/python/ql/src/Functions/NonCls.ql b/python/ql/src/Functions/NonCls.ql index d36eeb9a6ec..0808da00e0a 100644 --- a/python/ql/src/Functions/NonCls.ql +++ b/python/ql/src/Functions/NonCls.ql @@ -5,6 +5,7 @@ * @tags maintainability * readability * convention + * quality * @problem.severity recommendation * @sub-severity high * @precision high diff --git a/python/ql/src/Functions/NonSelf.ql b/python/ql/src/Functions/NonSelf.ql index cea15d3661a..35b7af9b800 100644 --- a/python/ql/src/Functions/NonSelf.ql +++ b/python/ql/src/Functions/NonSelf.ql @@ -5,6 +5,7 @@ * @tags maintainability * readability * convention + * quality * @problem.severity recommendation * @sub-severity high * @precision very-high diff --git a/python/ql/src/codeql-suites/python-code-quality.qls b/python/ql/src/codeql-suites/python-code-quality.qls index 3ada7e8eb4c..2074f9378cf 100644 --- a/python/ql/src/codeql-suites/python-code-quality.qls +++ b/python/ql/src/codeql-suites/python-code-quality.qls @@ -1,7 +1,3 @@ - queries: . -- include: - id: - - py/not-named-self - - py/not-named-cls - - py/file-not-closed - - py/special-method-wrong-signature +- apply: code-quality-selectors.yml + from: codeql/suite-helpers From e9e6d68a6ebaa4a2a9827b3c5f9fbcf50df1caca Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Tue, 29 Apr 2025 16:23:33 +0200 Subject: [PATCH 33/66] Use code-quality-selectors in Ruby suite --- .../query-suite/ruby-code-quality.qls.expected | 1 - ruby/ql/src/codeql-suites/ruby-code-quality.qls | 7 ++----- ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql | 1 + ruby/ql/src/queries/variables/DeadStoreOfLocal.ql | 1 + 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ruby/ql/integration-tests/query-suite/ruby-code-quality.qls.expected b/ruby/ql/integration-tests/query-suite/ruby-code-quality.qls.expected index 94b2f19caaa..db6b7590220 100644 --- a/ruby/ql/integration-tests/query-suite/ruby-code-quality.qls.expected +++ b/ruby/ql/integration-tests/query-suite/ruby-code-quality.qls.expected @@ -1,3 +1,2 @@ ql/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql -ql/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql ql/ruby/ql/src/queries/variables/UninitializedLocal.ql diff --git a/ruby/ql/src/codeql-suites/ruby-code-quality.qls b/ruby/ql/src/codeql-suites/ruby-code-quality.qls index 2111c6979ef..2074f9378cf 100644 --- a/ruby/ql/src/codeql-suites/ruby-code-quality.qls +++ b/ruby/ql/src/codeql-suites/ruby-code-quality.qls @@ -1,6 +1,3 @@ - queries: . -- include: - id: - - rb/database-query-in-loop - - rb/useless-assignment-to-local - - rb/uninitialized-local-variable \ No newline at end of file +- apply: code-quality-selectors.yml + from: codeql/suite-helpers diff --git a/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql b/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql index b17c5ecd9ba..835fe620984 100644 --- a/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql +++ b/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql @@ -6,6 +6,7 @@ * @precision high * @id rb/database-query-in-loop * @tags performance + * quality */ import ruby diff --git a/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql b/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql index 547d7d3cd89..a7b37515d7f 100644 --- a/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql +++ b/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql @@ -6,6 +6,7 @@ * @problem.severity warning * @id rb/useless-assignment-to-local * @tags maintainability + * quality * external/cwe/cwe-563 * @precision medium */ From 280ce058a934640b06c1dffcf6e0e478e06be189 Mon Sep 17 00:00:00 2001 From: Paolo Tranquilli Date: Tue, 29 Apr 2025 17:38:33 +0200 Subject: [PATCH 34/66] Revert "Bazel: update `rules_kotlin` to 2.1.3" --- MODULE.bazel | 2 +- .../{2.1.3-codeql.1 => 2.0.0-codeql.1}/MODULE.bazel | 4 ++-- .../patches/codeql_add_language_version_option.patch | 12 +++++++----- .../patches/codeql_do_not_emit_jdeps.patch | 0 .../modules/rules_kotlin/2.0.0-codeql.1/source.json | 9 +++++++++ .../modules/rules_kotlin/2.1.3-codeql.1/source.json | 9 --------- .../registry/modules/rules_kotlin/metadata.json | 2 +- 7 files changed, 20 insertions(+), 18 deletions(-) rename misc/bazel/registry/modules/rules_kotlin/{2.1.3-codeql.1 => 2.0.0-codeql.1}/MODULE.bazel (93%) rename misc/bazel/registry/modules/rules_kotlin/{2.1.3-codeql.1 => 2.0.0-codeql.1}/patches/codeql_add_language_version_option.patch (77%) rename misc/bazel/registry/modules/rules_kotlin/{2.1.3-codeql.1 => 2.0.0-codeql.1}/patches/codeql_do_not_emit_jdeps.patch (100%) create mode 100644 misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/source.json delete mode 100644 misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/source.json diff --git a/MODULE.bazel b/MODULE.bazel index 7e8e36b5309..ae00bca4390 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -24,7 +24,7 @@ bazel_dep(name = "bazel_skylib", version = "1.7.1") bazel_dep(name = "abseil-cpp", version = "20240116.1", repo_name = "absl") bazel_dep(name = "nlohmann_json", version = "3.11.3", repo_name = "json") bazel_dep(name = "fmt", version = "10.0.0") -bazel_dep(name = "rules_kotlin", version = "2.1.3-codeql.1") +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") diff --git a/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/MODULE.bazel b/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/MODULE.bazel similarity index 93% rename from misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/MODULE.bazel rename to misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/MODULE.bazel index 41bef52cf9f..6c11301e234 100644 --- a/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/MODULE.bazel +++ b/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/MODULE.bazel @@ -1,11 +1,11 @@ module( name = "rules_kotlin", - version = "2.1.3-codeql.1", + version = "2.0.0-codeql.1", compatibility_level = 1, repo_name = "rules_kotlin", ) -bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "platforms", version = "0.0.10") bazel_dep(name = "bazel_skylib", version = "1.7.1") bazel_dep(name = "rules_java", version = "7.2.0") bazel_dep(name = "rules_python", version = "0.23.1") diff --git a/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/patches/codeql_add_language_version_option.patch b/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/patches/codeql_add_language_version_option.patch similarity index 77% rename from misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/patches/codeql_add_language_version_option.patch rename to misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/patches/codeql_add_language_version_option.patch index b0bf85d4fae..d5716daba07 100644 --- a/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/patches/codeql_add_language_version_option.patch +++ b/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/patches/codeql_add_language_version_option.patch @@ -1,11 +1,13 @@ +We need to build different extractor variants with different -language-version options, which is not allowed +in current kotlin_rules diff --git a/src/main/starlark/core/options/opts.kotlinc.bzl b/src/main/starlark/core/options/opts.kotlinc.bzl -index 76df826..ef2d6ca 100644 +index 5e1461b..b93e6aa 100644 --- a/src/main/starlark/core/options/opts.kotlinc.bzl +++ b/src/main/starlark/core/options/opts.kotlinc.bzl @@ -33,6 +33,11 @@ def _map_jdk_release_to_flag(version): return None return ["-Xjdk-release=%s" % version] - + +def _map_language_version_to_flag(version): + if not version: + return None @@ -14,7 +16,7 @@ index 76df826..ef2d6ca 100644 _KOPTS_ALL = { "warn": struct( args = dict( -@@ -429,6 +434,15 @@ _KOPTS_ALL = { +@@ -417,6 +422,15 @@ _KOPTS_ALL = { value_to_flag = None, map_value_to_flag = _map_jdk_release_to_flag, ), @@ -28,5 +30,5 @@ index 76df826..ef2d6ca 100644 + map_value_to_flag = _map_language_version_to_flag, + ), } - - def _merge(key, rule_defined): + + # Filters out options that are not available in current compiler release \ No newline at end of file diff --git a/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/patches/codeql_do_not_emit_jdeps.patch b/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/patches/codeql_do_not_emit_jdeps.patch similarity index 100% rename from misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/patches/codeql_do_not_emit_jdeps.patch rename to misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/patches/codeql_do_not_emit_jdeps.patch diff --git a/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/source.json b/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/source.json new file mode 100644 index 00000000000..96d828e3455 --- /dev/null +++ b/misc/bazel/registry/modules/rules_kotlin/2.0.0-codeql.1/source.json @@ -0,0 +1,9 @@ +{ + "integrity": "sha256-2JcjzJ67t72y66yhr30jg+B0YVZDz5ejZrdYp2t9xEM=", + "url": "https://github.com/bazelbuild/rules_kotlin/releases/download/v2.0.0/rules_kotlin-v2.0.0.tar.gz", + "patches": { + "codeql_do_not_emit_jdeps.patch": "sha256-1ir4Aio1SICxnj1wafQ0GefT/m7bwn2n+SQwq19V3A8=", + "codeql_add_language_version_option.patch": "sha256-t8Fm0bYZ4Q4vTqcoXZjyK4WPEoAafjE4whJLNnrnRbg=" + }, + "patch_strip": 1 +} diff --git a/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/source.json b/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/source.json deleted file mode 100644 index 8abac8eb4bd..00000000000 --- a/misc/bazel/registry/modules/rules_kotlin/2.1.3-codeql.1/source.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "integrity": "sha256-4USKVrJGJAeyaI3qht9cN1s2oJkb1HjC3dlMlxaBJeI=", - "url": "https://github.com/bazelbuild/rules_kotlin/releases/download/v2.1.3/rules_kotlin-v2.1.3.tar.gz", - "patches": { - "codeql_do_not_emit_jdeps.patch": "sha256-1ir4Aio1SICxnj1wafQ0GefT/m7bwn2n+SQwq19V3A8=", - "codeql_add_language_version_option.patch": "sha256-F7RthnrO6kJlCNcQ76L1Utqll2OwyeFZ/HmT82NwgB4=" - }, - "patch_strip": 1 -} diff --git a/misc/bazel/registry/modules/rules_kotlin/metadata.json b/misc/bazel/registry/modules/rules_kotlin/metadata.json index dace87c72d1..ac259b2e729 100644 --- a/misc/bazel/registry/modules/rules_kotlin/metadata.json +++ b/misc/bazel/registry/modules/rules_kotlin/metadata.json @@ -21,7 +21,7 @@ "github:bazelbuild/rules_kotlin" ], "versions": [ - "2.1.3-codeql.1" + "2.0.0-codeql.1" ], "yanked_versions": {} } From eb7cd3d221919d99fd8443c7b99d7eb160b2d1a5 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Wed, 30 Apr 2025 08:54:57 +0200 Subject: [PATCH 35/66] Ruby: disable diff-informed mode on regex queries These queries were failing in `codeql test run --check-diff-informed` because they can select locations inside the regex. Until that can be fixed, diff-informed mode is disabled for these queries. --- .../ruby/security/regexp/MissingFullAnchorQuery.qll | 10 ---------- .../ruby/security/regexp/PolynomialReDoSQuery.qll | 10 ---------- 2 files changed, 20 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/security/regexp/MissingFullAnchorQuery.qll b/ruby/ql/lib/codeql/ruby/security/regexp/MissingFullAnchorQuery.qll index d511c6f3fbf..febfa0712d9 100644 --- a/ruby/ql/lib/codeql/ruby/security/regexp/MissingFullAnchorQuery.qll +++ b/ruby/ql/lib/codeql/ruby/security/regexp/MissingFullAnchorQuery.qll @@ -17,16 +17,6 @@ private module MissingFullAnchorConfig implements DataFlow::ConfigSig { predicate isSink(DataFlow::Node sink) { sink instanceof Sink } predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } - - predicate observeDiffInformedIncrementalMode() { any() } - - Location getASelectedSinkLocation(DataFlow::Node sink) { - result = sink.(Sink).getLocation() - or - result = sink.(Sink).getCallNode().getLocation() - or - result = sink.(Sink).getRegex().getLocation() - } } /** diff --git a/ruby/ql/lib/codeql/ruby/security/regexp/PolynomialReDoSQuery.qll b/ruby/ql/lib/codeql/ruby/security/regexp/PolynomialReDoSQuery.qll index 562b5dad37b..98a42fcf5e7 100644 --- a/ruby/ql/lib/codeql/ruby/security/regexp/PolynomialReDoSQuery.qll +++ b/ruby/ql/lib/codeql/ruby/security/regexp/PolynomialReDoSQuery.qll @@ -18,16 +18,6 @@ private module PolynomialReDoSConfig implements DataFlow::ConfigSig { predicate isSink(DataFlow::Node sink) { sink instanceof Sink } predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } - - predicate observeDiffInformedIncrementalMode() { any() } - - Location getASelectedSinkLocation(DataFlow::Node sink) { - result = sink.(Sink).getLocation() - or - result = sink.(Sink).getHighlight().getLocation() - or - result = sink.(Sink).getRegExp().getLocation() - } } /** From 97532525d85c838d58625247905ca2f881cac144 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 24 Apr 2025 11:03:59 +0200 Subject: [PATCH 36/66] Rust: Crate graph extraction workarounds --- .../codeql/rust/internal/PathResolution.qll | 35 +++++++++++++++-- rust/ql/lib/codeql/rust/internal/Type.qll | 39 ++++++++++++++----- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index a6cc51a21c5..37a895c0dcd 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -323,7 +323,16 @@ abstract private class AssocItemNode extends ItemNode, AssocItem { private class ConstItemNode extends AssocItemNode instanceof Const { override string getName() { result = Const.super.getName().getText() } - override predicate hasImplementation() { super.hasBody() } + override predicate hasImplementation() { + super.hasBody() + or + // for trait items from library code, we do not currently know if they + // have default implementations or not, so we assume they do + exists(TraitItemNode t | + this = t.getAnAssocItem() and + not this.fromSource() + ) + } override Namespace getNamespace() { result.isValue() } @@ -359,7 +368,16 @@ private class VariantItemNode extends ItemNode instanceof Variant { class FunctionItemNode extends AssocItemNode instanceof Function { override string getName() { result = Function.super.getName().getText() } - override predicate hasImplementation() { super.hasBody() } + override predicate hasImplementation() { + super.hasBody() + or + // for trait items from library code, we do not currently know if they + // have default implementations or not, so we assume they do + exists(TraitItemNode t | + this = t.getAnAssocItem() and + not this.fromSource() + ) + } override Namespace getNamespace() { result.isValue() } @@ -862,6 +880,12 @@ class RelevantPath extends Path { this.getQualifier().(RelevantPath).isCratePath("$crate", _) and this.getText() = name } + + // TODO: Remove once the crate graph extractor generates publicly visible paths + predicate requiresExtractorWorkaround() { + not this.fromSource() and + this = any(RelevantPath p).getQualifier() + } } private predicate isModule(ItemNode m) { m instanceof Module } @@ -1029,6 +1053,7 @@ pragma[nomagic] private ItemNode resolvePathPrivate( RelevantPath path, ModuleLikeNode itemParent, ModuleLikeNode pathParent ) { + not path.requiresExtractorWorkaround() and result = resolvePath1(path) and itemParent = result.getImmediateParentModule() and not result.isPublic() and @@ -1062,7 +1087,11 @@ private ModuleLikeNode getAPrivateVisibleModule(ModuleLikeNode itemParent) { cached ItemNode resolvePath(RelevantPath path) { result = resolvePath1(path) and - result.isPublic() + ( + result.isPublic() + or + path.requiresExtractorWorkaround() + ) or exists(ModuleLikeNode itemParent, ModuleLikeNode pathParent | result = resolvePathPrivate(path, itemParent, pathParent) and diff --git a/rust/ql/lib/codeql/rust/internal/Type.qll b/rust/ql/lib/codeql/rust/internal/Type.qll index 9e063d21516..5abada5726d 100644 --- a/rust/ql/lib/codeql/rust/internal/Type.qll +++ b/rust/ql/lib/codeql/rust/internal/Type.qll @@ -29,7 +29,26 @@ newtype TType = abstract class Type extends TType { /** Gets the method `name` belonging to this type, if any. */ pragma[nomagic] - abstract Function getMethod(string name); + final Function getMethod(string name) { + result = this.getAMethod(name) and + ( + // when a method exists in both source code and in library code, it is because + // we also extracted the source code as library code, and hence we only want + // the method from source code + result.fromSource() + or + not this.getAMethod(name).fromSource() + ) + } + + /** + * Gets a method `name` belonging to this type, if any. + * + * Multiple methods may exist with the same name when it exists in both + * source code and in library code. + */ + pragma[nomagic] + abstract Function getAMethod(string name); /** Gets the struct field `name` belonging to this type, if any. */ pragma[nomagic] @@ -74,7 +93,7 @@ abstract class Type extends TType { abstract private class StructOrEnumType extends Type { abstract ItemNode asItemNode(); - final override Function getMethod(string name) { + final override Function getAMethod(string name) { result = this.asItemNode().getASuccessor(name) and exists(ImplOrTraitItemNode impl | result = impl.getAnAssocItem() | impl instanceof Trait @@ -138,7 +157,7 @@ class TraitType extends Type, TTrait { TraitType() { this = TTrait(trait) } - override Function getMethod(string name) { result = trait.(ItemNode).getASuccessor(name) } + override Function getAMethod(string name) { result = trait.(ItemNode).getASuccessor(name) } override StructField getStructField(string name) { none() } @@ -220,7 +239,7 @@ class ImplType extends Type, TImpl { ImplType() { this = TImpl(impl) } - override Function getMethod(string name) { result = impl.(ItemNode).getASuccessor(name) } + override Function getAMethod(string name) { result = impl.(ItemNode).getASuccessor(name) } override StructField getStructField(string name) { none() } @@ -247,7 +266,7 @@ class ImplType extends Type, TImpl { class ArrayType extends Type, TArrayType { ArrayType() { this = TArrayType() } - override Function getMethod(string name) { none() } + override Function getAMethod(string name) { none() } override StructField getStructField(string name) { none() } @@ -273,7 +292,7 @@ class ArrayType extends Type, TArrayType { class RefType extends Type, TRefType { RefType() { this = TRefType() } - override Function getMethod(string name) { none() } + override Function getAMethod(string name) { none() } override StructField getStructField(string name) { none() } @@ -318,7 +337,7 @@ class TypeParamTypeParameter extends TypeParameter, TTypeParamTypeParameter { TypeParam getTypeParam() { result = typeParam } - override Function getMethod(string name) { + override Function getAMethod(string name) { // NOTE: If the type parameter has trait bounds, then this finds methods // on the bounding traits. result = typeParam.(ItemNode).getASuccessor(name) @@ -377,7 +396,7 @@ class AssociatedTypeTypeParameter extends TypeParameter, TAssociatedTypeTypePara int getIndex() { traitAliasIndex(_, result, typeAlias) } - override Function getMethod(string name) { none() } + override Function getAMethod(string name) { none() } override string toString() { result = typeAlias.getName().getText() } @@ -388,7 +407,7 @@ class AssociatedTypeTypeParameter extends TypeParameter, TAssociatedTypeTypePara /** An implicit reference type parameter. */ class RefTypeParameter extends TypeParameter, TRefTypeParameter { - override Function getMethod(string name) { none() } + override Function getAMethod(string name) { none() } override string toString() { result = "&T" } @@ -411,7 +430,7 @@ class SelfTypeParameter extends TypeParameter, TSelfTypeParameter { override TypeMention getABaseTypeMention() { result = trait } - override Function getMethod(string name) { + override Function getAMethod(string name) { // The `Self` type parameter is an implementation of the trait, so it has // all the trait's methods. result = trait.(ItemNode).getASuccessor(name) From 52bd99b852280eae52e31d13e8861eb44bb0f230 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 30 Apr 2025 11:01:29 +0200 Subject: [PATCH 37/66] Address review comments --- .../lib/codeql/rust/internal/CachedStages.qll | 2 +- .../codeql/rust/internal/PathResolution.qll | 55 ++++++++++++------- rust/ql/lib/codeql/rust/internal/Type.qll | 39 ++++--------- 3 files changed, 47 insertions(+), 49 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/CachedStages.qll b/rust/ql/lib/codeql/rust/internal/CachedStages.qll index 4041b2731f9..2a7447ed7a3 100644 --- a/rust/ql/lib/codeql/rust/internal/CachedStages.qll +++ b/rust/ql/lib/codeql/rust/internal/CachedStages.qll @@ -120,7 +120,7 @@ module Stages { or exists(resolvePath(_)) or - exists(any(ItemNode i).getASuccessor(_)) + exists(any(ItemNode i).getASuccessorFull(_)) or exists(any(ItemNode i).getASuccessorRec(_)) or diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index 37a895c0dcd..5bc45afecf1 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -172,9 +172,14 @@ abstract class ItemNode extends Locatable { result = this.(TypeParamItemNode).resolveABound().getASuccessorRec(name).(AssocItemNode) } - /** Gets a successor named `name` of this item, if any. */ + /** + * Gets a successor named `name` of this item, if any. + * + * Whenever a function exists in both source code and in library code, + * both are included + */ cached - ItemNode getASuccessor(string name) { + ItemNode getASuccessorFull(string name) { Stages::PathResolutionStage::ref() and result = this.getASuccessorRec(name) or @@ -202,6 +207,22 @@ abstract class ItemNode extends Locatable { result.(CrateItemNode).isPotentialDollarCrateTarget() } + /** Gets a successor named `name` of this item, if any. */ + pragma[nomagic] + ItemNode getASuccessor(string name) { + result = this.getASuccessorFull(name) and + ( + // when a function exists in both source code and in library code, it is because + // we also extracted the source code as library code, and hence we only want + // the function from source code + result.fromSource() + or + not result instanceof Function + or + not this.getASuccessorFull(name).(Function).fromSource() + ) + } + /** Gets the location of this item. */ Location getLocation() { result = super.getLocation() } } @@ -234,7 +255,7 @@ abstract private class ModuleLikeNode extends ItemNode { private class SourceFileItemNode extends ModuleLikeNode, SourceFile { pragma[nomagic] ModuleLikeNode getSuper() { - result = any(ModuleItemNode mod | fileImport(mod, this)).getASuccessor("super") + result = any(ModuleItemNode mod | fileImport(mod, this)).getASuccessorFull("super") } override string getName() { result = "(source file)" } @@ -297,7 +318,7 @@ class CrateItemNode extends ItemNode instanceof Crate { predicate isPotentialDollarCrateTarget() { exists(string name, RelevantPath p | p.isDollarCrateQualifiedPath(name) and - exists(this.getASuccessor(name)) + exists(this.getASuccessorFull(name)) ) } @@ -328,10 +349,8 @@ private class ConstItemNode extends AssocItemNode instanceof Const { or // for trait items from library code, we do not currently know if they // have default implementations or not, so we assume they do - exists(TraitItemNode t | - this = t.getAnAssocItem() and - not this.fromSource() - ) + not this.fromSource() and + this = any(TraitItemNode t).getAnAssocItem() } override Namespace getNamespace() { result.isValue() } @@ -373,10 +392,8 @@ class FunctionItemNode extends AssocItemNode instanceof Function { or // for trait items from library code, we do not currently know if they // have default implementations or not, so we assume they do - exists(TraitItemNode t | - this = t.getAnAssocItem() and - not this.fromSource() - ) + not this.fromSource() and + this = any(TraitItemNode t).getAnAssocItem() } override Namespace getNamespace() { result.isValue() } @@ -940,8 +957,8 @@ private predicate unqualifiedPathLookup(ItemNode encl, string name, Namespace ns } pragma[nomagic] -private ItemNode getASuccessor(ItemNode pred, string name, Namespace ns) { - result = pred.getASuccessor(name) and +private ItemNode getASuccessorFull(ItemNode pred, string name, Namespace ns) { + result = pred.getASuccessorFull(name) and ns = result.getNamespace() } @@ -978,7 +995,7 @@ private predicate keywordLookup(ItemNode encl, string name, Namespace ns, Releva pragma[nomagic] private ItemNode unqualifiedPathLookup(RelevantPath p, Namespace ns) { - exists(ItemNode encl, string name | result = getASuccessor(encl, name, ns) | + exists(ItemNode encl, string name | result = getASuccessorFull(encl, name, ns) | unqualifiedPathLookup(encl, name, ns, p) or keywordLookup(encl, name, ns, p) @@ -1002,7 +1019,7 @@ private ItemNode resolvePath0(RelevantPath path, Namespace ns) { or exists(ItemNode q, string name | q = resolvePathQualifier(path, name) and - result = getASuccessor(q, name, ns) + result = getASuccessorFull(q, name, ns) ) or result = resolveUseTreeListItem(_, _, path) and @@ -1127,12 +1144,12 @@ private ItemNode resolveUseTreeListItem(Use use, UseTree tree, RelevantPath path mid = resolveUseTreeListItem(use, midTree) and tree = midTree.getUseTreeList().getAUseTree() and isUseTreeSubPathUnqualified(tree, path, pragma[only_bind_into](name)) and - result = mid.getASuccessor(pragma[only_bind_into](name)) + result = mid.getASuccessorFull(pragma[only_bind_into](name)) ) or exists(ItemNode q, string name | q = resolveUseTreeListItemQualifier(use, tree, path, name) and - result = q.getASuccessor(name) + result = q.getASuccessorFull(name) ) } @@ -1162,7 +1179,7 @@ private predicate useImportEdge(Use use, string name, ItemNode item) { then exists(ItemNode encl, Namespace ns | encl.getADescendant() = use and - item = getASuccessor(used, name, ns) and + item = getASuccessorFull(used, name, ns) and // glob imports can be shadowed not declares(encl, ns, name) and not name = ["super", "self", "Self", "$crate", "crate"] diff --git a/rust/ql/lib/codeql/rust/internal/Type.qll b/rust/ql/lib/codeql/rust/internal/Type.qll index 5abada5726d..9e063d21516 100644 --- a/rust/ql/lib/codeql/rust/internal/Type.qll +++ b/rust/ql/lib/codeql/rust/internal/Type.qll @@ -29,26 +29,7 @@ newtype TType = abstract class Type extends TType { /** Gets the method `name` belonging to this type, if any. */ pragma[nomagic] - final Function getMethod(string name) { - result = this.getAMethod(name) and - ( - // when a method exists in both source code and in library code, it is because - // we also extracted the source code as library code, and hence we only want - // the method from source code - result.fromSource() - or - not this.getAMethod(name).fromSource() - ) - } - - /** - * Gets a method `name` belonging to this type, if any. - * - * Multiple methods may exist with the same name when it exists in both - * source code and in library code. - */ - pragma[nomagic] - abstract Function getAMethod(string name); + abstract Function getMethod(string name); /** Gets the struct field `name` belonging to this type, if any. */ pragma[nomagic] @@ -93,7 +74,7 @@ abstract class Type extends TType { abstract private class StructOrEnumType extends Type { abstract ItemNode asItemNode(); - final override Function getAMethod(string name) { + final override Function getMethod(string name) { result = this.asItemNode().getASuccessor(name) and exists(ImplOrTraitItemNode impl | result = impl.getAnAssocItem() | impl instanceof Trait @@ -157,7 +138,7 @@ class TraitType extends Type, TTrait { TraitType() { this = TTrait(trait) } - override Function getAMethod(string name) { result = trait.(ItemNode).getASuccessor(name) } + override Function getMethod(string name) { result = trait.(ItemNode).getASuccessor(name) } override StructField getStructField(string name) { none() } @@ -239,7 +220,7 @@ class ImplType extends Type, TImpl { ImplType() { this = TImpl(impl) } - override Function getAMethod(string name) { result = impl.(ItemNode).getASuccessor(name) } + override Function getMethod(string name) { result = impl.(ItemNode).getASuccessor(name) } override StructField getStructField(string name) { none() } @@ -266,7 +247,7 @@ class ImplType extends Type, TImpl { class ArrayType extends Type, TArrayType { ArrayType() { this = TArrayType() } - override Function getAMethod(string name) { none() } + override Function getMethod(string name) { none() } override StructField getStructField(string name) { none() } @@ -292,7 +273,7 @@ class ArrayType extends Type, TArrayType { class RefType extends Type, TRefType { RefType() { this = TRefType() } - override Function getAMethod(string name) { none() } + override Function getMethod(string name) { none() } override StructField getStructField(string name) { none() } @@ -337,7 +318,7 @@ class TypeParamTypeParameter extends TypeParameter, TTypeParamTypeParameter { TypeParam getTypeParam() { result = typeParam } - override Function getAMethod(string name) { + override Function getMethod(string name) { // NOTE: If the type parameter has trait bounds, then this finds methods // on the bounding traits. result = typeParam.(ItemNode).getASuccessor(name) @@ -396,7 +377,7 @@ class AssociatedTypeTypeParameter extends TypeParameter, TAssociatedTypeTypePara int getIndex() { traitAliasIndex(_, result, typeAlias) } - override Function getAMethod(string name) { none() } + override Function getMethod(string name) { none() } override string toString() { result = typeAlias.getName().getText() } @@ -407,7 +388,7 @@ class AssociatedTypeTypeParameter extends TypeParameter, TAssociatedTypeTypePara /** An implicit reference type parameter. */ class RefTypeParameter extends TypeParameter, TRefTypeParameter { - override Function getAMethod(string name) { none() } + override Function getMethod(string name) { none() } override string toString() { result = "&T" } @@ -430,7 +411,7 @@ class SelfTypeParameter extends TypeParameter, TSelfTypeParameter { override TypeMention getABaseTypeMention() { result = trait } - override Function getAMethod(string name) { + override Function getMethod(string name) { // The `Self` type parameter is an implementation of the trait, so it has // all the trait's methods. result = trait.(ItemNode).getASuccessor(name) From ba89a5de6f11b2a0fe354bc01b953d36c52acad1 Mon Sep 17 00:00:00 2001 From: Paolo Tranquilli Date: Wed, 30 Apr 2025 11:38:07 +0200 Subject: [PATCH 38/66] Codegen: make missing `codeql` error clearer --- misc/codegen/generators/qlgen.py | 10 ++++++++++ misc/codegen/test/test_qlgen.py | 24 +++++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/misc/codegen/generators/qlgen.py b/misc/codegen/generators/qlgen.py index eefcad3e943..05c945f3462 100755 --- a/misc/codegen/generators/qlgen.py +++ b/misc/codegen/generators/qlgen.py @@ -24,6 +24,7 @@ Moreover in the test directory for each in it will generate bene import logging import pathlib import re +import shutil import subprocess import typing import itertools @@ -257,6 +258,15 @@ def format(codeql, files): if not ql_files: return format_cmd = [codeql, "query", "format", "--in-place", "--"] + ql_files + if "/" in codeql: + if not pathlib.Path(codeql).exists(): + raise FormatError(f"Provided CodeQL binary `{codeql}` does not exist") + else: + codeql_path = shutil.which(codeql) + if not codeql_path: + raise FormatError( + f"`{codeql}` not found in PATH. Either install it, or pass `-- --codeql-binary` with a full path") + codeql = codeql_path res = subprocess.run(format_cmd, stderr=subprocess.PIPE, text=True) if res.returncode: for line in res.stderr.splitlines(): diff --git a/misc/codegen/test/test_qlgen.py b/misc/codegen/test/test_qlgen.py index 01dee251999..75e587fbd5e 100644 --- a/misc/codegen/test/test_qlgen.py +++ b/misc/codegen/test/test_qlgen.py @@ -52,6 +52,8 @@ def qlgen_opts(opts): opts.ql_format = True opts.root_dir = paths.root_dir opts.force = False + opts.codeql_binary = "./my_fake_codeql" + pathlib.Path(opts.codeql_binary).touch() return opts @@ -499,7 +501,6 @@ def test_class_dir_imports(generate_import_list): def test_format(opts, generate, render_manager, run_mock): - opts.codeql_binary = "my_fake_codeql" run_mock.return_value.stderr = "some\nlines\n" render_manager.written = [ pathlib.Path("x", "foo.ql"), @@ -508,13 +509,12 @@ def test_format(opts, generate, render_manager, run_mock): ] generate([schema.Class('A')]) assert run_mock.mock_calls == [ - mock.call(["my_fake_codeql", "query", "format", "--in-place", "--", "x/foo.ql", "bar.qll"], + mock.call([opts.codeql_binary, "query", "format", "--in-place", "--", "x/foo.ql", "bar.qll"], stderr=subprocess.PIPE, text=True), ] def test_format_error(opts, generate, render_manager, run_mock): - opts.codeql_binary = "my_fake_codeql" run_mock.return_value.stderr = "some\nlines\n" run_mock.return_value.returncode = 1 render_manager.written = [ @@ -526,6 +526,24 @@ def test_format_error(opts, generate, render_manager, run_mock): generate([schema.Class('A')]) +def test_format_no_codeql(opts, generate, render_manager, run_mock): + pathlib.Path(opts.codeql_binary).unlink() + render_manager.written = [ + pathlib.Path("bar.qll"), + ] + with pytest.raises(qlgen.FormatError): + generate([schema.Class('A')]) + + +def test_format_no_codeql_in_path(opts, generate, render_manager, run_mock): + opts.codeql_binary = "my_fake_codeql" + render_manager.written = [ + pathlib.Path("bar.qll"), + ] + with pytest.raises(qlgen.FormatError): + generate([schema.Class('A')]) + + @pytest.mark.parametrize("force", [False, True]) def test_manage_parameters(opts, generate, renderer, force): opts.force = force From 9958cc778470292da841a1f255d85919aefaf4aa Mon Sep 17 00:00:00 2001 From: Paolo Tranquilli Date: Wed, 30 Apr 2025 11:43:03 +0200 Subject: [PATCH 39/66] Codegen: consider windows paths in local `codeql` binary heuristic --- misc/codegen/generators/qlgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/codegen/generators/qlgen.py b/misc/codegen/generators/qlgen.py index 05c945f3462..7e898135d01 100755 --- a/misc/codegen/generators/qlgen.py +++ b/misc/codegen/generators/qlgen.py @@ -258,7 +258,7 @@ def format(codeql, files): if not ql_files: return format_cmd = [codeql, "query", "format", "--in-place", "--"] + ql_files - if "/" in codeql: + if "/" in codeql or "\\" in codeql: if not pathlib.Path(codeql).exists(): raise FormatError(f"Provided CodeQL binary `{codeql}` does not exist") else: From da5d7991521a243ae8accedf99b7c60bba2093ee Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 30 Apr 2025 11:59:47 +0200 Subject: [PATCH 40/66] JS: Change note --- javascript/ql/src/change-notes/2025-04-30-promise-all.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 javascript/ql/src/change-notes/2025-04-30-promise-all.md diff --git a/javascript/ql/src/change-notes/2025-04-30-promise-all.md b/javascript/ql/src/change-notes/2025-04-30-promise-all.md new file mode 100644 index 00000000000..a50e31ea01d --- /dev/null +++ b/javascript/ql/src/change-notes/2025-04-30-promise-all.md @@ -0,0 +1,5 @@ +--- +category: minorAnalysis +--- +* Type information is now propagated more precisely through `Promise.all()` calls, + leading to more resolved calls and more sources and sinks being detected. From f584d22b534801e54c6b4137042165bf60ed151c Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Wed, 30 Apr 2025 10:47:11 +0200 Subject: [PATCH 41/66] Rust: Use type inference to insert implicit borrows and derefs --- .../rust/dataflow/internal/DataFlowImpl.qll | 22 ++---------- .../codeql/rust/internal/TypeInference.qll | 26 ++++++++++++-- .../dataflow/local/DataFlowStep.expected | 35 ------------------- .../security/CWE-089/SqlInjection.expected | 12 ------- .../CaptureSummaryModels.expected | 8 ----- 5 files changed, 27 insertions(+), 76 deletions(-) diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll index 4376df7caf8..b2950622440 100644 --- a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll +++ b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll @@ -11,6 +11,7 @@ private import rust private import SsaImpl as SsaImpl private import codeql.rust.controlflow.internal.Scope as Scope private import codeql.rust.internal.PathResolution +private import codeql.rust.internal.TypeInference as TypeInference private import codeql.rust.controlflow.ControlFlowGraph private import codeql.rust.controlflow.CfgNodes private import codeql.rust.dataflow.Ssa @@ -321,23 +322,6 @@ predicate lambdaCallExpr(CallExprCfgNode call, LambdaCallKind kind, ExprCfgNode exists(kind) } -/** Holds if `mc` implicitly borrows its receiver. */ -private predicate implicitBorrow(MethodCallExpr mc) { - // Determining whether an implicit borrow happens depends on the type of the - // receiever as well as the target. As a heuristic we simply check if the - // target takes `self` as a borrow and limit the approximation to cases where - // the receiver is a simple variable. - mc.getReceiver() instanceof VariableAccess and - mc.getStaticTarget().getParamList().getSelfParam().isRef() -} - -/** Holds if `mc` implicitly dereferences its receiver. */ -private predicate implicitDeref(MethodCallExpr mc) { - // Similarly to `implicitBorrow` this is an approximation. - mc.getReceiver() instanceof VariableAccess and - not mc.getStaticTarget().getParamList().getSelfParam().isRef() -} - // Defines a set of aliases needed for the `RustDataFlow` module private module Aliases { class DataFlowCallableAlias = DataFlowCallable; @@ -520,15 +504,15 @@ module RustDataFlow implements InputSig { pragma[nomagic] private predicate implicitDerefToReceiver(Node node1, ReceiverNode node2, ReferenceContent c) { + TypeInference::receiverHasImplicitDeref(node1.asExpr().getExpr()) and node1.asExpr() = node2.getReceiver() and - implicitDeref(node2.getMethodCall().getMethodCallExpr()) and exists(c) } pragma[nomagic] private predicate implicitBorrowToReceiver(Node node1, ReceiverNode node2, ReferenceContent c) { + TypeInference::receiverHasImplicitBorrow(node1.asExpr().getExpr()) and node1.asExpr() = node2.getReceiver() and - implicitBorrow(node2.getMethodCall().getMethodCallExpr()) and exists(c) } diff --git a/rust/ql/lib/codeql/rust/internal/TypeInference.qll b/rust/ql/lib/codeql/rust/internal/TypeInference.qll index 2fae9ef1f5b..b999b72240b 100644 --- a/rust/ql/lib/codeql/rust/internal/TypeInference.qll +++ b/rust/ql/lib/codeql/rust/internal/TypeInference.qll @@ -690,7 +690,7 @@ private Type inferCallExprBaseType(AstNode n, TypePath path) { | if apos.isSelf() then - exists(Type receiverType | receiverType = CallExprBaseMatchingInput::inferReceiverType(n) | + exists(Type receiverType | receiverType = inferType(n) | if receiverType = TRefType() then path = path0 and @@ -840,7 +840,7 @@ private Type inferFieldExprType(AstNode n, TypePath path) { | if apos.isSelf() then - exists(Type receiverType | receiverType = FieldExprMatchingInput::inferReceiverType(n) | + exists(Type receiverType | receiverType = inferType(n) | if receiverType = TRefType() then // adjust for implicit deref @@ -895,6 +895,28 @@ cached private module Cached { private import codeql.rust.internal.CachedStages + /** Holds if `receiver` is the receiver of a method call with an implicit dereference. */ + cached + predicate receiverHasImplicitDeref(AstNode receiver) { + exists(CallExprBaseMatchingInput::Access a, CallExprBaseMatchingInput::AccessPosition apos | + apos.isSelf() and + receiver = a.getNodeAt(apos) and + inferType(receiver) = TRefType() and + CallExprBaseMatching::inferAccessType(a, apos, TypePath::nil()) != TRefType() + ) + } + + /** Holds if `receiver` is the receiver of a method call with an implicit borrow. */ + cached + predicate receiverHasImplicitBorrow(AstNode receiver) { + exists(CallExprBaseMatchingInput::Access a, CallExprBaseMatchingInput::AccessPosition apos | + apos.isSelf() and + receiver = a.getNodeAt(apos) and + CallExprBaseMatching::inferAccessType(a, apos, TypePath::nil()) = TRefType() and + inferType(receiver) != TRefType() + ) + } + pragma[inline] private Type getLookupType(AstNode n) { exists(Type t | diff --git a/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected b/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected index d9f17dbf4c4..6c2a7c2ba85 100644 --- a/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected +++ b/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected @@ -1931,33 +1931,16 @@ readStep | main.rs:221:9:221:23 | ...::Some(...) | Some | main.rs:221:22:221:22 | n | | main.rs:230:9:230:15 | Some(...) | Some | main.rs:230:14:230:14 | n | | main.rs:234:9:234:15 | Some(...) | Some | main.rs:234:14:234:14 | n | -| main.rs:241:10:241:11 | s1 | &ref | main.rs:241:10:241:11 | receiver for s1 | -| main.rs:246:10:246:11 | s1 | &ref | main.rs:246:10:246:11 | receiver for s1 | -| main.rs:249:10:249:11 | s2 | &ref | main.rs:249:10:249:11 | receiver for s2 | -| main.rs:254:10:254:11 | s1 | &ref | main.rs:254:10:254:11 | receiver for s1 | -| main.rs:257:10:257:11 | s2 | &ref | main.rs:257:10:257:11 | receiver for s2 | | main.rs:263:14:263:15 | s1 | Ok | main.rs:263:14:263:16 | TryExpr | | main.rs:263:14:263:15 | s1 | Some | main.rs:263:14:263:16 | TryExpr | | main.rs:265:10:265:11 | s2 | Ok | main.rs:265:10:265:12 | TryExpr | | main.rs:265:10:265:11 | s2 | Some | main.rs:265:10:265:12 | TryExpr | -| main.rs:271:29:271:30 | r1 | &ref | main.rs:271:29:271:30 | receiver for r1 | -| main.rs:272:29:272:30 | r1 | &ref | main.rs:272:29:272:30 | receiver for r1 | -| main.rs:273:10:273:12 | o1a | &ref | main.rs:273:10:273:12 | receiver for o1a | -| main.rs:274:10:274:12 | o1b | &ref | main.rs:274:10:274:12 | receiver for o1b | -| main.rs:277:29:277:30 | r2 | &ref | main.rs:277:29:277:30 | receiver for r2 | -| main.rs:278:29:278:30 | r2 | &ref | main.rs:278:29:278:30 | receiver for r2 | -| main.rs:279:10:279:12 | o2a | &ref | main.rs:279:10:279:12 | receiver for o2a | -| main.rs:280:10:280:12 | o2b | &ref | main.rs:280:10:280:12 | receiver for o2b | | main.rs:287:14:287:15 | s1 | Ok | main.rs:287:14:287:16 | TryExpr | | main.rs:287:14:287:15 | s1 | Some | main.rs:287:14:287:16 | TryExpr | | main.rs:288:14:288:15 | s2 | Ok | main.rs:288:14:288:16 | TryExpr | | main.rs:288:14:288:15 | s2 | Some | main.rs:288:14:288:16 | TryExpr | | main.rs:291:14:291:15 | s3 | Ok | main.rs:291:14:291:16 | TryExpr | | main.rs:291:14:291:15 | s3 | Some | main.rs:291:14:291:16 | TryExpr | -| main.rs:298:10:298:11 | s1 | &ref | main.rs:298:10:298:11 | receiver for s1 | -| main.rs:299:10:299:11 | s1 | &ref | main.rs:299:10:299:11 | receiver for s1 | -| main.rs:302:10:302:11 | s2 | &ref | main.rs:302:10:302:11 | receiver for s2 | -| main.rs:303:10:303:11 | s2 | &ref | main.rs:303:10:303:11 | receiver for s2 | | main.rs:315:9:315:25 | ...::A(...) | A | main.rs:315:24:315:24 | n | | main.rs:316:9:316:25 | ...::B(...) | B | main.rs:316:24:316:24 | n | | main.rs:319:9:319:25 | ...::A(...) | A | main.rs:319:24:319:24 | n | @@ -1997,40 +1980,22 @@ readStep | main.rs:442:9:442:20 | TuplePat | tuple.0 | main.rs:442:10:442:13 | cond | | main.rs:442:9:442:20 | TuplePat | tuple.1 | main.rs:442:16:442:19 | name | | main.rs:442:25:442:29 | names | element | main.rs:442:9:442:20 | TuplePat | -| main.rs:444:21:444:24 | name | &ref | main.rs:444:21:444:24 | receiver for name | | main.rs:444:41:444:67 | [post] \|...\| ... | captured default_name | main.rs:444:41:444:67 | [post] default_name | -| main.rs:444:44:444:55 | default_name | &ref | main.rs:444:44:444:55 | receiver for default_name | | main.rs:444:44:444:55 | this | captured default_name | main.rs:444:44:444:55 | default_name | -| main.rs:445:18:445:18 | n | &ref | main.rs:445:18:445:18 | receiver for n | -| main.rs:468:13:468:13 | a | &ref | main.rs:468:13:468:13 | receiver for a | -| main.rs:469:13:469:13 | b | &ref | main.rs:469:13:469:13 | receiver for b | -| main.rs:470:19:470:19 | b | &ref | main.rs:470:19:470:19 | receiver for b | | main.rs:481:10:481:11 | vs | element | main.rs:481:10:481:14 | vs[0] | -| main.rs:482:11:482:12 | vs | &ref | main.rs:482:11:482:12 | receiver for vs | | main.rs:482:11:482:35 | ... .unwrap() | &ref | main.rs:482:10:482:35 | * ... | -| main.rs:483:11:483:12 | vs | &ref | main.rs:483:11:483:12 | receiver for vs | | main.rs:483:11:483:35 | ... .unwrap() | &ref | main.rs:483:10:483:35 | * ... | | main.rs:485:14:485:15 | vs | element | main.rs:485:9:485:9 | v | | main.rs:488:9:488:10 | &... | &ref | main.rs:488:10:488:10 | v | -| main.rs:488:15:488:16 | vs | &ref | main.rs:488:15:488:16 | receiver for vs | | main.rs:488:15:488:23 | vs.iter() | element | main.rs:488:9:488:10 | &... | -| main.rs:492:27:492:28 | vs | &ref | main.rs:492:27:492:28 | receiver for vs | | main.rs:493:9:493:10 | &... | &ref | main.rs:493:10:493:10 | v | | main.rs:493:15:493:17 | vs2 | element | main.rs:493:9:493:10 | &... | -| main.rs:497:5:497:6 | vs | &ref | main.rs:497:5:497:6 | receiver for vs | | main.rs:497:29:497:29 | x | &ref | main.rs:497:28:497:29 | * ... | -| main.rs:498:5:498:6 | vs | &ref | main.rs:498:5:498:6 | receiver for vs | | main.rs:498:34:498:34 | x | &ref | main.rs:498:33:498:34 | * ... | -| main.rs:500:14:500:15 | vs | &ref | main.rs:500:14:500:15 | receiver for vs | | main.rs:500:14:500:27 | vs.into_iter() | element | main.rs:500:9:500:9 | v | | main.rs:506:10:506:15 | vs_mut | element | main.rs:506:10:506:18 | vs_mut[0] | -| main.rs:507:11:507:16 | vs_mut | &ref | main.rs:507:11:507:16 | receiver for vs_mut | | main.rs:507:11:507:39 | ... .unwrap() | &ref | main.rs:507:10:507:39 | * ... | -| main.rs:508:11:508:16 | vs_mut | &ref | main.rs:508:11:508:16 | receiver for vs_mut | | main.rs:508:11:508:39 | ... .unwrap() | &ref | main.rs:508:10:508:39 | * ... | | main.rs:510:9:510:14 | &mut ... | &ref | main.rs:510:14:510:14 | v | -| main.rs:510:19:510:24 | vs_mut | &ref | main.rs:510:19:510:24 | receiver for vs_mut | | main.rs:510:19:510:35 | vs_mut.iter_mut() | element | main.rs:510:9:510:14 | &mut ... | | main.rs:524:11:524:15 | c_ref | &ref | main.rs:524:10:524:15 | * ... | -| main.rs:531:10:531:10 | a | &ref | main.rs:531:10:531:10 | receiver for a | -| main.rs:537:10:537:10 | b | &ref | main.rs:537:10:537:10 | receiver for b | diff --git a/rust/ql/test/query-tests/security/CWE-089/SqlInjection.expected b/rust/ql/test/query-tests/security/CWE-089/SqlInjection.expected index b5aa4a386f2..7c3c1419474 100644 --- a/rust/ql/test/query-tests/security/CWE-089/SqlInjection.expected +++ b/rust/ql/test/query-tests/security/CWE-089/SqlInjection.expected @@ -31,15 +31,11 @@ edges | sqlx.rs:52:32:52:87 | ...::must_use(...) | sqlx.rs:52:9:52:20 | safe_query_3 | provenance | | | sqlx.rs:52:32:52:87 | MacroExpr | sqlx.rs:52:32:52:87 | ...::format(...) | provenance | MaD:4 | | sqlx.rs:52:32:52:87 | { ... } | sqlx.rs:52:32:52:87 | ...::must_use(...) | provenance | MaD:9 | -| sqlx.rs:53:9:53:22 | unsafe_query_1 [&ref] | sqlx.rs:63:26:63:39 | unsafe_query_1 [&ref] | provenance | | | sqlx.rs:53:9:53:22 | unsafe_query_1 [&ref] | sqlx.rs:63:26:63:48 | unsafe_query_1.as_str() | provenance | MaD:3 | -| sqlx.rs:53:9:53:22 | unsafe_query_1 [&ref] | sqlx.rs:74:25:74:38 | unsafe_query_1 [&ref] | provenance | | | sqlx.rs:53:9:53:22 | unsafe_query_1 [&ref] | sqlx.rs:74:25:74:47 | unsafe_query_1.as_str() | provenance | MaD:3 | | sqlx.rs:53:26:53:36 | &arg_string [&ref] | sqlx.rs:53:9:53:22 | unsafe_query_1 [&ref] | provenance | | | sqlx.rs:53:27:53:36 | arg_string | sqlx.rs:53:26:53:36 | &arg_string [&ref] | provenance | | -| sqlx.rs:54:9:54:22 | unsafe_query_2 [&ref] | sqlx.rs:65:30:65:43 | unsafe_query_2 [&ref] | provenance | | | sqlx.rs:54:9:54:22 | unsafe_query_2 [&ref] | sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | provenance | MaD:3 | -| sqlx.rs:54:9:54:22 | unsafe_query_2 [&ref] | sqlx.rs:76:29:76:42 | unsafe_query_2 [&ref] | provenance | | | sqlx.rs:54:9:54:22 | unsafe_query_2 [&ref] | sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | provenance | MaD:3 | | sqlx.rs:54:26:54:39 | &remote_string [&ref] | sqlx.rs:54:9:54:22 | unsafe_query_2 [&ref] | provenance | | | sqlx.rs:54:27:54:39 | remote_string | sqlx.rs:54:26:54:39 | &remote_string [&ref] | provenance | | @@ -50,10 +46,6 @@ edges | sqlx.rs:56:34:56:89 | ...::must_use(...) | sqlx.rs:56:9:56:22 | unsafe_query_4 | provenance | | | sqlx.rs:56:34:56:89 | MacroExpr | sqlx.rs:56:34:56:89 | ...::format(...) | provenance | MaD:4 | | sqlx.rs:56:34:56:89 | { ... } | sqlx.rs:56:34:56:89 | ...::must_use(...) | provenance | MaD:9 | -| sqlx.rs:63:26:63:39 | unsafe_query_1 [&ref] | sqlx.rs:63:26:63:48 | unsafe_query_1.as_str() | provenance | MaD:3 | -| sqlx.rs:65:30:65:43 | unsafe_query_2 [&ref] | sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | provenance | MaD:3 | -| sqlx.rs:74:25:74:38 | unsafe_query_1 [&ref] | sqlx.rs:74:25:74:47 | unsafe_query_1.as_str() | provenance | MaD:3 | -| sqlx.rs:76:29:76:42 | unsafe_query_2 [&ref] | sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | provenance | MaD:3 | models | 1 | Source: lang:std; crate::env::args; commandargs; ReturnValue.Element | | 2 | Source: repo:https://github.com/seanmonstar/reqwest:reqwest; crate::blocking::get; remote; ReturnValue.Field[crate::result::Result::Ok(0)] | @@ -100,15 +92,11 @@ nodes | sqlx.rs:56:34:56:89 | MacroExpr | semmle.label | MacroExpr | | sqlx.rs:56:34:56:89 | { ... } | semmle.label | { ... } | | sqlx.rs:62:26:62:46 | safe_query_3.as_str() | semmle.label | safe_query_3.as_str() | -| sqlx.rs:63:26:63:39 | unsafe_query_1 [&ref] | semmle.label | unsafe_query_1 [&ref] | | sqlx.rs:63:26:63:48 | unsafe_query_1.as_str() | semmle.label | unsafe_query_1.as_str() | -| sqlx.rs:65:30:65:43 | unsafe_query_2 [&ref] | semmle.label | unsafe_query_2 [&ref] | | sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | semmle.label | unsafe_query_2.as_str() | | sqlx.rs:67:30:67:52 | unsafe_query_4.as_str() | semmle.label | unsafe_query_4.as_str() | | sqlx.rs:73:25:73:45 | safe_query_3.as_str() | semmle.label | safe_query_3.as_str() | -| sqlx.rs:74:25:74:38 | unsafe_query_1 [&ref] | semmle.label | unsafe_query_1 [&ref] | | sqlx.rs:74:25:74:47 | unsafe_query_1.as_str() | semmle.label | unsafe_query_1.as_str() | -| sqlx.rs:76:29:76:42 | unsafe_query_2 [&ref] | semmle.label | unsafe_query_2 [&ref] | | sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | semmle.label | unsafe_query_2.as_str() | | sqlx.rs:78:29:78:51 | unsafe_query_4.as_str() | semmle.label | unsafe_query_4.as_str() | subpaths diff --git a/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.expected b/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.expected index b05e99f352b..e827c7320cc 100644 --- a/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.expected +++ b/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.expected @@ -1,12 +1,4 @@ unexpectedModel | Unexpected summary found: repo::test;::clone;Argument[self].Field[crate::option::MyOption::MySome(0)].Reference;ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated | -| Unexpected summary found: repo::test;::from;Argument[0].Field[crate::option::MyOption::MySome(0)];ReturnValue.Field[crate::option::MyOption::MySome(0)].Reference;value;dfc-generated | | Unexpected summary found: repo::test;::cloned;Argument[self].Field[crate::option::MyOption::MySome(0)].Reference;ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated | -| Unexpected summary found: repo::test;::get_or_insert;Argument[0];Argument[self].Field[crate::option::MyOption::MySome(0)];value;dfc-generated | -| Unexpected summary found: repo::test;::get_or_insert;Argument[self].Field[crate::option::MyOption::MySome(0)];ReturnValue.Reference;value;dfc-generated | -| Unexpected summary found: repo::test;::get_or_insert_default;Argument[self].Field[crate::option::MyOption::MySome(0)];ReturnValue.Reference;value;dfc-generated | -| Unexpected summary found: repo::test;::get_or_insert_with;Argument[self].Field[crate::option::MyOption::MySome(0)];ReturnValue.Reference;value;dfc-generated | -| Unexpected summary found: repo::test;::insert;Argument[self].Field[crate::option::MyOption::MySome(0)];ReturnValue.Reference;value;dfc-generated | expectedModel -| Expected summary missing: repo::test;::take_if;Argument[self].Reference.Field[crate::option::MyOption::MySome(0)];Argument[0].Parameter[0].Reference;value;dfc-generated | -| Expected summary missing: repo::test;::take_if;Argument[self].Reference;ReturnValue;value;dfc-generated | From 6d617663662a2dd4a152dff9c2357865508b0248 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 30 Apr 2025 14:50:35 +0200 Subject: [PATCH 42/66] Added test case for `fastify.all` --- .../query-tests/Security/CWE-094/CodeInjection/fastify.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js index e1cba0d277c..d538898f947 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js @@ -101,3 +101,10 @@ fastify.get('/flow-through-reply', async (request, reply) => { } return { result: null }; }); + +fastify.all('/eval', async (request, reply) => { + const userInput = request.query.code; // $ MISSING: Source[js/code-injection] + const result = eval(userInput); // $ MISSING: Alert[js/code-injection] + const replyResult = eval(reply.locals.nestedCode); // $ MISSING: Alert[js/code-injection] + return { method: request.method, result }; +}); From 71f1b82a56245c63d447f5fc704add27ee02996f Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 30 Apr 2025 14:54:09 +0200 Subject: [PATCH 43/66] Added support for `fastify.all` --- .../ql/lib/semmle/javascript/frameworks/Fastify.qll | 7 +++++-- .../CWE-094/CodeInjection/CodeInjection.expected | 13 +++++++++++++ .../HeuristicSourceCodeInjection.expected | 9 +++++++++ .../Security/CWE-094/CodeInjection/fastify.js | 6 +++--- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll index cf8dd76b75e..4b53292e148 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll @@ -138,7 +138,8 @@ module Fastify { RouteSetup() { this = server(server).getAMethodCall(methodName) and - methodName = ["route", "get", "head", "post", "put", "delete", "options", "patch", "addHook"] + methodName = + ["route", "get", "head", "post", "put", "delete", "options", "patch", "addHook", "all"] } override DataFlow::SourceNode getARouteHandler() { @@ -168,7 +169,9 @@ module Fastify { override string getRelativePath() { result = this.getArgument(0).getStringValue() } - override Http::RequestMethodName getHttpMethod() { result = this.getMethodName().toUpperCase() } + override Http::RequestMethodName getHttpMethod() { + if this.getMethodName() = "all" then any() else result = this.getMethodName().toUpperCase() + } } private class AddHookRouteSetup extends Routing::RouteSetup::MethodCall instanceof RouteSetup { diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected index 9ad04af3a2c..4d54adb2724 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected @@ -57,6 +57,10 @@ | fastify.js:84:30:84:43 | reply.userCode | fastify.js:79:20:79:42 | request ... plyCode | fastify.js:84:30:84:43 | reply.userCode | This code execution depends on a $@. | fastify.js:79:20:79:42 | request ... plyCode | user-provided value | | fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:94:29:94:41 | request.query | fastify.js:99:30:99:52 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:94:29:94:41 | request.query | user-provided value | | fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:94:29:94:51 | request ... plyCode | fastify.js:99:30:99:52 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:94:29:94:51 | request ... plyCode | user-provided value | +| fastify.js:107:23:107:31 | userInput | fastify.js:106:21:106:33 | request.query | fastify.js:107:23:107:31 | userInput | This code execution depends on a $@. | fastify.js:106:21:106:33 | request.query | user-provided value | +| fastify.js:107:23:107:31 | userInput | fastify.js:106:21:106:38 | request.query.code | fastify.js:107:23:107:31 | userInput | This code execution depends on a $@. | fastify.js:106:21:106:38 | request.query.code | user-provided value | +| fastify.js:108:28:108:50 | reply.l ... tedCode | fastify.js:94:29:94:41 | request.query | fastify.js:108:28:108:50 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:94:29:94:41 | request.query | user-provided value | +| fastify.js:108:28:108:50 | reply.l ... tedCode | fastify.js:94:29:94:51 | request ... plyCode | fastify.js:108:28:108:50 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:94:29:94:51 | request ... plyCode | user-provided value | | module.js:9:16:9:29 | req.query.code | module.js:9:16:9:29 | req.query.code | module.js:9:16:9:29 | req.query.code | This code execution depends on a $@. | module.js:9:16:9:29 | req.query.code | user-provided value | | module.js:11:17:11:30 | req.query.code | module.js:11:17:11:30 | req.query.code | module.js:11:17:11:30 | req.query.code | This code execution depends on a $@. | module.js:11:17:11:30 | req.query.code | user-provided value | | react-native.js:8:32:8:38 | tainted | react-native.js:7:17:7:33 | req.param("code") | react-native.js:8:32:8:38 | tainted | This code execution depends on a $@. | react-native.js:7:17:7:33 | req.param("code") | user-provided value | @@ -145,6 +149,10 @@ edges | fastify.js:79:20:79:42 | request ... plyCode | fastify.js:84:30:84:43 | reply.userCode | provenance | | | fastify.js:94:29:94:41 | request.query | fastify.js:94:29:94:51 | request ... plyCode | provenance | | | fastify.js:94:29:94:51 | request ... plyCode | fastify.js:99:30:99:52 | reply.l ... tedCode | provenance | | +| fastify.js:94:29:94:51 | request ... plyCode | fastify.js:108:28:108:50 | reply.l ... tedCode | provenance | | +| fastify.js:106:9:106:38 | userInput | fastify.js:107:23:107:31 | userInput | provenance | | +| fastify.js:106:21:106:33 | request.query | fastify.js:106:9:106:38 | userInput | provenance | | +| fastify.js:106:21:106:38 | request.query.code | fastify.js:106:9:106:38 | userInput | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:8:32:8:38 | tainted | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:10:23:10:29 | tainted | provenance | | | react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted | provenance | | @@ -268,6 +276,11 @@ nodes | fastify.js:94:29:94:41 | request.query | semmle.label | request.query | | fastify.js:94:29:94:51 | request ... plyCode | semmle.label | request ... plyCode | | fastify.js:99:30:99:52 | reply.l ... tedCode | semmle.label | reply.l ... tedCode | +| fastify.js:106:9:106:38 | userInput | semmle.label | userInput | +| fastify.js:106:21:106:33 | request.query | semmle.label | request.query | +| fastify.js:106:21:106:38 | request.query.code | semmle.label | request.query.code | +| fastify.js:107:23:107:31 | userInput | semmle.label | userInput | +| fastify.js:108:28:108:50 | reply.l ... tedCode | semmle.label | reply.l ... tedCode | | module.js:9:16:9:29 | req.query.code | semmle.label | req.query.code | | module.js:11:17:11:30 | req.query.code | semmle.label | req.query.code | | react-native.js:7:7:7:33 | tainted | semmle.label | tainted | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected index aa23d7a6d5a..a1c8354ecf7 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected @@ -51,6 +51,10 @@ edges | fastify.js:79:20:79:42 | request ... plyCode | fastify.js:84:30:84:43 | reply.userCode | provenance | | | fastify.js:94:29:94:41 | request.query | fastify.js:94:29:94:51 | request ... plyCode | provenance | | | fastify.js:94:29:94:51 | request ... plyCode | fastify.js:99:30:99:52 | reply.l ... tedCode | provenance | | +| fastify.js:94:29:94:51 | request ... plyCode | fastify.js:108:28:108:50 | reply.l ... tedCode | provenance | | +| fastify.js:106:9:106:38 | userInput | fastify.js:107:23:107:31 | userInput | provenance | | +| fastify.js:106:21:106:33 | request.query | fastify.js:106:9:106:38 | userInput | provenance | | +| fastify.js:106:21:106:38 | request.query.code | fastify.js:106:9:106:38 | userInput | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:8:32:8:38 | tainted | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:10:23:10:29 | tainted | provenance | | | react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted | provenance | | @@ -176,6 +180,11 @@ nodes | fastify.js:94:29:94:41 | request.query | semmle.label | request.query | | fastify.js:94:29:94:51 | request ... plyCode | semmle.label | request ... plyCode | | fastify.js:99:30:99:52 | reply.l ... tedCode | semmle.label | reply.l ... tedCode | +| fastify.js:106:9:106:38 | userInput | semmle.label | userInput | +| fastify.js:106:21:106:33 | request.query | semmle.label | request.query | +| fastify.js:106:21:106:38 | request.query.code | semmle.label | request.query.code | +| fastify.js:107:23:107:31 | userInput | semmle.label | userInput | +| fastify.js:108:28:108:50 | reply.l ... tedCode | semmle.label | reply.l ... tedCode | | module.js:9:16:9:29 | req.query.code | semmle.label | req.query.code | | module.js:11:17:11:30 | req.query.code | semmle.label | req.query.code | | react-native.js:7:7:7:33 | tainted | semmle.label | tainted | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js index d538898f947..05dd3f6eb46 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js @@ -103,8 +103,8 @@ fastify.get('/flow-through-reply', async (request, reply) => { }); fastify.all('/eval', async (request, reply) => { - const userInput = request.query.code; // $ MISSING: Source[js/code-injection] - const result = eval(userInput); // $ MISSING: Alert[js/code-injection] - const replyResult = eval(reply.locals.nestedCode); // $ MISSING: Alert[js/code-injection] + const userInput = request.query.code; // $ Source[js/code-injection] + const result = eval(userInput); // $ Alert[js/code-injection] + const replyResult = eval(reply.locals.nestedCode); // $ Alert[js/code-injection] return { method: request.method, result }; }); From 9624a413e4b3a464ec68a9073516572819fc535f Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 30 Apr 2025 14:57:00 +0200 Subject: [PATCH 44/66] Added change note --- javascript/ql/lib/change-notes/2025-04-30-fastify-all.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 javascript/ql/lib/change-notes/2025-04-30-fastify-all.md diff --git a/javascript/ql/lib/change-notes/2025-04-30-fastify-all.md b/javascript/ql/lib/change-notes/2025-04-30-fastify-all.md new file mode 100644 index 00000000000..a49092f6ba4 --- /dev/null +++ b/javascript/ql/lib/change-notes/2025-04-30-fastify-all.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Enhanced modeling of the [fastify](https://www.npmjs.com/package/fastify) framework to support the `all` route handler method. From 4f5b340278bf39aba61673cac360785068338a4a Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 30 Apr 2025 15:12:58 +0200 Subject: [PATCH 45/66] Rust: Add type inference debug predicates --- .../codeql/rust/internal/TypeInference.qll | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/rust/ql/lib/codeql/rust/internal/TypeInference.qll b/rust/ql/lib/codeql/rust/internal/TypeInference.qll index 2fae9ef1f5b..0f0c92306d1 100644 --- a/rust/ql/lib/codeql/rust/internal/TypeInference.qll +++ b/rust/ql/lib/codeql/rust/internal/TypeInference.qll @@ -1017,3 +1017,24 @@ import Cached * Gets a type that `n` infers to, if any. */ Type inferType(AstNode n) { result = inferType(n, TypePath::nil()) } + +/** Provides predicates for debugging the type inference implementation. */ +private module Debug { + private Locatable getRelevantLocatable() { + exists(string filepath, int startline, int startcolumn, int endline, int endcolumn | + result.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and + filepath.matches("%/tauri/src/app/plugin.rs") and + startline = 54 + ) + } + + Type debugInferType(AstNode n, TypePath path) { + n = getRelevantLocatable() and + result = inferType(n, path) + } + + Function debugResolveMethodCallExpr(MethodCallExpr mce) { + mce = getRelevantLocatable() and + result = resolveMethodCallExpr(mce) + } +} From edd18dc0521aa972fcee2a66b1aebf7fc44348a7 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Wed, 30 Apr 2025 16:23:06 +0200 Subject: [PATCH 46/66] C++: Address review comment --- .../header-variant-tests/clang-pch/test.py | 19 ++++++++++--------- .../microsoft-pch/test.py | 7 ++++--- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py b/cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py index 3cf2d42f764..d7bc5fe6352 100644 --- a/cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py @@ -3,14 +3,15 @@ import os def test(codeql, cpp): os.mkdir("pch") + extractor = f"{cpp.get_tool("extractor")}" codeql.database.create(command=[ - f'"{cpp.get_tool("extractor")}" --mimic-clang -emit-pch -o pch/a.pch a.c', - f'"{cpp.get_tool("extractor")}" --mimic-clang -include-pch pch/a.pch -Iextra_dummy_path b.c', - f'"{cpp.get_tool("extractor")}" --mimic-clang -include pch/a -Iextra_dummy_path c.c', - f'"{cpp.get_tool("extractor")}" --mimic-clang -emit-pch -o pch/d.pch d.c', - f'"{cpp.get_tool("extractor")}" --mimic-clang -include-pch pch/d.pch e.c', - f'"{cpp.get_tool("extractor")}" --mimic-clang -emit-pch -o pch/f.pch f.c', - f'"{cpp.get_tool("extractor")}" --mimic-clang -include-pch pch/f.pch g.c', - f'"{cpp.get_tool("extractor")}" --mimic-clang -emit-pch -o pch/h.pch h.c', - f'"{cpp.get_tool("extractor")}" --mimic-clang -include-pch pch/h.pch i.c', + f'"{extractor}" --mimic-clang -emit-pch -o pch/a.pch a.c', + f'"{extractor}" --mimic-clang -include-pch pch/a.pch -Iextra_dummy_path b.c', + f'"{extractor}" --mimic-clang -include pch/a -Iextra_dummy_path c.c', + f'"{extractor}" --mimic-clang -emit-pch -o pch/d.pch d.c', + f'"{extractor}" --mimic-clang -include-pch pch/d.pch e.c', + f'"{extractor}" --mimic-clang -emit-pch -o pch/f.pch f.c', + f'"{extractor}" --mimic-clang -include-pch pch/f.pch g.c', + f'"{extractor}" --mimic-clang -emit-pch -o pch/h.pch h.c', + f'"{extractor}" --mimic-clang -include-pch pch/h.pch i.c', ]) diff --git a/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/test.py b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/test.py index eaa0fdb8786..d734f0335f1 100644 --- a/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/test.py +++ b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/test.py @@ -3,8 +3,9 @@ import os def test(codeql, cpp): os.mkdir("pch") + extractor = f"{cpp.get_tool("extractor")}" codeql.database.create(command=[ - f'"{cpp.get_tool("extractor")}" --mimic-cl /Yca.h /Fppch/a.pch a.c', - f'"{cpp.get_tool("extractor")}" --mimic-cl /Yub.h /Fppch/a.pch b.c', - f'"{cpp.get_tool("extractor")}" --mimic-cl /Yuc.h /Fppch/a.pch c.c', + f'"{extractor}" --mimic-cl /Yca.h /Fppch/a.pch a.c', + f'"{extractor}" --mimic-cl /Yub.h /Fppch/a.pch b.c', + f'"{extractor}" --mimic-cl /Yuc.h /Fppch/a.pch c.c', ]) From c263d3faf93f29d691612f947482365d2e08fa14 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Wed, 30 Apr 2025 17:39:22 +0200 Subject: [PATCH 47/66] Rust: Remove predicates unused after refactor --- .../lib/codeql/rust/internal/TypeInference.qll | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/TypeInference.qll b/rust/ql/lib/codeql/rust/internal/TypeInference.qll index b999b72240b..f553351868b 100644 --- a/rust/ql/lib/codeql/rust/internal/TypeInference.qll +++ b/rust/ql/lib/codeql/rust/internal/TypeInference.qll @@ -662,15 +662,6 @@ private module CallExprBaseMatchingInput implements MatchingInputSig { tAdj = t ) } - - pragma[nomagic] - additional Type inferReceiverType(AstNode n) { - exists(Access a, AccessPosition apos | - result = inferType(n) and - n = a.getNodeAt(apos) and - apos.isSelf() - ) - } } private module CallExprBaseMatching = Matching; @@ -813,15 +804,6 @@ private module FieldExprMatchingInput implements MatchingInputSig { tAdj = t ) } - - pragma[nomagic] - additional Type inferReceiverType(AstNode n) { - exists(Access a, AccessPosition apos | - result = inferType(n) and - n = a.getNodeAt(apos) and - apos.isSelf() - ) - } } private module FieldExprMatching = Matching; From cf614a596d965f2584bfc568a9ec3dbd370ddbf6 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Wed, 30 Apr 2025 16:43:03 +0100 Subject: [PATCH 48/66] Fix cwe tags to include leading zero --- cpp/ql/src/Security/CWE/CWE-014/MemsetMayBeDeleted.ql | 2 +- .../src/Security/CWE/CWE-020/CountUntrustedDataToExternalAPI.ql | 2 +- .../Security/CWE/CWE-020/IRCountUntrustedDataToExternalAPI.ql | 2 +- cpp/ql/src/Security/CWE/CWE-020/IRUntrustedDataToExternalAPI.ql | 2 +- cpp/ql/src/Security/CWE/CWE-020/UntrustedDataToExternalAPI.ql | 2 +- .../Security/CWE/CWE-020/LateCheckOfFunctionArgument.ql | 2 +- csharp/ql/src/Configuration/PasswordInConfigurationFile.ql | 2 +- csharp/ql/src/Security Features/CWE-011/ASPNetDebug.ql | 2 +- .../ql/src/Security Features/CWE-016/ASPNetMaxRequestLength.ql | 2 +- .../src/Security Features/CWE-016/ASPNetPagesValidateRequest.ql | 2 +- .../CWE-020/ExternalAPIsUsedWithUntrustedData.ql | 2 +- csharp/ql/src/Security Features/CWE-020/RuntimeChecksBypass.ql | 2 +- .../src/Security Features/CWE-020/UntrustedDataToExternalAPI.ql | 2 +- .../CWE-248/MissingASPNETGlobalErrorHandler.ql | 2 +- go/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql | 2 +- go/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql | 2 +- go/ql/src/Security/CWE-020/MissingRegexpAnchor.ql | 2 +- go/ql/src/Security/CWE-020/SuspiciousCharacterInRegexp.ql | 2 +- go/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql | 2 +- go/ql/src/Security/CWE-020/UntrustedDataToUnknownExternalAPI.ql | 2 +- go/ql/src/experimental/CWE-090/LDAPInjection.ql | 2 +- go/ql/src/experimental/CWE-74/DsnInjection.ql | 2 +- go/ql/src/experimental/CWE-74/DsnInjectionLocal.ql | 2 +- .../src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql | 2 +- .../Security/CWE/CWE-020/ExternalAPIsUsedWithUntrustedData.ql | 2 +- java/ql/src/Security/CWE/CWE-020/UntrustedDataToExternalAPI.ql | 2 +- java/ql/src/Security/CWE/CWE-113/NettyResponseSplitting.ql | 2 +- javascript/ql/src/Electron/DisablingWebSecurity.ql | 2 +- .../src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql | 2 +- .../ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql | 2 +- .../ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql | 2 +- .../CWE-020-ExternalAPIs/ExternalAPIsUsedWithUntrustedData.ql | 2 +- .../Security/CWE-020-ExternalAPIs/UntrustedDataToExternalAPI.ql | 2 +- python/ql/src/Security/CWE-020/CookieInjection.ql | 2 +- .../src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql | 2 +- python/ql/src/experimental/Security/CWE-094/Js2Py.ql | 2 +- .../ql/src/experimental/template-injection/TemplateInjection.ql | 2 +- 37 files changed, 37 insertions(+), 37 deletions(-) diff --git a/cpp/ql/src/Security/CWE/CWE-014/MemsetMayBeDeleted.ql b/cpp/ql/src/Security/CWE/CWE-014/MemsetMayBeDeleted.ql index 33c31972295..3aff4e1dcc2 100644 --- a/cpp/ql/src/Security/CWE/CWE-014/MemsetMayBeDeleted.ql +++ b/cpp/ql/src/Security/CWE/CWE-014/MemsetMayBeDeleted.ql @@ -8,7 +8,7 @@ * @security-severity 7.8 * @precision high * @tags security - * external/cwe/cwe-14 + * external/cwe/cwe-014 */ import cpp diff --git a/cpp/ql/src/Security/CWE/CWE-020/CountUntrustedDataToExternalAPI.ql b/cpp/ql/src/Security/CWE/CWE-020/CountUntrustedDataToExternalAPI.ql index bebff32a5c1..80bf2b8a8b3 100644 --- a/cpp/ql/src/Security/CWE/CWE-020/CountUntrustedDataToExternalAPI.ql +++ b/cpp/ql/src/Security/CWE/CWE-020/CountUntrustedDataToExternalAPI.ql @@ -5,7 +5,7 @@ * to it. * @id cpp/count-untrusted-data-external-api * @kind table - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import cpp diff --git a/cpp/ql/src/Security/CWE/CWE-020/IRCountUntrustedDataToExternalAPI.ql b/cpp/ql/src/Security/CWE/CWE-020/IRCountUntrustedDataToExternalAPI.ql index 69911c22c6a..3bc364c4116 100644 --- a/cpp/ql/src/Security/CWE/CWE-020/IRCountUntrustedDataToExternalAPI.ql +++ b/cpp/ql/src/Security/CWE/CWE-020/IRCountUntrustedDataToExternalAPI.ql @@ -5,7 +5,7 @@ * to it. * @id cpp/count-untrusted-data-external-api-ir * @kind table - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import cpp diff --git a/cpp/ql/src/Security/CWE/CWE-020/IRUntrustedDataToExternalAPI.ql b/cpp/ql/src/Security/CWE/CWE-020/IRUntrustedDataToExternalAPI.ql index 432f47f6735..07c97ed77fd 100644 --- a/cpp/ql/src/Security/CWE/CWE-020/IRUntrustedDataToExternalAPI.ql +++ b/cpp/ql/src/Security/CWE/CWE-020/IRUntrustedDataToExternalAPI.ql @@ -6,7 +6,7 @@ * @precision low * @problem.severity error * @security-severity 7.8 - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import cpp diff --git a/cpp/ql/src/Security/CWE/CWE-020/UntrustedDataToExternalAPI.ql b/cpp/ql/src/Security/CWE/CWE-020/UntrustedDataToExternalAPI.ql index 1cfd0a7132f..34ea739e675 100644 --- a/cpp/ql/src/Security/CWE/CWE-020/UntrustedDataToExternalAPI.ql +++ b/cpp/ql/src/Security/CWE/CWE-020/UntrustedDataToExternalAPI.ql @@ -6,7 +6,7 @@ * @precision low * @problem.severity error * @security-severity 7.8 - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import cpp diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-020/LateCheckOfFunctionArgument.ql b/cpp/ql/src/experimental/Security/CWE/CWE-020/LateCheckOfFunctionArgument.ql index 07d18992db6..be53ba1fc68 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-020/LateCheckOfFunctionArgument.ql +++ b/cpp/ql/src/experimental/Security/CWE/CWE-020/LateCheckOfFunctionArgument.ql @@ -10,7 +10,7 @@ * @tags correctness * security * experimental - * external/cwe/cwe-20 + * external/cwe/cwe-020 */ import cpp diff --git a/csharp/ql/src/Configuration/PasswordInConfigurationFile.ql b/csharp/ql/src/Configuration/PasswordInConfigurationFile.ql index c6f004789a7..a2fe7cf2290 100644 --- a/csharp/ql/src/Configuration/PasswordInConfigurationFile.ql +++ b/csharp/ql/src/Configuration/PasswordInConfigurationFile.ql @@ -7,7 +7,7 @@ * @precision medium * @id cs/password-in-configuration * @tags security - * external/cwe/cwe-13 + * external/cwe/cwe-013 * external/cwe/cwe-256 * external/cwe/cwe-313 */ diff --git a/csharp/ql/src/Security Features/CWE-011/ASPNetDebug.ql b/csharp/ql/src/Security Features/CWE-011/ASPNetDebug.ql index 308f3eeeac2..4e0e52352b4 100644 --- a/csharp/ql/src/Security Features/CWE-011/ASPNetDebug.ql +++ b/csharp/ql/src/Security Features/CWE-011/ASPNetDebug.ql @@ -10,7 +10,7 @@ * @tags security * maintainability * frameworks/asp.net - * external/cwe/cwe-11 + * external/cwe/cwe-011 * external/cwe/cwe-532 */ diff --git a/csharp/ql/src/Security Features/CWE-016/ASPNetMaxRequestLength.ql b/csharp/ql/src/Security Features/CWE-016/ASPNetMaxRequestLength.ql index 89bd133d59a..b9ac41e0e39 100644 --- a/csharp/ql/src/Security Features/CWE-016/ASPNetMaxRequestLength.ql +++ b/csharp/ql/src/Security Features/CWE-016/ASPNetMaxRequestLength.ql @@ -8,7 +8,7 @@ * @id cs/web/large-max-request-length * @tags security * frameworks/asp.net - * external/cwe/cwe-16 + * external/cwe/cwe-016 */ import csharp diff --git a/csharp/ql/src/Security Features/CWE-016/ASPNetPagesValidateRequest.ql b/csharp/ql/src/Security Features/CWE-016/ASPNetPagesValidateRequest.ql index 68902a0622d..3c51b7a8e2e 100644 --- a/csharp/ql/src/Security Features/CWE-016/ASPNetPagesValidateRequest.ql +++ b/csharp/ql/src/Security Features/CWE-016/ASPNetPagesValidateRequest.ql @@ -8,7 +8,7 @@ * @id cs/web/request-validation-disabled * @tags security * frameworks/asp.net - * external/cwe/cwe-16 + * external/cwe/cwe-016 */ import csharp diff --git a/csharp/ql/src/Security Features/CWE-020/ExternalAPIsUsedWithUntrustedData.ql b/csharp/ql/src/Security Features/CWE-020/ExternalAPIsUsedWithUntrustedData.ql index b07b1093ec8..8427ceb87eb 100644 --- a/csharp/ql/src/Security Features/CWE-020/ExternalAPIsUsedWithUntrustedData.ql +++ b/csharp/ql/src/Security Features/CWE-020/ExternalAPIsUsedWithUntrustedData.ql @@ -5,7 +5,7 @@ * to it. * @id cs/count-untrusted-data-external-api * @kind table - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import csharp diff --git a/csharp/ql/src/Security Features/CWE-020/RuntimeChecksBypass.ql b/csharp/ql/src/Security Features/CWE-020/RuntimeChecksBypass.ql index 6148f0f6ae9..af029459033 100644 --- a/csharp/ql/src/Security Features/CWE-020/RuntimeChecksBypass.ql +++ b/csharp/ql/src/Security Features/CWE-020/RuntimeChecksBypass.ql @@ -7,7 +7,7 @@ * @security-severity 7.8 * @precision medium * @tags security - * external/cwe/cwe-20 + * external/cwe/cwe-020 */ import semmle.code.csharp.serialization.Serialization diff --git a/csharp/ql/src/Security Features/CWE-020/UntrustedDataToExternalAPI.ql b/csharp/ql/src/Security Features/CWE-020/UntrustedDataToExternalAPI.ql index a71a2705bdd..0543f198d22 100644 --- a/csharp/ql/src/Security Features/CWE-020/UntrustedDataToExternalAPI.ql +++ b/csharp/ql/src/Security Features/CWE-020/UntrustedDataToExternalAPI.ql @@ -6,7 +6,7 @@ * @precision low * @problem.severity error * @security-severity 7.8 - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import csharp diff --git a/csharp/ql/src/Security Features/CWE-248/MissingASPNETGlobalErrorHandler.ql b/csharp/ql/src/Security Features/CWE-248/MissingASPNETGlobalErrorHandler.ql index f37d4c497de..14d73c02e1e 100644 --- a/csharp/ql/src/Security Features/CWE-248/MissingASPNETGlobalErrorHandler.ql +++ b/csharp/ql/src/Security Features/CWE-248/MissingASPNETGlobalErrorHandler.ql @@ -8,7 +8,7 @@ * @precision high * @id cs/web/missing-global-error-handler * @tags security - * external/cwe/cwe-12 + * external/cwe/cwe-012 * external/cwe/cwe-248 */ diff --git a/go/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql b/go/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql index b23cd003023..2b32d8ffecc 100644 --- a/go/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql +++ b/go/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql @@ -5,7 +5,7 @@ * to it. * @id go/count-untrusted-data-external-api * @kind table - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import go diff --git a/go/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql b/go/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql index 03018ee1c32..89954b08f99 100644 --- a/go/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql +++ b/go/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql @@ -9,7 +9,7 @@ * @id go/incomplete-hostname-regexp * @tags correctness * security - * external/cwe/cwe-20 + * external/cwe/cwe-020 */ import go diff --git a/go/ql/src/Security/CWE-020/MissingRegexpAnchor.ql b/go/ql/src/Security/CWE-020/MissingRegexpAnchor.ql index df93440ac52..a478968e58b 100644 --- a/go/ql/src/Security/CWE-020/MissingRegexpAnchor.ql +++ b/go/ql/src/Security/CWE-020/MissingRegexpAnchor.ql @@ -8,7 +8,7 @@ * @id go/regex/missing-regexp-anchor * @tags correctness * security - * external/cwe/cwe-20 + * external/cwe/cwe-020 */ import go diff --git a/go/ql/src/Security/CWE-020/SuspiciousCharacterInRegexp.ql b/go/ql/src/Security/CWE-020/SuspiciousCharacterInRegexp.ql index 81cc634346a..e58cf864490 100644 --- a/go/ql/src/Security/CWE-020/SuspiciousCharacterInRegexp.ql +++ b/go/ql/src/Security/CWE-020/SuspiciousCharacterInRegexp.ql @@ -8,7 +8,7 @@ * @id go/suspicious-character-in-regex * @tags correctness * security - * external/cwe/cwe-20 + * external/cwe/cwe-020 */ import go diff --git a/go/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql b/go/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql index 4ab22af3a45..6e8d99471ee 100644 --- a/go/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql +++ b/go/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql @@ -6,7 +6,7 @@ * @precision low * @problem.severity error * @security-severity 7.8 - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import go diff --git a/go/ql/src/Security/CWE-020/UntrustedDataToUnknownExternalAPI.ql b/go/ql/src/Security/CWE-020/UntrustedDataToUnknownExternalAPI.ql index 23945e38d46..45198047904 100644 --- a/go/ql/src/Security/CWE-020/UntrustedDataToUnknownExternalAPI.ql +++ b/go/ql/src/Security/CWE-020/UntrustedDataToUnknownExternalAPI.ql @@ -6,7 +6,7 @@ * @precision low * @problem.severity error * @security-severity 7.8 - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import go diff --git a/go/ql/src/experimental/CWE-090/LDAPInjection.ql b/go/ql/src/experimental/CWE-090/LDAPInjection.ql index 7da669aa612..6b269df20ba 100644 --- a/go/ql/src/experimental/CWE-090/LDAPInjection.ql +++ b/go/ql/src/experimental/CWE-090/LDAPInjection.ql @@ -7,7 +7,7 @@ * @id go/ldap-injection * @tags security * experimental - * external/cwe/cwe-90 + * external/cwe/cwe-090 */ import go diff --git a/go/ql/src/experimental/CWE-74/DsnInjection.ql b/go/ql/src/experimental/CWE-74/DsnInjection.ql index 2b2ee0a62e4..c8df87e296e 100644 --- a/go/ql/src/experimental/CWE-74/DsnInjection.ql +++ b/go/ql/src/experimental/CWE-74/DsnInjection.ql @@ -6,7 +6,7 @@ * @id go/dsn-injection * @tags security * experimental - * external/cwe/cwe-74 + * external/cwe/cwe-074 */ import go diff --git a/go/ql/src/experimental/CWE-74/DsnInjectionLocal.ql b/go/ql/src/experimental/CWE-74/DsnInjectionLocal.ql index 1744a25848b..d741199ac22 100644 --- a/go/ql/src/experimental/CWE-74/DsnInjectionLocal.ql +++ b/go/ql/src/experimental/CWE-74/DsnInjectionLocal.ql @@ -6,7 +6,7 @@ * @id go/dsn-injection-local * @tags security * experimental - * external/cwe/cwe-74 + * external/cwe/cwe-074 */ import go diff --git a/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql b/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql index 0aff713f26b..ff63f6bfbec 100644 --- a/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql +++ b/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql @@ -7,7 +7,7 @@ * @id go/html-template-escaping-passthrough * @tags security * experimental - * external/cwe/cwe-79 + * external/cwe/cwe-079 */ import go diff --git a/java/ql/src/Security/CWE/CWE-020/ExternalAPIsUsedWithUntrustedData.ql b/java/ql/src/Security/CWE/CWE-020/ExternalAPIsUsedWithUntrustedData.ql index 23c82397de0..ffdfcaf9f80 100644 --- a/java/ql/src/Security/CWE/CWE-020/ExternalAPIsUsedWithUntrustedData.ql +++ b/java/ql/src/Security/CWE/CWE-020/ExternalAPIsUsedWithUntrustedData.ql @@ -5,7 +5,7 @@ * to it. * @id java/count-untrusted-data-external-api * @kind table - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import java diff --git a/java/ql/src/Security/CWE/CWE-020/UntrustedDataToExternalAPI.ql b/java/ql/src/Security/CWE/CWE-020/UntrustedDataToExternalAPI.ql index fdbb34b2247..a75672445fb 100644 --- a/java/ql/src/Security/CWE/CWE-020/UntrustedDataToExternalAPI.ql +++ b/java/ql/src/Security/CWE/CWE-020/UntrustedDataToExternalAPI.ql @@ -6,7 +6,7 @@ * @precision low * @problem.severity error * @security-severity 7.8 - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import java diff --git a/java/ql/src/Security/CWE/CWE-113/NettyResponseSplitting.ql b/java/ql/src/Security/CWE/CWE-113/NettyResponseSplitting.ql index 7376aa51e58..fb7a40052f0 100644 --- a/java/ql/src/Security/CWE/CWE-113/NettyResponseSplitting.ql +++ b/java/ql/src/Security/CWE/CWE-113/NettyResponseSplitting.ql @@ -9,7 +9,7 @@ * @precision high * @id java/netty-http-request-or-response-splitting * @tags security - * external/cwe/cwe-93 + * external/cwe/cwe-093 * external/cwe/cwe-113 */ diff --git a/javascript/ql/src/Electron/DisablingWebSecurity.ql b/javascript/ql/src/Electron/DisablingWebSecurity.ql index a2b0c0a8a01..392d8fb7322 100644 --- a/javascript/ql/src/Electron/DisablingWebSecurity.ql +++ b/javascript/ql/src/Electron/DisablingWebSecurity.ql @@ -7,7 +7,7 @@ * @precision very-high * @tags security * frameworks/electron - * external/cwe/cwe-79 + * external/cwe/cwe-079 * @id js/disabling-electron-websecurity */ diff --git a/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql b/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql index 045edc172f3..9f811c85c97 100644 --- a/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql +++ b/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql @@ -5,7 +5,7 @@ * to it. * @id js/count-untrusted-data-external-api * @kind table - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import javascript diff --git a/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql b/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql index 30931a6a582..1fd1df14887 100644 --- a/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql +++ b/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql @@ -6,7 +6,7 @@ * @precision low * @problem.severity error * @security-severity 7.8 - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import javascript diff --git a/javascript/ql/src/experimental/heuristics/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql b/javascript/ql/src/experimental/heuristics/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql index 4bf06b54447..fa7f313e9e5 100644 --- a/javascript/ql/src/experimental/heuristics/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql +++ b/javascript/ql/src/experimental/heuristics/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql @@ -7,7 +7,7 @@ * @problem.severity error * @security-severity 7.8 * @tags experimental - * security external/cwe/cwe-20 + * security external/cwe/cwe-020 */ import javascript diff --git a/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIsUsedWithUntrustedData.ql b/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIsUsedWithUntrustedData.ql index ac374311ee8..a0905e6626d 100644 --- a/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIsUsedWithUntrustedData.ql +++ b/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIsUsedWithUntrustedData.ql @@ -5,7 +5,7 @@ * to it. * @id py/count-untrusted-data-external-api * @kind table - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import python diff --git a/python/ql/src/Security/CWE-020-ExternalAPIs/UntrustedDataToExternalAPI.ql b/python/ql/src/Security/CWE-020-ExternalAPIs/UntrustedDataToExternalAPI.ql index f5706ccc3a6..feb5b77c02a 100644 --- a/python/ql/src/Security/CWE-020-ExternalAPIs/UntrustedDataToExternalAPI.ql +++ b/python/ql/src/Security/CWE-020-ExternalAPIs/UntrustedDataToExternalAPI.ql @@ -6,7 +6,7 @@ * @precision low * @problem.severity error * @security-severity 7.8 - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import python diff --git a/python/ql/src/Security/CWE-020/CookieInjection.ql b/python/ql/src/Security/CWE-020/CookieInjection.ql index 0cb9c2dadbb..e0600648eac 100644 --- a/python/ql/src/Security/CWE-020/CookieInjection.ql +++ b/python/ql/src/Security/CWE-020/CookieInjection.ql @@ -7,7 +7,7 @@ * @security-severity 5.0 * @id py/cookie-injection * @tags security - * external/cwe/cwe-20 + * external/cwe/cwe-020 */ import python diff --git a/python/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql b/python/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql index 5ab77438d63..1dbd95d5533 100644 --- a/python/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql +++ b/python/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql @@ -8,7 +8,7 @@ * @id py/incomplete-url-substring-sanitization * @tags correctness * security - * external/cwe/cwe-20 + * external/cwe/cwe-020 */ import python diff --git a/python/ql/src/experimental/Security/CWE-094/Js2Py.ql b/python/ql/src/experimental/Security/CWE-094/Js2Py.ql index 2bb3fea1b32..53c919d9732 100644 --- a/python/ql/src/experimental/Security/CWE-094/Js2Py.ql +++ b/python/ql/src/experimental/Security/CWE-094/Js2Py.ql @@ -8,7 +8,7 @@ * @id py/js2py-rce * @tags security * experimental - * external/cwe/cwe-94 + * external/cwe/cwe-094 */ import python diff --git a/ruby/ql/src/experimental/template-injection/TemplateInjection.ql b/ruby/ql/src/experimental/template-injection/TemplateInjection.ql index 7ad81c34123..7ad670d6ec1 100644 --- a/ruby/ql/src/experimental/template-injection/TemplateInjection.ql +++ b/ruby/ql/src/experimental/template-injection/TemplateInjection.ql @@ -8,7 +8,7 @@ * @precision high * @id rb/server-side-template-injection * @tags security - * external/cwe/cwe-94 + * external/cwe/cwe-094 */ import codeql.ruby.DataFlow From a9132c43d0072ec2fb270aef24183349cfc5121c Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Wed, 30 Apr 2025 16:47:35 +0100 Subject: [PATCH 49/66] Fix incorrect CWE tags --- python/ql/src/Expressions/UseofInput.ql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ql/src/Expressions/UseofInput.ql b/python/ql/src/Expressions/UseofInput.ql index b7e9b6f7d9d..b5e49a65f98 100644 --- a/python/ql/src/Expressions/UseofInput.ql +++ b/python/ql/src/Expressions/UseofInput.ql @@ -4,8 +4,8 @@ * @kind problem * @tags security * correctness - * security/cwe/cwe-94 - * security/cwe/cwe-95 + * external/cwe/cwe-094 + * external/cwe/cwe-095 * @problem.severity error * @security-severity 9.8 * @sub-severity high From 3423a1072ac2c39e91581f9d3d3241d185410321 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Wed, 30 Apr 2025 19:10:35 +0200 Subject: [PATCH 50/66] C++: Address review comments --- cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py | 2 +- .../header-variant-tests/microsoft-pch/test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py b/cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py index d7bc5fe6352..0a48fc3a79c 100644 --- a/cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py @@ -3,7 +3,7 @@ import os def test(codeql, cpp): os.mkdir("pch") - extractor = f"{cpp.get_tool("extractor")}" + extractor = cpp.get_tool("extractor") codeql.database.create(command=[ f'"{extractor}" --mimic-clang -emit-pch -o pch/a.pch a.c', f'"{extractor}" --mimic-clang -include-pch pch/a.pch -Iextra_dummy_path b.c', diff --git a/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/test.py b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/test.py index d734f0335f1..89bceec397e 100644 --- a/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/test.py +++ b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/test.py @@ -3,7 +3,7 @@ import os def test(codeql, cpp): os.mkdir("pch") - extractor = f"{cpp.get_tool("extractor")}" + extractor = cpp.get_tool("extractor") codeql.database.create(command=[ f'"{extractor}" --mimic-cl /Yca.h /Fppch/a.pch a.c', f'"{extractor}" --mimic-cl /Yub.h /Fppch/a.pch b.c', From 531f2a15a4414441ab19cdb7bc524175430529ff Mon Sep 17 00:00:00 2001 From: yoff Date: Wed, 30 Apr 2025 19:58:14 +0200 Subject: [PATCH 51/66] python: model `send_header` from `http.server` --- .../ql/lib/semmle/python/frameworks/Stdlib.qll | 16 ++++++++++++++++ .../frameworks/stdlib/http_server.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll index 4ad671bb19a..2d4bd83a55a 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll @@ -1963,6 +1963,22 @@ module StdlibPrivate { /** Gets a reference to an instance of the `BaseHttpRequestHandler` class or any subclass. */ DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) } + /** A call to a method that writes to a response header. */ + private class HeaderWriteCall extends Http::Server::ResponseHeaderWrite::Range, + DataFlow::MethodCallNode + { + HeaderWriteCall() { this.calls(instance(), "send_header") } + + override DataFlow::Node getNameArg() { result = this.getArg(0) } + + override DataFlow::Node getValueArg() { result = this.getArg(1) } + + // TODO: These checks perhaps could be made more precise. + override predicate nameAllowsNewline() { any() } + + override predicate valueAllowsNewline() { any() } + } + private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep { override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { nodeFrom = instance() and diff --git a/python/ql/test/library-tests/frameworks/stdlib/http_server.py b/python/ql/test/library-tests/frameworks/stdlib/http_server.py index 9110aa6a26a..8e9fd925c24 100644 --- a/python/ql/test/library-tests/frameworks/stdlib/http_server.py +++ b/python/ql/test/library-tests/frameworks/stdlib/http_server.py @@ -83,7 +83,7 @@ class MyHandler(BaseHTTPRequestHandler): def do_GET(self): # $ requestHandler # send_response will log a line to stderr self.send_response(200) - self.send_header("Content-type", "text/plain; charset=utf-8") + self.send_header("Content-type", "text/plain; charset=utf-8") # $ headerWriteNameUnsanitized="Content-type" headerWriteValueUnsanitized="text/plain; charset=utf-8" self.end_headers() self.wfile.write(b"Hello BaseHTTPRequestHandler\n") self.wfile.writelines([b"1\n", b"2\n", b"3\n"]) From cf45e771f3a1bd2b4e91f4dcfef0bda6bcb0b022 Mon Sep 17 00:00:00 2001 From: yoff Date: Wed, 30 Apr 2025 20:01:43 +0200 Subject: [PATCH 52/66] python: remove copied comment --- python/ql/lib/semmle/python/frameworks/Stdlib.qll | 1 - 1 file changed, 1 deletion(-) diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll index 2d4bd83a55a..4a3c346fb01 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll @@ -1973,7 +1973,6 @@ module StdlibPrivate { override DataFlow::Node getValueArg() { result = this.getArg(1) } - // TODO: These checks perhaps could be made more precise. override predicate nameAllowsNewline() { any() } override predicate valueAllowsNewline() { any() } From e63b38c515db9730258956573ad2784934ef8b54 Mon Sep 17 00:00:00 2001 From: yoff Date: Wed, 30 Apr 2025 20:05:55 +0200 Subject: [PATCH 53/66] python: add change note --- python/ql/lib/change-notes/2025-04-30-model-send-header.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 python/ql/lib/change-notes/2025-04-30-model-send-header.md diff --git a/python/ql/lib/change-notes/2025-04-30-model-send-header.md b/python/ql/lib/change-notes/2025-04-30-model-send-header.md new file mode 100644 index 00000000000..032e984bdf3 --- /dev/null +++ b/python/ql/lib/change-notes/2025-04-30-model-send-header.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added header write model for `send_header` in `http.server`. \ No newline at end of file From 723778fa82823b2355cabf90698da6392668e0cb Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Wed, 30 Apr 2025 20:31:12 +0200 Subject: [PATCH 54/66] C++: Limit flow through sinks and sources in `cpp/upcast-array-pointer-arithmetic` --- .../src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql b/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql index 7dda356353e..d1645007a32 100644 --- a/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql +++ b/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql @@ -44,6 +44,10 @@ module CastToPointerArithFlowConfig implements DataFlow::StateConfigSig { ) and getFullyConvertedType(node) = state } + + predicate isBarrierIn(DataFlow::Node node) { isSource(node, _) } + + predicate isBarrierOut(DataFlow::Node node) { isSink(node, _) } } /** From 51e70d0c3bff9ba69b40f50c6fc4267056f32f89 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 24 Apr 2025 13:04:21 +0200 Subject: [PATCH 55/66] Rust: Add Copilot generated test for `?` operator expressions --- .../test/library-tests/type-inference/main.rs | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/rust/ql/test/library-tests/type-inference/main.rs b/rust/ql/test/library-tests/type-inference/main.rs index 1972b181c83..7e8ab73d00d 100644 --- a/rust/ql/test/library-tests/type-inference/main.rs +++ b/rust/ql/test/library-tests/type-inference/main.rs @@ -919,6 +919,99 @@ mod borrowed_typed { } } +mod try_expressions { + use std::fmt::Debug; + + #[derive(Debug)] + struct S1; + + #[derive(Debug)] + struct S2; + + #[derive(Debug)] + enum MyResult { + MyOk(T), + MyErr(E), + } + + impl MyResult { + fn map(self, op: F) -> MyResult + where + F: FnOnce(T) -> U, + { + match self { + MyResult::MyOk(t) => MyResult::MyOk(op(t)), + MyResult::MyErr(e) => MyResult::MyErr(e), + } + } + + fn and_then(self, op: F) -> MyResult + where + F: FnOnce(T) -> MyResult, + { + match self { + MyResult::MyOk(t) => op(t), + MyResult::MyErr(e) => MyResult::MyErr(e), + } + } + } + + // For the try operator to work, we need to implement From for OtherE + impl From for S2 { + fn from(s: S1) -> S2 { + S2 + } + } + + // Simple function using ? operator with same error types + fn try_same_error() -> MyResult { + let x = MyResult::MyOk(S1)?; // $ type=x:S1 + MyResult::MyOk(x) + } + + // Function using ? operator with different error types that need conversion + fn try_convert_error() -> MyResult { + let x: MyResult = MyResult::MyOk(S1); + let y = x?; // $ type=y:S1 + MyResult::MyOk(y) + } + + // Chained ? operations + fn try_chained() -> MyResult { + let x: MyResult, S1> = MyResult::MyOk(MyResult::MyOk(S1)); + let y = x?.map(|s| s)?; // First ? returns MyResult, second ? returns S1 + MyResult::MyOk(y) + } + + // Function that uses ? with closures and complex error cases + fn try_complex(input: MyResult) -> MyResult { + let value = input?; // $ method=From::from + let mapped = MyResult::MyOk(value).and_then(|v| { + println!("{:?}", v); + MyResult::MyOk::<_, S1>(v) + })?; // $ method=From::from + MyResult::MyOk(mapped) + } + + pub fn f() { + if let MyResult::MyOk(result) = try_same_error() { + println!("{:?}", result); + } + + if let MyResult::MyOk(result) = try_convert_error() { + println!("{:?}", result); + } + + if let MyResult::MyOk(result) = try_chained() { + println!("{:?}", result); + } + + if let MyResult::MyOk(result) = try_complex(MyResult::MyOk(S1)) { + println!("{:?}", result); + } + } +} + fn main() { field_access::f(); method_impl::f(); @@ -935,4 +1028,5 @@ fn main() { trait_implicit_self_borrow::f(); implicit_self_borrow::f(); borrowed_typed::f(); + try_expressions::f(); } From 88075c4c8cdd60ad9a7db5f93f3e9741ab64a5f5 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 24 Apr 2025 13:05:14 +0200 Subject: [PATCH 56/66] Rust: Make manual tweaks to Copilot generated code --- .../test/library-tests/type-inference/main.rs | 78 +++++------------ .../type-inference/type-inference.expected | 86 ++++++++++++++++++- 2 files changed, 104 insertions(+), 60 deletions(-) diff --git a/rust/ql/test/library-tests/type-inference/main.rs b/rust/ql/test/library-tests/type-inference/main.rs index 7e8ab73d00d..0b93a8511a0 100644 --- a/rust/ql/test/library-tests/type-inference/main.rs +++ b/rust/ql/test/library-tests/type-inference/main.rs @@ -928,85 +928,51 @@ mod try_expressions { #[derive(Debug)] struct S2; - #[derive(Debug)] - enum MyResult { - MyOk(T), - MyErr(E), - } - - impl MyResult { - fn map(self, op: F) -> MyResult - where - F: FnOnce(T) -> U, - { - match self { - MyResult::MyOk(t) => MyResult::MyOk(op(t)), - MyResult::MyErr(e) => MyResult::MyErr(e), - } - } - - fn and_then(self, op: F) -> MyResult - where - F: FnOnce(T) -> MyResult, - { - match self { - MyResult::MyOk(t) => op(t), - MyResult::MyErr(e) => MyResult::MyErr(e), - } - } - } - - // For the try operator to work, we need to implement From for OtherE - impl From for S2 { - fn from(s: S1) -> S2 { - S2 - } - } - // Simple function using ? operator with same error types - fn try_same_error() -> MyResult { - let x = MyResult::MyOk(S1)?; // $ type=x:S1 - MyResult::MyOk(x) + fn try_same_error() -> Result { + let x = Result::Ok(S1)?; // $ MISSING: type=x:S1 + Result::Ok(S1) } // Function using ? operator with different error types that need conversion - fn try_convert_error() -> MyResult { - let x: MyResult = MyResult::MyOk(S1); - let y = x?; // $ type=y:S1 - MyResult::MyOk(y) + fn try_convert_error() -> Result { + let x = Result::Ok(S1); + let y = x?; // $ MISSING: type=y:S1 + Result::Ok(S1) } // Chained ? operations - fn try_chained() -> MyResult { - let x: MyResult, S1> = MyResult::MyOk(MyResult::MyOk(S1)); - let y = x?.map(|s| s)?; // First ? returns MyResult, second ? returns S1 - MyResult::MyOk(y) + fn try_chained() -> Result { + let x = Result::Ok(Result::Ok(S1)); + // First ? returns Result, second ? returns S1 + let y = x?.map(|s| s)?; // $ MISSING: method=map + Result::Ok(S1) } // Function that uses ? with closures and complex error cases - fn try_complex(input: MyResult) -> MyResult { - let value = input?; // $ method=From::from - let mapped = MyResult::MyOk(value).and_then(|v| { + fn try_complex(input: Result) -> Result { + let value = input?; + let mapped = Result::Ok(value).and_then(|v| { println!("{:?}", v); - MyResult::MyOk::<_, S1>(v) - })?; // $ method=From::from - MyResult::MyOk(mapped) + Result::Ok::<_, S1>(v) + })?; // $ method=and_then + Result::Err(S1) } pub fn f() { - if let MyResult::MyOk(result) = try_same_error() { + if let Result::Ok(result) = try_same_error() { println!("{:?}", result); } - if let MyResult::MyOk(result) = try_convert_error() { + if let Result::Ok(result) = try_convert_error() { println!("{:?}", result); } - if let MyResult::MyOk(result) = try_chained() { + if let Result::Ok(result) = try_chained() { println!("{:?}", result); } - if let MyResult::MyOk(result) = try_complex(MyResult::MyOk(S1)) { + if let Result::Ok(result) = try_complex(Result::Ok(S1)) { println!("{:?}", result); } } diff --git a/rust/ql/test/library-tests/type-inference/type-inference.expected b/rust/ql/test/library-tests/type-inference/type-inference.expected index c91b3cef3dc..c9b7b349a70 100644 --- a/rust/ql/test/library-tests/type-inference/type-inference.expected +++ b/rust/ql/test/library-tests/type-inference/type-inference.expected @@ -1005,7 +1005,85 @@ inferType | main.rs:918:15:918:16 | &x | | file://:0:0:0:0 | & | | main.rs:918:15:918:16 | &x | &T | main.rs:894:5:894:13 | S | | main.rs:918:16:918:16 | x | | main.rs:894:5:894:13 | S | -| main.rs:924:5:924:20 | ...::f(...) | | main.rs:67:5:67:21 | Foo | -| main.rs:925:5:925:60 | ...::g(...) | | main.rs:67:5:67:21 | Foo | -| main.rs:925:20:925:38 | ...::Foo {...} | | main.rs:67:5:67:21 | Foo | -| main.rs:925:41:925:59 | ...::Foo {...} | | main.rs:67:5:67:21 | Foo | +| main.rs:932:43:935:5 | { ... } | | file://:0:0:0:0 | Result | +| main.rs:932:43:935:5 | { ... } | E | main.rs:925:5:926:14 | S1 | +| main.rs:932:43:935:5 | { ... } | T | main.rs:925:5:926:14 | S1 | +| main.rs:933:17:933:30 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:933:17:933:30 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:933:28:933:29 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:934:9:934:22 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:934:9:934:22 | ...::Ok(...) | E | main.rs:925:5:926:14 | S1 | +| main.rs:934:9:934:22 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:934:20:934:21 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:938:46:942:5 | { ... } | | file://:0:0:0:0 | Result | +| main.rs:938:46:942:5 | { ... } | E | main.rs:928:5:929:14 | S2 | +| main.rs:938:46:942:5 | { ... } | T | main.rs:925:5:926:14 | S1 | +| main.rs:939:13:939:13 | x | | file://:0:0:0:0 | Result | +| main.rs:939:13:939:13 | x | T | main.rs:925:5:926:14 | S1 | +| main.rs:939:17:939:30 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:939:17:939:30 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:939:28:939:29 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:940:17:940:17 | x | | file://:0:0:0:0 | Result | +| main.rs:940:17:940:17 | x | T | main.rs:925:5:926:14 | S1 | +| main.rs:941:9:941:22 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:941:9:941:22 | ...::Ok(...) | E | main.rs:928:5:929:14 | S2 | +| main.rs:941:9:941:22 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:941:20:941:21 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:945:40:950:5 | { ... } | | file://:0:0:0:0 | Result | +| main.rs:945:40:950:5 | { ... } | E | main.rs:928:5:929:14 | S2 | +| main.rs:945:40:950:5 | { ... } | T | main.rs:925:5:926:14 | S1 | +| main.rs:946:13:946:13 | x | | file://:0:0:0:0 | Result | +| main.rs:946:13:946:13 | x | T | file://:0:0:0:0 | Result | +| main.rs:946:13:946:13 | x | T.T | main.rs:925:5:926:14 | S1 | +| main.rs:946:17:946:42 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:946:17:946:42 | ...::Ok(...) | T | file://:0:0:0:0 | Result | +| main.rs:946:17:946:42 | ...::Ok(...) | T.T | main.rs:925:5:926:14 | S1 | +| main.rs:946:28:946:41 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:946:28:946:41 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:946:39:946:40 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:948:17:948:17 | x | | file://:0:0:0:0 | Result | +| main.rs:948:17:948:17 | x | T | file://:0:0:0:0 | Result | +| main.rs:948:17:948:17 | x | T.T | main.rs:925:5:926:14 | S1 | +| main.rs:949:9:949:22 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:949:9:949:22 | ...::Ok(...) | E | main.rs:928:5:929:14 | S2 | +| main.rs:949:9:949:22 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:949:20:949:21 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:953:30:953:34 | input | | file://:0:0:0:0 | Result | +| main.rs:953:30:953:34 | input | E | main.rs:925:5:926:14 | S1 | +| main.rs:953:30:953:34 | input | T | main.rs:953:20:953:27 | T | +| main.rs:953:69:960:5 | { ... } | | file://:0:0:0:0 | Result | +| main.rs:953:69:960:5 | { ... } | E | main.rs:925:5:926:14 | S1 | +| main.rs:953:69:960:5 | { ... } | T | main.rs:953:20:953:27 | T | +| main.rs:954:21:954:25 | input | | file://:0:0:0:0 | Result | +| main.rs:954:21:954:25 | input | E | main.rs:925:5:926:14 | S1 | +| main.rs:954:21:954:25 | input | T | main.rs:953:20:953:27 | T | +| main.rs:955:22:955:38 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:955:22:958:10 | ... .and_then(...) | | file://:0:0:0:0 | Result | +| main.rs:955:53:958:9 | { ... } | | file://:0:0:0:0 | Result | +| main.rs:955:53:958:9 | { ... } | E | main.rs:925:5:926:14 | S1 | +| main.rs:957:13:957:34 | ...::Ok::<...>(...) | | file://:0:0:0:0 | Result | +| main.rs:957:13:957:34 | ...::Ok::<...>(...) | E | main.rs:925:5:926:14 | S1 | +| main.rs:959:9:959:23 | ...::Err(...) | | file://:0:0:0:0 | Result | +| main.rs:959:9:959:23 | ...::Err(...) | E | main.rs:925:5:926:14 | S1 | +| main.rs:959:9:959:23 | ...::Err(...) | T | main.rs:953:20:953:27 | T | +| main.rs:959:21:959:22 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:963:37:963:52 | try_same_error(...) | | file://:0:0:0:0 | Result | +| main.rs:963:37:963:52 | try_same_error(...) | E | main.rs:925:5:926:14 | S1 | +| main.rs:963:37:963:52 | try_same_error(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:967:37:967:55 | try_convert_error(...) | | file://:0:0:0:0 | Result | +| main.rs:967:37:967:55 | try_convert_error(...) | E | main.rs:928:5:929:14 | S2 | +| main.rs:967:37:967:55 | try_convert_error(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:971:37:971:49 | try_chained(...) | | file://:0:0:0:0 | Result | +| main.rs:971:37:971:49 | try_chained(...) | E | main.rs:928:5:929:14 | S2 | +| main.rs:971:37:971:49 | try_chained(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:975:37:975:63 | try_complex(...) | | file://:0:0:0:0 | Result | +| main.rs:975:37:975:63 | try_complex(...) | E | main.rs:925:5:926:14 | S1 | +| main.rs:975:37:975:63 | try_complex(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:975:49:975:62 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:975:49:975:62 | ...::Ok(...) | E | main.rs:925:5:926:14 | S1 | +| main.rs:975:49:975:62 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:975:60:975:61 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:983:5:983:20 | ...::f(...) | | main.rs:67:5:67:21 | Foo | +| main.rs:984:5:984:60 | ...::g(...) | | main.rs:67:5:67:21 | Foo | +| main.rs:984:20:984:38 | ...::Foo {...} | | main.rs:67:5:67:21 | Foo | +| main.rs:984:41:984:59 | ...::Foo {...} | | main.rs:67:5:67:21 | Foo | From a3c26b4bfe57958419b2d2432c9c5dbf98408637 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 24 Apr 2025 13:05:42 +0200 Subject: [PATCH 57/66] Rust: Type inference for `?` expressions --- .../lib/codeql/rust/internal/TypeInference.qll | 14 ++++++++++++++ .../PathResolutionConsistency.expected | 17 +++++++++++++++++ .../test/library-tests/type-inference/main.rs | 6 +++--- .../type-inference/type-inference.expected | 11 +++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 rust/ql/test/library-tests/frameworks/postgres/CONSISTENCY/PathResolutionConsistency.expected diff --git a/rust/ql/lib/codeql/rust/internal/TypeInference.qll b/rust/ql/lib/codeql/rust/internal/TypeInference.qll index 0f0c92306d1..5c5f678950a 100644 --- a/rust/ql/lib/codeql/rust/internal/TypeInference.qll +++ b/rust/ql/lib/codeql/rust/internal/TypeInference.qll @@ -6,6 +6,7 @@ private import Type private import Type as T private import TypeMention private import codeql.typeinference.internal.TypeInference +private import codeql.rust.frameworks.stdlib.Stdlib class Type = T::Type; @@ -891,6 +892,17 @@ private Type inferRefExprType(Expr e, TypePath path) { ) } +pragma[nomagic] +private Type inferTryExprType(TryExpr te, TypePath path) { + exists(TypeParam tp | + result = inferType(te.getExpr(), TypePath::cons(TTypeParamTypeParameter(tp), path)) + | + tp = any(ResultEnum r).getGenericParamList().getGenericParam(0) + or + tp = any(OptionEnum o).getGenericParamList().getGenericParam(0) + ) +} + cached private module Cached { private import codeql.rust.internal.CachedStages @@ -1008,6 +1020,8 @@ private module Cached { result = inferFieldExprType(n, path) or result = inferRefExprType(n, path) + or + result = inferTryExprType(n, path) } } diff --git a/rust/ql/test/library-tests/frameworks/postgres/CONSISTENCY/PathResolutionConsistency.expected b/rust/ql/test/library-tests/frameworks/postgres/CONSISTENCY/PathResolutionConsistency.expected new file mode 100644 index 00000000000..c47d16d6875 --- /dev/null +++ b/rust/ql/test/library-tests/frameworks/postgres/CONSISTENCY/PathResolutionConsistency.expected @@ -0,0 +1,17 @@ +multipleMethodCallTargets +| main.rs:11:5:18:5 | conn.execute(...) | file://:0:0:0:0 | fn execute | +| main.rs:11:5:18:5 | conn.execute(...) | file://:0:0:0:0 | fn execute | +| main.rs:22:5:22:37 | conn.execute(...) | file://:0:0:0:0 | fn execute | +| main.rs:22:5:22:37 | conn.execute(...) | file://:0:0:0:0 | fn execute | +| main.rs:23:5:23:38 | conn.batch_execute(...) | file://:0:0:0:0 | fn batch_execute | +| main.rs:23:5:23:38 | conn.batch_execute(...) | file://:0:0:0:0 | fn batch_execute | +| main.rs:25:5:25:32 | conn.prepare(...) | file://:0:0:0:0 | fn prepare | +| main.rs:25:5:25:32 | conn.prepare(...) | file://:0:0:0:0 | fn prepare | +| main.rs:28:5:28:35 | conn.query(...) | file://:0:0:0:0 | fn query | +| main.rs:28:5:28:35 | conn.query(...) | file://:0:0:0:0 | fn query | +| main.rs:29:5:29:39 | conn.query_one(...) | file://:0:0:0:0 | fn query_one | +| main.rs:29:5:29:39 | conn.query_one(...) | file://:0:0:0:0 | fn query_one | +| main.rs:30:5:30:39 | conn.query_opt(...) | file://:0:0:0:0 | fn query_opt | +| main.rs:30:5:30:39 | conn.query_opt(...) | file://:0:0:0:0 | fn query_opt | +| main.rs:35:17:35:67 | conn.query(...) | file://:0:0:0:0 | fn query | +| main.rs:35:17:35:67 | conn.query(...) | file://:0:0:0:0 | fn query | diff --git a/rust/ql/test/library-tests/type-inference/main.rs b/rust/ql/test/library-tests/type-inference/main.rs index 0b93a8511a0..fa16b626474 100644 --- a/rust/ql/test/library-tests/type-inference/main.rs +++ b/rust/ql/test/library-tests/type-inference/main.rs @@ -930,14 +930,14 @@ mod try_expressions { // Simple function using ? operator with same error types fn try_same_error() -> Result { - let x = Result::Ok(S1)?; // $ MISSING: type=x:S1 + let x = Result::Ok(S1)?; // $ type=x:S1 Result::Ok(S1) } // Function using ? operator with different error types that need conversion fn try_convert_error() -> Result { let x = Result::Ok(S1); - let y = x?; // $ MISSING: type=y:S1 + let y = x?; // $ type=y:S1 Result::Ok(S1) } @@ -945,7 +945,7 @@ mod try_expressions { fn try_chained() -> Result { let x = Result::Ok(Result::Ok(S1)); // First ? returns Result, second ? returns S1 - let y = x?.map(|s| s)?; // $ MISSING: method=map + let y = x?.map(|s| s)?; // $ method=map Result::Ok(S1) } diff --git a/rust/ql/test/library-tests/type-inference/type-inference.expected b/rust/ql/test/library-tests/type-inference/type-inference.expected index c9b7b349a70..42e5d90701b 100644 --- a/rust/ql/test/library-tests/type-inference/type-inference.expected +++ b/rust/ql/test/library-tests/type-inference/type-inference.expected @@ -1008,8 +1008,10 @@ inferType | main.rs:932:43:935:5 | { ... } | | file://:0:0:0:0 | Result | | main.rs:932:43:935:5 | { ... } | E | main.rs:925:5:926:14 | S1 | | main.rs:932:43:935:5 | { ... } | T | main.rs:925:5:926:14 | S1 | +| main.rs:933:13:933:13 | x | | main.rs:925:5:926:14 | S1 | | main.rs:933:17:933:30 | ...::Ok(...) | | file://:0:0:0:0 | Result | | main.rs:933:17:933:30 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:933:17:933:31 | TryExpr | | main.rs:925:5:926:14 | S1 | | main.rs:933:28:933:29 | S1 | | main.rs:925:5:926:14 | S1 | | main.rs:934:9:934:22 | ...::Ok(...) | | file://:0:0:0:0 | Result | | main.rs:934:9:934:22 | ...::Ok(...) | E | main.rs:925:5:926:14 | S1 | @@ -1023,8 +1025,10 @@ inferType | main.rs:939:17:939:30 | ...::Ok(...) | | file://:0:0:0:0 | Result | | main.rs:939:17:939:30 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | | main.rs:939:28:939:29 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:940:13:940:13 | y | | main.rs:925:5:926:14 | S1 | | main.rs:940:17:940:17 | x | | file://:0:0:0:0 | Result | | main.rs:940:17:940:17 | x | T | main.rs:925:5:926:14 | S1 | +| main.rs:940:17:940:18 | TryExpr | | main.rs:925:5:926:14 | S1 | | main.rs:941:9:941:22 | ...::Ok(...) | | file://:0:0:0:0 | Result | | main.rs:941:9:941:22 | ...::Ok(...) | E | main.rs:928:5:929:14 | S2 | | main.rs:941:9:941:22 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | @@ -1044,6 +1048,9 @@ inferType | main.rs:948:17:948:17 | x | | file://:0:0:0:0 | Result | | main.rs:948:17:948:17 | x | T | file://:0:0:0:0 | Result | | main.rs:948:17:948:17 | x | T.T | main.rs:925:5:926:14 | S1 | +| main.rs:948:17:948:18 | TryExpr | | file://:0:0:0:0 | Result | +| main.rs:948:17:948:18 | TryExpr | T | main.rs:925:5:926:14 | S1 | +| main.rs:948:17:948:29 | ... .map(...) | | file://:0:0:0:0 | Result | | main.rs:949:9:949:22 | ...::Ok(...) | | file://:0:0:0:0 | Result | | main.rs:949:9:949:22 | ...::Ok(...) | E | main.rs:928:5:929:14 | S2 | | main.rs:949:9:949:22 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | @@ -1054,11 +1061,15 @@ inferType | main.rs:953:69:960:5 | { ... } | | file://:0:0:0:0 | Result | | main.rs:953:69:960:5 | { ... } | E | main.rs:925:5:926:14 | S1 | | main.rs:953:69:960:5 | { ... } | T | main.rs:953:20:953:27 | T | +| main.rs:954:13:954:17 | value | | main.rs:953:20:953:27 | T | | main.rs:954:21:954:25 | input | | file://:0:0:0:0 | Result | | main.rs:954:21:954:25 | input | E | main.rs:925:5:926:14 | S1 | | main.rs:954:21:954:25 | input | T | main.rs:953:20:953:27 | T | +| main.rs:954:21:954:26 | TryExpr | | main.rs:953:20:953:27 | T | | main.rs:955:22:955:38 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:955:22:955:38 | ...::Ok(...) | T | main.rs:953:20:953:27 | T | | main.rs:955:22:958:10 | ... .and_then(...) | | file://:0:0:0:0 | Result | +| main.rs:955:33:955:37 | value | | main.rs:953:20:953:27 | T | | main.rs:955:53:958:9 | { ... } | | file://:0:0:0:0 | Result | | main.rs:955:53:958:9 | { ... } | E | main.rs:925:5:926:14 | S1 | | main.rs:957:13:957:34 | ...::Ok::<...>(...) | | file://:0:0:0:0 | Result | From 2ed48ae571c5303074bce306f0e6d9e9d78e922d Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Wed, 30 Apr 2025 20:51:27 +0200 Subject: [PATCH 58/66] C++: Update expected test results after barrier introduction --- .../CastArrayPointerArithmetic.expected | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/cpp/ql/test/query-tests/Likely Bugs/Conversion/CastArrayPointerArithmetic/CastArrayPointerArithmetic.expected b/cpp/ql/test/query-tests/Likely Bugs/Conversion/CastArrayPointerArithmetic/CastArrayPointerArithmetic.expected index 3f66b2c20b3..75e2e581664 100644 --- a/cpp/ql/test/query-tests/Likely Bugs/Conversion/CastArrayPointerArithmetic/CastArrayPointerArithmetic.expected +++ b/cpp/ql/test/query-tests/Likely Bugs/Conversion/CastArrayPointerArithmetic/CastArrayPointerArithmetic.expected @@ -3,22 +3,13 @@ edges | test.cpp:30:34:30:34 | b | test.cpp:31:2:31:2 | b | provenance | | | test.cpp:34:31:34:31 | b | test.cpp:35:2:35:2 | b | provenance | | | test.cpp:57:19:57:19 | d | test.cpp:26:29:26:29 | b | provenance | | -| test.cpp:57:19:57:19 | d | test.cpp:58:25:58:25 | d | provenance | | -| test.cpp:57:19:57:19 | d | test.cpp:59:21:59:21 | d | provenance | | | test.cpp:58:25:58:25 | d | test.cpp:30:34:30:34 | b | provenance | | -| test.cpp:58:25:58:25 | d | test.cpp:59:21:59:21 | d | provenance | | | test.cpp:59:21:59:21 | d | test.cpp:34:31:34:31 | b | provenance | | | test.cpp:74:19:74:21 | dss | test.cpp:26:29:26:29 | b | provenance | | -| test.cpp:74:19:74:21 | dss | test.cpp:75:25:75:27 | dss | provenance | | -| test.cpp:74:19:74:21 | dss | test.cpp:76:21:76:23 | dss | provenance | | | test.cpp:75:25:75:27 | dss | test.cpp:30:34:30:34 | b | provenance | | -| test.cpp:75:25:75:27 | dss | test.cpp:76:21:76:23 | dss | provenance | | | test.cpp:76:21:76:23 | dss | test.cpp:34:31:34:31 | b | provenance | | | test.cpp:86:19:86:20 | d2 | test.cpp:26:29:26:29 | b | provenance | | -| test.cpp:86:19:86:20 | d2 | test.cpp:87:25:87:26 | d2 | provenance | | -| test.cpp:86:19:86:20 | d2 | test.cpp:88:21:88:22 | d2 | provenance | | | test.cpp:87:25:87:26 | d2 | test.cpp:30:34:30:34 | b | provenance | | -| test.cpp:87:25:87:26 | d2 | test.cpp:88:21:88:22 | d2 | provenance | | | test.cpp:88:21:88:22 | d2 | test.cpp:34:31:34:31 | b | provenance | | nodes | test.cpp:26:29:26:29 | b | semmle.label | b | @@ -41,18 +32,9 @@ subpaths | test.cpp:27:2:27:2 | b | test.cpp:57:19:57:19 | d | test.cpp:27:2:27:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:57:19:57:19 | d | this cast | | test.cpp:27:2:27:2 | b | test.cpp:74:19:74:21 | dss | test.cpp:27:2:27:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:74:19:74:21 | dss | this cast | | test.cpp:27:2:27:2 | b | test.cpp:86:19:86:20 | d2 | test.cpp:27:2:27:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:86:19:86:20 | d2 | this cast | -| test.cpp:31:2:31:2 | b | test.cpp:57:19:57:19 | d | test.cpp:31:2:31:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:57:19:57:19 | d | this cast | | test.cpp:31:2:31:2 | b | test.cpp:58:25:58:25 | d | test.cpp:31:2:31:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:58:25:58:25 | d | this cast | -| test.cpp:31:2:31:2 | b | test.cpp:74:19:74:21 | dss | test.cpp:31:2:31:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:74:19:74:21 | dss | this cast | | test.cpp:31:2:31:2 | b | test.cpp:75:25:75:27 | dss | test.cpp:31:2:31:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:75:25:75:27 | dss | this cast | -| test.cpp:31:2:31:2 | b | test.cpp:86:19:86:20 | d2 | test.cpp:31:2:31:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:86:19:86:20 | d2 | this cast | | test.cpp:31:2:31:2 | b | test.cpp:87:25:87:26 | d2 | test.cpp:31:2:31:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:87:25:87:26 | d2 | this cast | -| test.cpp:35:2:35:2 | b | test.cpp:57:19:57:19 | d | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:57:19:57:19 | d | this cast | -| test.cpp:35:2:35:2 | b | test.cpp:58:25:58:25 | d | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:58:25:58:25 | d | this cast | | test.cpp:35:2:35:2 | b | test.cpp:59:21:59:21 | d | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:59:21:59:21 | d | this cast | -| test.cpp:35:2:35:2 | b | test.cpp:74:19:74:21 | dss | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:74:19:74:21 | dss | this cast | -| test.cpp:35:2:35:2 | b | test.cpp:75:25:75:27 | dss | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:75:25:75:27 | dss | this cast | | test.cpp:35:2:35:2 | b | test.cpp:76:21:76:23 | dss | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:76:21:76:23 | dss | this cast | -| test.cpp:35:2:35:2 | b | test.cpp:86:19:86:20 | d2 | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:86:19:86:20 | d2 | this cast | -| test.cpp:35:2:35:2 | b | test.cpp:87:25:87:26 | d2 | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:87:25:87:26 | d2 | this cast | | test.cpp:35:2:35:2 | b | test.cpp:88:21:88:22 | d2 | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:88:21:88:22 | d2 | this cast | From 6285c2e502aa2930d2ffb4ad2bfd3488d099aa55 Mon Sep 17 00:00:00 2001 From: Aditya Sharad Date: Wed, 30 Apr 2025 16:24:22 -0700 Subject: [PATCH 59/66] Actions: Retroactively add GA changenote This was manually added in the docs site at the time of 2.21.1 release and GA. Include the change note in the relevant places so it remains in future docs updates: - codeql/actions-queries@0.5.4 - codeql/actions-all@0.4.7 - 2.21.1 changelog --- actions/ql/lib/CHANGELOG.md | 4 +++- actions/ql/lib/change-notes/released/0.4.7.md | 4 +++- actions/ql/src/CHANGELOG.md | 4 ++++ actions/ql/src/change-notes/released/0.5.4.md | 4 ++++ .../codeql-changelog/codeql-cli-2.21.1.rst | 13 +++++++++++++ 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/actions/ql/lib/CHANGELOG.md b/actions/ql/lib/CHANGELOG.md index 0adf75f4cc0..e16567daffd 100644 --- a/actions/ql/lib/CHANGELOG.md +++ b/actions/ql/lib/CHANGELOG.md @@ -4,7 +4,9 @@ No user-facing changes. ## 0.4.7 -No user-facing changes. +### New Features + +* CodeQL and Copilot Autofix support for GitHub Actions is now Generally Available. ## 0.4.6 diff --git a/actions/ql/lib/change-notes/released/0.4.7.md b/actions/ql/lib/change-notes/released/0.4.7.md index e9bb7a76bcb..90e0acec41c 100644 --- a/actions/ql/lib/change-notes/released/0.4.7.md +++ b/actions/ql/lib/change-notes/released/0.4.7.md @@ -1,3 +1,5 @@ ## 0.4.7 -No user-facing changes. +### New Features + +* CodeQL and Copilot Autofix support for GitHub Actions is now Generally Available. diff --git a/actions/ql/src/CHANGELOG.md b/actions/ql/src/CHANGELOG.md index 4faccf28ebd..73cc65ebe21 100644 --- a/actions/ql/src/CHANGELOG.md +++ b/actions/ql/src/CHANGELOG.md @@ -20,6 +20,10 @@ ## 0.5.4 +### New Features + +* CodeQL and Copilot Autofix support for GitHub Actions is now Generally Available. + ### Bug Fixes * Alerts produced by the query `actions/missing-workflow-permissions` now include a minimal set of recommended permissions in the alert message, based on well-known actions seen within the workflow file. diff --git a/actions/ql/src/change-notes/released/0.5.4.md b/actions/ql/src/change-notes/released/0.5.4.md index d34090f9955..a6174ef6d1d 100644 --- a/actions/ql/src/change-notes/released/0.5.4.md +++ b/actions/ql/src/change-notes/released/0.5.4.md @@ -1,5 +1,9 @@ ## 0.5.4 +### New Features + +* CodeQL and Copilot Autofix support for GitHub Actions is now Generally Available. + ### Bug Fixes * Alerts produced by the query `actions/missing-workflow-permissions` now include a minimal set of recommended permissions in the alert message, based on well-known actions seen within the workflow file. diff --git a/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.1.rst b/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.1.rst index 1c28523b18f..2a8e20d84d1 100644 --- a/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.1.rst +++ b/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.1.rst @@ -37,6 +37,14 @@ Bug Fixes Query Packs ----------- +New Features +~~~~~~~~~~~~ + +GitHub Actions +"""""""""""""" + +* CodeQL and Copilot Autofix support for GitHub Actions is now Generally Available. + Bug Fixes ~~~~~~~~~ @@ -123,6 +131,11 @@ Ruby New Features ~~~~~~~~~~~~ +GitHub Actions +"""""""""""""" + +* CodeQL and Copilot Autofix support for GitHub Actions is now Generally Available. + C/C++ """"" From 36199b3f06253ba0a8a79af3a679c8d78a35d2dd Mon Sep 17 00:00:00 2001 From: Aditya Sharad Date: Wed, 30 Apr 2025 16:40:36 -0700 Subject: [PATCH 60/66] Docs: Fix escaping in 2.21.0 changelog These break when the RST is processed. Escape the backslashes and consistently add inline code blocks. --- .../codeql-overview/codeql-changelog/codeql-cli-2.21.0.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.0.rst b/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.0.rst index 7d62123a49a..aa604d702e7 100644 --- a/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.0.rst +++ b/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.0.rst @@ -165,7 +165,7 @@ Java/Kotlin """"""""""" * Java extraction is now able to download Maven 3.9.x if a Maven Enforcer Plugin configuration indicates it is necessary. Maven 3.8.x is still preferred if the enforcer-plugin configuration (if any) permits it. -* Added a path injection sanitizer for calls to :code:`java.lang.String.matches`, :code:`java.lang.String.replace`, and :code:`java.lang.String.replaceAll` that make sure '/', '\', '..' are not in the path. +* Added a path injection sanitizer for calls to :code:`java.lang.String.matches`, :code:`java.lang.String.replace`, and :code:`java.lang.String.replaceAll` that make sure :code:`/`, :code:`\\`, :code:`..` are not in the path. JavaScript/TypeScript """"""""""""""""""""" @@ -207,5 +207,5 @@ JavaScript/TypeScript * Intersection :code:`&&` * Subtraction :code:`--` - * :code:`\q` quoted string + * :code:`\\q` quoted string From 423e2dac9132d3e0572d8fea053dbafbad87bb82 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 1 May 2025 10:54:52 +0200 Subject: [PATCH 61/66] Rust: Strenghten the modeling of the `Clone` trait --- .../codeql/rust/frameworks/stdlib/Clone.qll | 4 +--- .../codeql/rust/internal/TypeInference.qll | 4 ++-- .../dataflow/modeled/inline-flow.expected | 22 ------------------- .../library-tests/dataflow/modeled/main.rs | 4 ++-- .../CaptureSummaryModels.expected | 2 +- .../test/utils-tests/modelgenerator/option.rs | 6 ++--- 6 files changed, 9 insertions(+), 33 deletions(-) diff --git a/rust/ql/lib/codeql/rust/frameworks/stdlib/Clone.qll b/rust/ql/lib/codeql/rust/frameworks/stdlib/Clone.qll index 514c3781355..8d3c41c4708 100644 --- a/rust/ql/lib/codeql/rust/frameworks/stdlib/Clone.qll +++ b/rust/ql/lib/codeql/rust/frameworks/stdlib/Clone.qll @@ -18,9 +18,7 @@ final class CloneCallable extends SummarizedCallable::Range { final override predicate propagatesFlow( string input, string output, boolean preservesValue, string model ) { - // The `clone` method takes a `&self` parameter and dereferences it; - // make sure to not clone the reference itself - input = ["Argument[self].Reference", "Argument[self].WithoutReference"] and + input = "Argument[self].Reference" and output = "ReturnValue" and preservesValue = true and model = "generated" diff --git a/rust/ql/lib/codeql/rust/internal/TypeInference.qll b/rust/ql/lib/codeql/rust/internal/TypeInference.qll index 8c741781c20..cb9450a84d7 100644 --- a/rust/ql/lib/codeql/rust/internal/TypeInference.qll +++ b/rust/ql/lib/codeql/rust/internal/TypeInference.qll @@ -1041,8 +1041,8 @@ private module Debug { private Locatable getRelevantLocatable() { exists(string filepath, int startline, int startcolumn, int endline, int endcolumn | result.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and - filepath.matches("%/tauri/src/app/plugin.rs") and - startline = 54 + filepath.matches("%/main.rs") and + startline = 28 ) } diff --git a/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected b/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected index b5e19855cfa..198f8ab9cd8 100644 --- a/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected +++ b/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected @@ -10,7 +10,6 @@ edges | main.rs:12:9:12:9 | a [Some] | main.rs:13:10:13:19 | a.unwrap() | provenance | MaD:2 | | main.rs:12:9:12:9 | a [Some] | main.rs:14:13:14:13 | a [Some] | provenance | | | main.rs:12:9:12:9 | a [Some] | main.rs:14:13:14:21 | a.clone() [Some] | provenance | MaD:1 | -| main.rs:12:9:12:9 | a [Some] | main.rs:14:13:14:21 | a.clone() [Some] | provenance | generated | | main.rs:12:13:12:28 | Some(...) [Some] | main.rs:12:9:12:9 | a [Some] | provenance | | | main.rs:12:18:12:27 | source(...) | main.rs:12:13:12:28 | Some(...) [Some] | provenance | | | main.rs:14:9:14:9 | b [Some] | main.rs:15:10:15:19 | b.unwrap() | provenance | MaD:2 | @@ -19,29 +18,19 @@ edges | main.rs:19:9:19:9 | a [Ok] | main.rs:20:10:20:19 | a.unwrap() | provenance | MaD:5 | | main.rs:19:9:19:9 | a [Ok] | main.rs:21:13:21:13 | a [Ok] | provenance | | | main.rs:19:9:19:9 | a [Ok] | main.rs:21:13:21:21 | a.clone() [Ok] | provenance | MaD:4 | -| main.rs:19:9:19:9 | a [Ok] | main.rs:21:13:21:21 | a.clone() [Ok] | provenance | generated | | main.rs:19:31:19:44 | Ok(...) [Ok] | main.rs:19:9:19:9 | a [Ok] | provenance | | | main.rs:19:34:19:43 | source(...) | main.rs:19:31:19:44 | Ok(...) [Ok] | provenance | | | main.rs:21:9:21:9 | b [Ok] | main.rs:22:10:22:19 | b.unwrap() | provenance | MaD:5 | | main.rs:21:13:21:13 | a [Ok] | main.rs:21:13:21:21 | a.clone() [Ok] | provenance | generated | | main.rs:21:13:21:21 | a.clone() [Ok] | main.rs:21:9:21:9 | b [Ok] | provenance | | | main.rs:26:9:26:9 | a | main.rs:27:10:27:10 | a | provenance | | -| main.rs:26:9:26:9 | a | main.rs:28:13:28:21 | a.clone() | provenance | generated | | main.rs:26:13:26:22 | source(...) | main.rs:26:9:26:9 | a | provenance | | -| main.rs:28:9:28:9 | b | main.rs:29:10:29:10 | b | provenance | | -| main.rs:28:13:28:21 | a.clone() | main.rs:28:9:28:9 | b | provenance | | | main.rs:41:13:41:13 | w [Wrapper] | main.rs:42:15:42:15 | w [Wrapper] | provenance | | | main.rs:41:17:41:41 | Wrapper {...} [Wrapper] | main.rs:41:13:41:13 | w [Wrapper] | provenance | | | main.rs:41:30:41:39 | source(...) | main.rs:41:17:41:41 | Wrapper {...} [Wrapper] | provenance | | | main.rs:42:15:42:15 | w [Wrapper] | main.rs:43:13:43:28 | Wrapper {...} [Wrapper] | provenance | | -| main.rs:42:15:42:15 | w [Wrapper] | main.rs:45:17:45:25 | w.clone() [Wrapper] | provenance | generated | | main.rs:43:13:43:28 | Wrapper {...} [Wrapper] | main.rs:43:26:43:26 | n | provenance | | | main.rs:43:26:43:26 | n | main.rs:43:38:43:38 | n | provenance | | -| main.rs:45:13:45:13 | u [Wrapper] | main.rs:46:15:46:15 | u [Wrapper] | provenance | | -| main.rs:45:17:45:25 | w.clone() [Wrapper] | main.rs:45:13:45:13 | u [Wrapper] | provenance | | -| main.rs:46:15:46:15 | u [Wrapper] | main.rs:47:13:47:28 | Wrapper {...} [Wrapper] | provenance | | -| main.rs:47:13:47:28 | Wrapper {...} [Wrapper] | main.rs:47:26:47:26 | n | provenance | | -| main.rs:47:26:47:26 | n | main.rs:47:38:47:38 | n | provenance | | | main.rs:58:13:58:13 | b [Some] | main.rs:59:23:59:23 | b [Some] | provenance | | | main.rs:58:17:58:32 | Some(...) [Some] | main.rs:58:13:58:13 | b [Some] | provenance | | | main.rs:58:22:58:31 | source(...) | main.rs:58:17:58:32 | Some(...) [Some] | provenance | | @@ -75,9 +64,6 @@ nodes | main.rs:26:9:26:9 | a | semmle.label | a | | main.rs:26:13:26:22 | source(...) | semmle.label | source(...) | | main.rs:27:10:27:10 | a | semmle.label | a | -| main.rs:28:9:28:9 | b | semmle.label | b | -| main.rs:28:13:28:21 | a.clone() | semmle.label | a.clone() | -| main.rs:29:10:29:10 | b | semmle.label | b | | main.rs:41:13:41:13 | w [Wrapper] | semmle.label | w [Wrapper] | | main.rs:41:17:41:41 | Wrapper {...} [Wrapper] | semmle.label | Wrapper {...} [Wrapper] | | main.rs:41:30:41:39 | source(...) | semmle.label | source(...) | @@ -85,12 +71,6 @@ nodes | main.rs:43:13:43:28 | Wrapper {...} [Wrapper] | semmle.label | Wrapper {...} [Wrapper] | | main.rs:43:26:43:26 | n | semmle.label | n | | main.rs:43:38:43:38 | n | semmle.label | n | -| main.rs:45:13:45:13 | u [Wrapper] | semmle.label | u [Wrapper] | -| main.rs:45:17:45:25 | w.clone() [Wrapper] | semmle.label | w.clone() [Wrapper] | -| main.rs:46:15:46:15 | u [Wrapper] | semmle.label | u [Wrapper] | -| main.rs:47:13:47:28 | Wrapper {...} [Wrapper] | semmle.label | Wrapper {...} [Wrapper] | -| main.rs:47:26:47:26 | n | semmle.label | n | -| main.rs:47:38:47:38 | n | semmle.label | n | | main.rs:58:13:58:13 | b [Some] | semmle.label | b [Some] | | main.rs:58:17:58:32 | Some(...) [Some] | semmle.label | Some(...) [Some] | | main.rs:58:22:58:31 | source(...) | semmle.label | source(...) | @@ -114,8 +94,6 @@ testFailures | main.rs:20:10:20:19 | a.unwrap() | main.rs:19:34:19:43 | source(...) | main.rs:20:10:20:19 | a.unwrap() | $@ | main.rs:19:34:19:43 | source(...) | source(...) | | main.rs:22:10:22:19 | b.unwrap() | main.rs:19:34:19:43 | source(...) | main.rs:22:10:22:19 | b.unwrap() | $@ | main.rs:19:34:19:43 | source(...) | source(...) | | main.rs:27:10:27:10 | a | main.rs:26:13:26:22 | source(...) | main.rs:27:10:27:10 | a | $@ | main.rs:26:13:26:22 | source(...) | source(...) | -| main.rs:29:10:29:10 | b | main.rs:26:13:26:22 | source(...) | main.rs:29:10:29:10 | b | $@ | main.rs:26:13:26:22 | source(...) | source(...) | | main.rs:43:38:43:38 | n | main.rs:41:30:41:39 | source(...) | main.rs:43:38:43:38 | n | $@ | main.rs:41:30:41:39 | source(...) | source(...) | -| main.rs:47:38:47:38 | n | main.rs:41:30:41:39 | source(...) | main.rs:47:38:47:38 | n | $@ | main.rs:41:30:41:39 | source(...) | source(...) | | main.rs:63:22:63:22 | m | main.rs:58:22:58:31 | source(...) | main.rs:63:22:63:22 | m | $@ | main.rs:58:22:58:31 | source(...) | source(...) | | main.rs:85:18:85:34 | ...::read(...) | main.rs:84:32:84:41 | source(...) | main.rs:85:18:85:34 | ...::read(...) | $@ | main.rs:84:32:84:41 | source(...) | source(...) | diff --git a/rust/ql/test/library-tests/dataflow/modeled/main.rs b/rust/ql/test/library-tests/dataflow/modeled/main.rs index cb955ce32bd..648ffd0a307 100644 --- a/rust/ql/test/library-tests/dataflow/modeled/main.rs +++ b/rust/ql/test/library-tests/dataflow/modeled/main.rs @@ -26,7 +26,7 @@ fn i64_clone() { let a = source(12); sink(a); // $ hasValueFlow=12 let b = a.clone(); - sink(b); // $ hasValueFlow=12 + sink(b); // $ MISSING: hasValueFlow=12 - lack of builtins means that we cannot resolve clone call above, and hence not insert implicit borrow } mod my_clone { @@ -44,7 +44,7 @@ mod my_clone { } let u = w.clone(); match u { - Wrapper { n: n } => sink(n), // $ hasValueFlow=73 + Wrapper { n: n } => sink(n), // $ MISSING: hasValueFlow=73 - lack of expanded derives means that we cannot resolve clone call above, and hence not insert implicit borrow } } } diff --git a/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.expected b/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.expected index e827c7320cc..b3a3717c930 100644 --- a/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.expected +++ b/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.expected @@ -1,4 +1,4 @@ unexpectedModel | Unexpected summary found: repo::test;::clone;Argument[self].Field[crate::option::MyOption::MySome(0)].Reference;ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated | -| Unexpected summary found: repo::test;::cloned;Argument[self].Field[crate::option::MyOption::MySome(0)].Reference;ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated | expectedModel +| Expected summary missing: repo::test;::clone;Argument[self].Reference.Field[crate::option::MyOption::MySome(0)];ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated | diff --git a/rust/ql/test/utils-tests/modelgenerator/option.rs b/rust/ql/test/utils-tests/modelgenerator/option.rs index 5e2e2391ba7..19b4a92fa37 100644 --- a/rust/ql/test/utils-tests/modelgenerator/option.rs +++ b/rust/ql/test/utils-tests/modelgenerator/option.rs @@ -414,7 +414,7 @@ impl MyOption<&T> { } } - // summary=repo::test;::cloned;Argument[self].Field[crate::option::MyOption::MySome(0)];ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated + // summary=repo::test;::cloned;Argument[self].Field[crate::option::MyOption::MySome(0)].Reference;ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated pub fn cloned(self) -> MyOption where T: Clone, @@ -438,7 +438,7 @@ impl MyOption<&mut T> { } } - // summary=repo::test;::cloned;Argument[self].Field[crate::option::MyOption::MySome(0)];ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated + // summary=repo::test;::cloned;Argument[self].Field[crate::option::MyOption::MySome(0)].Reference;ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated pub fn cloned(self) -> MyOption where T: Clone, @@ -466,7 +466,7 @@ impl Clone for MyOption where T: Clone, { - // summary=repo::test;::clone;Argument[self].Field[crate::option::MyOption::MySome(0)];ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated + // summary=repo::test;::clone;Argument[self].Reference.Field[crate::option::MyOption::MySome(0)];ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated fn clone(&self) -> Self { match self { MySome(x) => MySome(x.clone()), From 68a9dd9f9e72b2feaade18de2dbabc742406bdeb Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Thu, 1 May 2025 11:19:41 +0200 Subject: [PATCH 62/66] Address comments --- javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll index 4b53292e148..dafc38ca857 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll @@ -169,9 +169,7 @@ module Fastify { override string getRelativePath() { result = this.getArgument(0).getStringValue() } - override Http::RequestMethodName getHttpMethod() { - if this.getMethodName() = "all" then any() else result = this.getMethodName().toUpperCase() - } + override Http::RequestMethodName getHttpMethod() { result = this.getMethodName().toUpperCase() } } private class AddHookRouteSetup extends Routing::RouteSetup::MethodCall instanceof RouteSetup { From 0863c87572a3867de8fa9a571e8ee3574f809ea1 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Thu, 1 May 2025 10:33:24 +0100 Subject: [PATCH 63/66] Add change notes --- .../src/change-notes/2025-05-01-cwe-tag-changed.md | 9 +++++++++ .../src/change-notes/2025-05-01-cwe-tag-changed.md | 12 ++++++++++++ .../src/change-notes/2025-05-01-cwe-tag-changed.md | 14 ++++++++++++++ .../src/change-notes/2025-05-01-cwe-tag-changed.md | 7 +++++++ .../src/change-notes/2025-05-01-cwe-tag-changed.md | 8 ++++++++ .../src/change-notes/2025-05-01-cwe-tag-changed.md | 10 ++++++++++ .../src/change-notes/2025-05-01-cwe-tag-changed.md | 5 +++++ 7 files changed, 65 insertions(+) create mode 100644 cpp/ql/src/change-notes/2025-05-01-cwe-tag-changed.md create mode 100644 csharp/ql/src/change-notes/2025-05-01-cwe-tag-changed.md create mode 100644 go/ql/src/change-notes/2025-05-01-cwe-tag-changed.md create mode 100644 java/ql/src/change-notes/2025-05-01-cwe-tag-changed.md create mode 100644 javascript/ql/src/change-notes/2025-05-01-cwe-tag-changed.md create mode 100644 python/ql/src/change-notes/2025-05-01-cwe-tag-changed.md create mode 100644 ruby/ql/src/change-notes/2025-05-01-cwe-tag-changed.md diff --git a/cpp/ql/src/change-notes/2025-05-01-cwe-tag-changed.md b/cpp/ql/src/change-notes/2025-05-01-cwe-tag-changed.md new file mode 100644 index 00000000000..daefff65c31 --- /dev/null +++ b/cpp/ql/src/change-notes/2025-05-01-cwe-tag-changed.md @@ -0,0 +1,9 @@ +--- +category: queryMetadata +--- +* The tag `external/cwe/cwe-14` has been removed from `cpp/memset-may-be-deleted` and the tag `external/cwe/cwe-014` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cpp/count-untrusted-data-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cpp/count-untrusted-data-external-api-ir` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cpp/untrusted-data-to-external-api-ir` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cpp/untrusted-data-to-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cpp/late-check-of-function-argument` and the tag `external/cwe/cwe-020` has been added. diff --git a/csharp/ql/src/change-notes/2025-05-01-cwe-tag-changed.md b/csharp/ql/src/change-notes/2025-05-01-cwe-tag-changed.md new file mode 100644 index 00000000000..8b84ae3f077 --- /dev/null +++ b/csharp/ql/src/change-notes/2025-05-01-cwe-tag-changed.md @@ -0,0 +1,12 @@ +--- +category: queryMetadata +--- + +* The tag `external/cwe/cwe-13` has been removed from `cs/password-in-configuration` and the tag `external/cwe/cwe-013` has been added. +* The tag `external/cwe/cwe-11` has been removed from `cs/web/debug-binary` and the tag `external/cwe/cwe-011` has been added. +* The tag `external/cwe/cwe-16` has been removed from `cs/web/large-max-request-length` and the tag `external/cwe/cwe-016` has been added. +* The tag `external/cwe/cwe-16` has been removed from `cs/web/request-validation-disabled` and the tag `external/cwe/cwe-016` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cs/count-untrusted-data-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cs/serialization-check-bypass` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cs/untrusted-data-to-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-12` has been removed from `cs/web/missing-global-error-handler` and the tag `external/cwe/cwe-012` has been added. diff --git a/go/ql/src/change-notes/2025-05-01-cwe-tag-changed.md b/go/ql/src/change-notes/2025-05-01-cwe-tag-changed.md new file mode 100644 index 00000000000..d084aeeaf48 --- /dev/null +++ b/go/ql/src/change-notes/2025-05-01-cwe-tag-changed.md @@ -0,0 +1,14 @@ +--- +category: queryMetadata +--- + +* The tag `external/cwe/cwe-20` has been removed from `go/count-untrusted-data-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `go/incomplete-hostname-regexp` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `go/regex/missing-regexp-anchor` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `go/suspicious-character-in-regex` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `go/untrusted-data-to-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `go/untrusted-data-to-unknown-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-90` has been removed from `go/ldap-injection` and the tag `external/cwe/cwe-090` has been added. +* The tag `external/cwe/cwe-74` has been removed from `go/dsn-injection` and the tag `external/cwe/cwe-074` has been added. +* The tag `external/cwe/cwe-74` has been removed from `go/dsn-injection-local` and the tag `external/cwe/cwe-074` has been added. +* The tag `external/cwe/cwe-79` has been removed from `go/html-template-escaping-passthrough` and the tag `external/cwe/cwe-079` has been added. diff --git a/java/ql/src/change-notes/2025-05-01-cwe-tag-changed.md b/java/ql/src/change-notes/2025-05-01-cwe-tag-changed.md new file mode 100644 index 00000000000..2bbc6a6d776 --- /dev/null +++ b/java/ql/src/change-notes/2025-05-01-cwe-tag-changed.md @@ -0,0 +1,7 @@ +--- +category: queryMetadata +--- + +* The tag `external/cwe/cwe-20` has been removed from `java/count-untrusted-data-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `java/untrusted-data-to-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-93` has been removed from `java/netty-http-request-or-response-splitting` and the tag `external/cwe/cwe-093` has been added. diff --git a/javascript/ql/src/change-notes/2025-05-01-cwe-tag-changed.md b/javascript/ql/src/change-notes/2025-05-01-cwe-tag-changed.md new file mode 100644 index 00000000000..01e53adf5f5 --- /dev/null +++ b/javascript/ql/src/change-notes/2025-05-01-cwe-tag-changed.md @@ -0,0 +1,8 @@ +--- +category: queryMetadata +--- + +* The tag `external/cwe/cwe-79` has been removed from `js/disabling-electron-websecurity` and the tag `external/cwe/cwe-079` has been added. +* The tag `external/cwe/cwe-20` has been removed from `js/count-untrusted-data-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `js/untrusted-data-to-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `js/untrusted-data-to-external-api-more-sources` and the tag `external/cwe/cwe-020` has been added. diff --git a/python/ql/src/change-notes/2025-05-01-cwe-tag-changed.md b/python/ql/src/change-notes/2025-05-01-cwe-tag-changed.md new file mode 100644 index 00000000000..0267e9a3fbb --- /dev/null +++ b/python/ql/src/change-notes/2025-05-01-cwe-tag-changed.md @@ -0,0 +1,10 @@ +--- +category: queryMetadata +--- + +* The tags `security/cwe/cwe-94` and `security/cwe/cwe-95` have been removed from `py/use-of-input` and the tags `external/cwe/cwe-094` and `external/cwe/cwe-095` have been added. +* The tag `external/cwe/cwe-20` has been removed from `py/count-untrusted-data-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `py/untrusted-data-to-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `py/cookie-injection` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `py/incomplete-url-substring-sanitization` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-94` has been removed from `py/js2py-rce` and the tag `external/cwe/cwe-094` has been added. diff --git a/ruby/ql/src/change-notes/2025-05-01-cwe-tag-changed.md b/ruby/ql/src/change-notes/2025-05-01-cwe-tag-changed.md new file mode 100644 index 00000000000..c8fd8d62686 --- /dev/null +++ b/ruby/ql/src/change-notes/2025-05-01-cwe-tag-changed.md @@ -0,0 +1,5 @@ +--- +category: queryMetadata +--- + +* The tag `external/cwe/cwe-94` has been removed from `rb/server-side-template-injection` and the tag `external/cwe/cwe-094` has been added. From 0325f368fe27045757d8bb55e18879a2ac88f379 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Thu, 1 May 2025 13:57:14 +0200 Subject: [PATCH 64/66] Added test case for `hdbcli` --- .../frameworks/hdbcli/ConceptsTest.expected | 0 .../test/library-tests/frameworks/hdbcli/ConceptsTest.ql | 2 ++ python/ql/test/library-tests/frameworks/hdbcli/pep249.py | 9 +++++++++ 3 files changed, 11 insertions(+) create mode 100644 python/ql/test/library-tests/frameworks/hdbcli/ConceptsTest.expected create mode 100644 python/ql/test/library-tests/frameworks/hdbcli/ConceptsTest.ql create mode 100644 python/ql/test/library-tests/frameworks/hdbcli/pep249.py diff --git a/python/ql/test/library-tests/frameworks/hdbcli/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/hdbcli/ConceptsTest.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/ql/test/library-tests/frameworks/hdbcli/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/hdbcli/ConceptsTest.ql new file mode 100644 index 00000000000..b557a0bccb6 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/hdbcli/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/hdbcli/pep249.py b/python/ql/test/library-tests/frameworks/hdbcli/pep249.py new file mode 100644 index 00000000000..35c6e6d87e4 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/hdbcli/pep249.py @@ -0,0 +1,9 @@ +from hdbcli import dbapi + +conn = dbapi.connect(address="hostname", port=300, user="username", password="password") +cursor = conn.cursor() + +cursor.execute("some sql", (42,)) # $ MISSING: getSql="some sql" +cursor.executemany("some sql", (42,)) # $ MISSING: getSql="some sql" + +cursor.close() From e1fc0ca051d43b46aef2fd1adeeb3a5b3c25ed33 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Thu, 1 May 2025 14:01:33 +0200 Subject: [PATCH 65/66] Added implementation `hdbcli` as part of `PEP249::PEP249ModuleApiNode` --- python/ql/lib/semmle/python/Frameworks.qll | 1 + .../lib/semmle/python/frameworks/Hdbcli.qll | 24 +++++++++++++++++++ .../library-tests/frameworks/hdbcli/pep249.py | 4 ++-- 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 python/ql/lib/semmle/python/frameworks/Hdbcli.qll diff --git a/python/ql/lib/semmle/python/Frameworks.qll b/python/ql/lib/semmle/python/Frameworks.qll index e6af222a615..955385141f7 100644 --- a/python/ql/lib/semmle/python/Frameworks.qll +++ b/python/ql/lib/semmle/python/Frameworks.qll @@ -35,6 +35,7 @@ private import semmle.python.frameworks.FlaskAdmin private import semmle.python.frameworks.FlaskSqlAlchemy private import semmle.python.frameworks.Genshi private import semmle.python.frameworks.Gradio +private import semmle.python.frameworks.Hdbcli private import semmle.python.frameworks.Httpx private import semmle.python.frameworks.Idna private import semmle.python.frameworks.Invoke diff --git a/python/ql/lib/semmle/python/frameworks/Hdbcli.qll b/python/ql/lib/semmle/python/frameworks/Hdbcli.qll new file mode 100644 index 00000000000..6b91519ae63 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Hdbcli.qll @@ -0,0 +1,24 @@ +/** + * Provides classes modeling security-relevant aspects of the `hdbcli` PyPI package. + * See https://pypi.org/project/hdbcli/ + */ + +private import python +private import semmle.python.dataflow.new.RemoteFlowSources +private import semmle.python.Concepts +private import semmle.python.ApiGraphs +private import semmle.python.frameworks.PEP249 + +/** + * Provides models for the `hdbcli` PyPI package. + * See https://pypi.org/project/hdbcli/ + */ +private module Hdbcli { + /** + * A model of `hdbcli` as a module that implements PEP 249, providing ways to execute SQL statements + * against a database. + */ + class HdbcliPEP249 extends PEP249::PEP249ModuleApiNode { + HdbcliPEP249() { this = API::moduleImport("hdbcli").getMember("dbapi") } + } +} diff --git a/python/ql/test/library-tests/frameworks/hdbcli/pep249.py b/python/ql/test/library-tests/frameworks/hdbcli/pep249.py index 35c6e6d87e4..713f15cb6d4 100644 --- a/python/ql/test/library-tests/frameworks/hdbcli/pep249.py +++ b/python/ql/test/library-tests/frameworks/hdbcli/pep249.py @@ -3,7 +3,7 @@ from hdbcli import dbapi conn = dbapi.connect(address="hostname", port=300, user="username", password="password") cursor = conn.cursor() -cursor.execute("some sql", (42,)) # $ MISSING: getSql="some sql" -cursor.executemany("some sql", (42,)) # $ MISSING: getSql="some sql" +cursor.execute("some sql", (42,)) # $ getSql="some sql" +cursor.executemany("some sql", (42,)) # $ getSql="some sql" cursor.close() From da7c0931b8aead3a235008d0b5ca2ad88b2a941e Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Thu, 1 May 2025 14:08:54 +0200 Subject: [PATCH 66/66] Added `hdbcli` to be part of `supported-framework` as well as change note --- docs/codeql/reusables/supported-frameworks.rst | 1 + python/ql/lib/change-notes/2025-05-01-hdbcli.md | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 python/ql/lib/change-notes/2025-05-01-hdbcli.md diff --git a/docs/codeql/reusables/supported-frameworks.rst b/docs/codeql/reusables/supported-frameworks.rst index 402a3b9ee3d..07a5e509fec 100644 --- a/docs/codeql/reusables/supported-frameworks.rst +++ b/docs/codeql/reusables/supported-frameworks.rst @@ -254,6 +254,7 @@ and the CodeQL library pack ``codeql/python-all`` (`changelog