1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.analyzer;
19
20 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
21 import java.io.File;
22 import java.io.FileFilter;
23 import java.io.IOException;
24 import org.owasp.dependencycheck.Engine;
25 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
26 import org.owasp.dependencycheck.dependency.Dependency;
27 import org.owasp.dependencycheck.exception.InitializationException;
28 import org.owasp.dependencycheck.utils.FileFilterBuilder;
29 import org.owasp.dependencycheck.utils.Settings;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 import java.util.ArrayList;
34 import java.util.List;
35 import javax.json.stream.JsonParsingException;
36 import org.apache.commons.lang3.StringUtils;
37 import org.owasp.dependencycheck.data.nvd.ecosystem.Ecosystem;
38 import org.owasp.dependencycheck.processing.GoModProcessor;
39 import org.owasp.dependencycheck.utils.processing.ProcessReader;
40
41
42
43
44
45
46 @Experimental
47 public class GolangModAnalyzer extends AbstractFileTypeAnalyzer {
48
49
50
51
52 private static final Logger LOGGER = LoggerFactory.getLogger(GolangModAnalyzer.class);
53
54
55
56
57
58 public static final String DEPENDENCY_ECOSYSTEM = Ecosystem.GOLANG;
59
60
61
62
63 private static final String ANALYZER_NAME = "Golang Mod Analyzer";
64
65
66
67
68
69
70
71
72 public static final String GO_MOD = "go.mod";
73
74
75
76
77 private static String goPath = null;
78
79
80
81 private static final FileFilter GO_MOD_FILTER = FileFilterBuilder.newInstance()
82 .addFilenames(GO_MOD)
83 .build();
84
85
86
87
88
89
90 @Override
91 public String getName() {
92 return ANALYZER_NAME;
93 }
94
95
96
97
98
99
100 @Override
101 public AnalysisPhase getAnalysisPhase() {
102 return AnalysisPhase.INFORMATION_COLLECTION;
103 }
104
105
106
107
108
109
110 @Override
111 protected String getAnalyzerEnabledSettingKey() {
112 return Settings.KEYS.ANALYZER_GOLANG_MOD_ENABLED;
113 }
114
115
116
117
118
119
120 @Override
121 protected FileFilter getFileFilter() {
122 return GO_MOD_FILTER;
123 }
124
125
126
127
128
129
130 private String getGo() {
131 synchronized (this) {
132 if (goPath == null) {
133 final String path = getSettings().getString(Settings.KEYS.ANALYZER_GOLANG_PATH);
134 if (path == null) {
135 goPath = "go";
136 } else {
137 final File goFile = new File(path);
138 if (goFile.isFile()) {
139 goPath = goFile.getAbsolutePath();
140 } else {
141 LOGGER.warn("Provided path to `go` executable is invalid. Trying default location. "
142 + "If you do want to set it, please set the `{}` property",
143 Settings.KEYS.ANALYZER_GOLANG_PATH
144 );
145 goPath = "go";
146 }
147 }
148 }
149 }
150 return goPath;
151 }
152
153
154
155
156
157
158
159
160
161
162 private Process testGoMod(File folder) throws AnalysisException, IOException {
163 if (!folder.isDirectory()) {
164 throw new AnalysisException(String.format("%s should have been a directory.", folder.getAbsolutePath()));
165 }
166
167 final List<String> args = new ArrayList<>();
168 args.add(getGo());
169 args.add("mod");
170 args.add("edit");
171
172 final ProcessBuilder builder = new ProcessBuilder(args);
173 builder.directory(folder);
174 LOGGER.debug("Launching: {} from {}", args, folder);
175 return builder.start();
176 }
177
178
179
180
181
182
183
184
185 private Process launchGoListReadonly(File folder) throws AnalysisException {
186 if (!folder.isDirectory()) {
187 throw new AnalysisException(String.format("%s should have been a directory.", folder.getAbsolutePath()));
188 }
189
190 final List<String> args = new ArrayList<>();
191 args.add(getGo());
192 args.add("list");
193 args.add("-json");
194 args.add("-m");
195 args.add("-mod=readonly");
196 args.add("all");
197
198 final ProcessBuilder builder = new ProcessBuilder(args);
199 builder.directory(folder);
200 try {
201 LOGGER.debug("Launching: {} from {}", args, folder);
202 return builder.start();
203 } catch (IOException ioe) {
204 throw new AnalysisException("go initialization failure; this error can be ignored if you are not analyzing Go. "
205 + "Otherwise ensure that go is installed and the path to go is correctly specified", ioe);
206 }
207 }
208
209
210
211
212
213
214
215
216 @SuppressWarnings("fallthrough")
217 @SuppressFBWarnings(justification = "The fallthrough is intentional to avoid code duplication", value = {"SF_SWITCH_NO_DEFAULT"})
218 @Override
219 protected void prepareFileTypeAnalyzer(Engine engine) throws InitializationException {
220 setEnabled(false);
221 final File tempDirectory;
222 try {
223 tempDirectory = getSettings().getTempDirectory();
224 } catch (IOException ex) {
225 throw new InitializationException("Unable to create temporary file, the Go Mod Analyzer will be disabled", ex);
226 }
227 try {
228 final Process process = testGoMod(tempDirectory);
229 try (ProcessReader processReader = new ProcessReader(process)) {
230 processReader.readAll();
231 final int exitValue = process.waitFor();
232 final int expectedNoModuleFoundExitValue = 1;
233 final int possiblyGoTooOldExitValue = 2;
234 final int goExecutableNotFoundExitValue = 127;
235
236 switch (exitValue) {
237 case expectedNoModuleFoundExitValue:
238 setEnabled(true);
239 LOGGER.debug("{} is enabled.", ANALYZER_NAME);
240 break;
241 case goExecutableNotFoundExitValue:
242 throw new InitializationException(String.format("Go executable not found. Disabling %s: %s", ANALYZER_NAME, exitValue));
243 case possiblyGoTooOldExitValue:
244 final String error = processReader.getError();
245 if (!StringUtils.isBlank(error)) {
246 if (error.contains("unknown subcommand \"mod\"")) {
247 LOGGER.warn("Your version of `go` does not support modules. Disabling {}. Error: `{}`", ANALYZER_NAME, error);
248 throw new InitializationException("Go version does not support modules.");
249 }
250 LOGGER.warn("An error occurred calling `go` - no output could be read. Disabling {}.", ANALYZER_NAME);
251 throw new InitializationException("Error calling `go` - no output could be read.");
252 }
253
254 default:
255 final String msg = String.format("Unexpected exit code from go process. Disabling %s: %s", ANALYZER_NAME, exitValue);
256 throw new InitializationException(msg);
257 }
258 }
259 } catch (AnalysisException ae) {
260 final String msg = String.format("Exception from go process: %s. Disabling %s", ae.getCause(), ANALYZER_NAME);
261 throw new InitializationException(msg, ae);
262 } catch (InterruptedException ex) {
263 final String msg = String.format("Go mod process was interrupted. Disabling %s", ANALYZER_NAME);
264 Thread.currentThread().interrupt();
265 throw new InitializationException(msg);
266 } catch (IOException ex) {
267 throw new RuntimeException(ex);
268 }
269 }
270
271
272
273
274
275
276
277
278
279 @Override
280 protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
281
282
283 final int exitValue;
284 final File parentFile = dependency.getActualFile().getParentFile();
285 final Process process = launchGoListReadonly(parentFile);
286 String error = null;
287 try (GoModProcessor processor = new GoModProcessor(dependency, engine);
288 ProcessReader processReader = new ProcessReader(process, processor)) {
289 processReader.readAll();
290 error = processReader.getError();
291 if (!StringUtils.isBlank(error)) {
292 LOGGER.warn("While analyzing `{}` `go` generated the following warnings:\n{}", dependency.getFilePath(), error);
293 }
294 exitValue = process.exitValue();
295 if (exitValue < 0 || exitValue > 1) {
296 final String msg = String.format("Error analyzing '%s'; Unexpected exit code from go process; exit code: %s",
297 dependency.getFilePath(), exitValue);
298 throw new AnalysisException(msg);
299 }
300 } catch (InterruptedException ie) {
301 Thread.currentThread().interrupt();
302 throw new AnalysisException("go process interrupted while analyzing '" + dependency.getFilePath() + "'", ie);
303 } catch (IOException ex) {
304 throw new AnalysisException("Error closing the go process while analyzing '" + dependency.getFilePath() + "'", ex);
305 } catch (JsonParsingException ex) {
306 final String msg;
307 if (error != null) {
308 msg = String.format("Error analyzing '%s'; Unable to process output from `go list -json -m -mod=readonly all`; "
309 + "the command reported the following errors: %s", dependency.getFilePath(), error);
310 } else {
311 msg = String.format("Error analyzing '%s'; Unable to process output from `go list -json -m -mod=readonly all`; "
312 + "please validate that the command runs without errors.", dependency.getFilePath());
313 }
314 throw new AnalysisException(msg, ex);
315 }
316 }
317 }