diff --git a/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.ql b/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.ql index afa675c7f7b2..db17a7d19faa 100644 --- a/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.ql +++ b/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.ql @@ -26,6 +26,51 @@ predicate saneString(Expr expr) { or expr instanceof NullLiteral or + expr instanceof IntegerLiteral + or + expr instanceof LongLiteral + or + expr instanceof BooleanLiteral + or + // Enum constants are compile-time known + expr.(FieldAccess).getField().getDeclaringType() instanceof EnumType + or + // File.separator, File.pathSeparator + exists(Field f | expr = f.getAnAccess() | + f.getDeclaringType().hasQualifiedName("java.io", "File") and + f.getName() in ["separator", "pathSeparator", "separatorChar", "pathSeparatorChar"] + ) + or + // System.getProperty("literal") - system properties are not user-controlled + exists(MethodCall mc | mc = expr | + mc.getMethod().hasQualifiedName("java.lang", "System", "getProperty") and + mc.getArgument(0) instanceof StringLiteral + ) + or + // Class.getName(), getCanonicalName(), getSimpleName() + exists(MethodCall mc | mc = expr | + mc.getMethod().getDeclaringType().hasQualifiedName("java.lang", "Class") and + mc.getMethod().getName() in ["getName", "getCanonicalName", "getSimpleName"] + ) + or + // Integer.toString(literal), String.valueOf(literal) - type conversion of safe values + exists(MethodCall mc | mc = expr | + ( + mc.getMethod().hasName("toString") and + mc.getMethod().isStatic() and + mc.getMethod().getDeclaringType().getASupertype*().hasQualifiedName("java.lang", "Number") + or + mc.getMethod().hasQualifiedName("java.lang", "String", "valueOf") + ) and + forall(Expr arg | arg = mc.getAnArgument() | saneString(arg)) + ) + or + // Concatenation of sane strings + exists(AddExpr add | add = expr | + saneString(add.getLeftOperand()) and saneString(add.getRightOperand()) + ) + or + // Variable tracking exists(Variable var | var.getAnAccess() = expr and exists(var.getAnAssignedValue()) | forall(Expr other | var.getAnAssignedValue() = other | saneString(other)) ) diff --git a/java/ql/src/change-notes/2025-06-28-exec-unescaped-reduce-fps.md b/java/ql/src/change-notes/2025-06-28-exec-unescaped-reduce-fps.md new file mode 100644 index 000000000000..05403db304d7 --- /dev/null +++ b/java/ql/src/change-notes/2025-06-28-exec-unescaped-reduce-fps.md @@ -0,0 +1,4 @@ +--- +category: majorAnalysis +--- +* The `java/concatenated-command-line` query now recognizes additional safe string patterns including `System.getProperty()` calls with literal keys, `File.separator` constants, `Class.getName()` calls, enum constants, and numeric literals. This reduces false positives where non-user-controlled values are concatenated into command lines.