io.codemodder.codemods.SemgrepOverlyPermissiveFilePermissionsCodemod Maven / Gradle / Ivy
Show all versions of core-codemods Show documentation
package io.codemodder.codemods;
import static io.codemodder.javaparser.ASTExpectations.expect;
import com.contrastsecurity.sarif.Result;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.FieldAccessExpr;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.ast.stmt.ExpressionStmt;
import io.codemodder.*;
import io.codemodder.codetf.DetectorRule;
import io.codemodder.javaparser.ChangesResult;
import io.codemodder.providers.sarif.semgrep.ProvidedSemgrepScan;
import io.codemodder.providers.sarif.semgrep.SemgrepSarifJavaParserChanger;
import java.util.Optional;
import javax.inject.Inject;
@Codemod(
id =
"semgrep:java/java.lang.security.audit.overly-permissive-file-permission.overly-permissive-file-permission",
reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW,
importance = Importance.MEDIUM,
executionPriority = CodemodExecutionPriority.HIGH)
public final class SemgrepOverlyPermissiveFilePermissionsCodemod
extends CompositeJavaParserChanger {
@Inject
public SemgrepOverlyPermissiveFilePermissionsCodemod(
@ProvidedSemgrepScan(ruleId = RULE_ID) final RuleSarif sarif) {
super(
new PermissionAddCallChanger(sarif),
new FromStringChanger(sarif),
new InlineFromStringChanger(sarif));
}
/**
* This handles the case where the permission is added inline to {@code setPosixFilePermissions()}
* like:
*
* {@code Files.setPosixFilePermissions(startupScript,
* PosixFilePermissions.fromString("rwxrwxrwx"));}
*/
private static final class InlineFromStringChanger
extends SemgrepSarifJavaParserChanger {
private InlineFromStringChanger(final RuleSarif sarif) {
super(
sarif,
ExpressionStmt.class,
RegionNodeMatcher.MATCHES_START,
CodemodReporterStrategy.empty());
}
@Override
public DetectorRule detectorRule() {
return new DetectorRule(RULE_ID, RULE_NAME, RULE_URL);
}
@Override
public ChangesResult onResultFound(
final CodemodInvocationContext context,
final CompilationUnit cu,
final ExpressionStmt stmt,
final Result result) {
Optional setPosixFilePermissionsRef =
expect(stmt)
.toBeExpressionStatement()
.withMethodCallExpression()
.withName("setPosixFilePermissions")
.withArgumentsSize(2)
.result();
if (setPosixFilePermissionsRef.isPresent()) {
MethodCallExpr setPosixFilePermissions = setPosixFilePermissionsRef.get();
Optional fromStringRef =
expect(setPosixFilePermissions.getArgument(1))
.toBeMethodCallExpression()
.withName("fromString")
.withArgumentsSize(1)
.result();
if (fromStringRef.isPresent()) {
return fixFromString(fromStringRef.get());
}
}
return ChangesResult.noChanges;
}
}
/**
* This handles the case where permissions are created from UNIX permission strings like:
*
* {@code var p = Permissions.fromString("rwxrwxrwx")}
*/
private static final class FromStringChanger
extends SemgrepSarifJavaParserChanger {
private FromStringChanger(final RuleSarif sarif) {
super(
sarif,
ExpressionStmt.class,
RegionNodeMatcher.MATCHES_START,
CodemodReporterStrategy.empty());
}
@Override
public ChangesResult onResultFound(
final CodemodInvocationContext context,
final CompilationUnit cu,
final ExpressionStmt fromStringStmt,
final Result result) {
Optional fromStringMethodRef =
expect(fromStringStmt)
.toBeExpressionStatement()
.withSingleVariableDeclarationExpression()
.toBeInitializedByMethodCall()
.withArgumentsSize(1)
.withName("fromString")
.result();
if (fromStringMethodRef.isPresent()) {
return fixFromString(fromStringMethodRef.get());
}
return ChangesResult.noChanges;
}
@Override
public DetectorRule detectorRule() {
return new DetectorRule(RULE_ID, RULE_NAME, RULE_URL);
}
}
private static ChangesResult fixFromString(final MethodCallExpr fromStringCall) {
NodeList arguments = fromStringCall.getArguments();
if (arguments.size() == 1 && arguments.get(0).isStringLiteralExpr()) {
StringLiteralExpr permissionValue = arguments.get(0).asStringLiteralExpr();
String previousPermission = permissionValue.getValue();
if (previousPermission.length() == 9) {
String newPermission = previousPermission.substring(0, 6) + "---";
arguments.set(0, new StringLiteralExpr(newPermission));
return ChangesResult.changesApplied;
}
}
return ChangesResult.noChanges;
}
/**
* This handles the case where the permission is added to a set via {@link
* java.util.Set#add(Object)} call.
*/
private static final class PermissionAddCallChanger
extends SemgrepSarifJavaParserChanger {
private PermissionAddCallChanger(final RuleSarif sarif) {
super(
sarif,
MethodCallExpr.class,
RegionNodeMatcher.MATCHES_START,
CodemodReporterStrategy.empty());
}
@Override
public DetectorRule detectorRule() {
return new DetectorRule(RULE_ID, RULE_NAME, RULE_URL);
}
@Override
public ChangesResult onResultFound(
final CodemodInvocationContext context,
final CompilationUnit cu,
final MethodCallExpr methodCallExpr,
final Result result) {
Optional methodCall =
expect(methodCallExpr).toBeMethodCallExpression().withArguments().result();
if (methodCall.isEmpty()) {
return ChangesResult.noChanges;
}
MethodCallExpr call = methodCall.get();
String methodName = call.getNameAsString();
if ("add".equals(methodName) && call.getArguments().size() == 1) {
return fixAdd(call);
}
return ChangesResult.noChanges;
}
private ChangesResult fixAdd(final MethodCallExpr call) {
NodeList arguments = call.getArguments();
Expression permissionArgument = arguments.get(0);
if (!permissionArgument.isFieldAccessExpr()) {
return ChangesResult.noChanges;
}
FieldAccessExpr othersAccess = permissionArgument.asFieldAccessExpr();
final String newFieldName;
switch (othersAccess.getNameAsString()) {
case "OTHERS_READ" -> newFieldName = "GROUP_READ";
case "OTHERS_WRITE" -> newFieldName = "GROUP_WRITE";
case "OTHERS_EXECUTE" -> newFieldName = "GROUP_EXECUTE";
default -> {
return ChangesResult.noChanges;
}
}
othersAccess.setName(newFieldName);
return ChangesResult.changesApplied;
}
}
private static final String RULE_ID =
"java.lang.security.audit.overly-permissive-file-permission.overly-permissive-file-permission";
private static final String RULE_NAME =
"Fix overly permissive file permissions (issue discovered by Semgrep)";
private static final String RULE_URL =
"https://registry.semgrep.dev/rule/java.lang.security.audit.overly-permissive-file-permission.overly-permissive-file-permission";
}