Skip to content

Data flow: since 2.27.0, ConfigSig::accessPathLimit() values above the language default have no effect #23041

Open

Description

@yuan2li

A configuration that raises accessPathLimit() above the language default (5) finds longer flows on CodeQL 2.26.2. It no longer does on 2.27.4, and the result is the same as with the default. Lower values still work.

CodeQL CLI release binaries 2.26.2 and 2.27.4 on macOS 26.6 (arm64), with the codeql/java-all and codeql/dataflow packs bundled with each.

Each program NestedN (N = 3 to 7) passes a parameter through N nested java.util.Lists and back into Runtime.exec. Nested6:

public class Nested6 {
  void handle(String input) throws java.io.IOException {
    java.util.List c0 = new java.util.ArrayList(); c0.add(input);
    java.util.List c1 = new java.util.ArrayList(); c1.add(c0);
    java.util.List c2 = new java.util.ArrayList(); c2.add(c1);
    java.util.List c3 = new java.util.ArrayList(); c3.add(c2);
    java.util.List c4 = new java.util.ArrayList(); c4.add(c3);
    java.util.List c5 = new java.util.ArrayList(); c5.add(c4);
    java.util.List cur = c5;
    cur = (java.util.List) cur.get(0);
    cur = (java.util.List) cur.get(0);
    cur = (java.util.List) cur.get(0);
    cur = (java.util.List) cur.get(0);
    cur = (java.util.List) cur.get(0);
    Runtime.getRuntime().exec((String) cur.get(0));
  }
}

The query only varies accessPathLimit:

import java
import semmle.code.java.dataflow.TaintTracking
module C implements DataFlow::ConfigSig {
  predicate isSource(DataFlow::Node n) { n.asParameter().getName() = "input" }
  predicate isSink(DataFlow::Node n) {
    exists(MethodCall mc | mc.getMethod().hasName("exec") and n.asExpr() = mc.getArgument(0))
  }
  int accessPathLimit() { result = 8 }   // varied below
}
module F = TaintTracking::Global<C>;
from DataFlow::Node s, DataFlow::Node t where F::flow(s, t)
select t, s.getEnclosingCallable().getDeclaringType().getName()

Programs found (codeql database create --build-mode=none, then codeql query run):

accessPathLimit() 2.26.2 (java-all 9.2.2, dataflow 2.1.10) 2.27.4 (java-all 9.3.4, dataflow 2.1.17)
default (5) Nested3-5 Nested3-5
3 Nested3 Nested3
4 Nested3-4 Nested3-4
6 Nested3-6 Nested3-5
8 Nested3-7 Nested3-5
10, 20 Nested3-7 Nested3-5

I ran only 2.26.2 and 2.27.4. From the sources, the change below is not in 2.26.3 or 2.26.4, and it is in 2.27.0.

Where it seems to come from. #22392 ("Track access path lengths in stages 3 and 4") gave the cached access path fronts a length, in shared/dataflow/codeql/dataflow/internal/DataFlowImplCommon.qll:

TFrontHead(Content c, int length) { length in [1 .. accessPathLimit()] }
TApproxFrontHead(ContentApprox c, int length) { length in [1 .. accessPathLimit()] }

MakeImplCommon does private import Lang, so this accessPathLimit() is the language default, not the configuration's. DataFlowImpl.qll then uses Config::accessPathLimit(), for example len in [2 .. Config::accessPathLimit()]. A configured limit above the default cannot be represented by these fronts, so the longer paths are pruned.

As far as I can tell, the cap is a side effect rather than a decision. #22392 is about performance and does not mention the per-configuration
limit, and the fronts are cached per language, so they cannot see the configuration. Configurations in this repository only lower the limit (the
reason ConfigSig::accessPathLimit was added in #15867), so the existing tests would not notice the change.

Expected. As before 2.27.0, a configured accessPathLimit() is honoured in both directions. If capping it at the language default is intended, for example to keep the cached fronts small, the ConfigSig::accessPathLimit documentation should say so.

Activity

  1. sam-robson commented on Nov 6, 2026

    @sam-robson
    Contributor

    Thanks for the report @yuan2li . We'll look into this and get back to you.

  2. aschackmull commented on Nov 9, 2026

    @aschackmull
    Contributor

    As far as I can tell, the cap is a side effect rather than a decision

    This is correct. I've opened a PR (#23049) to allow configured limits of at least twice the language default, such that configurations can still opt to increase the limit if so desired. It does come with a bit of an up-front materialisation cost, which is why I didn't increase the cap too much. Hopefully 10 should be enough.

  3. yuan2li commented on Nov 9, 2026

    @yuan2li
    Author

    Thanks a lot. I checked the PR (#23049) locally on CodeQL 2.27.4: I applied the diff to a copy of the bundled codeql/dataflow 2.1.17 pack and ran the same configuration with --additional-packs, once with the patched copy and once with an unpatched copy. The test programs pass a tainted value through N nested lists, N = 3 to 12.

    accessPathLimit() 2.27.4 2.27.4 + #23049
    default N ≤ 5 N ≤ 5
    6 N ≤ 5 N ≤ 6
    8 N ≤ 5 N ≤ 8
    10 N ≤ 5 N ≤ 10
    11, 12, 20 N ≤ 5 N ≤ 10

    So the configured limit takes effect again, up to the new cap. A value above 10 behaves like 10, without a warning. Once #23049 is merged, I'll update #23047 to say that a configuration can raise the limit up to 10 for Java, so that is documented.

  4. aschackmull commented on Nov 10, 2026

    @aschackmull
    Contributor

    The PR is now merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions