diff --git a/java/ql/lib/change-notes/2026-04-04-path-injection-torealpath.md b/java/ql/lib/change-notes/2026-04-04-path-injection-torealpath.md new file mode 100644 index 000000000000..8856d419bce0 --- /dev/null +++ b/java/ql/lib/change-notes/2026-04-04-path-injection-torealpath.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The `java/path-injection` and `java/zipslip` queries now recognize `Path.toRealPath()` as a path normalization sanitizer, consistent with the existing treatment of `Path.normalize()` and `File.getCanonicalPath()`. This reduces false positives for code that uses the NIO.2 API for path canonicalization. diff --git a/java/ql/lib/semmle/code/java/security/PathSanitizer.qll b/java/ql/lib/semmle/code/java/security/PathSanitizer.qll index 788cd5429397..e2957f6b02f6 100644 --- a/java/ql/lib/semmle/code/java/security/PathSanitizer.qll +++ b/java/ql/lib/semmle/code/java/security/PathSanitizer.qll @@ -243,7 +243,7 @@ private class PathNormalizeSanitizer extends MethodCall { PathNormalizeSanitizer() { exists(RefType t | this.getMethod().getDeclaringType() = t | (t instanceof TypePath or t instanceof FilesKt) and - this.getMethod().hasName("normalize") + this.getMethod().hasName(["normalize", "toRealPath"]) or t instanceof TypeFile and this.getMethod().hasName(["getCanonicalPath", "getCanonicalFile"]) diff --git a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.java b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.java index 442873b54a44..fb87c6878235 100644 --- a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.java +++ b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.java @@ -72,6 +72,27 @@ public void sendUserFileGood3(Socket sock, String user) throws Exception { } } + public void sendUserFileGood5(Socket sock, String user) throws Exception { + BufferedReader filenameReader = + new BufferedReader(new InputStreamReader(sock.getInputStream(), "UTF-8")); + String filename = filenameReader.readLine(); + + Path publicFolder = Paths.get("/home/" + user + "/public").toRealPath(); + Path filePath = publicFolder.resolve(filename).toRealPath(); + + // GOOD: toRealPath() normalizes the path (resolves ".." and symlinks), + // equivalent to File.getCanonicalPath() + if (!filePath.startsWith(publicFolder + File.separator)) { + throw new IllegalArgumentException("Invalid filename"); + } + BufferedReader fileReader = new BufferedReader(new FileReader(filePath.toString())); + String fileLine = fileReader.readLine(); + while (fileLine != null) { + sock.getOutputStream().write(fileLine.getBytes()); + fileLine = fileReader.readLine(); + } + } + public void sendUserFileGood4(Socket sock, String user) throws IOException { BufferedReader filenameReader = new BufferedReader(new InputStreamReader(sock.getInputStream(), "UTF-8"));