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