Coverage Report - org.owasp.dependencycheck.analyzer.AssemblyAnalyzer
 
Classes in this File Line Coverage Branch Coverage Complexity
AssemblyAnalyzer
30%
45/148
19%
9/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.xpath.XPath;
 41  
 import javax.xml.xpath.XPathExpressionException;
 42  
 import javax.xml.xpath.XPathFactory;
 43  
 import java.util.ArrayList;
 44  
 import java.util.List;
 45  
 import javax.xml.parsers.ParserConfigurationException;
 46  
 import org.owasp.dependencycheck.exception.InitializationException;
 47  
 import org.apache.commons.lang3.SystemUtils;
 48  
 import org.owasp.dependencycheck.utils.XmlUtils;
 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 analyzeDependency(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  0
             final DocumentBuilder builder = XmlUtils.buildSecureDocumentBuilder();
 127  
 
 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
             LOGGER.error("----------------------------------------------------");
 182  0
             LOGGER.error("Failed to read the Assembly Analyzer results. "
 183  
                     + "On some systems mono-runtime and mono-devel need to be installed.");
 184  0
             LOGGER.error("----------------------------------------------------");
 185  0
             throw new AnalysisException("Couldn't parse Assembly Analzyzer results (GrokAssembly)", saxe);
 186  0
         } catch (XPathExpressionException xpe) {
 187  
             // This shouldn't happen
 188  0
             throw new AnalysisException(xpe);
 189  0
         }
 190  0
     }
 191  
 
 192  
     /**
 193  
      * Initialize the analyzer. In this case, extract GrokAssembly.exe to a
 194  
      * temporary location.
 195  
      *
 196  
      * @throws InitializationException thrown if anything goes wrong
 197  
      */
 198  
     @Override
 199  
     public void initializeFileTypeAnalyzer() throws InitializationException {
 200  
         final File tempFile;
 201  
         try {
 202  5
             tempFile = File.createTempFile("GKA", ".exe", Settings.getTempDirectory());
 203  0
         } catch (IOException ex) {
 204  0
             setEnabled(false);
 205  0
             throw new InitializationException("Unable to create temporary file for the assembly analyzerr", ex);
 206  5
         }
 207  5
         FileOutputStream fos = null;
 208  5
         InputStream is = null;
 209  
         try {
 210  5
             fos = new FileOutputStream(tempFile);
 211  5
             is = AssemblyAnalyzer.class.getClassLoader().getResourceAsStream("GrokAssembly.exe");
 212  5
             IOUtils.copy(is, fos);
 213  
 
 214  5
             grokAssemblyExe = tempFile;
 215  5
             LOGGER.debug("Extracted GrokAssembly.exe to {}", grokAssemblyExe.getPath());
 216  0
         } catch (IOException ioe) {
 217  0
             this.setEnabled(false);
 218  0
             LOGGER.warn("Could not extract GrokAssembly.exe: {}", ioe.getMessage());
 219  0
             throw new InitializationException("Could not extract GrokAssembly.exe", ioe);
 220  
         } finally {
 221  5
             if (fos != null) {
 222  
                 try {
 223  5
                     fos.close();
 224  0
                 } catch (Throwable e) {
 225  0
                     LOGGER.debug("Error closing output stream");
 226  5
                 }
 227  
             }
 228  5
             if (is != null) {
 229  
                 try {
 230  5
                     is.close();
 231  0
                 } catch (Throwable e) {
 232  0
                     LOGGER.debug("Error closing input stream");
 233  5
                 }
 234  
             }
 235  
         }
 236  
 
 237  
         // Now, need to see if GrokAssembly actually runs from this location.
 238  5
         final List<String> args = buildArgumentList();
 239  
         //TODO this creates an "unreported" error - if someone doesn't look
 240  
         // at the command output this could easily be missed (especially in an
 241  
         // Ant or Maven build.
 242  
         //
 243  
         // We need to create a non-fatal warning error type that will
 244  
         // get added to the report.
 245  
         //TOOD this idea needs to get replicated to the bundle audit analyzer.
 246  5
         if (args == null) {
 247  0
             setEnabled(false);
 248  0
             LOGGER.error("----------------------------------------------------");
 249  0
             LOGGER.error(".NET Assembly Analyzer could not be initialized and at least one "
 250  
                     + "'exe' or 'dll' was scanned. The 'mono' executable could not be found on "
 251  
                     + "the path; either disable the Assembly Analyzer or configure the path mono. "
 252  
                     + "On some systems mono-runtime and mono-devel need to be installed.");
 253  0
             LOGGER.error("----------------------------------------------------");
 254  0
             return;
 255  
         }
 256  
         try {
 257  5
             final ProcessBuilder pb = new ProcessBuilder(args);
 258  5
             final Process p = pb.start();
 259  
             // Try evacuating the error stream
 260  5
             IOUtils.copy(p.getErrorStream(), NullOutputStream.NULL_OUTPUT_STREAM);
 261  
 
 262  5
             final DocumentBuilder builder = XmlUtils.buildSecureDocumentBuilder();
 263  5
             final Document doc = builder.parse(p.getInputStream());
 264  5
             final XPath xpath = XPathFactory.newInstance().newXPath();
 265  5
             final String error = xpath.evaluate("/assembly/error", doc);
 266  5
             if (p.waitFor() != 1 || error == null || error.isEmpty()) {
 267  0
                 LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer, please see the log for more details.");
 268  0
                 LOGGER.debug("GrokAssembly.exe is not working properly");
 269  0
                 grokAssemblyExe = null;
 270  0
                 setEnabled(false);
 271  0
                 throw new InitializationException("Could not execute .NET AssemblyAnalyzer");
 272  
             }
 273  0
         } catch (InitializationException e) {
 274  0
             setEnabled(false);
 275  0
             throw e;
 276  0
         } catch (Throwable e) {
 277  0
             LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer;\n"
 278  
                     + "this can be ignored unless you are scanning .NET DLLs. Please see the log for more details.");
 279  0
             LOGGER.debug("Could not execute GrokAssembly {}", e.getMessage());
 280  0
             setEnabled(false);
 281  0
             throw new InitializationException("An error occurred with the .NET AssemblyAnalyzer", e);
 282  5
         }
 283  5
     }
 284  
 
 285  
     /**
 286  
      * Removes resources used from the local file system.
 287  
      *
 288  
      * @throws Exception thrown if there is a problem closing the analyzer
 289  
      */
 290  
     @Override
 291  
     public void closeAnalyzer() throws Exception {
 292  
         try {
 293  5
             if (grokAssemblyExe != null && !grokAssemblyExe.delete()) {
 294  0
                 LOGGER.debug("Unable to delete temporary GrokAssembly.exe; attempting delete on exit");
 295  0
                 grokAssemblyExe.deleteOnExit();
 296  
             }
 297  0
         } catch (SecurityException se) {
 298  0
             LOGGER.debug("Can't delete temporary GrokAssembly.exe");
 299  0
             grokAssemblyExe.deleteOnExit();
 300  5
         }
 301  5
     }
 302  
 
 303  
     /**
 304  
      * The File Filter used to filter supported extensions.
 305  
      */
 306  2
     private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(
 307  1
             SUPPORTED_EXTENSIONS).build();
 308  
 
 309  
     @Override
 310  
     protected FileFilter getFileFilter() {
 311  863
         return FILTER;
 312  
     }
 313  
 
 314  
     /**
 315  
      * Gets this analyzer's name.
 316  
      *
 317  
      * @return the analyzer name
 318  
      */
 319  
     @Override
 320  
     public String getName() {
 321  18
         return ANALYZER_NAME;
 322  
     }
 323  
 
 324  
     /**
 325  
      * Returns the phase this analyzer runs under.
 326  
      *
 327  
      * @return the phase this runs under
 328  
      */
 329  
     @Override
 330  
     public AnalysisPhase getAnalysisPhase() {
 331  6
         return ANALYSIS_PHASE;
 332  
     }
 333  
 
 334  
     /**
 335  
      * Returns the key used in the properties file to reference the analyzer's
 336  
      * enabled property.
 337  
      *
 338  
      * @return the analyzer's enabled property setting key
 339  
      */
 340  
     @Override
 341  
     protected String getAnalyzerEnabledSettingKey() {
 342  7
         return Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED;
 343  
     }
 344  
 
 345  
     /**
 346  
      * Tests to see if a file is in the system path. <b>Note</b> - the current
 347  
      * implementation only works on non-windows platforms. For purposes of the
 348  
      * AssemblyAnalyzer this is okay as this is only needed on Mac/*nix.
 349  
      *
 350  
      * @param file the executable to look for
 351  
      * @return <code>true</code> if the file exists; otherwise
 352  
      * <code>false</code>
 353  
      */
 354  
     private boolean isInPath(String file) {
 355  0
         final ProcessBuilder pb = new ProcessBuilder("which", file);
 356  
         try {
 357  0
             final Process proc = pb.start();
 358  0
             final int retCode = proc.waitFor();
 359  0
             if (retCode == 0) {
 360  0
                 return true;
 361  
             }
 362  0
         } catch (IOException ex) {
 363  0
             LOGGER.debug("Path seach failed for " + file);
 364  0
         } catch (InterruptedException ex) {
 365  0
             LOGGER.debug("Path seach failed for " + file);
 366  0
         }
 367  0
         return false;
 368  
     }
 369  
 }