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.BufferedReader;
21  import java.io.File;
22  import java.io.FileFilter;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
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         BufferedReader rdr = null;
119         Document doc = null;
120         try {
121             final Process proc = pb.start();
122             // Try evacuating the error stream
123             rdr = new BufferedReader(new InputStreamReader(proc.getErrorStream(), "UTF-8"));
124             String line = null;
125             // CHECKSTYLE:OFF
126             while (rdr.ready() && (line = rdr.readLine()) != null) {
127                 LOGGER.warn("Error from GrokAssembly: {}", line);
128             }
129             // CHECKSTYLE:ON
130             int rc = 0;
131             doc = builder.parse(proc.getInputStream());
132 
133             try {
134                 rc = proc.waitFor();
135             } catch (InterruptedException ie) {
136                 return;
137             }
138             if (rc == 3) {
139                 LOGGER.debug("{} is not a .NET assembly or executable and as such cannot be analyzed by dependency-check",
140                         dependency.getActualFilePath());
141                 return;
142             } else if (rc != 0) {
143                 LOGGER.warn("Return code {} from GrokAssembly", rc);
144             }
145 
146             final XPath xpath = XPathFactory.newInstance().newXPath();
147 
148             // First, see if there was an error
149             final String error = xpath.evaluate("/assembly/error", doc);
150             if (error != null && !error.isEmpty()) {
151                 throw new AnalysisException(error);
152             }
153 
154             final String version = xpath.evaluate("/assembly/version", doc);
155             if (version != null) {
156                 dependency.getVersionEvidence().addEvidence(new Evidence("grokassembly", "version",
157                         version, Confidence.HIGHEST));
158             }
159 
160             final String vendor = xpath.evaluate("/assembly/company", doc);
161             if (vendor != null) {
162                 dependency.getVendorEvidence().addEvidence(new Evidence("grokassembly", "vendor",
163                         vendor, Confidence.HIGH));
164             }
165 
166             final String product = xpath.evaluate("/assembly/product", doc);
167             if (product != null) {
168                 dependency.getProductEvidence().addEvidence(new Evidence("grokassembly", "product",
169                         product, Confidence.HIGH));
170             }
171 
172         } catch (IOException ioe) {
173             throw new AnalysisException(ioe);
174         } catch (SAXException saxe) {
175             throw new AnalysisException("Couldn't parse GrokAssembly result", saxe);
176         } catch (XPathExpressionException xpe) {
177             // This shouldn't happen
178             throw new AnalysisException(xpe);
179         } finally {
180             if (rdr != null) {
181                 try {
182                     rdr.close();
183                 } catch (IOException ex) {
184                     LOGGER.debug("ignore", ex);
185                 }
186             }
187         }
188     }
189 
190     /**
191      * Initialize the analyzer. In this case, extract GrokAssembly.exe to a temporary location.
192      *
193      * @throws Exception if anything goes wrong
194      */
195     @Override
196     public void initializeFileTypeAnalyzer() throws Exception {
197         final File tempFile = File.createTempFile("GKA", ".exe", Settings.getTempDirectory());
198         FileOutputStream fos = null;
199         InputStream is = null;
200         try {
201             fos = new FileOutputStream(tempFile);
202             is = AssemblyAnalyzer.class.getClassLoader().getResourceAsStream("GrokAssembly.exe");
203             final byte[] buff = new byte[4096];
204             int bread = -1;
205             while ((bread = is.read(buff)) >= 0) {
206                 fos.write(buff, 0, bread);
207             }
208             grokAssemblyExe = tempFile;
209             // Set the temp file to get deleted when we're done
210             grokAssemblyExe.deleteOnExit();
211             LOGGER.debug("Extracted GrokAssembly.exe to {}", grokAssemblyExe.getPath());
212         } catch (IOException ioe) {
213             this.setEnabled(false);
214             LOGGER.warn("Could not extract GrokAssembly.exe: {}", ioe.getMessage());
215             throw new AnalysisException("Could not extract GrokAssembly.exe", ioe);
216         } finally {
217             if (fos != null) {
218                 try {
219                     fos.close();
220                 } catch (Throwable e) {
221                     LOGGER.debug("Error closing output stream");
222                 }
223             }
224             if (is != null) {
225                 try {
226                     is.close();
227                 } catch (Throwable e) {
228                     LOGGER.debug("Error closing input stream");
229                 }
230             }
231         }
232 
233         // Now, need to see if GrokAssembly actually runs from this location.
234         final List<String> args = buildArgumentList();
235         BufferedReader rdr = null;
236         try {
237             final ProcessBuilder pb = new ProcessBuilder(args);
238             final Process p = pb.start();
239             // Try evacuating the error stream
240             rdr = new BufferedReader(new InputStreamReader(p.getErrorStream(), "UTF-8"));
241             // CHECKSTYLE:OFF
242             while (rdr.ready() && rdr.readLine() != null) {
243                 // We expect this to complain
244             }
245             // CHECKSTYLE:ON
246             final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(p.getInputStream());
247             final XPath xpath = XPathFactory.newInstance().newXPath();
248             final String error = xpath.evaluate("/assembly/error", doc);
249             if (p.waitFor() != 1 || error == null || error.isEmpty()) {
250                 LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer, please see the log for more details.");
251                 LOGGER.debug("GrokAssembly.exe is not working properly");
252                 grokAssemblyExe = null;
253                 this.setEnabled(false);
254                 throw new AnalysisException("Could not execute .NET AssemblyAnalyzer");
255             }
256         } catch (Throwable e) {
257             if (e instanceof AnalysisException) {
258                 throw (AnalysisException) e;
259             } else {
260                 LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer;\n"
261                         + "this can be ignored unless you are scanning .NET DLLs. Please see the log for more details.");
262                 LOGGER.debug("Could not execute GrokAssembly {}", e.getMessage());
263                 this.setEnabled(false);
264                 throw new AnalysisException("An error occured with the .NET AssemblyAnalyzer", e);
265             }
266         } finally {
267             if (rdr != null) {
268                 try {
269                     rdr.close();
270                 } catch (IOException ex) {
271                     LOGGER.trace("ignore", ex);
272                 }
273             }
274         }
275         builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
276     }
277 
278     /**
279      * Removes resources used from the local file system.
280      *
281      * @throws Exception thrown if there is a problem closing the analyzer
282      */
283     @Override
284     public void close() throws Exception {
285         super.close();
286         try {
287             if (grokAssemblyExe != null && !grokAssemblyExe.delete()) {
288                 grokAssemblyExe.deleteOnExit();
289             }
290         } catch (SecurityException se) {
291             LOGGER.debug("Can't delete temporary GrokAssembly.exe");
292         }
293     }
294 
295     /**
296      * The File Filter used to filter supported extensions.
297      */
298     private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(
299             SUPPORTED_EXTENSIONS).build();
300 
301     @Override
302     protected FileFilter getFileFilter() {
303         return FILTER;
304     }
305 
306     /**
307      * Gets this analyzer's name.
308      *
309      * @return the analyzer name
310      */
311     @Override
312     public String getName() {
313         return ANALYZER_NAME;
314     }
315 
316     /**
317      * Returns the phase this analyzer runs under.
318      *
319      * @return the phase this runs under
320      */
321     @Override
322     public AnalysisPhase getAnalysisPhase() {
323         return ANALYSIS_PHASE;
324     }
325 
326     /**
327      * Returns the key used in the properties file to reference the analyzer's enabled property.
328      *
329      * @return the analyzer's enabled property setting key
330      */
331     @Override
332     protected String getAnalyzerEnabledSettingKey() {
333         return Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED;
334     }
335 }