From 65f74635caaddbe921c1a939d16005b4fb20571e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:58:21 +0000 Subject: [PATCH 1/6] Initial plan From eb4d30de7f20d2f0279381f8877b64b80261b918 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:22:05 +0000 Subject: [PATCH 2/6] Add XXE query for Rust (CWE-611) Co-authored-by: geoffw0 <40627776+geoffw0@users.noreply.github.com> --- .../codeql/rust/security/XxeExtensions.qll | 104 +++++++++++++ rust/ql/src/change-notes/2026-02-20-xxe.md | 4 + .../ql/src/queries/security/CWE-611/Xxe.qhelp | 50 ++++++ rust/ql/src/queries/security/CWE-611/Xxe.ql | 46 ++++++ .../security/CWE-611/examples/XxeBad.rs | 16 ++ .../security/CWE-611/examples/XxeGood.rs | 16 ++ .../query-tests/security/CWE-611/Cargo.lock | 7 + .../query-tests/security/CWE-611/Xxe.expected | 94 ++++++++++++ .../query-tests/security/CWE-611/Xxe.qlref | 3 + .../test/query-tests/security/CWE-611/main.rs | 145 ++++++++++++++++++ .../query-tests/security/CWE-611/options.yml | 1 + 11 files changed, 486 insertions(+) create mode 100644 rust/ql/lib/codeql/rust/security/XxeExtensions.qll create mode 100644 rust/ql/src/change-notes/2026-02-20-xxe.md create mode 100644 rust/ql/src/queries/security/CWE-611/Xxe.qhelp create mode 100644 rust/ql/src/queries/security/CWE-611/Xxe.ql create mode 100644 rust/ql/src/queries/security/CWE-611/examples/XxeBad.rs create mode 100644 rust/ql/src/queries/security/CWE-611/examples/XxeGood.rs create mode 100644 rust/ql/test/query-tests/security/CWE-611/Cargo.lock create mode 100644 rust/ql/test/query-tests/security/CWE-611/Xxe.expected create mode 100644 rust/ql/test/query-tests/security/CWE-611/Xxe.qlref create mode 100644 rust/ql/test/query-tests/security/CWE-611/main.rs create mode 100644 rust/ql/test/query-tests/security/CWE-611/options.yml diff --git a/rust/ql/lib/codeql/rust/security/XxeExtensions.qll b/rust/ql/lib/codeql/rust/security/XxeExtensions.qll new file mode 100644 index 000000000000..baeced78c3d3 --- /dev/null +++ b/rust/ql/lib/codeql/rust/security/XxeExtensions.qll @@ -0,0 +1,104 @@ +/** + * Provides classes and predicates to reason about XML external entity (XXE) + * vulnerabilities. + */ + +import rust +private import codeql.rust.dataflow.DataFlow +private import codeql.rust.dataflow.FlowSink +private import codeql.rust.Concepts + +/** + * Provides default sources, sinks and barriers for detecting XML external + * entity (XXE) vulnerabilities, as well as extension points for adding your + * own. + */ +module Xxe { + /** + * A data flow source for XXE vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for XXE vulnerabilities. + */ + abstract class Sink extends QuerySink::Range { + override string getSinkType() { result = "Xxe" } + } + + /** + * A barrier for XXE vulnerabilities. + */ + abstract class Barrier extends DataFlow::Node { } + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { } + + /** + * A libxml2 XML parsing call with unsafe parser options, considered as a + * flow sink. + */ + private class Libxml2XxeSink extends Sink { + Libxml2XxeSink() { + exists(Call call, int xmlArg, int optionsArg | + libxml2ParseCall(call, xmlArg, optionsArg) and + this.asExpr() = call.getPositionalArgument(xmlArg) and + hasXxeOption(call.getPositionalArgument(optionsArg)) + ) + } + } +} + +/** + * Holds if `call` is a call to a `libxml2` XML parsing function, where + * `xmlArg` is the index of the XML content argument and `optionsArg` is the + * index of the parser options argument. + */ +private predicate libxml2ParseCall(Call call, int xmlArg, int optionsArg) { + exists(string fname | call.getStaticTarget().getName().getText() = fname | + fname = "xmlCtxtUseOptions" and xmlArg = 0 and optionsArg = 1 + or + fname = "xmlReadFile" and xmlArg = 0 and optionsArg = 2 + or + fname = ["xmlReadDoc", "xmlReadFd"] and xmlArg = 0 and optionsArg = 3 + or + fname = ["xmlCtxtReadFile", "xmlParseInNodeContext"] and xmlArg = 1 and optionsArg = 3 + or + fname = ["xmlCtxtReadDoc", "xmlCtxtReadFd"] and xmlArg = 1 and optionsArg = 4 + or + fname = "xmlReadMemory" and xmlArg = 0 and optionsArg = 4 + or + fname = "xmlCtxtReadMemory" and xmlArg = 1 and optionsArg = 5 + or + fname = "xmlReadIO" and xmlArg = 0 and optionsArg = 5 + or + fname = "xmlCtxtReadIO" and xmlArg = 1 and optionsArg = 6 + ) +} + +/** + * Holds if `e` is an expression that includes an unsafe `xmlParserOption`, + * specifically `XML_PARSE_NOENT` (value 2, enables entity substitution) or + * `XML_PARSE_DTDLOAD` (value 4, loads external DTD subsets). + */ +private predicate hasXxeOption(Expr e) { + // Named constant XML_PARSE_NOENT or XML_PARSE_DTDLOAD + e.(PathExpr).getPath().getText() = ["XML_PARSE_NOENT", "XML_PARSE_DTDLOAD"] + or + // Integer literal with XML_PARSE_NOENT (bit 1) or XML_PARSE_DTDLOAD (bit 2) set + exists(int v | + v = e.(IntegerLiteralExpr).getTextValue().regexpCapture("^([0-9]+).*$", 1).toInt() + | + v.bitAnd(6) != 0 // 6 = 2 | 4 = XML_PARSE_NOENT | XML_PARSE_DTDLOAD + ) + or + // Bitwise OR expression + hasXxeOption(e.(BinaryExpr).getLhs()) + or + hasXxeOption(e.(BinaryExpr).getRhs()) + or + // Cast expression (e.g., `XML_PARSE_NOENT as i32`) + hasXxeOption(e.(CastExpr).getExpr()) +} diff --git a/rust/ql/src/change-notes/2026-02-20-xxe.md b/rust/ql/src/change-notes/2026-02-20-xxe.md new file mode 100644 index 000000000000..2fc35c11bd57 --- /dev/null +++ b/rust/ql/src/change-notes/2026-02-20-xxe.md @@ -0,0 +1,4 @@ +--- +category: newQuery +--- +* Added a new query, `rust/xxe`, to detect XML external entity (XXE) vulnerabilities in Rust code that uses the `libxml` crate (bindings to C's `libxml2`). The query flags calls to `libxml2` parsing functions with unsafe options (`XML_PARSE_NOENT` or `XML_PARSE_DTDLOAD`) when the XML input comes from a user-controlled source. diff --git a/rust/ql/src/queries/security/CWE-611/Xxe.qhelp b/rust/ql/src/queries/security/CWE-611/Xxe.qhelp new file mode 100644 index 000000000000..753e4fa6c5b1 --- /dev/null +++ b/rust/ql/src/queries/security/CWE-611/Xxe.qhelp @@ -0,0 +1,50 @@ + + + + +

+Parsing XML input with external entity (XXE) expansion enabled while the input +is controlled by a user can lead to a variety of attacks. An attacker who +controls the XML input may be able to use an XML external entity declaration +to read the contents of arbitrary files from the server's file system, perform +server-side request forgery (SSRF), or perform denial-of-service attacks. +

+

+The Rust libxml crate (bindings to C's libxml2 +library) exposes several XML parsing functions that accept a parser options +argument. The options XML_PARSE_NOENT and +XML_PARSE_DTDLOAD enable external entity expansion and loading of +external DTD subsets, respectively. Enabling these options when parsing +user-controlled XML is dangerous. +

+
+ + +

+Do not enable XML_PARSE_NOENT or XML_PARSE_DTDLOAD +when parsing user-controlled XML. Parse XML with safe options (for example, +using 0 as the options argument) to disable external entity +expansion. +

+
+ + +

+In the following example, the program reads an XML document supplied by the +user and parses it with external entity expansion enabled: +

+ +

+The following example shows a corrected version that parses with safe options: +

+ +
+ + +
  • OWASP: XML External Entity (XXE) Processing.
  • +
  • CWE: CWE-611: Improper Restriction of XML External Entity Reference.
  • +
    + +
    diff --git a/rust/ql/src/queries/security/CWE-611/Xxe.ql b/rust/ql/src/queries/security/CWE-611/Xxe.ql new file mode 100644 index 000000000000..cd00f1418168 --- /dev/null +++ b/rust/ql/src/queries/security/CWE-611/Xxe.ql @@ -0,0 +1,46 @@ +/** + * @name XML external entity expansion + * @description Parsing user-controlled XML with external entity expansion + * enabled may lead to disclosure of confidential data or + * server-side request forgery. + * @kind path-problem + * @problem.severity error + * @security-severity 9.1 + * @precision high + * @id rust/xxe + * @tags security + * external/cwe/cwe-611 + * external/cwe/cwe-776 + * external/cwe/cwe-827 + */ + +import rust +import codeql.rust.dataflow.DataFlow +import codeql.rust.dataflow.TaintTracking +import codeql.rust.security.XxeExtensions + +/** + * A taint configuration for user-controlled data reaching an XML parser with + * external entity expansion enabled. + */ +module XxeConfig implements DataFlow::ConfigSig { + import Xxe + + predicate isSource(DataFlow::Node node) { node instanceof Source } + + predicate isSink(DataFlow::Node node) { node instanceof Sink } + + predicate isBarrier(DataFlow::Node barrier) { barrier instanceof Barrier } + + predicate observeDiffInformedIncrementalMode() { any() } +} + +module XxeFlow = TaintTracking::Global; + +import XxeFlow::PathGraph + +from XxeFlow::PathNode sourceNode, XxeFlow::PathNode sinkNode +where XxeFlow::flowPath(sourceNode, sinkNode) +select sinkNode.getNode(), sourceNode, sinkNode, + "XML parsing depends on a $@ without guarding against external entity expansion.", + sourceNode.getNode(), "user-provided value" diff --git a/rust/ql/src/queries/security/CWE-611/examples/XxeBad.rs b/rust/ql/src/queries/security/CWE-611/examples/XxeBad.rs new file mode 100644 index 000000000000..d3e8ba137c0c --- /dev/null +++ b/rust/ql/src/queries/security/CWE-611/examples/XxeBad.rs @@ -0,0 +1,16 @@ +use libxml::bindings::{xmlReadMemory, XML_PARSE_NOENT}; +use std::ffi::CString; + +fn parse_user_xml(user_input: &str) { + let c_input = CString::new(user_input).unwrap(); + // BAD: external entity expansion is enabled via XML_PARSE_NOENT + unsafe { + xmlReadMemory( + c_input.as_ptr(), + c_input.as_bytes().len() as i32, + std::ptr::null(), + std::ptr::null(), + XML_PARSE_NOENT as i32, + ); + } +} diff --git a/rust/ql/src/queries/security/CWE-611/examples/XxeGood.rs b/rust/ql/src/queries/security/CWE-611/examples/XxeGood.rs new file mode 100644 index 000000000000..b7c0728020c4 --- /dev/null +++ b/rust/ql/src/queries/security/CWE-611/examples/XxeGood.rs @@ -0,0 +1,16 @@ +use libxml::bindings::xmlReadMemory; +use std::ffi::CString; + +fn parse_user_xml(user_input: &str) { + let c_input = CString::new(user_input).unwrap(); + // GOOD: safe options (0) disable external entity expansion + unsafe { + xmlReadMemory( + c_input.as_ptr(), + c_input.as_bytes().len() as i32, + std::ptr::null(), + std::ptr::null(), + 0, + ); + } +} diff --git a/rust/ql/test/query-tests/security/CWE-611/Cargo.lock b/rust/ql/test/query-tests/security/CWE-611/Cargo.lock new file mode 100644 index 000000000000..b9856cfaf77d --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-611/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "test" +version = "0.0.1" diff --git a/rust/ql/test/query-tests/security/CWE-611/Xxe.expected b/rust/ql/test/query-tests/security/CWE-611/Xxe.expected new file mode 100644 index 000000000000..20285b29ff9c --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-611/Xxe.expected @@ -0,0 +1,94 @@ +#select +| main.rs:68:19:68:26 | user_xml | main.rs:132:20:132:33 | ...::args | main.rs:68:19:68:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:132:20:132:33 | ...::args | user-provided value | +| main.rs:73:19:73:26 | user_xml | main.rs:132:20:132:33 | ...::args | main.rs:73:19:73:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:132:20:132:33 | ...::args | user-provided value | +| main.rs:78:19:78:26 | user_xml | main.rs:132:20:132:33 | ...::args | main.rs:78:19:78:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:132:20:132:33 | ...::args | user-provided value | +| main.rs:83:17:83:29 | user_filename | main.rs:133:25:133:38 | ...::args | main.rs:83:17:83:29 | user_filename | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:133:25:133:38 | ...::args | user-provided value | +| main.rs:88:16:88:23 | user_xml | main.rs:132:20:132:33 | ...::args | main.rs:88:16:88:23 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:132:20:132:33 | ...::args | user-provided value | +| main.rs:93:42:93:49 | user_xml | main.rs:132:20:132:33 | ...::args | main.rs:93:42:93:49 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:132:20:132:33 | ...::args | user-provided value | +| main.rs:100:9:100:16 | user_xml | main.rs:132:20:132:33 | ...::args | main.rs:100:9:100:16 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:132:20:132:33 | ...::args | user-provided value | +| main.rs:110:19:110:26 | user_xml | main.rs:132:20:132:33 | ...::args | main.rs:110:19:110:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:132:20:132:33 | ...::args | user-provided value | +edges +| main.rs:66:25:66:38 | ...: ... [&ref] | main.rs:68:19:68:26 | user_xml | provenance | | +| main.rs:71:27:71:40 | ...: ... [&ref] | main.rs:73:19:73:26 | user_xml | provenance | | +| main.rs:76:28:76:41 | ...: ... [&ref] | main.rs:78:19:78:26 | user_xml | provenance | | +| main.rs:81:27:81:45 | ...: ... [&ref] | main.rs:83:17:83:29 | user_filename | provenance | | +| main.rs:86:26:86:39 | ...: ... [&ref] | main.rs:88:16:88:23 | user_xml | provenance | | +| main.rs:91:31:91:44 | ...: ... [&ref] | main.rs:93:42:93:49 | user_xml | provenance | | +| main.rs:96:34:96:47 | ...: ... [&ref] | main.rs:100:9:100:16 | user_xml | provenance | | +| main.rs:108:29:108:42 | ...: ... [&ref] | main.rs:110:19:110:26 | user_xml | provenance | | +| main.rs:132:9:132:16 | user_xml | main.rs:135:27:135:34 | user_xml | provenance | | +| main.rs:132:9:132:16 | user_xml | main.rs:136:29:136:36 | user_xml | provenance | | +| main.rs:132:9:132:16 | user_xml | main.rs:137:30:137:37 | user_xml | provenance | | +| main.rs:132:9:132:16 | user_xml | main.rs:139:28:139:35 | user_xml | provenance | | +| main.rs:132:9:132:16 | user_xml | main.rs:140:33:140:40 | user_xml | provenance | | +| main.rs:132:9:132:16 | user_xml | main.rs:141:36:141:43 | user_xml | provenance | | +| main.rs:132:9:132:16 | user_xml | main.rs:142:31:142:38 | user_xml | provenance | | +| main.rs:132:20:132:33 | ...::args | main.rs:132:20:132:35 | ...::args(...) [element] | provenance | Src:MaD:488 | +| main.rs:132:20:132:35 | ...::args(...) [element] | main.rs:132:20:132:42 | ... .nth(...) [Some] | provenance | MaD:440 | +| main.rs:132:20:132:42 | ... .nth(...) [Some] | main.rs:132:20:132:62 | ... .unwrap_or_default() | provenance | MaD:9229 | +| main.rs:132:20:132:62 | ... .unwrap_or_default() | main.rs:132:9:132:16 | user_xml | provenance | | +| main.rs:133:9:133:21 | user_filename | main.rs:138:29:138:41 | user_filename | provenance | | +| main.rs:133:25:133:38 | ...::args | main.rs:133:25:133:40 | ...::args(...) [element] | provenance | Src:MaD:488 | +| main.rs:133:25:133:40 | ...::args(...) [element] | main.rs:133:25:133:47 | ... .nth(...) [Some] | provenance | MaD:440 | +| main.rs:133:25:133:47 | ... .nth(...) [Some] | main.rs:133:25:133:67 | ... .unwrap_or_default() | provenance | MaD:9229 | +| main.rs:133:25:133:67 | ... .unwrap_or_default() | main.rs:133:9:133:21 | user_filename | provenance | | +| main.rs:135:26:135:34 | &user_xml [&ref] | main.rs:66:25:66:38 | ...: ... [&ref] | provenance | | +| main.rs:135:27:135:34 | user_xml | main.rs:135:26:135:34 | &user_xml [&ref] | provenance | | +| main.rs:136:28:136:36 | &user_xml [&ref] | main.rs:71:27:71:40 | ...: ... [&ref] | provenance | | +| main.rs:136:29:136:36 | user_xml | main.rs:136:28:136:36 | &user_xml [&ref] | provenance | | +| main.rs:137:29:137:37 | &user_xml [&ref] | main.rs:76:28:76:41 | ...: ... [&ref] | provenance | | +| main.rs:137:30:137:37 | user_xml | main.rs:137:29:137:37 | &user_xml [&ref] | provenance | | +| main.rs:138:28:138:41 | &user_filename [&ref] | main.rs:81:27:81:45 | ...: ... [&ref] | provenance | | +| main.rs:138:29:138:41 | user_filename | main.rs:138:28:138:41 | &user_filename [&ref] | provenance | | +| main.rs:139:27:139:35 | &user_xml [&ref] | main.rs:86:26:86:39 | ...: ... [&ref] | provenance | | +| main.rs:139:28:139:35 | user_xml | main.rs:139:27:139:35 | &user_xml [&ref] | provenance | | +| main.rs:140:32:140:40 | &user_xml [&ref] | main.rs:91:31:91:44 | ...: ... [&ref] | provenance | | +| main.rs:140:33:140:40 | user_xml | main.rs:140:32:140:40 | &user_xml [&ref] | provenance | | +| main.rs:141:35:141:43 | &user_xml [&ref] | main.rs:96:34:96:47 | ...: ... [&ref] | provenance | | +| main.rs:141:36:141:43 | user_xml | main.rs:141:35:141:43 | &user_xml [&ref] | provenance | | +| main.rs:142:30:142:38 | &user_xml [&ref] | main.rs:108:29:108:42 | ...: ... [&ref] | provenance | | +| main.rs:142:31:142:38 | user_xml | main.rs:142:30:142:38 | &user_xml [&ref] | provenance | | +nodes +| main.rs:66:25:66:38 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:68:19:68:26 | user_xml | semmle.label | user_xml | +| main.rs:71:27:71:40 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:73:19:73:26 | user_xml | semmle.label | user_xml | +| main.rs:76:28:76:41 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:78:19:78:26 | user_xml | semmle.label | user_xml | +| main.rs:81:27:81:45 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:83:17:83:29 | user_filename | semmle.label | user_filename | +| main.rs:86:26:86:39 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:88:16:88:23 | user_xml | semmle.label | user_xml | +| main.rs:91:31:91:44 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:93:42:93:49 | user_xml | semmle.label | user_xml | +| main.rs:96:34:96:47 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:100:9:100:16 | user_xml | semmle.label | user_xml | +| main.rs:108:29:108:42 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:110:19:110:26 | user_xml | semmle.label | user_xml | +| main.rs:132:9:132:16 | user_xml | semmle.label | user_xml | +| main.rs:132:20:132:33 | ...::args | semmle.label | ...::args | +| main.rs:132:20:132:35 | ...::args(...) [element] | semmle.label | ...::args(...) [element] | +| main.rs:132:20:132:42 | ... .nth(...) [Some] | semmle.label | ... .nth(...) [Some] | +| main.rs:132:20:132:62 | ... .unwrap_or_default() | semmle.label | ... .unwrap_or_default() | +| main.rs:133:9:133:21 | user_filename | semmle.label | user_filename | +| main.rs:133:25:133:38 | ...::args | semmle.label | ...::args | +| main.rs:133:25:133:40 | ...::args(...) [element] | semmle.label | ...::args(...) [element] | +| main.rs:133:25:133:47 | ... .nth(...) [Some] | semmle.label | ... .nth(...) [Some] | +| main.rs:133:25:133:67 | ... .unwrap_or_default() | semmle.label | ... .unwrap_or_default() | +| main.rs:135:26:135:34 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:135:27:135:34 | user_xml | semmle.label | user_xml | +| main.rs:136:28:136:36 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:136:29:136:36 | user_xml | semmle.label | user_xml | +| main.rs:137:29:137:37 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:137:30:137:37 | user_xml | semmle.label | user_xml | +| main.rs:138:28:138:41 | &user_filename [&ref] | semmle.label | &user_filename [&ref] | +| main.rs:138:29:138:41 | user_filename | semmle.label | user_filename | +| main.rs:139:27:139:35 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:139:28:139:35 | user_xml | semmle.label | user_xml | +| main.rs:140:32:140:40 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:140:33:140:40 | user_xml | semmle.label | user_xml | +| main.rs:141:35:141:43 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:141:36:141:43 | user_xml | semmle.label | user_xml | +| main.rs:142:30:142:38 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:142:31:142:38 | user_xml | semmle.label | user_xml | +subpaths diff --git a/rust/ql/test/query-tests/security/CWE-611/Xxe.qlref b/rust/ql/test/query-tests/security/CWE-611/Xxe.qlref new file mode 100644 index 000000000000..1098d2d2737c --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-611/Xxe.qlref @@ -0,0 +1,3 @@ +query: queries/security/CWE-611/Xxe.ql +postprocess: + - utils/test/InlineExpectationsTestQuery.ql diff --git a/rust/ql/test/query-tests/security/CWE-611/main.rs b/rust/ql/test/query-tests/security/CWE-611/main.rs new file mode 100644 index 000000000000..12bb11773be9 --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-611/main.rs @@ -0,0 +1,145 @@ +// Stub types and constants to simulate libxml2 bindings +pub struct XmlDoc; +pub struct XmlParserCtxt; + +// xmlParserOption constants +const XML_PARSE_NOENT: i32 = 2; // substitute entities +const XML_PARSE_DTDLOAD: i32 = 4; // load the external subset + +// Stub libxml2 parsing functions (simplified signatures using &str for clarity) +fn xmlReadFile(_url: &str, _encoding: &str, _options: i32) -> *mut XmlDoc { + std::ptr::null_mut() +} + +fn xmlReadMemory(buffer: &str, _size: i32, _url: &str, _encoding: &str, _options: i32) -> *mut XmlDoc { + let _ = buffer; + std::ptr::null_mut() +} + +fn xmlReadDoc(cur: &str, _url: &str, _encoding: &str, _options: i32) -> *mut XmlDoc { + let _ = cur; + std::ptr::null_mut() +} + +fn xmlReadFd(_fd: i32, _url: &str, _encoding: &str, _options: i32) -> *mut XmlDoc { + std::ptr::null_mut() +} + +fn xmlCtxtReadFile( + _ctxt: *mut XmlParserCtxt, + _url: &str, + _encoding: &str, + _options: i32, +) -> *mut XmlDoc { + std::ptr::null_mut() +} + +fn xmlCtxtReadDoc( + _ctxt: *mut XmlParserCtxt, + cur: &str, + _url: &str, + _encoding: &str, + _options: i32, +) -> *mut XmlDoc { + let _ = cur; + std::ptr::null_mut() +} + +fn xmlCtxtReadMemory( + _ctxt: *mut XmlParserCtxt, + buffer: &str, + _size: i32, + _url: &str, + _encoding: &str, + _options: i32, +) -> *mut XmlDoc { + let _ = buffer; + std::ptr::null_mut() +} + +fn xmlCtxtUseOptions(_ctxt: *mut XmlParserCtxt, _options: i32) -> i32 { + 0 +} + +// --- BAD: user-controlled XML with unsafe parser options --- + +fn test_xml_parse_noent(user_xml: &str) { + // BAD: XML_PARSE_NOENT enables external entity substitution + xmlReadMemory(user_xml, user_xml.len() as i32, "", "", XML_PARSE_NOENT); // $ Alert[rust/xxe] +} + +fn test_xml_parse_dtdload(user_xml: &str) { + // BAD: XML_PARSE_DTDLOAD enables loading of external DTD subsets + xmlReadMemory(user_xml, user_xml.len() as i32, "", "", XML_PARSE_DTDLOAD); // $ Alert[rust/xxe] +} + +fn test_xml_parse_combined(user_xml: &str) { + // BAD: combining both unsafe options + xmlReadMemory(user_xml, user_xml.len() as i32, "", "", XML_PARSE_NOENT | XML_PARSE_DTDLOAD); // $ Alert[rust/xxe] +} + +fn test_xml_read_file_bad(user_filename: &str) { + // BAD: user-controlled filename with XML_PARSE_NOENT + xmlReadFile(user_filename, "", XML_PARSE_NOENT); // $ Alert[rust/xxe] +} + +fn test_xml_read_doc_bad(user_xml: &str) { + // BAD: user-controlled XML document with XML_PARSE_DTDLOAD + xmlReadDoc(user_xml, "", "", XML_PARSE_DTDLOAD); // $ Alert[rust/xxe] +} + +fn test_xml_ctxt_read_doc_bad(user_xml: &str) { + // BAD: user-controlled XML with unsafe options via ctxt variant + xmlCtxtReadDoc(std::ptr::null_mut(), user_xml, "", "", XML_PARSE_NOENT); // $ Alert[rust/xxe] +} + +fn test_xml_ctxt_read_memory_bad(user_xml: &str) { + // BAD: user-controlled XML with unsafe options via ctxt variant + xmlCtxtReadMemory( + std::ptr::null_mut(), + user_xml, // $ Alert[rust/xxe] + user_xml.len() as i32, + "", + "", + XML_PARSE_NOENT, + ); +} + +fn test_integer_literal_bad(user_xml: &str) { + // BAD: literal value 2 = XML_PARSE_NOENT + xmlReadMemory(user_xml, user_xml.len() as i32, "", "", 2); // $ Alert[rust/xxe] +} + +// --- GOOD: user-controlled XML with safe parser options --- + +fn test_xml_parse_safe_options(user_xml: &str) { + // GOOD: options = 0 means no entity expansion + xmlReadMemory(user_xml, user_xml.len() as i32, "", "", 0); + xmlReadFile(user_xml, "", 0); + xmlReadDoc(user_xml, "", "", 0); +} + +// --- GOOD: hardcoded (non-user-controlled) XML with unsafe parser options --- + +fn test_xml_hardcoded_unsafe() { + let xml = ""; + // GOOD: XML content is not user-controlled + xmlReadMemory(xml, xml.len() as i32, "", "", XML_PARSE_NOENT); + xmlReadFile("trusted/input.xml", "", XML_PARSE_NOENT); +} + +fn main() { + let user_xml = std::env::args().nth(1).unwrap_or_default(); // $ Source + let user_filename = std::env::args().nth(2).unwrap_or_default(); // $ Source + + test_xml_parse_noent(&user_xml); + test_xml_parse_dtdload(&user_xml); + test_xml_parse_combined(&user_xml); + test_xml_read_file_bad(&user_filename); + test_xml_read_doc_bad(&user_xml); + test_xml_ctxt_read_doc_bad(&user_xml); + test_xml_ctxt_read_memory_bad(&user_xml); + test_integer_literal_bad(&user_xml); + test_xml_parse_safe_options(&user_xml); + test_xml_hardcoded_unsafe(); +} diff --git a/rust/ql/test/query-tests/security/CWE-611/options.yml b/rust/ql/test/query-tests/security/CWE-611/options.yml new file mode 100644 index 000000000000..c7a0beabb538 --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-611/options.yml @@ -0,0 +1 @@ +qltest_cargo_check: true From dce8bcdf2ba23f51845baf55d40be3b611551588 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:31:49 +0000 Subject: [PATCH 3/6] Fix: remove xmlCtxtUseOptions from XXE model (not an XML content sink) Co-authored-by: geoffw0 <40627776+geoffw0@users.noreply.github.com> --- .../lib/codeql/rust/security/XxeExtensions.qll | 2 -- .../query-tests/security/CWE-611/Xxe.expected | 16 ++++++++++------ .../test/query-tests/security/CWE-611/Xxe.qlref | 1 + 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/XxeExtensions.qll b/rust/ql/lib/codeql/rust/security/XxeExtensions.qll index baeced78c3d3..53f591113982 100644 --- a/rust/ql/lib/codeql/rust/security/XxeExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/XxeExtensions.qll @@ -58,8 +58,6 @@ module Xxe { */ private predicate libxml2ParseCall(Call call, int xmlArg, int optionsArg) { exists(string fname | call.getStaticTarget().getName().getText() = fname | - fname = "xmlCtxtUseOptions" and xmlArg = 0 and optionsArg = 1 - or fname = "xmlReadFile" and xmlArg = 0 and optionsArg = 2 or fname = ["xmlReadDoc", "xmlReadFd"] and xmlArg = 0 and optionsArg = 3 diff --git a/rust/ql/test/query-tests/security/CWE-611/Xxe.expected b/rust/ql/test/query-tests/security/CWE-611/Xxe.expected index 20285b29ff9c..4fa39224d6c3 100644 --- a/rust/ql/test/query-tests/security/CWE-611/Xxe.expected +++ b/rust/ql/test/query-tests/security/CWE-611/Xxe.expected @@ -23,14 +23,14 @@ edges | main.rs:132:9:132:16 | user_xml | main.rs:140:33:140:40 | user_xml | provenance | | | main.rs:132:9:132:16 | user_xml | main.rs:141:36:141:43 | user_xml | provenance | | | main.rs:132:9:132:16 | user_xml | main.rs:142:31:142:38 | user_xml | provenance | | -| main.rs:132:20:132:33 | ...::args | main.rs:132:20:132:35 | ...::args(...) [element] | provenance | Src:MaD:488 | -| main.rs:132:20:132:35 | ...::args(...) [element] | main.rs:132:20:132:42 | ... .nth(...) [Some] | provenance | MaD:440 | -| main.rs:132:20:132:42 | ... .nth(...) [Some] | main.rs:132:20:132:62 | ... .unwrap_or_default() | provenance | MaD:9229 | +| main.rs:132:20:132:33 | ...::args | main.rs:132:20:132:35 | ...::args(...) [element] | provenance | Src:MaD:1 | +| main.rs:132:20:132:35 | ...::args(...) [element] | main.rs:132:20:132:42 | ... .nth(...) [Some] | provenance | MaD:2 | +| main.rs:132:20:132:42 | ... .nth(...) [Some] | main.rs:132:20:132:62 | ... .unwrap_or_default() | provenance | MaD:3 | | main.rs:132:20:132:62 | ... .unwrap_or_default() | main.rs:132:9:132:16 | user_xml | provenance | | | main.rs:133:9:133:21 | user_filename | main.rs:138:29:138:41 | user_filename | provenance | | -| main.rs:133:25:133:38 | ...::args | main.rs:133:25:133:40 | ...::args(...) [element] | provenance | Src:MaD:488 | -| main.rs:133:25:133:40 | ...::args(...) [element] | main.rs:133:25:133:47 | ... .nth(...) [Some] | provenance | MaD:440 | -| main.rs:133:25:133:47 | ... .nth(...) [Some] | main.rs:133:25:133:67 | ... .unwrap_or_default() | provenance | MaD:9229 | +| main.rs:133:25:133:38 | ...::args | main.rs:133:25:133:40 | ...::args(...) [element] | provenance | Src:MaD:1 | +| main.rs:133:25:133:40 | ...::args(...) [element] | main.rs:133:25:133:47 | ... .nth(...) [Some] | provenance | MaD:2 | +| main.rs:133:25:133:47 | ... .nth(...) [Some] | main.rs:133:25:133:67 | ... .unwrap_or_default() | provenance | MaD:3 | | main.rs:133:25:133:67 | ... .unwrap_or_default() | main.rs:133:9:133:21 | user_filename | provenance | | | main.rs:135:26:135:34 | &user_xml [&ref] | main.rs:66:25:66:38 | ...: ... [&ref] | provenance | | | main.rs:135:27:135:34 | user_xml | main.rs:135:26:135:34 | &user_xml [&ref] | provenance | | @@ -48,6 +48,10 @@ edges | main.rs:141:36:141:43 | user_xml | main.rs:141:35:141:43 | &user_xml [&ref] | provenance | | | main.rs:142:30:142:38 | &user_xml [&ref] | main.rs:108:29:108:42 | ...: ... [&ref] | provenance | | | main.rs:142:31:142:38 | user_xml | main.rs:142:30:142:38 | &user_xml [&ref] | provenance | | +models +| 1 | Source: std::env::args; ReturnValue.Element; commandargs | +| 2 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Reference.Element; ReturnValue.Field[core::option::Option::Some(0)]; value | +| 3 | Summary: ::unwrap_or_default; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | nodes | main.rs:66:25:66:38 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | | main.rs:68:19:68:26 | user_xml | semmle.label | user_xml | diff --git a/rust/ql/test/query-tests/security/CWE-611/Xxe.qlref b/rust/ql/test/query-tests/security/CWE-611/Xxe.qlref index 1098d2d2737c..450de0af97ee 100644 --- a/rust/ql/test/query-tests/security/CWE-611/Xxe.qlref +++ b/rust/ql/test/query-tests/security/CWE-611/Xxe.qlref @@ -1,3 +1,4 @@ query: queries/security/CWE-611/Xxe.ql postprocess: + - utils/test/PrettyPrintModels.ql - utils/test/InlineExpectationsTestQuery.ql From bd195e59a5d87f9c820636a2b50fe910e61c263c Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 27 Mar 2026 18:37:52 +0000 Subject: [PATCH 4/6] Rust: Add MaD barriers, since we have that feature now. --- rust/ql/lib/codeql/rust/security/XxeExtensions.qll | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rust/ql/lib/codeql/rust/security/XxeExtensions.qll b/rust/ql/lib/codeql/rust/security/XxeExtensions.qll index 53f591113982..328911da7343 100644 --- a/rust/ql/lib/codeql/rust/security/XxeExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/XxeExtensions.qll @@ -5,6 +5,7 @@ import rust private import codeql.rust.dataflow.DataFlow +private import codeql.rust.dataflow.FlowBarrier private import codeql.rust.dataflow.FlowSink private import codeql.rust.Concepts @@ -49,6 +50,13 @@ module Xxe { ) } } + + /** + * A barrier for XXE vulnerabilities from model data. + */ + private class ModelsAsDataBarrier extends Barrier { + ModelsAsDataBarrier() { barrierNode(this, "xxe") } + } } /** From 0cb077cd66fff5d7aa1c38c019b255e47a11574c Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Tue, 24 Feb 2026 16:43:58 +0000 Subject: [PATCH 5/6] Rust: Add test cases for xmlReadFd, xmlCtxtReadFile that were stubbed but not used. --- .../query-tests/security/CWE-611/Xxe.expected | 221 ++++++++++-------- .../test/query-tests/security/CWE-611/main.rs | 16 ++ 2 files changed, 144 insertions(+), 93 deletions(-) diff --git a/rust/ql/test/query-tests/security/CWE-611/Xxe.expected b/rust/ql/test/query-tests/security/CWE-611/Xxe.expected index 4fa39224d6c3..d86a2726b9c6 100644 --- a/rust/ql/test/query-tests/security/CWE-611/Xxe.expected +++ b/rust/ql/test/query-tests/security/CWE-611/Xxe.expected @@ -1,98 +1,133 @@ #select -| main.rs:68:19:68:26 | user_xml | main.rs:132:20:132:33 | ...::args | main.rs:68:19:68:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:132:20:132:33 | ...::args | user-provided value | -| main.rs:73:19:73:26 | user_xml | main.rs:132:20:132:33 | ...::args | main.rs:73:19:73:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:132:20:132:33 | ...::args | user-provided value | -| main.rs:78:19:78:26 | user_xml | main.rs:132:20:132:33 | ...::args | main.rs:78:19:78:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:132:20:132:33 | ...::args | user-provided value | -| main.rs:83:17:83:29 | user_filename | main.rs:133:25:133:38 | ...::args | main.rs:83:17:83:29 | user_filename | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:133:25:133:38 | ...::args | user-provided value | -| main.rs:88:16:88:23 | user_xml | main.rs:132:20:132:33 | ...::args | main.rs:88:16:88:23 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:132:20:132:33 | ...::args | user-provided value | -| main.rs:93:42:93:49 | user_xml | main.rs:132:20:132:33 | ...::args | main.rs:93:42:93:49 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:132:20:132:33 | ...::args | user-provided value | -| main.rs:100:9:100:16 | user_xml | main.rs:132:20:132:33 | ...::args | main.rs:100:9:100:16 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:132:20:132:33 | ...::args | user-provided value | -| main.rs:110:19:110:26 | user_xml | main.rs:132:20:132:33 | ...::args | main.rs:110:19:110:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:132:20:132:33 | ...::args | user-provided value | +| main.rs:70:19:70:26 | user_xml | main.rs:144:20:144:33 | ...::args | main.rs:70:19:70:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:144:20:144:33 | ...::args | user-provided value | +| main.rs:75:19:75:26 | user_xml | main.rs:144:20:144:33 | ...::args | main.rs:75:19:75:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:144:20:144:33 | ...::args | user-provided value | +| main.rs:80:19:80:26 | user_xml | main.rs:144:20:144:33 | ...::args | main.rs:80:19:80:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:144:20:144:33 | ...::args | user-provided value | +| main.rs:85:17:85:29 | user_filename | main.rs:145:25:145:38 | ...::args | main.rs:85:17:85:29 | user_filename | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:145:25:145:38 | ...::args | user-provided value | +| main.rs:90:16:90:23 | user_xml | main.rs:144:20:144:33 | ...::args | main.rs:90:16:90:23 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:144:20:144:33 | ...::args | user-provided value | +| main.rs:95:15:95:21 | user_fd | main.rs:146:21:146:39 | ...::open | main.rs:95:15:95:21 | user_fd | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:146:21:146:39 | ...::open | user-provided value | +| main.rs:100:43:100:55 | user_filename | main.rs:145:25:145:38 | ...::args | main.rs:100:43:100:55 | user_filename | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:145:25:145:38 | ...::args | user-provided value | +| main.rs:105:42:105:49 | user_xml | main.rs:144:20:144:33 | ...::args | main.rs:105:42:105:49 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:144:20:144:33 | ...::args | user-provided value | +| main.rs:112:9:112:16 | user_xml | main.rs:144:20:144:33 | ...::args | main.rs:112:9:112:16 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:144:20:144:33 | ...::args | user-provided value | +| main.rs:122:19:122:26 | user_xml | main.rs:144:20:144:33 | ...::args | main.rs:122:19:122:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:144:20:144:33 | ...::args | user-provided value | edges -| main.rs:66:25:66:38 | ...: ... [&ref] | main.rs:68:19:68:26 | user_xml | provenance | | -| main.rs:71:27:71:40 | ...: ... [&ref] | main.rs:73:19:73:26 | user_xml | provenance | | -| main.rs:76:28:76:41 | ...: ... [&ref] | main.rs:78:19:78:26 | user_xml | provenance | | -| main.rs:81:27:81:45 | ...: ... [&ref] | main.rs:83:17:83:29 | user_filename | provenance | | -| main.rs:86:26:86:39 | ...: ... [&ref] | main.rs:88:16:88:23 | user_xml | provenance | | -| main.rs:91:31:91:44 | ...: ... [&ref] | main.rs:93:42:93:49 | user_xml | provenance | | -| main.rs:96:34:96:47 | ...: ... [&ref] | main.rs:100:9:100:16 | user_xml | provenance | | -| main.rs:108:29:108:42 | ...: ... [&ref] | main.rs:110:19:110:26 | user_xml | provenance | | -| main.rs:132:9:132:16 | user_xml | main.rs:135:27:135:34 | user_xml | provenance | | -| main.rs:132:9:132:16 | user_xml | main.rs:136:29:136:36 | user_xml | provenance | | -| main.rs:132:9:132:16 | user_xml | main.rs:137:30:137:37 | user_xml | provenance | | -| main.rs:132:9:132:16 | user_xml | main.rs:139:28:139:35 | user_xml | provenance | | -| main.rs:132:9:132:16 | user_xml | main.rs:140:33:140:40 | user_xml | provenance | | -| main.rs:132:9:132:16 | user_xml | main.rs:141:36:141:43 | user_xml | provenance | | -| main.rs:132:9:132:16 | user_xml | main.rs:142:31:142:38 | user_xml | provenance | | -| main.rs:132:20:132:33 | ...::args | main.rs:132:20:132:35 | ...::args(...) [element] | provenance | Src:MaD:1 | -| main.rs:132:20:132:35 | ...::args(...) [element] | main.rs:132:20:132:42 | ... .nth(...) [Some] | provenance | MaD:2 | -| main.rs:132:20:132:42 | ... .nth(...) [Some] | main.rs:132:20:132:62 | ... .unwrap_or_default() | provenance | MaD:3 | -| main.rs:132:20:132:62 | ... .unwrap_or_default() | main.rs:132:9:132:16 | user_xml | provenance | | -| main.rs:133:9:133:21 | user_filename | main.rs:138:29:138:41 | user_filename | provenance | | -| main.rs:133:25:133:38 | ...::args | main.rs:133:25:133:40 | ...::args(...) [element] | provenance | Src:MaD:1 | -| main.rs:133:25:133:40 | ...::args(...) [element] | main.rs:133:25:133:47 | ... .nth(...) [Some] | provenance | MaD:2 | -| main.rs:133:25:133:47 | ... .nth(...) [Some] | main.rs:133:25:133:67 | ... .unwrap_or_default() | provenance | MaD:3 | -| main.rs:133:25:133:67 | ... .unwrap_or_default() | main.rs:133:9:133:21 | user_filename | provenance | | -| main.rs:135:26:135:34 | &user_xml [&ref] | main.rs:66:25:66:38 | ...: ... [&ref] | provenance | | -| main.rs:135:27:135:34 | user_xml | main.rs:135:26:135:34 | &user_xml [&ref] | provenance | | -| main.rs:136:28:136:36 | &user_xml [&ref] | main.rs:71:27:71:40 | ...: ... [&ref] | provenance | | -| main.rs:136:29:136:36 | user_xml | main.rs:136:28:136:36 | &user_xml [&ref] | provenance | | -| main.rs:137:29:137:37 | &user_xml [&ref] | main.rs:76:28:76:41 | ...: ... [&ref] | provenance | | -| main.rs:137:30:137:37 | user_xml | main.rs:137:29:137:37 | &user_xml [&ref] | provenance | | -| main.rs:138:28:138:41 | &user_filename [&ref] | main.rs:81:27:81:45 | ...: ... [&ref] | provenance | | -| main.rs:138:29:138:41 | user_filename | main.rs:138:28:138:41 | &user_filename [&ref] | provenance | | -| main.rs:139:27:139:35 | &user_xml [&ref] | main.rs:86:26:86:39 | ...: ... [&ref] | provenance | | -| main.rs:139:28:139:35 | user_xml | main.rs:139:27:139:35 | &user_xml [&ref] | provenance | | -| main.rs:140:32:140:40 | &user_xml [&ref] | main.rs:91:31:91:44 | ...: ... [&ref] | provenance | | -| main.rs:140:33:140:40 | user_xml | main.rs:140:32:140:40 | &user_xml [&ref] | provenance | | -| main.rs:141:35:141:43 | &user_xml [&ref] | main.rs:96:34:96:47 | ...: ... [&ref] | provenance | | -| main.rs:141:36:141:43 | user_xml | main.rs:141:35:141:43 | &user_xml [&ref] | provenance | | -| main.rs:142:30:142:38 | &user_xml [&ref] | main.rs:108:29:108:42 | ...: ... [&ref] | provenance | | -| main.rs:142:31:142:38 | user_xml | main.rs:142:30:142:38 | &user_xml [&ref] | provenance | | +| main.rs:68:25:68:38 | ...: ... [&ref] | main.rs:70:19:70:26 | user_xml | provenance | | +| main.rs:73:27:73:40 | ...: ... [&ref] | main.rs:75:19:75:26 | user_xml | provenance | | +| main.rs:78:28:78:41 | ...: ... [&ref] | main.rs:80:19:80:26 | user_xml | provenance | | +| main.rs:83:27:83:45 | ...: ... [&ref] | main.rs:85:17:85:29 | user_filename | provenance | | +| main.rs:88:26:88:39 | ...: ... [&ref] | main.rs:90:16:90:23 | user_xml | provenance | | +| main.rs:93:25:93:36 | ...: i32 [&ref] | main.rs:95:15:95:21 | user_fd | provenance | | +| main.rs:98:32:98:50 | ...: ... [&ref] | main.rs:100:43:100:55 | user_filename | provenance | | +| main.rs:103:31:103:44 | ...: ... [&ref] | main.rs:105:42:105:49 | user_xml | provenance | | +| main.rs:108:34:108:47 | ...: ... [&ref] | main.rs:112:9:112:16 | user_xml | provenance | | +| main.rs:120:29:120:42 | ...: ... [&ref] | main.rs:122:19:122:26 | user_xml | provenance | | +| main.rs:144:9:144:16 | user_xml | main.rs:149:27:149:34 | user_xml | provenance | | +| main.rs:144:9:144:16 | user_xml | main.rs:150:29:150:36 | user_xml | provenance | | +| main.rs:144:9:144:16 | user_xml | main.rs:151:30:151:37 | user_xml | provenance | | +| main.rs:144:9:144:16 | user_xml | main.rs:153:28:153:35 | user_xml | provenance | | +| main.rs:144:9:144:16 | user_xml | main.rs:156:33:156:40 | user_xml | provenance | | +| main.rs:144:9:144:16 | user_xml | main.rs:157:36:157:43 | user_xml | provenance | | +| main.rs:144:9:144:16 | user_xml | main.rs:158:31:158:38 | user_xml | provenance | | +| main.rs:144:20:144:33 | ...::args | main.rs:144:20:144:35 | ...::args(...) [element] | provenance | Src:MaD:2 | +| main.rs:144:20:144:35 | ...::args(...) [element] | main.rs:144:20:144:42 | ... .nth(...) [Some] | provenance | MaD:3 | +| main.rs:144:20:144:42 | ... .nth(...) [Some] | main.rs:144:20:144:62 | ... .unwrap_or_default() | provenance | MaD:6 | +| main.rs:144:20:144:62 | ... .unwrap_or_default() | main.rs:144:9:144:16 | user_xml | provenance | | +| main.rs:145:9:145:21 | user_filename | main.rs:152:29:152:41 | user_filename | provenance | | +| main.rs:145:9:145:21 | user_filename | main.rs:155:34:155:46 | user_filename | provenance | | +| main.rs:145:25:145:38 | ...::args | main.rs:145:25:145:40 | ...::args(...) [element] | provenance | Src:MaD:2 | +| main.rs:145:25:145:40 | ...::args(...) [element] | main.rs:145:25:145:47 | ... .nth(...) [Some] | provenance | MaD:3 | +| main.rs:145:25:145:47 | ... .nth(...) [Some] | main.rs:145:25:145:67 | ... .unwrap_or_default() | provenance | MaD:6 | +| main.rs:145:25:145:67 | ... .unwrap_or_default() | main.rs:145:9:145:21 | user_filename | provenance | | +| main.rs:146:9:146:17 | user_file [Some] | main.rs:147:19:147:27 | user_file [Some] | provenance | | +| main.rs:146:21:146:39 | ...::open | main.rs:146:21:146:55 | ...::open(...) [Ok] | provenance | Src:MaD:1 | +| main.rs:146:21:146:55 | ...::open(...) [Ok] | main.rs:146:21:146:60 | ... .ok() [Some] | provenance | MaD:7 | +| main.rs:146:21:146:60 | ... .ok() [Some] | main.rs:146:9:146:17 | user_file [Some] | provenance | | +| main.rs:147:9:147:15 | user_fd [&ref] | main.rs:154:26:154:32 | user_fd [&ref] | provenance | | +| main.rs:147:19:147:27 | user_file [Some] | main.rs:147:19:147:36 | user_file.as_ref() [Some, &ref] | provenance | MaD:4 | +| main.rs:147:19:147:36 | user_file.as_ref() [Some, &ref] | main.rs:147:19:147:72 | ... .map_or(...) [&ref] | provenance | MaD:5 | +| main.rs:147:19:147:72 | ... .map_or(...) [&ref] | main.rs:147:9:147:15 | user_fd [&ref] | provenance | | +| main.rs:149:26:149:34 | &user_xml [&ref] | main.rs:68:25:68:38 | ...: ... [&ref] | provenance | | +| main.rs:149:27:149:34 | user_xml | main.rs:149:26:149:34 | &user_xml [&ref] | provenance | | +| main.rs:150:28:150:36 | &user_xml [&ref] | main.rs:73:27:73:40 | ...: ... [&ref] | provenance | | +| main.rs:150:29:150:36 | user_xml | main.rs:150:28:150:36 | &user_xml [&ref] | provenance | | +| main.rs:151:29:151:37 | &user_xml [&ref] | main.rs:78:28:78:41 | ...: ... [&ref] | provenance | | +| main.rs:151:30:151:37 | user_xml | main.rs:151:29:151:37 | &user_xml [&ref] | provenance | | +| main.rs:152:28:152:41 | &user_filename [&ref] | main.rs:83:27:83:45 | ...: ... [&ref] | provenance | | +| main.rs:152:29:152:41 | user_filename | main.rs:152:28:152:41 | &user_filename [&ref] | provenance | | +| main.rs:153:27:153:35 | &user_xml [&ref] | main.rs:88:26:88:39 | ...: ... [&ref] | provenance | | +| main.rs:153:28:153:35 | user_xml | main.rs:153:27:153:35 | &user_xml [&ref] | provenance | | +| main.rs:154:26:154:32 | user_fd [&ref] | main.rs:93:25:93:36 | ...: i32 [&ref] | provenance | | +| main.rs:155:33:155:46 | &user_filename [&ref] | main.rs:98:32:98:50 | ...: ... [&ref] | provenance | | +| main.rs:155:34:155:46 | user_filename | main.rs:155:33:155:46 | &user_filename [&ref] | provenance | | +| main.rs:156:32:156:40 | &user_xml [&ref] | main.rs:103:31:103:44 | ...: ... [&ref] | provenance | | +| main.rs:156:33:156:40 | user_xml | main.rs:156:32:156:40 | &user_xml [&ref] | provenance | | +| main.rs:157:35:157:43 | &user_xml [&ref] | main.rs:108:34:108:47 | ...: ... [&ref] | provenance | | +| main.rs:157:36:157:43 | user_xml | main.rs:157:35:157:43 | &user_xml [&ref] | provenance | | +| main.rs:158:30:158:38 | &user_xml [&ref] | main.rs:120:29:120:42 | ...: ... [&ref] | provenance | | +| main.rs:158:31:158:38 | user_xml | main.rs:158:30:158:38 | &user_xml [&ref] | provenance | | models -| 1 | Source: std::env::args; ReturnValue.Element; commandargs | -| 2 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Reference.Element; ReturnValue.Field[core::option::Option::Some(0)]; value | -| 3 | Summary: ::unwrap_or_default; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | +| 1 | Source: ::open; ReturnValue.Field[core::result::Result::Ok(0)]; file | +| 2 | Source: std::env::args; ReturnValue.Element; commandargs | +| 3 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Reference.Element; ReturnValue.Field[core::option::Option::Some(0)]; value | +| 4 | Summary: ::as_ref; Argument[self].Reference.Field[core::option::Option::Some(0)]; ReturnValue.Field[core::option::Option::Some(0)].Reference; value | +| 5 | Summary: ::map_or; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | +| 6 | Summary: ::unwrap_or_default; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | +| 7 | Summary: ::ok; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue.Field[core::option::Option::Some(0)]; value | nodes -| main.rs:66:25:66:38 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | -| main.rs:68:19:68:26 | user_xml | semmle.label | user_xml | -| main.rs:71:27:71:40 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | -| main.rs:73:19:73:26 | user_xml | semmle.label | user_xml | -| main.rs:76:28:76:41 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | -| main.rs:78:19:78:26 | user_xml | semmle.label | user_xml | -| main.rs:81:27:81:45 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | -| main.rs:83:17:83:29 | user_filename | semmle.label | user_filename | -| main.rs:86:26:86:39 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | -| main.rs:88:16:88:23 | user_xml | semmle.label | user_xml | -| main.rs:91:31:91:44 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | -| main.rs:93:42:93:49 | user_xml | semmle.label | user_xml | -| main.rs:96:34:96:47 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | -| main.rs:100:9:100:16 | user_xml | semmle.label | user_xml | -| main.rs:108:29:108:42 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | -| main.rs:110:19:110:26 | user_xml | semmle.label | user_xml | -| main.rs:132:9:132:16 | user_xml | semmle.label | user_xml | -| main.rs:132:20:132:33 | ...::args | semmle.label | ...::args | -| main.rs:132:20:132:35 | ...::args(...) [element] | semmle.label | ...::args(...) [element] | -| main.rs:132:20:132:42 | ... .nth(...) [Some] | semmle.label | ... .nth(...) [Some] | -| main.rs:132:20:132:62 | ... .unwrap_or_default() | semmle.label | ... .unwrap_or_default() | -| main.rs:133:9:133:21 | user_filename | semmle.label | user_filename | -| main.rs:133:25:133:38 | ...::args | semmle.label | ...::args | -| main.rs:133:25:133:40 | ...::args(...) [element] | semmle.label | ...::args(...) [element] | -| main.rs:133:25:133:47 | ... .nth(...) [Some] | semmle.label | ... .nth(...) [Some] | -| main.rs:133:25:133:67 | ... .unwrap_or_default() | semmle.label | ... .unwrap_or_default() | -| main.rs:135:26:135:34 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | -| main.rs:135:27:135:34 | user_xml | semmle.label | user_xml | -| main.rs:136:28:136:36 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | -| main.rs:136:29:136:36 | user_xml | semmle.label | user_xml | -| main.rs:137:29:137:37 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | -| main.rs:137:30:137:37 | user_xml | semmle.label | user_xml | -| main.rs:138:28:138:41 | &user_filename [&ref] | semmle.label | &user_filename [&ref] | -| main.rs:138:29:138:41 | user_filename | semmle.label | user_filename | -| main.rs:139:27:139:35 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | -| main.rs:139:28:139:35 | user_xml | semmle.label | user_xml | -| main.rs:140:32:140:40 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | -| main.rs:140:33:140:40 | user_xml | semmle.label | user_xml | -| main.rs:141:35:141:43 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | -| main.rs:141:36:141:43 | user_xml | semmle.label | user_xml | -| main.rs:142:30:142:38 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | -| main.rs:142:31:142:38 | user_xml | semmle.label | user_xml | +| main.rs:68:25:68:38 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:70:19:70:26 | user_xml | semmle.label | user_xml | +| main.rs:73:27:73:40 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:75:19:75:26 | user_xml | semmle.label | user_xml | +| main.rs:78:28:78:41 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:80:19:80:26 | user_xml | semmle.label | user_xml | +| main.rs:83:27:83:45 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:85:17:85:29 | user_filename | semmle.label | user_filename | +| main.rs:88:26:88:39 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:90:16:90:23 | user_xml | semmle.label | user_xml | +| main.rs:93:25:93:36 | ...: i32 [&ref] | semmle.label | ...: i32 [&ref] | +| main.rs:95:15:95:21 | user_fd | semmle.label | user_fd | +| main.rs:98:32:98:50 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:100:43:100:55 | user_filename | semmle.label | user_filename | +| main.rs:103:31:103:44 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:105:42:105:49 | user_xml | semmle.label | user_xml | +| main.rs:108:34:108:47 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:112:9:112:16 | user_xml | semmle.label | user_xml | +| main.rs:120:29:120:42 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| main.rs:122:19:122:26 | user_xml | semmle.label | user_xml | +| main.rs:144:9:144:16 | user_xml | semmle.label | user_xml | +| main.rs:144:20:144:33 | ...::args | semmle.label | ...::args | +| main.rs:144:20:144:35 | ...::args(...) [element] | semmle.label | ...::args(...) [element] | +| main.rs:144:20:144:42 | ... .nth(...) [Some] | semmle.label | ... .nth(...) [Some] | +| main.rs:144:20:144:62 | ... .unwrap_or_default() | semmle.label | ... .unwrap_or_default() | +| main.rs:145:9:145:21 | user_filename | semmle.label | user_filename | +| main.rs:145:25:145:38 | ...::args | semmle.label | ...::args | +| main.rs:145:25:145:40 | ...::args(...) [element] | semmle.label | ...::args(...) [element] | +| main.rs:145:25:145:47 | ... .nth(...) [Some] | semmle.label | ... .nth(...) [Some] | +| main.rs:145:25:145:67 | ... .unwrap_or_default() | semmle.label | ... .unwrap_or_default() | +| main.rs:146:9:146:17 | user_file [Some] | semmle.label | user_file [Some] | +| main.rs:146:21:146:39 | ...::open | semmle.label | ...::open | +| main.rs:146:21:146:55 | ...::open(...) [Ok] | semmle.label | ...::open(...) [Ok] | +| main.rs:146:21:146:60 | ... .ok() [Some] | semmle.label | ... .ok() [Some] | +| main.rs:147:9:147:15 | user_fd [&ref] | semmle.label | user_fd [&ref] | +| main.rs:147:19:147:27 | user_file [Some] | semmle.label | user_file [Some] | +| main.rs:147:19:147:36 | user_file.as_ref() [Some, &ref] | semmle.label | user_file.as_ref() [Some, &ref] | +| main.rs:147:19:147:72 | ... .map_or(...) [&ref] | semmle.label | ... .map_or(...) [&ref] | +| main.rs:149:26:149:34 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:149:27:149:34 | user_xml | semmle.label | user_xml | +| main.rs:150:28:150:36 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:150:29:150:36 | user_xml | semmle.label | user_xml | +| main.rs:151:29:151:37 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:151:30:151:37 | user_xml | semmle.label | user_xml | +| main.rs:152:28:152:41 | &user_filename [&ref] | semmle.label | &user_filename [&ref] | +| main.rs:152:29:152:41 | user_filename | semmle.label | user_filename | +| main.rs:153:27:153:35 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:153:28:153:35 | user_xml | semmle.label | user_xml | +| main.rs:154:26:154:32 | user_fd [&ref] | semmle.label | user_fd [&ref] | +| main.rs:155:33:155:46 | &user_filename [&ref] | semmle.label | &user_filename [&ref] | +| main.rs:155:34:155:46 | user_filename | semmle.label | user_filename | +| main.rs:156:32:156:40 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:156:33:156:40 | user_xml | semmle.label | user_xml | +| main.rs:157:35:157:43 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:157:36:157:43 | user_xml | semmle.label | user_xml | +| main.rs:158:30:158:38 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:158:31:158:38 | user_xml | semmle.label | user_xml | subpaths diff --git a/rust/ql/test/query-tests/security/CWE-611/main.rs b/rust/ql/test/query-tests/security/CWE-611/main.rs index 12bb11773be9..17d80b741966 100644 --- a/rust/ql/test/query-tests/security/CWE-611/main.rs +++ b/rust/ql/test/query-tests/security/CWE-611/main.rs @@ -1,3 +1,5 @@ +use std::os::fd::AsRawFd; + // Stub types and constants to simulate libxml2 bindings pub struct XmlDoc; pub struct XmlParserCtxt; @@ -88,6 +90,16 @@ fn test_xml_read_doc_bad(user_xml: &str) { xmlReadDoc(user_xml, "", "", XML_PARSE_DTDLOAD); // $ Alert[rust/xxe] } +fn test_xml_read_fd_bad(user_fd: i32) { + // BAD: user-controlled file descriptor with XML_PARSE_DTDLOAD + xmlReadFd(user_fd, "", "", XML_PARSE_DTDLOAD); // $ Alert[rust/xxe] +} + +fn test_xml_ctxt_read_file_bad(user_filename: &str) { + // BAD: user-controlled filename with XML_PARSE_NOENT via ctxt variant + xmlCtxtReadFile(std::ptr::null_mut(), user_filename, "", XML_PARSE_NOENT); // $ Alert[rust/xxe] +} + fn test_xml_ctxt_read_doc_bad(user_xml: &str) { // BAD: user-controlled XML with unsafe options via ctxt variant xmlCtxtReadDoc(std::ptr::null_mut(), user_xml, "", "", XML_PARSE_NOENT); // $ Alert[rust/xxe] @@ -131,12 +143,16 @@ fn test_xml_hardcoded_unsafe() { fn main() { let user_xml = std::env::args().nth(1).unwrap_or_default(); // $ Source let user_filename = std::env::args().nth(2).unwrap_or_default(); // $ Source + let user_file = std::fs::File::open(&user_filename).ok(); // $ Source + let user_fd = user_file.as_ref().map_or(-1, |file| file.as_raw_fd()); test_xml_parse_noent(&user_xml); test_xml_parse_dtdload(&user_xml); test_xml_parse_combined(&user_xml); test_xml_read_file_bad(&user_filename); test_xml_read_doc_bad(&user_xml); + test_xml_read_fd_bad(user_fd); + test_xml_ctxt_read_file_bad(&user_filename); test_xml_ctxt_read_doc_bad(&user_xml); test_xml_ctxt_read_memory_bad(&user_xml); test_integer_literal_bad(&user_xml); From 975843889ad70a7e42cf5c87203d69894816933c Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Tue, 24 Feb 2026 16:49:12 +0000 Subject: [PATCH 6/6] Rust: Add a test case showing the lack of data flow on flag values. --- .../query-tests/security/CWE-611/Xxe.expected | 182 +++++++++--------- .../test/query-tests/security/CWE-611/main.rs | 7 + 2 files changed, 98 insertions(+), 91 deletions(-) diff --git a/rust/ql/test/query-tests/security/CWE-611/Xxe.expected b/rust/ql/test/query-tests/security/CWE-611/Xxe.expected index d86a2726b9c6..ea26db0a077f 100644 --- a/rust/ql/test/query-tests/security/CWE-611/Xxe.expected +++ b/rust/ql/test/query-tests/security/CWE-611/Xxe.expected @@ -1,14 +1,14 @@ #select -| main.rs:70:19:70:26 | user_xml | main.rs:144:20:144:33 | ...::args | main.rs:70:19:70:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:144:20:144:33 | ...::args | user-provided value | -| main.rs:75:19:75:26 | user_xml | main.rs:144:20:144:33 | ...::args | main.rs:75:19:75:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:144:20:144:33 | ...::args | user-provided value | -| main.rs:80:19:80:26 | user_xml | main.rs:144:20:144:33 | ...::args | main.rs:80:19:80:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:144:20:144:33 | ...::args | user-provided value | -| main.rs:85:17:85:29 | user_filename | main.rs:145:25:145:38 | ...::args | main.rs:85:17:85:29 | user_filename | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:145:25:145:38 | ...::args | user-provided value | -| main.rs:90:16:90:23 | user_xml | main.rs:144:20:144:33 | ...::args | main.rs:90:16:90:23 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:144:20:144:33 | ...::args | user-provided value | -| main.rs:95:15:95:21 | user_fd | main.rs:146:21:146:39 | ...::open | main.rs:95:15:95:21 | user_fd | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:146:21:146:39 | ...::open | user-provided value | -| main.rs:100:43:100:55 | user_filename | main.rs:145:25:145:38 | ...::args | main.rs:100:43:100:55 | user_filename | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:145:25:145:38 | ...::args | user-provided value | -| main.rs:105:42:105:49 | user_xml | main.rs:144:20:144:33 | ...::args | main.rs:105:42:105:49 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:144:20:144:33 | ...::args | user-provided value | -| main.rs:112:9:112:16 | user_xml | main.rs:144:20:144:33 | ...::args | main.rs:112:9:112:16 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:144:20:144:33 | ...::args | user-provided value | -| main.rs:122:19:122:26 | user_xml | main.rs:144:20:144:33 | ...::args | main.rs:122:19:122:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:144:20:144:33 | ...::args | user-provided value | +| main.rs:70:19:70:26 | user_xml | main.rs:150:20:150:33 | ...::args | main.rs:70:19:70:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:150:20:150:33 | ...::args | user-provided value | +| main.rs:75:19:75:26 | user_xml | main.rs:150:20:150:33 | ...::args | main.rs:75:19:75:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:150:20:150:33 | ...::args | user-provided value | +| main.rs:80:19:80:26 | user_xml | main.rs:150:20:150:33 | ...::args | main.rs:80:19:80:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:150:20:150:33 | ...::args | user-provided value | +| main.rs:85:17:85:29 | user_filename | main.rs:151:25:151:38 | ...::args | main.rs:85:17:85:29 | user_filename | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:151:25:151:38 | ...::args | user-provided value | +| main.rs:90:16:90:23 | user_xml | main.rs:150:20:150:33 | ...::args | main.rs:90:16:90:23 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:150:20:150:33 | ...::args | user-provided value | +| main.rs:95:15:95:21 | user_fd | main.rs:152:21:152:39 | ...::open | main.rs:95:15:95:21 | user_fd | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:152:21:152:39 | ...::open | user-provided value | +| main.rs:100:43:100:55 | user_filename | main.rs:151:25:151:38 | ...::args | main.rs:100:43:100:55 | user_filename | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:151:25:151:38 | ...::args | user-provided value | +| main.rs:105:42:105:49 | user_xml | main.rs:150:20:150:33 | ...::args | main.rs:105:42:105:49 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:150:20:150:33 | ...::args | user-provided value | +| main.rs:112:9:112:16 | user_xml | main.rs:150:20:150:33 | ...::args | main.rs:112:9:112:16 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:150:20:150:33 | ...::args | user-provided value | +| main.rs:122:19:122:26 | user_xml | main.rs:150:20:150:33 | ...::args | main.rs:122:19:122:26 | user_xml | XML parsing depends on a $@ without guarding against external entity expansion. | main.rs:150:20:150:33 | ...::args | user-provided value | edges | main.rs:68:25:68:38 | ...: ... [&ref] | main.rs:70:19:70:26 | user_xml | provenance | | | main.rs:73:27:73:40 | ...: ... [&ref] | main.rs:75:19:75:26 | user_xml | provenance | | @@ -20,50 +20,50 @@ edges | main.rs:103:31:103:44 | ...: ... [&ref] | main.rs:105:42:105:49 | user_xml | provenance | | | main.rs:108:34:108:47 | ...: ... [&ref] | main.rs:112:9:112:16 | user_xml | provenance | | | main.rs:120:29:120:42 | ...: ... [&ref] | main.rs:122:19:122:26 | user_xml | provenance | | -| main.rs:144:9:144:16 | user_xml | main.rs:149:27:149:34 | user_xml | provenance | | -| main.rs:144:9:144:16 | user_xml | main.rs:150:29:150:36 | user_xml | provenance | | -| main.rs:144:9:144:16 | user_xml | main.rs:151:30:151:37 | user_xml | provenance | | -| main.rs:144:9:144:16 | user_xml | main.rs:153:28:153:35 | user_xml | provenance | | -| main.rs:144:9:144:16 | user_xml | main.rs:156:33:156:40 | user_xml | provenance | | -| main.rs:144:9:144:16 | user_xml | main.rs:157:36:157:43 | user_xml | provenance | | -| main.rs:144:9:144:16 | user_xml | main.rs:158:31:158:38 | user_xml | provenance | | -| main.rs:144:20:144:33 | ...::args | main.rs:144:20:144:35 | ...::args(...) [element] | provenance | Src:MaD:2 | -| main.rs:144:20:144:35 | ...::args(...) [element] | main.rs:144:20:144:42 | ... .nth(...) [Some] | provenance | MaD:3 | -| main.rs:144:20:144:42 | ... .nth(...) [Some] | main.rs:144:20:144:62 | ... .unwrap_or_default() | provenance | MaD:6 | -| main.rs:144:20:144:62 | ... .unwrap_or_default() | main.rs:144:9:144:16 | user_xml | provenance | | -| main.rs:145:9:145:21 | user_filename | main.rs:152:29:152:41 | user_filename | provenance | | -| main.rs:145:9:145:21 | user_filename | main.rs:155:34:155:46 | user_filename | provenance | | -| main.rs:145:25:145:38 | ...::args | main.rs:145:25:145:40 | ...::args(...) [element] | provenance | Src:MaD:2 | -| main.rs:145:25:145:40 | ...::args(...) [element] | main.rs:145:25:145:47 | ... .nth(...) [Some] | provenance | MaD:3 | -| main.rs:145:25:145:47 | ... .nth(...) [Some] | main.rs:145:25:145:67 | ... .unwrap_or_default() | provenance | MaD:6 | -| main.rs:145:25:145:67 | ... .unwrap_or_default() | main.rs:145:9:145:21 | user_filename | provenance | | -| main.rs:146:9:146:17 | user_file [Some] | main.rs:147:19:147:27 | user_file [Some] | provenance | | -| main.rs:146:21:146:39 | ...::open | main.rs:146:21:146:55 | ...::open(...) [Ok] | provenance | Src:MaD:1 | -| main.rs:146:21:146:55 | ...::open(...) [Ok] | main.rs:146:21:146:60 | ... .ok() [Some] | provenance | MaD:7 | -| main.rs:146:21:146:60 | ... .ok() [Some] | main.rs:146:9:146:17 | user_file [Some] | provenance | | -| main.rs:147:9:147:15 | user_fd [&ref] | main.rs:154:26:154:32 | user_fd [&ref] | provenance | | -| main.rs:147:19:147:27 | user_file [Some] | main.rs:147:19:147:36 | user_file.as_ref() [Some, &ref] | provenance | MaD:4 | -| main.rs:147:19:147:36 | user_file.as_ref() [Some, &ref] | main.rs:147:19:147:72 | ... .map_or(...) [&ref] | provenance | MaD:5 | -| main.rs:147:19:147:72 | ... .map_or(...) [&ref] | main.rs:147:9:147:15 | user_fd [&ref] | provenance | | -| main.rs:149:26:149:34 | &user_xml [&ref] | main.rs:68:25:68:38 | ...: ... [&ref] | provenance | | -| main.rs:149:27:149:34 | user_xml | main.rs:149:26:149:34 | &user_xml [&ref] | provenance | | -| main.rs:150:28:150:36 | &user_xml [&ref] | main.rs:73:27:73:40 | ...: ... [&ref] | provenance | | -| main.rs:150:29:150:36 | user_xml | main.rs:150:28:150:36 | &user_xml [&ref] | provenance | | -| main.rs:151:29:151:37 | &user_xml [&ref] | main.rs:78:28:78:41 | ...: ... [&ref] | provenance | | -| main.rs:151:30:151:37 | user_xml | main.rs:151:29:151:37 | &user_xml [&ref] | provenance | | -| main.rs:152:28:152:41 | &user_filename [&ref] | main.rs:83:27:83:45 | ...: ... [&ref] | provenance | | -| main.rs:152:29:152:41 | user_filename | main.rs:152:28:152:41 | &user_filename [&ref] | provenance | | -| main.rs:153:27:153:35 | &user_xml [&ref] | main.rs:88:26:88:39 | ...: ... [&ref] | provenance | | -| main.rs:153:28:153:35 | user_xml | main.rs:153:27:153:35 | &user_xml [&ref] | provenance | | -| main.rs:154:26:154:32 | user_fd [&ref] | main.rs:93:25:93:36 | ...: i32 [&ref] | provenance | | -| main.rs:155:33:155:46 | &user_filename [&ref] | main.rs:98:32:98:50 | ...: ... [&ref] | provenance | | -| main.rs:155:34:155:46 | user_filename | main.rs:155:33:155:46 | &user_filename [&ref] | provenance | | -| main.rs:156:32:156:40 | &user_xml [&ref] | main.rs:103:31:103:44 | ...: ... [&ref] | provenance | | -| main.rs:156:33:156:40 | user_xml | main.rs:156:32:156:40 | &user_xml [&ref] | provenance | | -| main.rs:157:35:157:43 | &user_xml [&ref] | main.rs:108:34:108:47 | ...: ... [&ref] | provenance | | -| main.rs:157:36:157:43 | user_xml | main.rs:157:35:157:43 | &user_xml [&ref] | provenance | | -| main.rs:158:30:158:38 | &user_xml [&ref] | main.rs:120:29:120:42 | ...: ... [&ref] | provenance | | -| main.rs:158:31:158:38 | user_xml | main.rs:158:30:158:38 | &user_xml [&ref] | provenance | | +| main.rs:150:9:150:16 | user_xml | main.rs:155:27:155:34 | user_xml | provenance | | +| main.rs:150:9:150:16 | user_xml | main.rs:156:29:156:36 | user_xml | provenance | | +| main.rs:150:9:150:16 | user_xml | main.rs:157:30:157:37 | user_xml | provenance | | +| main.rs:150:9:150:16 | user_xml | main.rs:159:28:159:35 | user_xml | provenance | | +| main.rs:150:9:150:16 | user_xml | main.rs:162:33:162:40 | user_xml | provenance | | +| main.rs:150:9:150:16 | user_xml | main.rs:163:36:163:43 | user_xml | provenance | | +| main.rs:150:9:150:16 | user_xml | main.rs:164:31:164:38 | user_xml | provenance | | +| main.rs:150:20:150:33 | ...::args | main.rs:150:20:150:35 | ...::args(...) [element] | provenance | Src:MaD:2 | +| main.rs:150:20:150:35 | ...::args(...) [element] | main.rs:150:20:150:42 | ... .nth(...) [Some] | provenance | MaD:3 | +| main.rs:150:20:150:42 | ... .nth(...) [Some] | main.rs:150:20:150:62 | ... .unwrap_or_default() | provenance | MaD:6 | +| main.rs:150:20:150:62 | ... .unwrap_or_default() | main.rs:150:9:150:16 | user_xml | provenance | | +| main.rs:151:9:151:21 | user_filename | main.rs:158:29:158:41 | user_filename | provenance | | +| main.rs:151:9:151:21 | user_filename | main.rs:161:34:161:46 | user_filename | provenance | | +| main.rs:151:25:151:38 | ...::args | main.rs:151:25:151:40 | ...::args(...) [element] | provenance | Src:MaD:2 | +| main.rs:151:25:151:40 | ...::args(...) [element] | main.rs:151:25:151:47 | ... .nth(...) [Some] | provenance | MaD:3 | +| main.rs:151:25:151:47 | ... .nth(...) [Some] | main.rs:151:25:151:67 | ... .unwrap_or_default() | provenance | MaD:6 | +| main.rs:151:25:151:67 | ... .unwrap_or_default() | main.rs:151:9:151:21 | user_filename | provenance | | +| main.rs:152:9:152:17 | user_file [Some] | main.rs:153:19:153:27 | user_file [Some] | provenance | | +| main.rs:152:21:152:39 | ...::open | main.rs:152:21:152:55 | ...::open(...) [Ok] | provenance | Src:MaD:1 | +| main.rs:152:21:152:55 | ...::open(...) [Ok] | main.rs:152:21:152:60 | ... .ok() [Some] | provenance | MaD:7 | +| main.rs:152:21:152:60 | ... .ok() [Some] | main.rs:152:9:152:17 | user_file [Some] | provenance | | +| main.rs:153:9:153:15 | user_fd [&ref] | main.rs:160:26:160:32 | user_fd [&ref] | provenance | | +| main.rs:153:19:153:27 | user_file [Some] | main.rs:153:19:153:36 | user_file.as_ref() [Some, &ref] | provenance | MaD:4 | +| main.rs:153:19:153:36 | user_file.as_ref() [Some, &ref] | main.rs:153:19:153:72 | ... .map_or(...) [&ref] | provenance | MaD:5 | +| main.rs:153:19:153:72 | ... .map_or(...) [&ref] | main.rs:153:9:153:15 | user_fd [&ref] | provenance | | +| main.rs:155:26:155:34 | &user_xml [&ref] | main.rs:68:25:68:38 | ...: ... [&ref] | provenance | | +| main.rs:155:27:155:34 | user_xml | main.rs:155:26:155:34 | &user_xml [&ref] | provenance | | +| main.rs:156:28:156:36 | &user_xml [&ref] | main.rs:73:27:73:40 | ...: ... [&ref] | provenance | | +| main.rs:156:29:156:36 | user_xml | main.rs:156:28:156:36 | &user_xml [&ref] | provenance | | +| main.rs:157:29:157:37 | &user_xml [&ref] | main.rs:78:28:78:41 | ...: ... [&ref] | provenance | | +| main.rs:157:30:157:37 | user_xml | main.rs:157:29:157:37 | &user_xml [&ref] | provenance | | +| main.rs:158:28:158:41 | &user_filename [&ref] | main.rs:83:27:83:45 | ...: ... [&ref] | provenance | | +| main.rs:158:29:158:41 | user_filename | main.rs:158:28:158:41 | &user_filename [&ref] | provenance | | +| main.rs:159:27:159:35 | &user_xml [&ref] | main.rs:88:26:88:39 | ...: ... [&ref] | provenance | | +| main.rs:159:28:159:35 | user_xml | main.rs:159:27:159:35 | &user_xml [&ref] | provenance | | +| main.rs:160:26:160:32 | user_fd [&ref] | main.rs:93:25:93:36 | ...: i32 [&ref] | provenance | | +| main.rs:161:33:161:46 | &user_filename [&ref] | main.rs:98:32:98:50 | ...: ... [&ref] | provenance | | +| main.rs:161:34:161:46 | user_filename | main.rs:161:33:161:46 | &user_filename [&ref] | provenance | | +| main.rs:162:32:162:40 | &user_xml [&ref] | main.rs:103:31:103:44 | ...: ... [&ref] | provenance | | +| main.rs:162:33:162:40 | user_xml | main.rs:162:32:162:40 | &user_xml [&ref] | provenance | | +| main.rs:163:35:163:43 | &user_xml [&ref] | main.rs:108:34:108:47 | ...: ... [&ref] | provenance | | +| main.rs:163:36:163:43 | user_xml | main.rs:163:35:163:43 | &user_xml [&ref] | provenance | | +| main.rs:164:30:164:38 | &user_xml [&ref] | main.rs:120:29:120:42 | ...: ... [&ref] | provenance | | +| main.rs:164:31:164:38 | user_xml | main.rs:164:30:164:38 | &user_xml [&ref] | provenance | | models | 1 | Source: ::open; ReturnValue.Field[core::result::Result::Ok(0)]; file | | 2 | Source: std::env::args; ReturnValue.Element; commandargs | @@ -93,41 +93,41 @@ nodes | main.rs:112:9:112:16 | user_xml | semmle.label | user_xml | | main.rs:120:29:120:42 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | | main.rs:122:19:122:26 | user_xml | semmle.label | user_xml | -| main.rs:144:9:144:16 | user_xml | semmle.label | user_xml | -| main.rs:144:20:144:33 | ...::args | semmle.label | ...::args | -| main.rs:144:20:144:35 | ...::args(...) [element] | semmle.label | ...::args(...) [element] | -| main.rs:144:20:144:42 | ... .nth(...) [Some] | semmle.label | ... .nth(...) [Some] | -| main.rs:144:20:144:62 | ... .unwrap_or_default() | semmle.label | ... .unwrap_or_default() | -| main.rs:145:9:145:21 | user_filename | semmle.label | user_filename | -| main.rs:145:25:145:38 | ...::args | semmle.label | ...::args | -| main.rs:145:25:145:40 | ...::args(...) [element] | semmle.label | ...::args(...) [element] | -| main.rs:145:25:145:47 | ... .nth(...) [Some] | semmle.label | ... .nth(...) [Some] | -| main.rs:145:25:145:67 | ... .unwrap_or_default() | semmle.label | ... .unwrap_or_default() | -| main.rs:146:9:146:17 | user_file [Some] | semmle.label | user_file [Some] | -| main.rs:146:21:146:39 | ...::open | semmle.label | ...::open | -| main.rs:146:21:146:55 | ...::open(...) [Ok] | semmle.label | ...::open(...) [Ok] | -| main.rs:146:21:146:60 | ... .ok() [Some] | semmle.label | ... .ok() [Some] | -| main.rs:147:9:147:15 | user_fd [&ref] | semmle.label | user_fd [&ref] | -| main.rs:147:19:147:27 | user_file [Some] | semmle.label | user_file [Some] | -| main.rs:147:19:147:36 | user_file.as_ref() [Some, &ref] | semmle.label | user_file.as_ref() [Some, &ref] | -| main.rs:147:19:147:72 | ... .map_or(...) [&ref] | semmle.label | ... .map_or(...) [&ref] | -| main.rs:149:26:149:34 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | -| main.rs:149:27:149:34 | user_xml | semmle.label | user_xml | -| main.rs:150:28:150:36 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | -| main.rs:150:29:150:36 | user_xml | semmle.label | user_xml | -| main.rs:151:29:151:37 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | -| main.rs:151:30:151:37 | user_xml | semmle.label | user_xml | -| main.rs:152:28:152:41 | &user_filename [&ref] | semmle.label | &user_filename [&ref] | -| main.rs:152:29:152:41 | user_filename | semmle.label | user_filename | -| main.rs:153:27:153:35 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | -| main.rs:153:28:153:35 | user_xml | semmle.label | user_xml | -| main.rs:154:26:154:32 | user_fd [&ref] | semmle.label | user_fd [&ref] | -| main.rs:155:33:155:46 | &user_filename [&ref] | semmle.label | &user_filename [&ref] | -| main.rs:155:34:155:46 | user_filename | semmle.label | user_filename | -| main.rs:156:32:156:40 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | -| main.rs:156:33:156:40 | user_xml | semmle.label | user_xml | -| main.rs:157:35:157:43 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | -| main.rs:157:36:157:43 | user_xml | semmle.label | user_xml | -| main.rs:158:30:158:38 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | -| main.rs:158:31:158:38 | user_xml | semmle.label | user_xml | +| main.rs:150:9:150:16 | user_xml | semmle.label | user_xml | +| main.rs:150:20:150:33 | ...::args | semmle.label | ...::args | +| main.rs:150:20:150:35 | ...::args(...) [element] | semmle.label | ...::args(...) [element] | +| main.rs:150:20:150:42 | ... .nth(...) [Some] | semmle.label | ... .nth(...) [Some] | +| main.rs:150:20:150:62 | ... .unwrap_or_default() | semmle.label | ... .unwrap_or_default() | +| main.rs:151:9:151:21 | user_filename | semmle.label | user_filename | +| main.rs:151:25:151:38 | ...::args | semmle.label | ...::args | +| main.rs:151:25:151:40 | ...::args(...) [element] | semmle.label | ...::args(...) [element] | +| main.rs:151:25:151:47 | ... .nth(...) [Some] | semmle.label | ... .nth(...) [Some] | +| main.rs:151:25:151:67 | ... .unwrap_or_default() | semmle.label | ... .unwrap_or_default() | +| main.rs:152:9:152:17 | user_file [Some] | semmle.label | user_file [Some] | +| main.rs:152:21:152:39 | ...::open | semmle.label | ...::open | +| main.rs:152:21:152:55 | ...::open(...) [Ok] | semmle.label | ...::open(...) [Ok] | +| main.rs:152:21:152:60 | ... .ok() [Some] | semmle.label | ... .ok() [Some] | +| main.rs:153:9:153:15 | user_fd [&ref] | semmle.label | user_fd [&ref] | +| main.rs:153:19:153:27 | user_file [Some] | semmle.label | user_file [Some] | +| main.rs:153:19:153:36 | user_file.as_ref() [Some, &ref] | semmle.label | user_file.as_ref() [Some, &ref] | +| main.rs:153:19:153:72 | ... .map_or(...) [&ref] | semmle.label | ... .map_or(...) [&ref] | +| main.rs:155:26:155:34 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:155:27:155:34 | user_xml | semmle.label | user_xml | +| main.rs:156:28:156:36 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:156:29:156:36 | user_xml | semmle.label | user_xml | +| main.rs:157:29:157:37 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:157:30:157:37 | user_xml | semmle.label | user_xml | +| main.rs:158:28:158:41 | &user_filename [&ref] | semmle.label | &user_filename [&ref] | +| main.rs:158:29:158:41 | user_filename | semmle.label | user_filename | +| main.rs:159:27:159:35 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:159:28:159:35 | user_xml | semmle.label | user_xml | +| main.rs:160:26:160:32 | user_fd [&ref] | semmle.label | user_fd [&ref] | +| main.rs:161:33:161:46 | &user_filename [&ref] | semmle.label | &user_filename [&ref] | +| main.rs:161:34:161:46 | user_filename | semmle.label | user_filename | +| main.rs:162:32:162:40 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:162:33:162:40 | user_xml | semmle.label | user_xml | +| main.rs:163:35:163:43 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:163:36:163:43 | user_xml | semmle.label | user_xml | +| main.rs:164:30:164:38 | &user_xml [&ref] | semmle.label | &user_xml [&ref] | +| main.rs:164:31:164:38 | user_xml | semmle.label | user_xml | subpaths diff --git a/rust/ql/test/query-tests/security/CWE-611/main.rs b/rust/ql/test/query-tests/security/CWE-611/main.rs index 17d80b741966..7244a47110fc 100644 --- a/rust/ql/test/query-tests/security/CWE-611/main.rs +++ b/rust/ql/test/query-tests/security/CWE-611/main.rs @@ -122,6 +122,12 @@ fn test_integer_literal_bad(user_xml: &str) { xmlReadMemory(user_xml, user_xml.len() as i32, "", "", 2); // $ Alert[rust/xxe] } +fn test_dataflow_bad(user_xml: &str) { + // BAD: user-controlled XML with unsafe parser options via dataflow + let flags = XML_PARSE_NOENT | 1024; + xmlReadMemory(user_xml, user_xml.len() as i32, "", "", flags); // $ MISSING: Alert[rust/xxe] +} + // --- GOOD: user-controlled XML with safe parser options --- fn test_xml_parse_safe_options(user_xml: &str) { @@ -156,6 +162,7 @@ fn main() { test_xml_ctxt_read_doc_bad(&user_xml); test_xml_ctxt_read_memory_bad(&user_xml); test_integer_literal_bad(&user_xml); + test_dataflow_bad(&user_xml); test_xml_parse_safe_options(&user_xml); test_xml_hardcoded_unsafe(); }