diff --git a/Ghidra/Features/Base/src/main/help/help/topics/AutoAnalysisPlugin/AutoAnalysis.htm b/Ghidra/Features/Base/src/main/help/help/topics/AutoAnalysisPlugin/AutoAnalysis.htm index 2e40e0162c..284082788f 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/AutoAnalysisPlugin/AutoAnalysis.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/AutoAnalysisPlugin/AutoAnalysis.htm @@ -451,6 +451,13 @@ The GNU Demangler adds the following analysis options:
+

+ Timeout (seconds) - + The maximum amount of seconds to allow the native GNU Demangler process to + attempt to demangle a mangled string before failing. Some inputs to the + native GNU Demangler program have been shown to not terminate and consume excessive + resources. This timeout protects against these inputs. +

Use Deprecated Demangler - By default, GCC symbols will be demangled using the most up-to-date demangler diff --git a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerAnalyzer.java b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerAnalyzer.java index 31beb384f0..a35d6f9431 100644 --- a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerAnalyzer.java +++ b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerAnalyzer.java @@ -66,10 +66,15 @@ public class GnuDemanglerAnalyzer extends AbstractDemanglerAnalyzer { private static final String OPTION_DESCRIPTION_DEMANGLER_FORMAT = "The demangling format to use"; + static final String OPTION_NAME_TIMEOUT_SECONDS = "Timeout (seconds)"; + private static final String OPTION_DESCRIPTION_TIMEOUT_SECONDS = + "The maximum amount of seconds to spend demangling a string"; + private boolean applyFunctionSignature = true; private boolean applyCallingConvention = true; private boolean demangleOnlyKnownPatterns = false; private boolean useStandardReplacements = true; + private long timeoutSeconds = GnuDemanglerOptions.DEFAULT_TIMEOUT_SECONDS; private GnuDemanglerFormat demanglerFormat = GnuDemanglerFormat.AUTO; private boolean useDeprecatedDemangler = false; @@ -110,6 +115,8 @@ public class GnuDemanglerAnalyzer extends AbstractDemanglerAnalyzer { demanglerFormat, help, OPTION_DESCRIPTION_DEMANGLER_FORMAT, () -> optionsEditor.getFormatEditor()); + options.registerOption(OPTION_NAME_TIMEOUT_SECONDS, timeoutSeconds, help, + OPTION_DESCRIPTION_TIMEOUT_SECONDS); } @Override @@ -123,6 +130,8 @@ public class GnuDemanglerAnalyzer extends AbstractDemanglerAnalyzer { useStandardReplacements = options.getBoolean(OPTION_NAME_DEMANGLE_USE_STANDARD_REPLACEMENTS, useStandardReplacements); + timeoutSeconds = options.getLong(OPTION_NAME_TIMEOUT_SECONDS, + GnuDemanglerOptions.DEFAULT_TIMEOUT_SECONDS); demanglerFormat = options.getEnum(OPTION_NAME_DEMANGLER_FORMAT, GnuDemanglerFormat.AUTO); useDeprecatedDemangler = options.getBoolean(OPTION_NAME_USE_DEPRECATED_DEMANGLER, useDeprecatedDemangler); @@ -131,7 +140,7 @@ public class GnuDemanglerAnalyzer extends AbstractDemanglerAnalyzer { @Override protected DemanglerOptions getOptions() { GnuDemanglerOptions options = - new GnuDemanglerOptions(demanglerFormat, useDeprecatedDemangler); + new GnuDemanglerOptions(demanglerFormat, useDeprecatedDemangler, timeoutSeconds); options.setDoDisassembly(true); options.setApplySignature(applyFunctionSignature); options.setApplyCallingConvention(applyCallingConvention); diff --git a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemangler.java b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemangler.java index 4c051e89f2..00de9ff4f3 100644 --- a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemangler.java +++ b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemangler.java @@ -108,7 +108,7 @@ public class GnuDemangler implements Demangler { try { GnuDemanglerNativeProcess process = getNativeProcess(options); - String demangled = process.demangle(mangled); + String demangled = process.demangle(mangled, options.getTimeoutSeconds()); if (demangled == null) { throw new DemangledException(false); } diff --git a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerNativeProcess.java b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerNativeProcess.java index 8453d87ffd..8e80ff35e5 100644 --- a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerNativeProcess.java +++ b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerNativeProcess.java @@ -15,16 +15,34 @@ */ package ghidra.app.util.demangler.gnu; -import java.io.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.UncheckedIOException; import java.nio.charset.Charset; -import java.util.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; -import ghidra.framework.*; +import com.google.common.util.concurrent.SimpleTimeLimiter; + +import ghidra.app.plugin.core.analysis.AutoAnalysisManager; +import ghidra.framework.Application; +import ghidra.framework.OSFileNotFoundException; +import ghidra.framework.Platform; import ghidra.util.Msg; /** @@ -113,16 +131,39 @@ public class GnuDemanglerNativeProcess { createProcess(); } + /** + * {@return the demangled string} + * + * @param mangled The string to demangle + * @throws IOException if an IO-related error occurred + */ public synchronized String demangle(String mangled) throws IOException { + return demangle(mangled, true, null); + } + + /** + * {@return the demangled string} + * + * @param mangled The string to demangle + * @param timeoutSeconds The number of seconds to attempt the demangle, or {@code null} for no + * timeout + * @throws IOException if a timeout or IO-related error occurred + */ + public synchronized String demangle(String mangled, Long timeoutSeconds) throws IOException { if (isDisposed) { throw new IOException("Demangled process has been terminated."); } - return demangle(mangled, true); + return demangle(mangled, true, timeoutSeconds); } - private String demangle(String mangled, boolean restart) throws IOException { + private String demangle(String mangled, boolean restart, Long timeoutSeconds) + throws IOException { try { - return doDemangle(mangled); + return doDemangle(mangled, timeoutSeconds); + } + catch (TimeoutException e) { + dispose(); + throw new IOException("Timeout reached", e); } catch (IOException e) { dispose(); @@ -130,14 +171,25 @@ public class GnuDemanglerNativeProcess { throw new IOException("Demangler process is not running.", e); } createProcess(); - return demangle(mangled, false); + return demangle(mangled, false, timeoutSeconds); } } - private String doDemangle(String mangled) throws IOException { + private String doDemangle(String mangled, Long timeoutSeconds) + throws TimeoutException, IOException { writer.println(mangled); writer.flush(); - return reader.readLine(); + try { + return timeoutSeconds != null + ? SimpleTimeLimiter + .create(AutoAnalysisManager.getSharedAnalsysThreadPool() + .getExecutorService()) + .callWithTimeout(reader::readLine, timeoutSeconds, TimeUnit.SECONDS) + : reader.readLine(); + } + catch (ExecutionException | InterruptedException e) { + throw new IOException(e); + } } public void dispose() { @@ -161,6 +213,7 @@ public class GnuDemanglerNativeProcess { } } + @SuppressWarnings("resource") private void createProcess() throws IOException { String[] command = buildCommand(); @@ -227,9 +280,14 @@ public class GnuDemanglerNativeProcess { // Send a test string over and read the result. If the test string is blank, then // there was an error. // - String testResult = doDemangle("test"); - if (!StringUtils.isBlank(testResult)) { - return; + try { + String testResult = doDemangle("test", null); + if (!StringUtils.isBlank(testResult)) { + return; + } + } + catch (TimeoutException e) { + throw new IOException(e); } InputStream err = process.getErrorStream(); diff --git a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerOptions.java b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerOptions.java index 26f79880bc..23fcbe4884 100644 --- a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerOptions.java +++ b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerOptions.java @@ -44,9 +44,15 @@ public class GnuDemanglerOptions extends DemanglerOptions { */ public static final String GNU_DEMANGLER_DEFAULT = GNU_DEMANGLER_V2_41; + /** + * The default GNU demangler timeout (in seconds) + */ + public static final long DEFAULT_TIMEOUT_SECONDS = 3; + private final GnuDemanglerFormat format; private final boolean isDeprecated; private boolean useStandardReplacements; + private long timeout; /** * Default constructor to use the modern demangler with auto-detect for the format. This @@ -75,9 +81,25 @@ public class GnuDemanglerOptions extends DemanglerOptions { * demangler */ public GnuDemanglerOptions(GnuDemanglerFormat format, boolean isDeprecated) { + this(format, isDeprecated, GnuDemanglerOptions.DEFAULT_TIMEOUT_SECONDS); + } + + /** + * Constructor to specify the format to use, whether to prefer the deprecated format when + * both deprecated and modern are available, and the timeout + * + * @param format the format + * @param isDeprecated true if the format is not available in the modern demangler + * @param timeoutSeconds the demangler timeout in seconds + * @throws IllegalArgumentException if the given format is not available in the deprecated + * demangler + */ + public GnuDemanglerOptions(GnuDemanglerFormat format, boolean isDeprecated, + long timeoutSeconds) { this.format = format; this.isDeprecated = isDeprecated; this.useStandardReplacements = true; + this.timeout = timeoutSeconds; if (!format.isAvailable(isDeprecated)) { throw new IllegalArgumentException( format.name() + " is not available in the " + getDemanglerName()); @@ -91,30 +113,32 @@ public class GnuDemanglerOptions extends DemanglerOptions { public GnuDemanglerOptions(DemanglerOptions copy) { super(copy); - if (copy instanceof GnuDemanglerOptions) { - GnuDemanglerOptions gCopy = (GnuDemanglerOptions) copy; + if (copy instanceof GnuDemanglerOptions gCopy) { format = gCopy.format; isDeprecated = gCopy.isDeprecated; + timeout = gCopy.timeout; } else { format = GnuDemanglerFormat.AUTO; isDeprecated = false; + timeout = DEFAULT_TIMEOUT_SECONDS; } this.useStandardReplacements = true; } private GnuDemanglerOptions(GnuDemanglerOptions copy, GnuDemanglerFormat format, - boolean deprecated) { - this(copy, format, deprecated, true); + boolean deprecated, long timeoutSeconds) { + this(copy, format, deprecated, true, timeoutSeconds); } private GnuDemanglerOptions(GnuDemanglerOptions copy, GnuDemanglerFormat format, - boolean deprecated, boolean useStandardReplacements) { + boolean deprecated, boolean useStandardReplacements, long timeoutSeconds) { super(copy); this.format = format; this.isDeprecated = deprecated; this.useStandardReplacements = useStandardReplacements; + this.timeout = timeoutSeconds; } /** @@ -150,7 +174,7 @@ public class GnuDemanglerOptions extends DemanglerOptions { return this; } if (demanglerFormat.isAvailable(useDeprecated)) { - return new GnuDemanglerOptions(this, demanglerFormat, useDeprecated); + return new GnuDemanglerOptions(this, demanglerFormat, useDeprecated, this.timeout); } throw new IllegalArgumentException( demanglerFormat.name() + " is not available in the " + getDemanglerName()); @@ -186,6 +210,13 @@ public class GnuDemanglerOptions extends DemanglerOptions { return useStandardReplacements; } + /** + * {@return the demangler timeout (in seconds)} + */ + public long getTimeoutSeconds() { + return timeout; + } + @Override public String toString() { //@formatter:off @@ -194,6 +225,7 @@ public class GnuDemanglerOptions extends DemanglerOptions { "\tapplySignature: " + applySignature() + ",\n" + "\tuseStandardReplacements: " + useStandardReplacements + ",\n" + "\tdemangleOnlyKnownPatterns: " + demangleOnlyKnownPatterns() + ",\n" + + "\ttimeout (sec): " + timeout + ",\n" + "\tdemanglerName: " + getDemanglerName() + ",\n" + "\tdemanglerApplicationArguments: " + getDemanglerApplicationArguments() + ",\n" + "}"; diff --git a/Ghidra/Framework/Utility/src/main/java/generic/concurrent/GThreadPool.java b/Ghidra/Framework/Utility/src/main/java/generic/concurrent/GThreadPool.java index eb8957a35b..a122f4cd51 100644 --- a/Ghidra/Framework/Utility/src/main/java/generic/concurrent/GThreadPool.java +++ b/Ghidra/Framework/Utility/src/main/java/generic/concurrent/GThreadPool.java @@ -188,6 +188,21 @@ public class GThreadPool { public Executor getExecutor() { return executor; } + + + /** + * Returns the {@link ExecutorService} used by this thread pool. + * + *

Note: normal usage of this thread pool contraindicates accessing the executor service of + * this pool. For managing your own jobs, you should use the method on this class directly. + * The intent of this method is to provide access to the executor service so that it may be + * passed to other asynchronous APIs. + * + * @return the executor service + */ + public ExecutorService getExecutorService() { + return executor; + } //================================================================================================== // Inner Classes