Skip to content

Java: Fix File.getName() path sanitizer#22175

Open
owen-mc wants to merge 2 commits into
github:mainfrom
owen-mc:java/fix-path-sanitizer
Open

Java: Fix File.getName() path sanitizer#22175
owen-mc wants to merge 2 commits into
github:mainfrom
owen-mc:java/fix-path-sanitizer

Conversation

@owen-mc

@owen-mc owen-mc commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Reported in #22144. I have now downgraded it to a partial sanitizer. The return value has to be checked to confirm that it doesn't have any ".." components to become fully sanitized.

@owen-mc owen-mc requested a review from a team as a code owner July 11, 2026 16:58
Copilot AI review requested due to automatic review settings July 11, 2026 16:58
@owen-mc owen-mc changed the title Java: Fix path sanitizer Java: Fix File.getName() path sanitizer Jul 11, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an unsound Java path-injection model by removing java.io.File.getName() as a full java/path-injection sanitizer, and adjusting the sanitizer logic/tests to treat it as safe only when paired with an explicit .. check—preventing false negatives like those reported in issue #22144.

Changes:

  • Remove the barrierModel entry that previously treated File.getName()’s return value as a complete path-injection sanitizer.
  • Update the path-sanitizer guard logic to recognize sanitization when a ..-check occurs after data derived from File.getName().
  • Update the CWE-022 test to add a BAD case (no .. check) and keep a GOOD case (with a .. check), plus add a change note documenting new alerts.
Show a summary per file
File Description
java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.java Adjusts regression test to reflect that getName() alone is not a complete path-injection sanitizer; adds BAD + guarded GOOD variants.
java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected Updates expected query results to include the newly-detected path-injection flow for the BAD case.
java/ql/lib/semmle/code/java/security/PathSanitizer.qll Updates sanitizer-guard recognition so getName() can contribute to sanitization only when followed by a .. check.
java/ql/lib/ext/java.io.model.yml Removes the barrierModel row marking File.getName() as a path-injection barrier.
java/ql/lib/change-notes/2026-07-11-file-getname-path-sanitizer.md Documents the modeling change and that it may produce new alerts.

Review details

  • Files reviewed: 5/5 changed files
  • Comments generated: 0
  • Review effort level: Low

Comment on lines +109 to +121
private predicate dotDotCheckGuard(Guard g, Expr e, boolean branch) {
pathTraversalGuard(g, e, branch) and
exists(Guard previousGuard |
previousGuard.(AllowedPrefixGuard).controls(g.getBasicBlock(), true)
(
exists(Guard previousGuard |
previousGuard.(AllowedPrefixGuard).controls(g.getBasicBlock(), true)
or
previousGuard.(BlockListGuard).controls(g.getBasicBlock(), false)
)
or
previousGuard.(BlockListGuard).controls(g.getBasicBlock(), false)
// `File.getName` strips any directory prefix, returning only the final path
// component. The only remaining path traversal risk is when that component is
// itself `..`, so a check for `..` components completes the sanitization.
exists(FileGetNameCall getName | localTaintFlowToPathGuard(getName, g))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a bit odd: If g guards the expression e for branch and g also guards (any) call to getName (for any branch), then g guards e for branch?
Should the logic be more something like: g guards e, if the result of a call to getName is guarded by g and there is flow from the result of getName to e?

Something like (it should be its own top level disjunkt):

exists(FileGetNameCall getName |
    pathTraversalGuard(g, getName, branch) and
    TaintTracking::localExprTaint(getName, e)
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. My mistake for not checking code written by copilot well enough.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I'm not impressed that CCR didn't spot that.)

@owen-mc owen-mc requested a review from michaelnebel July 13, 2026 18:41

@michaelnebel michaelnebel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

We should probably run DCA before merging (will start a run now).

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants