From 88ef6cffb4847f3b69d431ac405351d6dbcb03b2 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Fri, 16 Oct 2026 14:56:49 +0100 Subject: [PATCH 1/2] Test command injection through delegating launchers Document supported simple forms and known limitations with inline expectations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../security/CWE-078/ExecRelative.expected | 2 +- .../security/CWE-078/ExecTainted.expected | 80 +++---- .../security/CWE-078/ExecUnescaped.expected | 4 +- .../query-tests/security/CWE-078/Test.java | 204 +++++++++++++++++- 4 files changed, 240 insertions(+), 50 deletions(-) diff --git a/java/ql/test/query-tests/security/CWE-078/ExecRelative.expected b/java/ql/test/query-tests/security/CWE-078/ExecRelative.expected index e5b842b4395e..4ff84d888b6c 100644 --- a/java/ql/test/query-tests/security/CWE-078/ExecRelative.expected +++ b/java/ql/test/query-tests/security/CWE-078/ExecRelative.expected @@ -1,2 +1,2 @@ | TaintedEnvironment.java:39:35:39:55 | new String[] | Command with a relative path 'ls' is executed. | -| Test.java:50:46:50:49 | "ls" | Command with a relative path 'ls' is executed. | +| Test.java:219:46:219:49 | "ls" | Command with a relative path 'ls' is executed. | diff --git a/java/ql/test/query-tests/security/CWE-078/ExecTainted.expected b/java/ql/test/query-tests/security/CWE-078/ExecTainted.expected index e65a61d76988..fd2dd9d59447 100644 --- a/java/ql/test/query-tests/security/CWE-078/ExecTainted.expected +++ b/java/ql/test/query-tests/security/CWE-078/ExecTainted.expected @@ -1,48 +1,48 @@ #select -| Test.java:7:44:7:69 | ... + ... | Test.java:57:27:57:39 | args : String[] | Test.java:7:25:7:70 | new ..[] { .. } | This command line depends on a $@. | Test.java:57:27:57:39 | args | user-provided value | -| Test.java:10:29:10:74 | new String[] | Test.java:57:27:57:39 | args : String[] | Test.java:10:29:10:74 | new String[] | This command line depends on a $@. | Test.java:57:27:57:39 | args | user-provided value | -| Test.java:18:29:18:31 | cmd | Test.java:57:27:57:39 | args : String[] | Test.java:18:29:18:31 | cmd | This command line depends on a $@. | Test.java:57:27:57:39 | args | user-provided value | -| Test.java:24:29:24:32 | cmd1 | Test.java:57:27:57:39 | args : String[] | Test.java:24:29:24:32 | cmd1 | This command line depends on a $@. | Test.java:57:27:57:39 | args | user-provided value | -| Test.java:29:44:29:64 | ... + ... | Test.java:57:27:57:39 | args : String[] | Test.java:29:25:29:65 | new ..[] { .. } | This command line depends on a $@. | Test.java:57:27:57:39 | args | user-provided value | +| Test.java:8:44:8:69 | ... + ... | Test.java:226:27:226:39 | args : String[] | Test.java:8:25:8:70 | new ..[] { .. } | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:11:29:11:74 | new String[] | Test.java:226:27:226:39 | args : String[] | Test.java:11:29:11:74 | new String[] | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:19:29:19:31 | cmd | Test.java:226:27:226:39 | args : String[] | Test.java:19:29:19:31 | cmd | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:25:29:25:32 | cmd1 | Test.java:226:27:226:39 | args : String[] | Test.java:25:29:25:32 | cmd1 | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:30:44:30:64 | ... + ... | Test.java:226:27:226:39 | args : String[] | Test.java:30:25:30:65 | new ..[] { .. } | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | edges -| Test.java:6:35:6:44 | arg : String | Test.java:7:44:7:69 | ... + ... : String | provenance | | -| Test.java:6:35:6:44 | arg : String | Test.java:10:61:10:73 | ... + ... : String | provenance | | -| Test.java:6:35:6:44 | arg : String | Test.java:16:13:16:25 | ... + ... : String | provenance | | -| Test.java:6:35:6:44 | arg : String | Test.java:22:15:22:27 | ... + ... : String | provenance | | -| Test.java:7:44:7:69 | ... + ... : String | Test.java:7:25:7:70 | new ..[] { .. } | provenance | Sink:MaD:2 | -| Test.java:10:29:10:74 | {...} : String[] [[]] : String | Test.java:10:29:10:74 | new String[] | provenance | Sink:MaD:2 | -| Test.java:10:61:10:73 | ... + ... : String | Test.java:10:29:10:74 | {...} : String[] [[]] : String | provenance | | -| Test.java:16:5:16:7 | cmd [post update] : ArrayList [] : String | Test.java:18:29:18:31 | cmd | provenance | Sink:MaD:1 | -| Test.java:16:13:16:25 | ... + ... : String | Test.java:16:5:16:7 | cmd [post update] : ArrayList [] : String | provenance | MaD:3 | -| Test.java:22:5:22:8 | cmd1 [post update] : String[] [[]] : String | Test.java:24:29:24:32 | cmd1 | provenance | Sink:MaD:2 | -| Test.java:22:15:22:27 | ... + ... : String | Test.java:22:5:22:8 | cmd1 [post update] : String[] [[]] : String | provenance | | -| Test.java:28:38:28:47 | arg : String | Test.java:29:44:29:64 | ... + ... : String | provenance | | -| Test.java:29:44:29:64 | ... + ... : String | Test.java:29:25:29:65 | new ..[] { .. } | provenance | Sink:MaD:2 | -| Test.java:57:27:57:39 | args : String[] | Test.java:60:20:60:22 | arg : String | provenance | | -| Test.java:57:27:57:39 | args : String[] | Test.java:61:23:61:25 | arg : String | provenance | | -| Test.java:60:20:60:22 | arg : String | Test.java:6:35:6:44 | arg : String | provenance | | -| Test.java:61:23:61:25 | arg : String | Test.java:28:38:28:47 | arg : String | provenance | | +| Test.java:7:35:7:44 | arg : String | Test.java:8:44:8:69 | ... + ... : String | provenance | | +| Test.java:7:35:7:44 | arg : String | Test.java:11:61:11:73 | ... + ... : String | provenance | | +| Test.java:7:35:7:44 | arg : String | Test.java:17:13:17:25 | ... + ... : String | provenance | | +| Test.java:7:35:7:44 | arg : String | Test.java:23:15:23:27 | ... + ... : String | provenance | | +| Test.java:8:44:8:69 | ... + ... : String | Test.java:8:25:8:70 | new ..[] { .. } | provenance | Sink:MaD:2 | +| Test.java:11:29:11:74 | {...} : String[] [[]] : String | Test.java:11:29:11:74 | new String[] | provenance | Sink:MaD:2 | +| Test.java:11:61:11:73 | ... + ... : String | Test.java:11:29:11:74 | {...} : String[] [[]] : String | provenance | | +| Test.java:17:5:17:7 | cmd [post update] : ArrayList [] : String | Test.java:19:29:19:31 | cmd | provenance | Sink:MaD:1 | +| Test.java:17:13:17:25 | ... + ... : String | Test.java:17:5:17:7 | cmd [post update] : ArrayList [] : String | provenance | MaD:3 | +| Test.java:23:5:23:8 | cmd1 [post update] : String[] [[]] : String | Test.java:25:29:25:32 | cmd1 | provenance | Sink:MaD:2 | +| Test.java:23:15:23:27 | ... + ... : String | Test.java:23:5:23:8 | cmd1 [post update] : String[] [[]] : String | provenance | | +| Test.java:29:38:29:47 | arg : String | Test.java:30:44:30:64 | ... + ... : String | provenance | | +| Test.java:30:44:30:64 | ... + ... : String | Test.java:30:25:30:65 | new ..[] { .. } | provenance | Sink:MaD:2 | +| Test.java:226:27:226:39 | args : String[] | Test.java:229:20:229:22 | arg : String | provenance | | +| Test.java:226:27:226:39 | args : String[] | Test.java:230:23:230:25 | arg : String | provenance | | +| Test.java:229:20:229:22 | arg : String | Test.java:7:35:7:44 | arg : String | provenance | | +| Test.java:230:23:230:25 | arg : String | Test.java:29:38:29:47 | arg : String | provenance | | models | 1 | Sink: java.lang; ProcessBuilder; false; ProcessBuilder; (List); ; Argument[0]; command-injection; ai-manual | | 2 | Sink: java.lang; ProcessBuilder; false; ProcessBuilder; (String[]); ; Argument[0]; command-injection; ai-manual | | 3 | Summary: java.util; Collection; true; add; ; ; Argument[0]; Argument[this].Element; value; manual | nodes -| Test.java:6:35:6:44 | arg : String | semmle.label | arg : String | -| Test.java:7:25:7:70 | new ..[] { .. } | semmle.label | new ..[] { .. } | -| Test.java:7:44:7:69 | ... + ... : String | semmle.label | ... + ... : String | -| Test.java:10:29:10:74 | new String[] | semmle.label | new String[] | -| Test.java:10:29:10:74 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | -| Test.java:10:61:10:73 | ... + ... : String | semmle.label | ... + ... : String | -| Test.java:16:5:16:7 | cmd [post update] : ArrayList [] : String | semmle.label | cmd [post update] : ArrayList [] : String | -| Test.java:16:13:16:25 | ... + ... : String | semmle.label | ... + ... : String | -| Test.java:18:29:18:31 | cmd | semmle.label | cmd | -| Test.java:22:5:22:8 | cmd1 [post update] : String[] [[]] : String | semmle.label | cmd1 [post update] : String[] [[]] : String | -| Test.java:22:15:22:27 | ... + ... : String | semmle.label | ... + ... : String | -| Test.java:24:29:24:32 | cmd1 | semmle.label | cmd1 | -| Test.java:28:38:28:47 | arg : String | semmle.label | arg : String | -| Test.java:29:25:29:65 | new ..[] { .. } | semmle.label | new ..[] { .. } | -| Test.java:29:44:29:64 | ... + ... : String | semmle.label | ... + ... : String | -| Test.java:57:27:57:39 | args : String[] | semmle.label | args : String[] | -| Test.java:60:20:60:22 | arg : String | semmle.label | arg : String | -| Test.java:61:23:61:25 | arg : String | semmle.label | arg : String | +| Test.java:7:35:7:44 | arg : String | semmle.label | arg : String | +| Test.java:8:25:8:70 | new ..[] { .. } | semmle.label | new ..[] { .. } | +| Test.java:8:44:8:69 | ... + ... : String | semmle.label | ... + ... : String | +| Test.java:11:29:11:74 | new String[] | semmle.label | new String[] | +| Test.java:11:29:11:74 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:11:61:11:73 | ... + ... : String | semmle.label | ... + ... : String | +| Test.java:17:5:17:7 | cmd [post update] : ArrayList [] : String | semmle.label | cmd [post update] : ArrayList [] : String | +| Test.java:17:13:17:25 | ... + ... : String | semmle.label | ... + ... : String | +| Test.java:19:29:19:31 | cmd | semmle.label | cmd | +| Test.java:23:5:23:8 | cmd1 [post update] : String[] [[]] : String | semmle.label | cmd1 [post update] : String[] [[]] : String | +| Test.java:23:15:23:27 | ... + ... : String | semmle.label | ... + ... : String | +| Test.java:25:29:25:32 | cmd1 | semmle.label | cmd1 | +| Test.java:29:38:29:47 | arg : String | semmle.label | arg : String | +| Test.java:30:25:30:65 | new ..[] { .. } | semmle.label | new ..[] { .. } | +| Test.java:30:44:30:64 | ... + ... : String | semmle.label | ... + ... : String | +| Test.java:226:27:226:39 | args : String[] | semmle.label | args : String[] | +| Test.java:229:20:229:22 | arg : String | semmle.label | arg : String | +| Test.java:230:23:230:25 | arg : String | semmle.label | arg : String | subpaths diff --git a/java/ql/test/query-tests/security/CWE-078/ExecUnescaped.expected b/java/ql/test/query-tests/security/CWE-078/ExecUnescaped.expected index 5058f1bb26e9..d3e20e36fc98 100644 --- a/java/ql/test/query-tests/security/CWE-078/ExecUnescaped.expected +++ b/java/ql/test/query-tests/security/CWE-078/ExecUnescaped.expected @@ -1,2 +1,2 @@ -| Test.java:7:44:7:69 | ... + ... | Command line is built with string concatenation. | -| Test.java:29:44:29:64 | ... + ... | Command line is built with string concatenation. | +| Test.java:8:44:8:69 | ... + ... | Command line is built with string concatenation. | +| Test.java:30:44:30:64 | ... + ... | Command line is built with string concatenation. | diff --git a/java/ql/test/query-tests/security/CWE-078/Test.java b/java/ql/test/query-tests/security/CWE-078/Test.java index 6850a3a19e3b..853ad08cd515 100644 --- a/java/ql/test/query-tests/security/CWE-078/Test.java +++ b/java/ql/test/query-tests/security/CWE-078/Test.java @@ -1,6 +1,7 @@ import java.lang.ProcessBuilder; import java.util.List; import java.util.ArrayList; +import java.util.Arrays; class Test { public static void shellCommand(String arg) throws java.io.IOException { @@ -28,22 +29,190 @@ public static void shellCommand(String arg) throws java.io.IOException { public static void nonShellCommand(String arg) throws java.io.IOException { ProcessBuilder pb = new ProcessBuilder("./customTool " + arg); // $ Alert[java/concatenated-command-line] Alert[java/command-line-injection] pb.start(); + } - pb = new ProcessBuilder(new String[]{"./customTool", arg}); - pb.start(); + public static void nonShellCommandLiteralArguments(String arg) throws java.io.IOException { + new ProcessBuilder(new String[]{"./customTool", arg}).start(); // GOOD + } + public static void nonShellCommandListArguments(String arg) throws java.io.IOException { List cmd = new ArrayList(); cmd.add("./customTool"); cmd.add(arg); + new ProcessBuilder(cmd).start(); // GOOD + } - pb = new ProcessBuilder(cmd); - pb.start(); - + public static void nonShellCommandArrayArguments(String arg) throws java.io.IOException { String[] cmd1 = new String[]{"./customTool", ""}; cmd1[1] = arg; + new ProcessBuilder(cmd1).start(); // GOOD + } - pb = new ProcessBuilder(cmd1); - pb.start(); + public static void nonShellCommandWithIntermediateRead(String arg) throws java.io.IOException { + List readList = new ArrayList(); + readList.add("./customTool"); + readList.size(); + readList.add(arg); + new ProcessBuilder(readList).start(); // GOOD + } + + public static void nonShellCommandWithAddAll(String arg) throws java.io.IOException { + List addAllList = new ArrayList(); + addAllList.add("./customTool"); + addAllList.addAll(Arrays.asList("-v")); + addAllList.add(arg); + new ProcessBuilder(addAllList).start(); // GOOD + } + + public static void delegatingLauncherLiterals(String arg) throws java.io.IOException { + new ProcessBuilder(new String[]{"/usr/bin/env", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/sudo", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/xargs", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/sbin/chroot", "/tmp/root", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/nice", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/nohup", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/setsid", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/stdbuf", "-oL", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/timeout", "10", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/env", "/bin/sh", "-c", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + } + + public static void fixedDelegatingLauncherLiteral(String arg) throws java.io.IOException { + new ProcessBuilder(new String[]{"/usr/bin/env", "/bin/cp", arg, "/tmp/destination"}).start(); // GOOD + } + + public static void fixedDelegatingLauncherLiteralWithPrefix(String arg) + throws java.io.IOException { + new ProcessBuilder(new String[]{"/usr/bin/timeout", "10", "/bin/cp", arg, "/tmp/destination"}).start(); // GOOD + } + + public static void delegatingLauncherListAlerts(String arg, boolean condition) + throws java.io.IOException { + List delegatedList = new ArrayList(); + delegatedList.add("/usr/bin/env"); + delegatedList.add(arg); + new ProcessBuilder(delegatedList).start(); // $ MISSING: Alert[java/command-line-injection] + + List branchingPosition = new ArrayList(); + branchingPosition.add("/usr/bin/timeout"); + if (condition) { + branchingPosition.add("10"); + } + branchingPosition.add(arg); + new ProcessBuilder(branchingPosition).start(); // $ MISSING: Alert[java/command-line-injection] + + List branchingLauncher = new ArrayList(); + if (condition) { + branchingLauncher.add("/usr/bin/env"); + } else { + branchingLauncher.add("/usr/bin/timeout"); + } + branchingLauncher.add("10"); + branchingLauncher.add(arg); + new ProcessBuilder(branchingLauncher).start(); // $ MISSING: Alert[java/command-line-injection] + } + + public static void fixedDelegatingLauncherLaterListArgument(String arg) + throws java.io.IOException { + List laterArgumentList = new ArrayList(); + laterArgumentList.add("/usr/bin/timeout"); + laterArgumentList.add("10"); + laterArgumentList.add("/bin/cp"); + laterArgumentList.add(arg); + laterArgumentList.add("/tmp/destination"); + new ProcessBuilder(laterArgumentList).start(); // GOOD + } + + public static void fixedDelegatingLauncherList(String arg) throws java.io.IOException { + List fixedList = new ArrayList(); + fixedList.add("/usr/bin/env"); + fixedList.add("/bin/cp"); + fixedList.add(arg); + fixedList.add("/tmp/destination"); + new ProcessBuilder(fixedList).start(); // GOOD + } + + public static void fixedDelegatingLauncherLoopList(String arg) throws java.io.IOException { + List loopList = new ArrayList(); + loopList.add("/usr/bin/env"); + loopList.add("/bin/cp"); + for (String value : Arrays.asList(arg)) { + loopList.add(value); + } + new ProcessBuilder(loopList).start(); // GOOD + } + + public static void delegatingLauncherArrayAlerts(String arg, boolean condition) + throws java.io.IOException { + String[] delegatedArray = new String[2]; + delegatedArray[0] = "/usr/bin/env"; + delegatedArray[1] = arg; + new ProcessBuilder(delegatedArray).start(); // $ MISSING: Alert[java/command-line-injection] + + String launcher; + if (condition) { + launcher = "/usr/bin/env"; + } else { + launcher = "/usr/bin/timeout"; + } + new ProcessBuilder(new String[]{launcher, "10", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + } + + public static void fixedDelegatingLauncherArray(String arg) throws java.io.IOException { + String[] fixedArray = new String[]{"/usr/bin/env", "/bin/cp", "", "/tmp/destination"}; + fixedArray[2] = arg; + new ProcessBuilder(fixedArray).start(); // GOOD + } + + public static void changedDelegatedCommand(String arg) throws java.io.IOException { + String[] command = new String[]{"/usr/bin/env", "/bin/cp", "-c", arg}; + command[1] = "/bin/sh"; + new ProcessBuilder(command).start(); // $ MISSING: Alert[java/command-line-injection] + + String[] copiedInline = + Arrays.copyOf(new String[]{"/usr/bin/env", "/bin/cp", "-c", arg}, 4); + copiedInline[1] = "/bin/sh"; + new ProcessBuilder(copiedInline).start(); // $ MISSING: Alert[java/command-line-injection] + + String[] base = new String[]{"/usr/bin/env", "/bin/cp", "-c", arg}; + String[] copiedStored = Arrays.copyOf(base, 4); + copiedStored[1] = "/bin/sh"; + new ProcessBuilder(copiedStored).start(); // $ MISSING: Alert[java/command-line-injection] + } + + public static String[] returnedNonShellCommand(String arg) { + return new String[]{"./customTool", arg}; + } + + public static void nestedNonShellCommand(String arg) throws java.io.IOException { + new ProcessBuilder( + arg.isEmpty() + ? new String[]{"./customTool", arg} + : new String[]{"./otherCustomTool", arg}) + .start(); // GOOD + } + + public static void useReturnedNonShellCommand(String arg) throws java.io.IOException { + new ProcessBuilder(returnedNonShellCommand(arg)).start(); // GOOD + } + + public static void unknownDelegatedCommand(String arg, String command) throws java.io.IOException { + new ProcessBuilder(new String[]{"/usr/bin/env", command, "-c", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + } + + public static void unsupportedEnvAssignment(String arg) throws java.io.IOException { + new ProcessBuilder(new String[]{"/usr/bin/env", "LANG=C", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + } + + public static void unsupportedSudoOption(String arg) throws java.io.IOException { + new ProcessBuilder(new String[]{"/usr/bin/sudo", "-u", "nobody", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + } + + public static void changedDelegatedCommandThroughAlias(String arg) throws java.io.IOException { + String[] command = new String[]{"/usr/bin/env", "/bin/cp", "-c", arg}; + String[] alias = command; + alias[1] = "/bin/sh"; + new ProcessBuilder(command).start(); // $ MISSING: Alert[java/command-line-injection] } public static void relativeCommand() throws java.io.IOException { @@ -59,6 +228,27 @@ public static void main(String[] args) throws java.io.IOException { // $ Source[ shellCommand(arg); nonShellCommand(arg); + nonShellCommandLiteralArguments(arg); + nonShellCommandListArguments(arg); + nonShellCommandArrayArguments(arg); + nonShellCommandWithIntermediateRead(arg); + nonShellCommandWithAddAll(arg); + delegatingLauncherLiterals(arg); + fixedDelegatingLauncherLiteral(arg); + fixedDelegatingLauncherLiteralWithPrefix(arg); + delegatingLauncherListAlerts(arg, args.length > 2); + fixedDelegatingLauncherLaterListArgument(arg); + fixedDelegatingLauncherList(arg); + fixedDelegatingLauncherLoopList(arg); + delegatingLauncherArrayAlerts(arg, args.length > 2); + fixedDelegatingLauncherArray(arg); + changedDelegatedCommand(arg); + nestedNonShellCommand(arg); + useReturnedNonShellCommand(arg); + unknownDelegatedCommand(arg, "/bin/sh"); + unsupportedEnvAssignment(arg); + unsupportedSudoOption(arg); + changedDelegatedCommandThroughAlias(arg); relativeCommand(); } } From 8f59bbb81e39759d164bfcbb7774b6951009f53c Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Fri, 16 Oct 2026 14:57:41 +0100 Subject: [PATCH 2/2] Detect commands passed through simple launchers Recognize fixed command positions without parsing launcher options. Only sanitize later arguments when the delegated command is one unchanged string literal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../2026-10-16-delegating-launchers.md | 4 + java/ql/lib/semmle/code/java/JDK.qll | 14 +- .../code/java/security/CommandArguments.qll | 225 +++++++++++++++--- .../security/CWE-078/ExecTainted.expected | 177 +++++++++++++- .../query-tests/security/CWE-078/Test.java | 40 ++-- 5 files changed, 394 insertions(+), 66 deletions(-) create mode 100644 java/ql/lib/change-notes/2026-10-16-delegating-launchers.md diff --git a/java/ql/lib/change-notes/2026-10-16-delegating-launchers.md b/java/ql/lib/change-notes/2026-10-16-delegating-launchers.md new file mode 100644 index 000000000000..738b2226c8bd --- /dev/null +++ b/java/ql/lib/change-notes/2026-10-16-delegating-launchers.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The `java/command-line-injection` query now detects command injection through simple invocations of delegating launchers such as `env`, `sudo`, and `timeout`. diff --git a/java/ql/lib/semmle/code/java/JDK.qll b/java/ql/lib/semmle/code/java/JDK.qll index 93562c7c3d80..665623aa3206 100644 --- a/java/ql/lib/semmle/code/java/JDK.qll +++ b/java/ql/lib/semmle/code/java/JDK.qll @@ -104,6 +104,14 @@ class StringPartialMatchMethod extends Method { } } +/** The type `String[]`. */ +class ArrayOfStringType extends Array { + ArrayOfStringType() { + this.getDimension() = 1 and + this.getElementType() instanceof TypeString + } +} + /** The class `java.lang.StringBuffer`. */ class TypeStringBuffer extends Class { TypeStringBuffer() { this.hasQualifiedName("java.lang", "StringBuffer") } @@ -444,11 +452,7 @@ class MainMethod extends Method { this.getReturnType().hasName("void") and this.hasName("main") and this.getNumberOfParameters() = 1 and - exists(Array a | - a = this.getAParameter().getType() and - a.getDimension() = 1 and - a.getElementType() instanceof TypeString - ) + this.getAParameter().getType() instanceof ArrayOfStringType } } diff --git a/java/ql/lib/semmle/code/java/security/CommandArguments.qll b/java/ql/lib/semmle/code/java/security/CommandArguments.qll index 1ac42192e040..eb96c964a8e9 100644 --- a/java/ql/lib/semmle/code/java/security/CommandArguments.qll +++ b/java/ql/lib/semmle/code/java/security/CommandArguments.qll @@ -10,46 +10,145 @@ import semmle.code.java.Collections /** * Holds if `ex` is used safely as an argument to a command; - * i.e. it's not in the first position and it's not a shell command. + * i.e. it's not in a position that selects a command to execute. */ predicate isSafeCommandArgument(Expr ex) { - exists(ArrayInit ai, int i | - ex = ai.getInit(i) and - i > 0 and - not isShell(ai.getInit(0)) + exists(ArrayInit init, int index | + ex = init.getInit(index) and + index > 0 and + isNonDelegatingCommand(init.getInit(0)) ) or - exists(CommandArgumentList cal | - not cal.isShell() and - ex = cal.getASubsequentAdd().getArgument(0) + exists(CommandArgumentList list | + not list.isShell() and + not list.isDelegatingLauncher() and + ex = list.getASubsequentAdd().getArgument(0) ) or - exists(CommandArgArrayImmutableFirst caa | - not caa.isShell() and - ex = caa.getAWrite(any(int i | i > 0)) + exists(CommandArgArrayImmutableFirst array | + not array.isShell() and + not array.isDelegatingLauncher() and + ex = array.getAWrite(any(int index | index > 0)) + ) + or + forex(CommandArguments arguments, int index | ex = arguments.getArgument(index) | + index > 0 and arguments.hasSafeDelegatedArgument(index) ) } -/** - * Holds if the given expression is the name of a shell command such as bash or python - */ -private predicate isShell(Expr ex) { - exists(string cmd | cmd = ex.(StringLiteral).getValue() | - cmd.regexpMatch(".*(sh|javac?|python[23]?|osascript|cmd)(\\.exe)?$") - ) +private string commandName(Expr ex) { + result = ex.(StringLiteral).getValue() or exists(SsaDefinition ssa, SsaExplicitWrite def | ex = ssa.getARead() and - isShell(def.getDefiningExpr()) + result = commandName(def.getDefiningExpr()) | ssa.getAnUltimateDefinition() = def or ssa.(SsaCapturedDefinition).getAnUltimateCapturedDefinition() = def ) or - isShell(ex.(Assignment).getRhs()) + result = commandName(ex.(Assignment).getRhs()) or - isShell(ex.(LocalVariableDeclExpr).getInit()) + result = commandName(ex.(LocalVariableDeclExpr).getInit()) +} + +/** + * Holds if the given expression is the name of a shell command such as bash or python + */ +private predicate isShell(Expr ex) { + commandName(ex).regexpMatch(".*(sh|javac?|python[23]?|osascript|cmd)(\\.exe)?$") +} + +/** + * Gets the fixed position of the command executed by a supported delegating launcher. + * + * Only simple invocation forms with a command at the expected position are recognized. Launcher + * options and environment assignments are intentionally not interpreted. + */ +private int delegatedCommandIndex(Expr ex) { + exists(string launcher | + launcher = commandName(ex).regexpCapture("(.*/)?([^/.]+)(\\.exe)?$", 2) and + ( + launcher = ["env", "nice", "nohup", "setsid", "sudo", "xargs"] and result = 1 + or + launcher = ["chroot", "stdbuf", "timeout"] and result = 2 + ) + ) +} + +private predicate isNonDelegatingCommand(Expr ex) { + exists(commandName(ex)) and + not isShell(ex) and + not exists(delegatedCommandIndex(ex)) +} + +/** Holds if `init` initializes an array passed directly to a call. */ +private predicate isInlineArgumentArray(ArrayInit init) { + exists(ArrayCreationExpr array, Call call | + array.getInit() = init and + array = call.getAnArgument() and + not exists(MethodCall methodCall | + call = methodCall and + methodCall.getMethod().getDeclaringType().hasQualifiedName("java.util", "Arrays") and + methodCall.getMethod().hasName("copyOf") + ) + ) +} + +private newtype TCommandArguments = + TLiteralArguments(ArrayInit init) { isInlineArgumentArray(init) } or + TListArguments(CommandArgumentList list) or + TMutableArguments(CommandArgArrayImmutableFirst array) + +/** An ordered sequence of command arguments. */ +private class CommandArguments extends TCommandArguments { + string toString() { result = this.getArgument(0).toString() } + + /** Gets a possible value at the given index. */ + Expr getArgument(int index) { + exists(ArrayInit init | + this = TLiteralArguments(init) and + result = init.getInit(index) + ) + or + exists(CommandArgumentList list | + this = TListArguments(list) and + ( + result = list.getArgument(index) + or + index = 3 and result = list.getAnArgumentAfterKnownPrefix().getArgument(0) + ) + ) + or + exists(CommandArgArrayImmutableFirst array | + this = TMutableArguments(array) and + result = array.getAnElement(index) + ) + } + + /** Gets the single string literal stored at the given index, if there is one. */ + private StringLiteral getFixedLiteralArgument(int index) { + forex(Expr value | value = this.getArgument(index) | value = result) + } + + predicate hasSafeDelegatedArgument(int index) { + exists(this.getArgument(index)) and + forex(Expr launcher | launcher = this.getArgument(0) | + forex(int commandIndex | commandIndex = delegatedCommandIndex(launcher) | + ( + index < commandIndex + or + index > commandIndex and + exists(StringLiteral command | + command = this.getFixedLiteralArgument(commandIndex) and + not isShell(command) and + not exists(delegatedCommandIndex(command)) + ) + ) + ) + ) + } } /** @@ -101,6 +200,39 @@ private class CommandArgumentList extends SsaExplicitWrite { result.getQualifier() = this.getAUseBeforeFirstAdd() } + /** Gets a single-element addition at the given index. */ + private MethodCall getAnAddAtIndex(int index) { + index = 0 and + result = this.getAFirstAdd() and + result.getMethod().hasName("add") and + result.getNumArgument() = 1 + or + index > 0 and + index <= 2 and + exists(MethodCall previous | + previous = this.getAnAddAtIndex(index - 1) and + result = this.getAnAdd() and + result.getMethod().hasName("add") and + result.getNumArgument() = 1 and + adjacentUseUse(previous.getQualifier(), result.getQualifier()) + ) + } + + /** Gets a single-element addition after the first three elements. */ + MethodCall getAnArgumentAfterKnownPrefix() { + result = this.getAnAdd() and + result.getMethod().hasName("add") and + result.getNumArgument() = 1 and + exists(MethodCall previous | + previous = this.getAnAddAtIndex(2) or previous = this.getAnArgumentAfterKnownPrefix() + | + adjacentUseUse(previous.getQualifier(), result.getQualifier()) + ) + } + + /** Gets the argument added at the given index. */ + Expr getArgument(int index) { result = this.getAnAddAtIndex(index).getArgument(0) } + /** Gets an addition to this list which is not the first element. */ MethodCall getASubsequentAdd() { result = this.getAnAdd() and @@ -109,15 +241,15 @@ private class CommandArgumentList extends SsaExplicitWrite { /** Holds if the first element of this list is a shell command. */ predicate isShell() { - exists(MethodCall ma | ma = this.getAFirstAdd() and isShell(ma.getArgument(0))) + exists(MethodCall add | add = this.getAFirstAdd() and isShell(add.getArgument(0))) } -} -/** - * The type `String[]`. - */ -private class ArrayOfStringType extends Array { - ArrayOfStringType() { this.getElementType() instanceof TypeString } + /** Holds if the first element of this list is a delegating launcher. */ + predicate isDelegatingLauncher() { + exists(MethodCall add | add = this.getAFirstAdd() | + exists(delegatedCommandIndex(add.getArgument(0))) + ) + } } private predicate arrayVarWrite(ArrayAccess acc) { exists(Assignment a | a.getDest() = acc) } @@ -153,7 +285,7 @@ private class CommandArgumentArray extends SsaExplicitWrite { */ private class CommandArgArrayImmutableFirst extends CommandArgumentArray { CommandArgArrayImmutableFirst() { - (exists(this.getAWrite(0)) or exists(firstElementOf(this.getDefiningExpr()))) and + (exists(this.getAWrite(0)) or exists(arrayElementOf(this.getDefiningExpr(), 0))) and forall(VarRead use | exists(this.getAWrite(0, use)) | use = ssaGetAFirstUse(this)) } @@ -162,23 +294,38 @@ private class CommandArgArrayImmutableFirst extends CommandArgumentArray { result = this.getAWrite(0) or not exists(this.getAWrite(0)) and - result = firstElementOf(this.getDefiningExpr()) + result = arrayElementOf(this.getDefiningExpr(), 0) } /** Holds if the first element of this array is a shell command. */ predicate isShell() { isShell(this.getFirstElement()) } + + /** Holds if the first element of this array is a delegating launcher. */ + predicate isDelegatingLauncher() { exists(delegatedCommandIndex(this.getFirstElement())) } + + /** Gets a possible value for the element at the given index. */ + Expr getAnElement(int index) { + index = 0 and result = this.getFirstElement() + or + index > 0 and + ( + result = this.getAWrite(index) + or + result = arrayElementOf(this.getDefiningExpr(), index) + ) + } } -/** Gets the first element of an imutable array of strings */ -private Expr firstElementOf(Expr arr) { +/** Gets an element of an immutable array of strings. */ +private Expr arrayElementOf(Expr arr, int index) { arr.getType() instanceof ArrayOfStringType and ( - result = firstElementOf(arr.(Assignment).getRhs()) + result = arrayElementOf(arr.(Assignment).getRhs(), index) or - result = firstElementOf(arr.(LocalVariableDeclExpr).getInit()) + result = arrayElementOf(arr.(LocalVariableDeclExpr).getInit(), index) or exists(CommandArgArrayImmutableFirst caa | arr = caa.getARead() | - result = caa.getFirstElement() + result = caa.getAnElement(index) ) or exists(MethodCall ma, Method m | @@ -186,17 +333,17 @@ private Expr firstElementOf(Expr arr) { ma.getMethod() = m and m.getDeclaringType().hasQualifiedName("java.util", "Arrays") and m.hasName("copyOf") and - result = firstElementOf(ma.getArgument(0)) + result = arrayElementOf(ma.getArgument(0), index) ) or exists(Field f | f.isStatic() and arr.(FieldRead).getField() = f and - result = firstElementOf(f.getInitializer()) + result = arrayElementOf(f.getInitializer(), index) ) or - result = arr.(ArrayInit).getInit(0) + result = arr.(ArrayInit).getInit(index) or - result = arr.(ArrayCreationExpr).getInit().getInit(0) + result = arr.(ArrayCreationExpr).getInit().getInit(index) ) } diff --git a/java/ql/test/query-tests/security/CWE-078/ExecTainted.expected b/java/ql/test/query-tests/security/CWE-078/ExecTainted.expected index fd2dd9d59447..b7a288cefdf2 100644 --- a/java/ql/test/query-tests/security/CWE-078/ExecTainted.expected +++ b/java/ql/test/query-tests/security/CWE-078/ExecTainted.expected @@ -4,6 +4,26 @@ | Test.java:19:29:19:31 | cmd | Test.java:226:27:226:39 | args : String[] | Test.java:19:29:19:31 | cmd | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | | Test.java:25:29:25:32 | cmd1 | Test.java:226:27:226:39 | args : String[] | Test.java:25:29:25:32 | cmd1 | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | | Test.java:30:44:30:64 | ... + ... | Test.java:226:27:226:39 | args : String[] | Test.java:30:25:30:65 | new ..[] { .. } | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:68:24:68:56 | new String[] | Test.java:226:27:226:39 | args : String[] | Test.java:68:24:68:56 | new String[] | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:69:24:69:57 | new String[] | Test.java:226:27:226:39 | args : String[] | Test.java:69:24:69:57 | new String[] | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:70:24:70:58 | new String[] | Test.java:226:27:226:39 | args : String[] | Test.java:70:24:70:58 | new String[] | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:71:24:71:73 | new String[] | Test.java:226:27:226:39 | args : String[] | Test.java:71:24:71:73 | new String[] | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:72:24:72:57 | new String[] | Test.java:226:27:226:39 | args : String[] | Test.java:72:24:72:57 | new String[] | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:73:24:73:58 | new String[] | Test.java:226:27:226:39 | args : String[] | Test.java:73:24:73:58 | new String[] | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:74:24:74:59 | new String[] | Test.java:226:27:226:39 | args : String[] | Test.java:74:24:74:59 | new String[] | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:75:24:75:66 | new String[] | Test.java:226:27:226:39 | args : String[] | Test.java:75:24:75:66 | new String[] | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:76:24:76:66 | new String[] | Test.java:226:27:226:39 | args : String[] | Test.java:76:24:76:66 | new String[] | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:77:24:77:73 | new String[] | Test.java:226:27:226:39 | args : String[] | Test.java:77:24:77:73 | new String[] | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:94:24:94:36 | delegatedList | Test.java:226:27:226:39 | args : String[] | Test.java:94:24:94:36 | delegatedList | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:102:24:102:40 | branchingPosition | Test.java:226:27:226:39 | args : String[] | Test.java:102:24:102:40 | branchingPosition | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:112:24:112:40 | branchingLauncher | Test.java:226:27:226:39 | args : String[] | Test.java:112:24:112:40 | branchingLauncher | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:150:24:150:37 | delegatedArray | Test.java:226:27:226:39 | args : String[] | Test.java:150:24:150:37 | delegatedArray | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:158:24:158:56 | new String[] | Test.java:226:27:226:39 | args : String[] | Test.java:158:24:158:56 | new String[] | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:170:24:170:30 | command | Test.java:226:27:226:39 | args : String[] | Test.java:170:24:170:30 | command | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:175:24:175:35 | copiedInline | Test.java:226:27:226:39 | args : String[] | Test.java:175:24:175:35 | copiedInline | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:180:24:180:35 | copiedStored | Test.java:226:27:226:39 | args : String[] | Test.java:180:24:180:35 | copiedStored | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:200:24:200:71 | new String[] | Test.java:226:27:226:39 | args : String[] | Test.java:200:24:200:71 | new String[] | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | +| Test.java:215:24:215:30 | command | Test.java:226:27:226:39 | args : String[] | Test.java:215:24:215:30 | command | This command line depends on a $@. | Test.java:226:27:226:39 | args | user-provided value | edges | Test.java:7:35:7:44 | arg : String | Test.java:8:44:8:69 | ... + ... : String | provenance | | | Test.java:7:35:7:44 | arg : String | Test.java:11:61:11:73 | ... + ... : String | provenance | | @@ -13,19 +33,96 @@ edges | Test.java:11:29:11:74 | {...} : String[] [[]] : String | Test.java:11:29:11:74 | new String[] | provenance | Sink:MaD:2 | | Test.java:11:61:11:73 | ... + ... : String | Test.java:11:29:11:74 | {...} : String[] [[]] : String | provenance | | | Test.java:17:5:17:7 | cmd [post update] : ArrayList [] : String | Test.java:19:29:19:31 | cmd | provenance | Sink:MaD:1 | -| Test.java:17:13:17:25 | ... + ... : String | Test.java:17:5:17:7 | cmd [post update] : ArrayList [] : String | provenance | MaD:3 | +| Test.java:17:13:17:25 | ... + ... : String | Test.java:17:5:17:7 | cmd [post update] : ArrayList [] : String | provenance | MaD:4 | | Test.java:23:5:23:8 | cmd1 [post update] : String[] [[]] : String | Test.java:25:29:25:32 | cmd1 | provenance | Sink:MaD:2 | | Test.java:23:15:23:27 | ... + ... : String | Test.java:23:5:23:8 | cmd1 [post update] : String[] [[]] : String | provenance | | | Test.java:29:38:29:47 | arg : String | Test.java:30:44:30:64 | ... + ... : String | provenance | | | Test.java:30:44:30:64 | ... + ... : String | Test.java:30:25:30:65 | new ..[] { .. } | provenance | Sink:MaD:2 | +| Test.java:67:49:67:58 | arg : String | Test.java:68:53:68:55 | arg : String | provenance | | +| Test.java:67:49:67:58 | arg : String | Test.java:69:54:69:56 | arg : String | provenance | | +| Test.java:67:49:67:58 | arg : String | Test.java:70:55:70:57 | arg : String | provenance | | +| Test.java:67:49:67:58 | arg : String | Test.java:71:70:71:72 | arg : String | provenance | | +| Test.java:67:49:67:58 | arg : String | Test.java:72:54:72:56 | arg : String | provenance | | +| Test.java:67:49:67:58 | arg : String | Test.java:73:55:73:57 | arg : String | provenance | | +| Test.java:67:49:67:58 | arg : String | Test.java:74:56:74:58 | arg : String | provenance | | +| Test.java:67:49:67:58 | arg : String | Test.java:75:63:75:65 | arg : String | provenance | | +| Test.java:67:49:67:58 | arg : String | Test.java:76:63:76:65 | arg : String | provenance | | +| Test.java:67:49:67:58 | arg : String | Test.java:77:70:77:72 | arg : String | provenance | | +| Test.java:68:24:68:56 | {...} : String[] [[]] : String | Test.java:68:24:68:56 | new String[] | provenance | Sink:MaD:2 | +| Test.java:68:53:68:55 | arg : String | Test.java:68:24:68:56 | {...} : String[] [[]] : String | provenance | | +| Test.java:69:24:69:57 | {...} : String[] [[]] : String | Test.java:69:24:69:57 | new String[] | provenance | Sink:MaD:2 | +| Test.java:69:54:69:56 | arg : String | Test.java:69:24:69:57 | {...} : String[] [[]] : String | provenance | | +| Test.java:70:24:70:58 | {...} : String[] [[]] : String | Test.java:70:24:70:58 | new String[] | provenance | Sink:MaD:2 | +| Test.java:70:55:70:57 | arg : String | Test.java:70:24:70:58 | {...} : String[] [[]] : String | provenance | | +| Test.java:71:24:71:73 | {...} : String[] [[]] : String | Test.java:71:24:71:73 | new String[] | provenance | Sink:MaD:2 | +| Test.java:71:70:71:72 | arg : String | Test.java:71:24:71:73 | {...} : String[] [[]] : String | provenance | | +| Test.java:72:24:72:57 | {...} : String[] [[]] : String | Test.java:72:24:72:57 | new String[] | provenance | Sink:MaD:2 | +| Test.java:72:54:72:56 | arg : String | Test.java:72:24:72:57 | {...} : String[] [[]] : String | provenance | | +| Test.java:73:24:73:58 | {...} : String[] [[]] : String | Test.java:73:24:73:58 | new String[] | provenance | Sink:MaD:2 | +| Test.java:73:55:73:57 | arg : String | Test.java:73:24:73:58 | {...} : String[] [[]] : String | provenance | | +| Test.java:74:24:74:59 | {...} : String[] [[]] : String | Test.java:74:24:74:59 | new String[] | provenance | Sink:MaD:2 | +| Test.java:74:56:74:58 | arg : String | Test.java:74:24:74:59 | {...} : String[] [[]] : String | provenance | | +| Test.java:75:24:75:66 | {...} : String[] [[]] : String | Test.java:75:24:75:66 | new String[] | provenance | Sink:MaD:2 | +| Test.java:75:63:75:65 | arg : String | Test.java:75:24:75:66 | {...} : String[] [[]] : String | provenance | | +| Test.java:76:24:76:66 | {...} : String[] [[]] : String | Test.java:76:24:76:66 | new String[] | provenance | Sink:MaD:2 | +| Test.java:76:63:76:65 | arg : String | Test.java:76:24:76:66 | {...} : String[] [[]] : String | provenance | | +| Test.java:77:24:77:73 | {...} : String[] [[]] : String | Test.java:77:24:77:73 | new String[] | provenance | Sink:MaD:2 | +| Test.java:77:70:77:72 | arg : String | Test.java:77:24:77:73 | {...} : String[] [[]] : String | provenance | | +| Test.java:89:51:89:60 | arg : String | Test.java:93:23:93:25 | arg : String | provenance | | +| Test.java:89:51:89:60 | arg : String | Test.java:101:27:101:29 | arg : String | provenance | | +| Test.java:89:51:89:60 | arg : String | Test.java:111:27:111:29 | arg : String | provenance | | +| Test.java:93:5:93:17 | delegatedList [post update] : ArrayList [] : String | Test.java:94:24:94:36 | delegatedList | provenance | Sink:MaD:1 | +| Test.java:93:23:93:25 | arg : String | Test.java:93:5:93:17 | delegatedList [post update] : ArrayList [] : String | provenance | MaD:4 | +| Test.java:101:5:101:21 | branchingPosition [post update] : ArrayList [] : String | Test.java:102:24:102:40 | branchingPosition | provenance | Sink:MaD:1 | +| Test.java:101:27:101:29 | arg : String | Test.java:101:5:101:21 | branchingPosition [post update] : ArrayList [] : String | provenance | MaD:4 | +| Test.java:111:5:111:21 | branchingLauncher [post update] : ArrayList [] : String | Test.java:112:24:112:40 | branchingLauncher | provenance | Sink:MaD:1 | +| Test.java:111:27:111:29 | arg : String | Test.java:111:5:111:21 | branchingLauncher [post update] : ArrayList [] : String | provenance | MaD:4 | +| Test.java:145:52:145:61 | arg : String | Test.java:149:25:149:27 | arg : String | provenance | | +| Test.java:145:52:145:61 | arg : String | Test.java:158:53:158:55 | arg : String | provenance | | +| Test.java:149:5:149:18 | delegatedArray [post update] : String[] [[]] : String | Test.java:150:24:150:37 | delegatedArray | provenance | Sink:MaD:2 | +| Test.java:149:25:149:27 | arg : String | Test.java:149:5:149:18 | delegatedArray [post update] : String[] [[]] : String | provenance | | +| Test.java:158:24:158:56 | {...} : String[] [[]] : String | Test.java:158:24:158:56 | new String[] | provenance | Sink:MaD:2 | +| Test.java:158:53:158:55 | arg : String | Test.java:158:24:158:56 | {...} : String[] [[]] : String | provenance | | +| Test.java:167:46:167:55 | arg : String | Test.java:168:70:168:72 | arg : String | provenance | | +| Test.java:167:46:167:55 | arg : String | Test.java:173:69:173:71 | arg : String | provenance | | +| Test.java:167:46:167:55 | arg : String | Test.java:177:67:177:69 | arg : String | provenance | | +| Test.java:168:24:168:73 | {...} : String[] [[]] : String | Test.java:170:24:170:30 | command | provenance | Sink:MaD:2 | +| Test.java:168:70:168:72 | arg : String | Test.java:168:24:168:73 | {...} : String[] [[]] : String | provenance | | +| Test.java:173:9:173:76 | copyOf(...) : String[] [[]] : String | Test.java:175:24:175:35 | copiedInline | provenance | Sink:MaD:2 | +| Test.java:173:23:173:72 | new String[] : String[] [[]] : String | Test.java:173:9:173:76 | copyOf(...) : String[] [[]] : String | provenance | MaD:3 | +| Test.java:173:23:173:72 | {...} : String[] [[]] : String | Test.java:173:23:173:72 | new String[] : String[] [[]] : String | provenance | | +| Test.java:173:69:173:71 | arg : String | Test.java:173:23:173:72 | {...} : String[] [[]] : String | provenance | | +| Test.java:177:21:177:70 | {...} : String[] [[]] : String | Test.java:178:43:178:46 | base : String[] [[]] : String | provenance | | +| Test.java:177:67:177:69 | arg : String | Test.java:177:21:177:70 | {...} : String[] [[]] : String | provenance | | +| Test.java:178:29:178:50 | copyOf(...) : String[] [[]] : String | Test.java:180:24:180:35 | copiedStored | provenance | Sink:MaD:2 | +| Test.java:178:43:178:46 | base : String[] [[]] : String | Test.java:178:29:178:50 | copyOf(...) : String[] [[]] : String | provenance | MaD:3 | +| Test.java:199:46:199:55 | arg : String | Test.java:200:68:200:70 | arg : String | provenance | | +| Test.java:200:24:200:71 | {...} : String[] [[]] : String | Test.java:200:24:200:71 | new String[] | provenance | Sink:MaD:2 | +| Test.java:200:68:200:70 | arg : String | Test.java:200:24:200:71 | {...} : String[] [[]] : String | provenance | | +| Test.java:211:58:211:67 | arg : String | Test.java:212:70:212:72 | arg : String | provenance | | +| Test.java:212:24:212:73 | {...} : String[] [[]] : String | Test.java:215:24:215:30 | command | provenance | Sink:MaD:2 | +| Test.java:212:70:212:72 | arg : String | Test.java:212:24:212:73 | {...} : String[] [[]] : String | provenance | | | Test.java:226:27:226:39 | args : String[] | Test.java:229:20:229:22 | arg : String | provenance | | | Test.java:226:27:226:39 | args : String[] | Test.java:230:23:230:25 | arg : String | provenance | | +| Test.java:226:27:226:39 | args : String[] | Test.java:236:34:236:36 | arg : String | provenance | | +| Test.java:226:27:226:39 | args : String[] | Test.java:239:36:239:38 | arg : String | provenance | | +| Test.java:226:27:226:39 | args : String[] | Test.java:243:37:243:39 | arg : String | provenance | | +| Test.java:226:27:226:39 | args : String[] | Test.java:245:31:245:33 | arg : String | provenance | | +| Test.java:226:27:226:39 | args : String[] | Test.java:248:31:248:33 | arg : String | provenance | | +| Test.java:226:27:226:39 | args : String[] | Test.java:251:43:251:45 | arg : String | provenance | | | Test.java:229:20:229:22 | arg : String | Test.java:7:35:7:44 | arg : String | provenance | | | Test.java:230:23:230:25 | arg : String | Test.java:29:38:29:47 | arg : String | provenance | | +| Test.java:236:34:236:36 | arg : String | Test.java:67:49:67:58 | arg : String | provenance | | +| Test.java:239:36:239:38 | arg : String | Test.java:89:51:89:60 | arg : String | provenance | | +| Test.java:243:37:243:39 | arg : String | Test.java:145:52:145:61 | arg : String | provenance | | +| Test.java:245:31:245:33 | arg : String | Test.java:167:46:167:55 | arg : String | provenance | | +| Test.java:248:31:248:33 | arg : String | Test.java:199:46:199:55 | arg : String | provenance | | +| Test.java:251:43:251:45 | arg : String | Test.java:211:58:211:67 | arg : String | provenance | | models | 1 | Sink: java.lang; ProcessBuilder; false; ProcessBuilder; (List); ; Argument[0]; command-injection; ai-manual | | 2 | Sink: java.lang; ProcessBuilder; false; ProcessBuilder; (String[]); ; Argument[0]; command-injection; ai-manual | -| 3 | Summary: java.util; Collection; true; add; ; ; Argument[0]; Argument[this].Element; value; manual | +| 3 | Summary: java.util; Arrays; false; copyOf; ; ; Argument[0].ArrayElement; ReturnValue.ArrayElement; value; manual | +| 4 | Summary: java.util; Collection; true; add; ; ; Argument[0]; Argument[this].Element; value; manual | nodes | Test.java:7:35:7:44 | arg : String | semmle.label | arg : String | | Test.java:8:25:8:70 | new ..[] { .. } | semmle.label | new ..[] { .. } | @@ -42,7 +139,83 @@ nodes | Test.java:29:38:29:47 | arg : String | semmle.label | arg : String | | Test.java:30:25:30:65 | new ..[] { .. } | semmle.label | new ..[] { .. } | | Test.java:30:44:30:64 | ... + ... : String | semmle.label | ... + ... : String | +| Test.java:67:49:67:58 | arg : String | semmle.label | arg : String | +| Test.java:68:24:68:56 | new String[] | semmle.label | new String[] | +| Test.java:68:24:68:56 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:68:53:68:55 | arg : String | semmle.label | arg : String | +| Test.java:69:24:69:57 | new String[] | semmle.label | new String[] | +| Test.java:69:24:69:57 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:69:54:69:56 | arg : String | semmle.label | arg : String | +| Test.java:70:24:70:58 | new String[] | semmle.label | new String[] | +| Test.java:70:24:70:58 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:70:55:70:57 | arg : String | semmle.label | arg : String | +| Test.java:71:24:71:73 | new String[] | semmle.label | new String[] | +| Test.java:71:24:71:73 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:71:70:71:72 | arg : String | semmle.label | arg : String | +| Test.java:72:24:72:57 | new String[] | semmle.label | new String[] | +| Test.java:72:24:72:57 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:72:54:72:56 | arg : String | semmle.label | arg : String | +| Test.java:73:24:73:58 | new String[] | semmle.label | new String[] | +| Test.java:73:24:73:58 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:73:55:73:57 | arg : String | semmle.label | arg : String | +| Test.java:74:24:74:59 | new String[] | semmle.label | new String[] | +| Test.java:74:24:74:59 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:74:56:74:58 | arg : String | semmle.label | arg : String | +| Test.java:75:24:75:66 | new String[] | semmle.label | new String[] | +| Test.java:75:24:75:66 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:75:63:75:65 | arg : String | semmle.label | arg : String | +| Test.java:76:24:76:66 | new String[] | semmle.label | new String[] | +| Test.java:76:24:76:66 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:76:63:76:65 | arg : String | semmle.label | arg : String | +| Test.java:77:24:77:73 | new String[] | semmle.label | new String[] | +| Test.java:77:24:77:73 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:77:70:77:72 | arg : String | semmle.label | arg : String | +| Test.java:89:51:89:60 | arg : String | semmle.label | arg : String | +| Test.java:93:5:93:17 | delegatedList [post update] : ArrayList [] : String | semmle.label | delegatedList [post update] : ArrayList [] : String | +| Test.java:93:23:93:25 | arg : String | semmle.label | arg : String | +| Test.java:94:24:94:36 | delegatedList | semmle.label | delegatedList | +| Test.java:101:5:101:21 | branchingPosition [post update] : ArrayList [] : String | semmle.label | branchingPosition [post update] : ArrayList [] : String | +| Test.java:101:27:101:29 | arg : String | semmle.label | arg : String | +| Test.java:102:24:102:40 | branchingPosition | semmle.label | branchingPosition | +| Test.java:111:5:111:21 | branchingLauncher [post update] : ArrayList [] : String | semmle.label | branchingLauncher [post update] : ArrayList [] : String | +| Test.java:111:27:111:29 | arg : String | semmle.label | arg : String | +| Test.java:112:24:112:40 | branchingLauncher | semmle.label | branchingLauncher | +| Test.java:145:52:145:61 | arg : String | semmle.label | arg : String | +| Test.java:149:5:149:18 | delegatedArray [post update] : String[] [[]] : String | semmle.label | delegatedArray [post update] : String[] [[]] : String | +| Test.java:149:25:149:27 | arg : String | semmle.label | arg : String | +| Test.java:150:24:150:37 | delegatedArray | semmle.label | delegatedArray | +| Test.java:158:24:158:56 | new String[] | semmle.label | new String[] | +| Test.java:158:24:158:56 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:158:53:158:55 | arg : String | semmle.label | arg : String | +| Test.java:167:46:167:55 | arg : String | semmle.label | arg : String | +| Test.java:168:24:168:73 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:168:70:168:72 | arg : String | semmle.label | arg : String | +| Test.java:170:24:170:30 | command | semmle.label | command | +| Test.java:173:9:173:76 | copyOf(...) : String[] [[]] : String | semmle.label | copyOf(...) : String[] [[]] : String | +| Test.java:173:23:173:72 | new String[] : String[] [[]] : String | semmle.label | new String[] : String[] [[]] : String | +| Test.java:173:23:173:72 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:173:69:173:71 | arg : String | semmle.label | arg : String | +| Test.java:175:24:175:35 | copiedInline | semmle.label | copiedInline | +| Test.java:177:21:177:70 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:177:67:177:69 | arg : String | semmle.label | arg : String | +| Test.java:178:29:178:50 | copyOf(...) : String[] [[]] : String | semmle.label | copyOf(...) : String[] [[]] : String | +| Test.java:178:43:178:46 | base : String[] [[]] : String | semmle.label | base : String[] [[]] : String | +| Test.java:180:24:180:35 | copiedStored | semmle.label | copiedStored | +| Test.java:199:46:199:55 | arg : String | semmle.label | arg : String | +| Test.java:200:24:200:71 | new String[] | semmle.label | new String[] | +| Test.java:200:24:200:71 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:200:68:200:70 | arg : String | semmle.label | arg : String | +| Test.java:211:58:211:67 | arg : String | semmle.label | arg : String | +| Test.java:212:24:212:73 | {...} : String[] [[]] : String | semmle.label | {...} : String[] [[]] : String | +| Test.java:212:70:212:72 | arg : String | semmle.label | arg : String | +| Test.java:215:24:215:30 | command | semmle.label | command | | Test.java:226:27:226:39 | args : String[] | semmle.label | args : String[] | | Test.java:229:20:229:22 | arg : String | semmle.label | arg : String | | Test.java:230:23:230:25 | arg : String | semmle.label | arg : String | +| Test.java:236:34:236:36 | arg : String | semmle.label | arg : String | +| Test.java:239:36:239:38 | arg : String | semmle.label | arg : String | +| Test.java:243:37:243:39 | arg : String | semmle.label | arg : String | +| Test.java:245:31:245:33 | arg : String | semmle.label | arg : String | +| Test.java:248:31:248:33 | arg : String | semmle.label | arg : String | +| Test.java:251:43:251:45 | arg : String | semmle.label | arg : String | subpaths diff --git a/java/ql/test/query-tests/security/CWE-078/Test.java b/java/ql/test/query-tests/security/CWE-078/Test.java index 853ad08cd515..fc4a409983ac 100644 --- a/java/ql/test/query-tests/security/CWE-078/Test.java +++ b/java/ql/test/query-tests/security/CWE-078/Test.java @@ -65,16 +65,16 @@ public static void nonShellCommandWithAddAll(String arg) throws java.io.IOExcept } public static void delegatingLauncherLiterals(String arg) throws java.io.IOException { - new ProcessBuilder(new String[]{"/usr/bin/env", arg}).start(); // $ MISSING: Alert[java/command-line-injection] - new ProcessBuilder(new String[]{"/usr/bin/sudo", arg}).start(); // $ MISSING: Alert[java/command-line-injection] - new ProcessBuilder(new String[]{"/usr/bin/xargs", arg}).start(); // $ MISSING: Alert[java/command-line-injection] - new ProcessBuilder(new String[]{"/usr/sbin/chroot", "/tmp/root", arg}).start(); // $ MISSING: Alert[java/command-line-injection] - new ProcessBuilder(new String[]{"/usr/bin/nice", arg}).start(); // $ MISSING: Alert[java/command-line-injection] - new ProcessBuilder(new String[]{"/usr/bin/nohup", arg}).start(); // $ MISSING: Alert[java/command-line-injection] - new ProcessBuilder(new String[]{"/usr/bin/setsid", arg}).start(); // $ MISSING: Alert[java/command-line-injection] - new ProcessBuilder(new String[]{"/usr/bin/stdbuf", "-oL", arg}).start(); // $ MISSING: Alert[java/command-line-injection] - new ProcessBuilder(new String[]{"/usr/bin/timeout", "10", arg}).start(); // $ MISSING: Alert[java/command-line-injection] - new ProcessBuilder(new String[]{"/usr/bin/env", "/bin/sh", "-c", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/env", arg}).start(); // $ Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/sudo", arg}).start(); // $ Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/xargs", arg}).start(); // $ Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/sbin/chroot", "/tmp/root", arg}).start(); // $ Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/nice", arg}).start(); // $ Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/nohup", arg}).start(); // $ Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/setsid", arg}).start(); // $ Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/stdbuf", "-oL", arg}).start(); // $ Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/timeout", "10", arg}).start(); // $ Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/env", "/bin/sh", "-c", arg}).start(); // $ Alert[java/command-line-injection] } public static void fixedDelegatingLauncherLiteral(String arg) throws java.io.IOException { @@ -91,7 +91,7 @@ public static void delegatingLauncherListAlerts(String arg, boolean condition) List delegatedList = new ArrayList(); delegatedList.add("/usr/bin/env"); delegatedList.add(arg); - new ProcessBuilder(delegatedList).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(delegatedList).start(); // $ Alert[java/command-line-injection] List branchingPosition = new ArrayList(); branchingPosition.add("/usr/bin/timeout"); @@ -99,7 +99,7 @@ public static void delegatingLauncherListAlerts(String arg, boolean condition) branchingPosition.add("10"); } branchingPosition.add(arg); - new ProcessBuilder(branchingPosition).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(branchingPosition).start(); // $ Alert[java/command-line-injection] List branchingLauncher = new ArrayList(); if (condition) { @@ -109,7 +109,7 @@ public static void delegatingLauncherListAlerts(String arg, boolean condition) } branchingLauncher.add("10"); branchingLauncher.add(arg); - new ProcessBuilder(branchingLauncher).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(branchingLauncher).start(); // $ Alert[java/command-line-injection] } public static void fixedDelegatingLauncherLaterListArgument(String arg) @@ -147,7 +147,7 @@ public static void delegatingLauncherArrayAlerts(String arg, boolean condition) String[] delegatedArray = new String[2]; delegatedArray[0] = "/usr/bin/env"; delegatedArray[1] = arg; - new ProcessBuilder(delegatedArray).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(delegatedArray).start(); // $ Alert[java/command-line-injection] String launcher; if (condition) { @@ -155,7 +155,7 @@ public static void delegatingLauncherArrayAlerts(String arg, boolean condition) } else { launcher = "/usr/bin/timeout"; } - new ProcessBuilder(new String[]{launcher, "10", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(new String[]{launcher, "10", arg}).start(); // $ Alert[java/command-line-injection] } public static void fixedDelegatingLauncherArray(String arg) throws java.io.IOException { @@ -167,17 +167,17 @@ public static void fixedDelegatingLauncherArray(String arg) throws java.io.IOExc public static void changedDelegatedCommand(String arg) throws java.io.IOException { String[] command = new String[]{"/usr/bin/env", "/bin/cp", "-c", arg}; command[1] = "/bin/sh"; - new ProcessBuilder(command).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(command).start(); // $ Alert[java/command-line-injection] String[] copiedInline = Arrays.copyOf(new String[]{"/usr/bin/env", "/bin/cp", "-c", arg}, 4); copiedInline[1] = "/bin/sh"; - new ProcessBuilder(copiedInline).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(copiedInline).start(); // $ Alert[java/command-line-injection] String[] base = new String[]{"/usr/bin/env", "/bin/cp", "-c", arg}; String[] copiedStored = Arrays.copyOf(base, 4); copiedStored[1] = "/bin/sh"; - new ProcessBuilder(copiedStored).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(copiedStored).start(); // $ Alert[java/command-line-injection] } public static String[] returnedNonShellCommand(String arg) { @@ -197,7 +197,7 @@ public static void useReturnedNonShellCommand(String arg) throws java.io.IOExcep } public static void unknownDelegatedCommand(String arg, String command) throws java.io.IOException { - new ProcessBuilder(new String[]{"/usr/bin/env", command, "-c", arg}).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(new String[]{"/usr/bin/env", command, "-c", arg}).start(); // $ Alert[java/command-line-injection] } public static void unsupportedEnvAssignment(String arg) throws java.io.IOException { @@ -212,7 +212,7 @@ public static void changedDelegatedCommandThroughAlias(String arg) throws java.i String[] command = new String[]{"/usr/bin/env", "/bin/cp", "-c", arg}; String[] alias = command; alias[1] = "/bin/sh"; - new ProcessBuilder(command).start(); // $ MISSING: Alert[java/command-line-injection] + new ProcessBuilder(command).start(); // $ Alert[java/command-line-injection] } public static void relativeCommand() throws java.io.IOException {