Skip to content

Commit f3e481e

Browse files
petertrrsonartech
authored andcommitted
SONARIAC-2495 Fix NPE in ShellFormOverExecFormCheck (#659)
GitOrigin-RevId: 7a6bdc8323038202150323d2e3c9cb67d8ebec7b
1 parent 614ec72 commit f3e481e

File tree

2 files changed

+56
-11
lines changed

2 files changed

+56
-11
lines changed

iac-extensions/docker/src/main/java/org/sonar/iac/docker/checks/ShellFormOverExecFormCheck.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import org.sonar.iac.common.api.checks.CheckContext;
2424
import org.sonar.iac.common.api.checks.IacCheck;
2525
import org.sonar.iac.common.api.checks.InitContext;
26-
import org.sonar.iac.common.api.tree.Tree;
2726
import org.sonar.iac.docker.checks.utils.MultiStageBuildInspector;
2827
import org.sonar.iac.docker.tree.api.Body;
2928
import org.sonar.iac.docker.tree.api.CmdInstruction;
@@ -33,7 +32,6 @@
3332
import org.sonar.iac.docker.tree.api.EntrypointInstruction;
3433
import org.sonar.iac.docker.tree.api.ShellCode;
3534
import org.sonar.iac.docker.tree.api.ShellInstruction;
36-
import org.sonar.iac.docker.tree.api.Variable;
3735

3836
import static org.sonar.iac.docker.tree.TreeUtils.getDockerImageName;
3937
import static org.sonar.iac.docker.tree.TreeUtils.getParentDockerImageName;
@@ -43,7 +41,10 @@ public class ShellFormOverExecFormCheck implements IacCheck {
4341

4442
private static final String DEFAULT_MESSAGE = "Replace this shell form with exec form.";
4543
private static final String WRAPPING_SCRIPT_MESSAGE = "Consider wrapping this instruction in a script file and call it with exec form.";
46-
private static final Pattern UNSUPPORTED_FEATURES_IN_EXEC_FORM = Pattern.compile("&&|\\|\\||\\||;");
44+
/**
45+
* Because exec form doesn't create a shell, it does not support chaining commands or using variables.
46+
*/
47+
private static final Pattern UNSUPPORTED_FEATURES_IN_EXEC_FORM = Pattern.compile("&&|\\|\\||\\||;|\\$");
4748

4849
private final ShellInstructionsInfo checkContext = new ShellInstructionsInfo();
4950
private MultiStageBuildInspector multiStageBuildInspector = null;
@@ -84,8 +85,8 @@ private void checkCommandInstructionForm(CheckContext ctx, CodeInstruction instr
8485
}
8586
}
8687

87-
private boolean hasAnyParentDockerImageWithShellInstruction(CodeInstruction transferInstruction) {
88-
return getParentDockerImageName(transferInstruction)
88+
private boolean hasAnyParentDockerImageWithShellInstruction(CodeInstruction instruction) {
89+
return getParentDockerImageName(instruction)
8990
.map((String parentDockerImageName) -> {
9091
if (hasShellInstruction(parentDockerImageName)) {
9192
return true;
@@ -101,11 +102,11 @@ private boolean hasShellInstruction(String imageName) {
101102
}
102103

103104
private static boolean containFeatureNotSupportedByExecForm(ShellCode<?> code) {
104-
return UNSUPPORTED_FEATURES_IN_EXEC_FORM.matcher(code.originalSourceCode()).find();
105-
}
106-
107-
private static boolean hasVariableReference(Tree tree) {
108-
return tree instanceof Variable || tree.children().stream().anyMatch(ShellFormOverExecFormCheck::hasVariableReference);
105+
var originalCode = code.originalSourceCode();
106+
if (originalCode != null) {
107+
return UNSUPPORTED_FEATURES_IN_EXEC_FORM.matcher(originalCode).find();
108+
}
109+
return false;
109110
}
110111

111112
static class ShellInstructionsInfo {

iac-extensions/docker/src/test/resources/checks/ShellFormOverExecFormCheck/dockerfile

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,27 @@ HEALTHCHECK --interval=30s CMD node healthcheck.js
1919

2020
HEALTHCHECK CMD curl --fail http://localhost:8080/healthcheck || exit 1
2121

22+
# Noncompliant@+1 {{Consider wrapping this instruction in a script file and call it with exec form.}}
23+
CMD echo $message
24+
25+
# Noncompliant@+1 {{Consider wrapping this instruction in a script file and call it with exec form.}}
26+
CMD echo ${message-default}
27+
28+
# Noncompliant@+1 {{Consider wrapping this instruction in a script file and call it with exec form.}}
29+
CMD echo ${message:=default}
30+
31+
# Noncompliant@+1 {{Consider wrapping this instruction in a script file and call it with exec form.}}
32+
CMD echo ${message:+var}
33+
34+
# Noncompliant@+1 {{Consider wrapping this instruction in a script file and call it with exec form.}}
35+
CMD echo ${message:7}
36+
37+
# Noncompliant@+1 {{Consider wrapping this instruction in a script file and call it with exec form.}}
38+
CMD echo ${message:7:0}
39+
40+
# Noncompliant@+1 {{Consider wrapping this instruction in a script file and call it with exec form.}}
41+
CMD echo "$message"
42+
2243
# FNs: exec form with explicit shell invocation are not supported
2344
CMD ["sh", "-c", "echo $message"]
2445
CMD ["sh", "-c", "echo ${message-default}"]
@@ -34,6 +55,21 @@ CMD ["sh", "-c", "echo \"Welcome\" ; echo \"Goodbye\""]
3455
CMD ["sh", "-c", "echo \"Welcome\" | echo \"Goodbye\""]
3556
CMD ["sh", "-c", "echo \"Welcome\" && echo \"Goodbye\" || echo \"Goodbye\" ; echo \"Goodbye\" | echo \"Goodbye\""]
3657

58+
# Noncompliant@+1 {{Consider wrapping this instruction in a script file and call it with exec form.}}
59+
CMD echo "Welcome" && echo "Goodbye"
60+
61+
# Noncompliant@+1 {{Consider wrapping this instruction in a script file and call it with exec form.}}
62+
CMD echo "Welcome" || echo "Goodbye"
63+
64+
# Noncompliant@+1 {{Consider wrapping this instruction in a script file and call it with exec form.}}
65+
CMD echo "Welcome" ; echo "Goodbye"
66+
67+
# Noncompliant@+1 {{Consider wrapping this instruction in a script file and call it with exec form.}}
68+
CMD echo "Welcome" | echo "Goodbye"
69+
70+
# Noncompliant@+1 {{Consider wrapping this instruction in a script file and call it with exec form.}}
71+
CMD echo "Welcome" && echo "Goodbye" || echo "Goodbye" ; echo "Goodbye" | echo "Goodbye"
72+
3773
# Noncompliant@+1
3874
CMD this is a very \
3975
long multi-line \
@@ -43,13 +79,14 @@ CMD this is a very \
4379
CMD this is a very long instruction that is settled on a single line aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
4480
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4581

46-
4782
RUN echo "Welcome!"
4883
CMD ["echo", "Welcome!"]
4984
ENTRYPOINT ["echo", "Welcome!"]
5085

5186
SHELL ["sh", "-c"]
5287
# Compliant: if we met a SHELL instruction before, then we consider it's a conscious decision and don't raise an issue
88+
CMD echo "Welcome!"
89+
CMD echo "Welcome" && echo "Goodbye" || echo "Goodbye" ; echo "Goodbye" | echo "Goodbye"
5390
CMD ["echo", "\"Welcome!\""]
5491
CMD ["sh", "-c", "echo \"Welcome\" && echo \"Goodbye\" || echo \"Goodbye\" ; echo \"Goodbye\" | echo \"Goodbye\""]
5592

@@ -82,3 +119,10 @@ FROM scratch
82119
SHELL ["sh", "-c"]
83120
# Compliant: extra use case with a SHELL instruction in an image without alias
84121
CMD ["echo", "\"Welcome!\""]
122+
123+
FROM scratch AS builder2
124+
# Invalid exec form: no quotes around python. Parsed as shell form.
125+
# Noncompliant@+1
126+
CMD [python, /usr/src/app/app.py]
127+
# Noncompliant@+1
128+
ENTRYPOINT [top, -b]

0 commit comments

Comments
 (0)