diff --git a/core/opentaint-java-querylang/samples/src/main/java/issues/issue98.java b/core/opentaint-java-querylang/samples/src/main/java/issues/issue98.java new file mode 100644 index 000000000..a1e82657a --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/java/issues/issue98.java @@ -0,0 +1,60 @@ +package issues; + +import base.RuleSample; +import base.RuleSet; + +/** + * Repro: taint is lost when an element of a tainted array is itself an array + * that is later indexed (array-of-arrays / Object[] holding a String[]). + * + * The source marks the returned {@code Object[]} with element-level taint + * ({@code args[*]}). Reading a scalar element ({@code args[0]}) keeps the taint + * (see {@link PositiveScalarElementControl}), but reading an element that is + * itself an array and then indexing that inner array + * ({@code ((String[]) args[1])[0]}) drops it: the engine transfers the source's + * element path to a reference fact on the destination ({@code types.$}) instead + * of a nested element fact ({@code types[*]}), so the inner element read is not + * considered tainted. + * + * This is the shape of Apache Dubbo's GenericFilter provider path: + * String[] parameterTypes = (String[]) invocation.getArguments()[1]; + * ... ReflectUtils.name2class(parameterTypes[i]) ... + */ +@RuleSet("issues/issue98.yaml") +public abstract class issue98 implements RuleSample { + + Object[] src() { + return new Object[] {"", new String[] {""}}; + } + + void sink(String data) {} + + /** + * Control: scalar element of a tainted array reaches the sink. This already + * works today and anchors that the source/sink/element-read all function; + * the only difference from the failing case is that the element here is a + * scalar, not a nested array. + */ + static class PositiveScalarElementControl extends issue98 { + @Override + public void entrypoint() { + Object[] args = src(); + String name = (String) args[0]; + sink(name); + } + } + + /** + * False negative: the element {@code args[1]} is itself a {@code String[]}; + * indexing it ({@code types[0]}) loses the taint. Expected: a finding at the + * {@code sink(types[0])} call. Observed: none. + */ + static class PositiveNestedArrayElement extends issue98 { + @Override + public void entrypoint() { + Object[] args = src(); + String[] types = (String[]) args[1]; + sink(types[0]); + } + } +} diff --git a/core/opentaint-java-querylang/samples/src/main/resources/issues/issue98.yaml b/core/opentaint-java-querylang/samples/src/main/resources/issues/issue98.yaml new file mode 100644 index 000000000..2eb26a4d4 --- /dev/null +++ b/core/opentaint-java-querylang/samples/src/main/resources/issues/issue98.yaml @@ -0,0 +1,15 @@ +rules: + - id: i98 + languages: + - java + severity: ERROR + message: tainted array element reaches sink + mode: taint + pattern-sources: + - patterns: + - focus-metavariable: $X + - pattern: $X = src(); + pattern-sinks: + - patterns: + - pattern: sink($Y); + - focus-metavariable: $Y diff --git a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt index 185fe851c..7e0aa4e5e 100644 --- a/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt +++ b/core/opentaint-java-querylang/src/test/kotlin/org/opentaint/semgrep/IssuesTest.kt @@ -16,6 +16,7 @@ import issues.issue94 import issues.issue95 import issues.issue96 import issues.issue97 +import issues.issue98 import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.TestInstance @@ -105,6 +106,9 @@ class IssuesTest : SampleBasedTest() { @Test fun `issue 97`() = runTest() + @Test // todo: nested array element taint — element of a tainted array that is itself an array loses taint when indexed + fun `issue 98`() = runTest() + @AfterAll fun close() { closeRunner()