Coverage Report - org.owasp.dependencycheck.analyzer.AssemblyAnalyzer
 
Classes in this File Line Coverage Branch Coverage Complexity
AssemblyAnalyzer
31%
47/147
21%
10/46
7
 
 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  
 import javax.xml.parsers.ParserConfigurationException;
 47  
 import org.owasp.dependencycheck.exception.InitializationException;
 48  
 import org.apache.commons.lang3.SystemUtils;
 49  
 
 50  
 /**
 51  
  * Analyzer for getting company, product, and version information from a .NET
 52  
  * assembly.
 53  
  *
 54  
  * @author colezlaw
 55  
  *
 56  
  */
 57  11
 public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer {
 58  
 
 59  
     /**
 60  
      * The analyzer name
 61  
      */
 62  
     private static final String ANALYZER_NAME = "Assembly Analyzer";
 63  
     /**
 64  
      * The analysis phase
 65  
      */
 66  1
     private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
 67  
     /**
 68  
      * The list of supported extensions
 69  
      */
 70  1
     private static final String[] SUPPORTED_EXTENSIONS = {"dll", "exe"};
 71  
     /**
 72  
      * The temp value for GrokAssembly.exe
 73  
      */
 74  11
     private File grokAssemblyExe = null;
 75  
     /**
 76  
      * The DocumentBuilder for parsing the XML
 77  
      */
 78  
     private DocumentBuilder builder;
 79  
     /**
 80  
      * Logger
 81  
      */
 82  1
     private static final Logger LOGGER = LoggerFactory.getLogger(AssemblyAnalyzer.class);
 83  
 
 84  
     /**
 85  
      * Builds the beginnings of a List for ProcessBuilder
 86  
      *
 87  
      * @return the list of arguments to begin populating the ProcessBuilder
 88  
      */
 89  
     protected List<String> buildArgumentList() {
 90  
         // Use file.separator as a wild guess as to whether this is Windows
 91  10
         final List<String> args = new ArrayList<String>();
 92  10
         if (!SystemUtils.IS_OS_WINDOWS) {
 93  0
             if (Settings.getString(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH) != null) {
 94  0
                 args.add(Settings.getString(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH));
 95  0
             } else if (isInPath("mono")) {
 96  0
                 args.add("mono");
 97  
             } else {
 98  0
                 return null;
 99  
             }
 100  
         }
 101  10
         args.add(grokAssemblyExe.getPath());
 102  10
         return args;
 103  
     }
 104  
 
 105  
     /**
 106  
      * Performs the analysis on a single Dependency.
 107  
      *
 108  
      * @param dependency the dependency to analyze
 109  
      * @param engine the engine to perform the analysis under
 110  
      * @throws AnalysisException if anything goes sideways
 111  
      */
 112  
     @Override
 113  
     public void analyzeFileType(Dependency dependency, Engine engine)
 114  
             throws AnalysisException {
 115  0
         if (grokAssemblyExe == null) {
 116  0
             LOGGER.warn("GrokAssembly didn't get deployed");
 117  0
             return;
 118  
         }
 119  
 
 120  0
         final List<String> args = buildArgumentList();
 121  0
         if (args == null) {
 122  0
             LOGGER.warn("Assembly Analyzer was unable to execute");
 123  0
             return;
 124  
         }
 125  0
         args.add(dependency.getActualFilePath());
 126  0
         final ProcessBuilder pb = new ProcessBuilder(args);
 127  0
         Document doc = null;
 128  
         try {
 129  0
             final Process proc = pb.start();
 130  
 
 131  0
             doc = builder.parse(proc.getInputStream());
 132  
 
 133  
             // Try evacuating the error stream
 134  0
             final String errorStream = IOUtils.toString(proc.getErrorStream(), "UTF-8");
 135  0
             if (null != errorStream && !errorStream.isEmpty()) {
 136  0
                 LOGGER.warn("Error from GrokAssembly: {}", errorStream);
 137  
             }
 138  
 
 139  0
             int rc = 0;
 140  
             try {
 141  0
                 rc = proc.waitFor();
 142  0
             } catch (InterruptedException ie) {
 143  0
                 return;
 144  0
             }
 145  0
             if (rc == 3) {
 146  0
                 LOGGER.debug("{} is not a .NET assembly or executable and as such cannot be analyzed by dependency-check",
 147  0
                         dependency.getActualFilePath());
 148  0
                 return;
 149  0
             } else if (rc != 0) {
 150  0
                 LOGGER.warn("Return code {} from GrokAssembly", rc);
 151  
             }
 152  
 
 153  0
             final XPath xpath = XPathFactory.newInstance().newXPath();
 154  
 
 155  
             // First, see if there was an error
 156  0
             final String error = xpath.evaluate("/assembly/error", doc);
 157  0
             if (error != null && !error.isEmpty()) {
 158  0
                 throw new AnalysisException(error);
 159  
             }
 160  
 
 161  0
             final String version = xpath.evaluate("/assembly/version", doc);
 162  0
             if (version != null) {
 163  0
                 dependency.getVersionEvidence().addEvidence(new Evidence("grokassembly", "version",
 164  
                         version, Confidence.HIGHEST));
 165  
             }
 166  
 
 167  0
             final String vendor = xpath.evaluate("/assembly/company", doc);
 168  0
             if (vendor != null) {
 169  0
                 dependency.getVendorEvidence().addEvidence(new Evidence("grokassembly", "vendor",
 170  
                         vendor, Confidence.HIGH));
 171  
             }
 172  
 
 173  0
             final String product = xpath.evaluate("/assembly/product", doc);
 174  0
             if (product != null) {
 175  0
                 dependency.getProductEvidence().addEvidence(new Evidence("grokassembly", "product",
 176  
                         product, Confidence.HIGH));
 177  
             }
 178  
 
 179  0
         } catch (IOException ioe) {
 180  0
             throw new AnalysisException(ioe);
 181  0
         } catch (SAXException saxe) {
 182  0
             throw new AnalysisException("Couldn't parse GrokAssembly result", saxe);
 183  0
         } catch (XPathExpressionException xpe) {
 184  
             // This shouldn't happen
 185  0
             throw new AnalysisException(xpe);
 186  0
         }
 187  0
     }
 188  
 
 189  
     /**
 190  
      * Initialize the analyzer. In this case, extract GrokAssembly.exe to a
 191  
      * temporary location.
 192  
      *
 193  
      * @throws InitializationException thrown if anything goes wrong
 194  
      */
 195  
     @Override
 196  
     public void initializeFileTypeAnalyzer() throws InitializationException {
 197  
         final File tempFile;
 198  
         try {
 199  5
             tempFile = File.createTempFile("GKA", ".exe", Settings.getTempDirectory());
 200  0
         } catch (IOException ex) {
 201  0
             setEnabled(false);
 202  0
             throw new InitializationException("Unable to create temporary file for the assembly analyzerr", ex);
 203  5
         }
 204  5
         FileOutputStream fos = null;
 205  5
         InputStream is = null;
 206  
         try {
 207  5
             fos = new FileOutputStream(tempFile);
 208  5
             is = AssemblyAnalyzer.class.getClassLoader().getResourceAsStream("GrokAssembly.exe");
 209  5
             IOUtils.copy(is, fos);
 210  
 
 211  5
             grokAssemblyExe = tempFile;
 212  5
             LOGGER.debug("Extracted GrokAssembly.exe to {}", grokAssemblyExe.getPath());
 213  0
         } catch (IOException ioe) {
 214  0
             this.setEnabled(false);
 215  0
             LOGGER.warn("Could not extract GrokAssembly.exe: {}", ioe.getMessage());
 216  0
             throw new InitializationException("Could not extract GrokAssembly.exe", ioe);
 217  
         } finally {
 218  5
             if (fos != null) {
 219  
                 try {
 220  5
                     fos.close();
 221  0
                 } catch (Throwable e) {
 222  0
                     LOGGER.debug("Error closing output stream");
 223  5
                 }
 224  
             }
 225  5
             if (is != null) {
 226  
                 try {
 227  5
                     is.close();
 228  0
                 } catch (Throwable e) {
 229  0
                     LOGGER.debug("Error closing input stream");
 230  5
                 }
 231  
             }
 232  
         }
 233  
 
 234  
         // Now, need to see if GrokAssembly actually runs from this location.
 235  5
         final List<String> args = buildArgumentList();
 236  
         //TODO this creaes an "unreported" error - if someone doesn't look
 237  
         // at the command output this could easily be missed (especially in an
 238  
         // Ant or Mmaven build.
 239  
         //
 240  
         // We need to create a non-fatal warning error type that will
 241  
         // get added to the report.
 242  
         //TOOD this idea needs to get replicated to the bundle audit analyzer.
 243  5
         if (args == null) {
 244  0
             setEnabled(false);
 245  0
             LOGGER.error("----------------------------------------------------");
 246  0
             LOGGER.error(".NET Assembly Analyzer could not be initialized and at least one "
 247  
                     + "'exe' or 'dll' was scanned. The 'mono' executale could not be found on "
 248  
                     + "the path; either disable the Assembly Analyzer or configure the path mono.");
 249  0
             LOGGER.error("----------------------------------------------------");
 250  0
             return;
 251  
         }
 252  
         try {
 253  5
             final ProcessBuilder pb = new ProcessBuilder(args);
 254  5
             final Process p = pb.start();
 255  
             // Try evacuating the error stream
 256  5
             IOUtils.copy(p.getErrorStream(), NullOutputStream.NULL_OUTPUT_STREAM);
 257  
 
 258  5
             final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(p.getInputStream());
 259  5
             final XPath xpath = XPathFactory.newInstance().newXPath();
 260  5
             final String error = xpath.evaluate("/assembly/error", doc);
 261  5
             if (p.waitFor() != 1 || error == null || error.isEmpty()) {
 262  0
                 LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer, please see the log for more details.");
 263  0
                 LOGGER.debug("GrokAssembly.exe is not working properly");
 264  0
                 grokAssemblyExe = null;
 265  0
                 setEnabled(false);
 266  0
                 throw new InitializationException("Could not execute .NET AssemblyAnalyzer");
 267  
             }
 268  0
         } catch (InitializationException e) {
 269  0
             setEnabled(false);
 270  0
             throw e;
 271  0
         } catch (Throwable e) {
 272  0
             LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer;\n"
 273  
                     + "this can be ignored unless you are scanning .NET DLLs. Please see the log for more details.");
 274  0
             LOGGER.debug("Could not execute GrokAssembly {}", e.getMessage());
 275  0
             setEnabled(false);
 276  0
             throw new InitializationException("An error occurred with the .NET AssemblyAnalyzer", e);
 277  5
         }
 278  
         try {
 279  5
             builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 280  0
         } catch (ParserConfigurationException ex) {
 281  0
             setEnabled(false);
 282  0
             throw new InitializationException("Error initializing the assembly analyzer", ex);
 283  5
         }
 284  5
     }
 285  
 
 286  
     /**
 287  
      * Removes resources used from the local file system.
 288  
      *
 289  
      * @throws Exception thrown if there is a problem closing the analyzer
 290  
      */
 291  
     @Override
 292  
     public void close() throws Exception {
 293  7
         super.close();
 294  
         try {
 295  7
             if (grokAssemblyExe != null && !grokAssemblyExe.delete()) {
 296  0
                 LOGGER.debug("Unable to delete temporary GrokAssembly.exe; attempting delete on exit");
 297  0
                 grokAssemblyExe.deleteOnExit();
 298  
             }
 299  0
         } catch (SecurityException se) {
 300  0
             LOGGER.debug("Can't delete temporary GrokAssembly.exe");
 301  0
             grokAssemblyExe.deleteOnExit();
 302  7
         }
 303  7
     }
 304  
 
 305  
     /**
 306  
      * The File Filter used to filter supported extensions.
 307  
      */
 308  2
     private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(
 309  1
             SUPPORTED_EXTENSIONS).build();
 310  
 
 311  
     @Override
 312  
     protected FileFilter getFileFilter() {
 313  864
         return FILTER;
 314  
     }
 315  
 
 316  
     /**
 317  
      * Gets this analyzer's name.
 318  
      *
 319  
      * @return the analyzer name
 320  
      */
 321  
     @Override
 322  
     public String getName() {
 323  16
         return ANALYZER_NAME;
 324  
     }
 325  
 
 326  
     /**
 327  
      * Returns the phase this analyzer runs under.
 328  
      *
 329  
      * @return the phase this runs under
 330  
      */
 331  
     @Override
 332  
     public AnalysisPhase getAnalysisPhase() {
 333  4
         return ANALYSIS_PHASE;
 334  
     }
 335  
 
 336  
     /**
 337  
      * Returns the key used in the properties file to reference the analyzer's
 338  
      * enabled property.
 339  
      *
 340  
      * @return the analyzer's enabled property setting key
 341  
      */
 342  
     @Override
 343  
     protected String getAnalyzerEnabledSettingKey() {
 344  11
         return Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED;
 345  
     }
 346  
 
 347  
     /**
 348  
      * Tests to see if a file is in the system path. <b>Note</b> - the current
 349  
      * implementation only works on non-windows platforms. For purposes of the
 350  
      * AssemblyAnalyzer this is okay as this is only needed on Mac/*nix.
 351  
      *
 352  
      * @param file the executable to look for
 353  
      * @return <code>true</code> if the file exists; otherwise
 354  
      * <code>false</code>
 355  
      */
 356  
     private boolean isInPath(String file) {
 357  0
         final ProcessBuilder pb = new ProcessBuilder("which", file);
 358  
         try {
 359  0
             final Process proc = pb.start();
 360  0
             final int retCode = proc.waitFor();
 361  0
             if (retCode == 0) {
 362  0
                 return true;
 363  
             }
 364  0
         } catch (IOException ex) {
 365  0
             LOGGER.debug("Path seach failed for " + file);
 366  0
         } catch (InterruptedException ex) {
 367  0
             LOGGER.debug("Path seach failed for " + file);
 368  0
         }
 369  0
         return false;
 370  
     }
 371  
 }