View Javadoc
1   /*
2    * This file is part of dependency-check-core.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.analyzer;
19  
20  import java.io.File;
21  import java.io.FileFilter;
22  import java.io.FileOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import org.apache.commons.io.IOUtils;
26  import org.apache.commons.io.output.NullOutputStream;
27  import org.owasp.dependencycheck.Engine;
28  import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
29  import org.owasp.dependencycheck.dependency.Confidence;
30  import org.owasp.dependencycheck.dependency.Dependency;
31  import org.owasp.dependencycheck.dependency.Evidence;
32  import org.owasp.dependencycheck.utils.FileFilterBuilder;
33  import org.owasp.dependencycheck.utils.Settings;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  import org.w3c.dom.Document;
37  import org.xml.sax.SAXException;
38  
39  import javax.xml.parsers.DocumentBuilder;
40  import javax.xml.parsers.DocumentBuilderFactory;
41  import javax.xml.xpath.XPath;
42  import javax.xml.xpath.XPathExpressionException;
43  import javax.xml.xpath.XPathFactory;
44  import java.util.ArrayList;
45  import java.util.List;
46  
47  /**
48   * Analyzer for getting company, product, and version information from a .NET assembly.
49   *
50   * @author colezlaw
51   *
52   */
53  public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer {
54  
55      /**
56       * The analyzer name
57       */
58      private static final String ANALYZER_NAME = "Assembly Analyzer";
59      /**
60       * The analysis phase
61       */
62      private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
63      /**
64       * The list of supported extensions
65       */
66      private static final String[] SUPPORTED_EXTENSIONS = {"dll", "exe"};
67      /**
68       * The temp value for GrokAssembly.exe
69       */
70      private File grokAssemblyExe = null;
71      /**
72       * The DocumentBuilder for parsing the XML
73       */
74      private DocumentBuilder builder;
75      /**
76       * Logger
77       */
78      private static final Logger LOGGER = LoggerFactory.getLogger(AssemblyAnalyzer.class);
79  
80      /**
81       * Builds the beginnings of a List for ProcessBuilder
82       *
83       * @return the list of arguments to begin populating the ProcessBuilder
84       */
85      private List<String> buildArgumentList() {
86          // Use file.separator as a wild guess as to whether this is Windows
87          final List<String> args = new ArrayList<String>();
88          if (!"\\".equals(System.getProperty("file.separator"))) {
89              if (Settings.getString(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH) != null) {
90                  args.add(Settings.getString(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH));
91              } else {
92                  args.add("mono");
93              }
94          }
95          args.add(grokAssemblyExe.getPath());
96  
97          return args;
98      }
99  
100     /**
101      * Performs the analysis on a single Dependency.
102      *
103      * @param dependency the dependency to analyze
104      * @param engine the engine to perform the analysis under
105      * @throws AnalysisException if anything goes sideways
106      */
107     @Override
108     public void analyzeFileType(Dependency dependency, Engine engine)
109             throws AnalysisException {
110         if (grokAssemblyExe == null) {
111             LOGGER.warn("GrokAssembly didn't get deployed");
112             return;
113         }
114 
115         final List<String> args = buildArgumentList();
116         args.add(dependency.getActualFilePath());
117         final ProcessBuilder pb = new ProcessBuilder(args);
118         Document doc = null;
119         try {
120             final Process proc = pb.start();
121 
122             doc = builder.parse(proc.getInputStream());
123 
124             // Try evacuating the error stream
125             final String errorStream = IOUtils.toString(proc.getErrorStream(), "UTF-8");
126             if (null != errorStream && !errorStream.isEmpty()) {
127                 LOGGER.warn("Error from GrokAssembly: {}", errorStream);
128             }
129 
130             int rc = 0;
131             try {
132                 rc = proc.waitFor();
133             } catch (InterruptedException ie) {
134                 return;
135             }
136             if (rc == 3) {
137                 LOGGER.debug("{} is not a .NET assembly or executable and as such cannot be analyzed by dependency-check",
138                         dependency.getActualFilePath());
139                 return;
140             } else if (rc != 0) {
141                 LOGGER.warn("Return code {} from GrokAssembly", rc);
142             }
143 
144             final XPath xpath = XPathFactory.newInstance().newXPath();
145 
146             // First, see if there was an error
147             final String error = xpath.evaluate("/assembly/error", doc);
148             if (error != null && !error.isEmpty()) {
149                 throw new AnalysisException(error);
150             }
151 
152             final String version = xpath.evaluate("/assembly/version", doc);
153             if (version != null) {
154                 dependency.getVersionEvidence().addEvidence(new Evidence("grokassembly", "version",
155                         version, Confidence.HIGHEST));
156             }
157 
158             final String vendor = xpath.evaluate("/assembly/company", doc);
159             if (vendor != null) {
160                 dependency.getVendorEvidence().addEvidence(new Evidence("grokassembly", "vendor",
161                         vendor, Confidence.HIGH));
162             }
163 
164             final String product = xpath.evaluate("/assembly/product", doc);
165             if (product != null) {
166                 dependency.getProductEvidence().addEvidence(new Evidence("grokassembly", "product",
167                         product, Confidence.HIGH));
168             }
169 
170         } catch (IOException ioe) {
171             throw new AnalysisException(ioe);
172         } catch (SAXException saxe) {
173             throw new AnalysisException("Couldn't parse GrokAssembly result", saxe);
174         } catch (XPathExpressionException xpe) {
175             // This shouldn't happen
176             throw new AnalysisException(xpe);
177         }
178     }
179 
180     /**
181      * Initialize the analyzer. In this case, extract GrokAssembly.exe to a temporary location.
182      *
183      * @throws Exception if anything goes wrong
184      */
185     @Override
186     public void initializeFileTypeAnalyzer() throws Exception {
187         final File tempFile = File.createTempFile("GKA", ".exe", Settings.getTempDirectory());
188         FileOutputStream fos = null;
189         InputStream is = null;
190         try {
191             fos = new FileOutputStream(tempFile);
192             is = AssemblyAnalyzer.class.getClassLoader().getResourceAsStream("GrokAssembly.exe");
193             IOUtils.copy(is, fos);
194 
195             grokAssemblyExe = tempFile;
196             // Set the temp file to get deleted when we're done
197             grokAssemblyExe.deleteOnExit();
198             LOGGER.debug("Extracted GrokAssembly.exe to {}", grokAssemblyExe.getPath());
199         } catch (IOException ioe) {
200             this.setEnabled(false);
201             LOGGER.warn("Could not extract GrokAssembly.exe: {}", ioe.getMessage());
202             throw new AnalysisException("Could not extract GrokAssembly.exe", ioe);
203         } finally {
204             if (fos != null) {
205                 try {
206                     fos.close();
207                 } catch (Throwable e) {
208                     LOGGER.debug("Error closing output stream");
209                 }
210             }
211             if (is != null) {
212                 try {
213                     is.close();
214                 } catch (Throwable e) {
215                     LOGGER.debug("Error closing input stream");
216                 }
217             }
218         }
219 
220         // Now, need to see if GrokAssembly actually runs from this location.
221         final List<String> args = buildArgumentList();
222         try {
223             final ProcessBuilder pb = new ProcessBuilder(args);
224             final Process p = pb.start();
225             // Try evacuating the error stream
226             IOUtils.copy(p.getErrorStream(), NullOutputStream.NULL_OUTPUT_STREAM);
227 
228             final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(p.getInputStream());
229             final XPath xpath = XPathFactory.newInstance().newXPath();
230             final String error = xpath.evaluate("/assembly/error", doc);
231             if (p.waitFor() != 1 || error == null || error.isEmpty()) {
232                 LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer, please see the log for more details.");
233                 LOGGER.debug("GrokAssembly.exe is not working properly");
234                 grokAssemblyExe = null;
235                 this.setEnabled(false);
236                 throw new AnalysisException("Could not execute .NET AssemblyAnalyzer");
237             }
238         } catch (AnalysisException e) {
239             throw e;
240         } catch (Throwable e) {
241             LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer;\n"
242                     + "this can be ignored unless you are scanning .NET DLLs. Please see the log for more details.");
243             LOGGER.debug("Could not execute GrokAssembly {}", e.getMessage());
244             this.setEnabled(false);
245             throw new AnalysisException("An error occurred with the .NET AssemblyAnalyzer", e);
246         }
247         builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
248     }
249 
250     /**
251      * Removes resources used from the local file system.
252      *
253      * @throws Exception thrown if there is a problem closing the analyzer
254      */
255     @Override
256     public void close() throws Exception {
257         super.close();
258         try {
259             if (grokAssemblyExe != null && !grokAssemblyExe.delete()) {
260                 grokAssemblyExe.deleteOnExit();
261             }
262         } catch (SecurityException se) {
263             LOGGER.debug("Can't delete temporary GrokAssembly.exe");
264         }
265     }
266 
267     /**
268      * The File Filter used to filter supported extensions.
269      */
270     private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(
271             SUPPORTED_EXTENSIONS).build();
272 
273     @Override
274     protected FileFilter getFileFilter() {
275         return FILTER;
276     }
277 
278     /**
279      * Gets this analyzer's name.
280      *
281      * @return the analyzer name
282      */
283     @Override
284     public String getName() {
285         return ANALYZER_NAME;
286     }
287 
288     /**
289      * Returns the phase this analyzer runs under.
290      *
291      * @return the phase this runs under
292      */
293     @Override
294     public AnalysisPhase getAnalysisPhase() {
295         return ANALYSIS_PHASE;
296     }
297 
298     /**
299      * Returns the key used in the properties file to reference the analyzer's enabled property.
300      *
301      * @return the analyzer's enabled property setting key
302      */
303     @Override
304     protected String getAnalyzerEnabledSettingKey() {
305         return Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED;
306     }
307 }