diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Annotations/Annotations.html b/Ghidra/Features/Base/src/main/help/help/topics/Annotations/Annotations.html index 5c3c9ee6c8..86974ba456 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/Annotations/Annotations.html +++ b/Ghidra/Features/Base/src/main/help/help/topics/Annotations/Annotations.html @@ -285,48 +285,6 @@ - - Execute
- - - Launches the specified executable with given optional - parameters.
- - - -
    -
  1. "executable path"
  2. -
- - OR -
    -
  1. "executable path"
  2. -
  3. "parameter list" (may be empty quotes)
  4. -
  5. "display text" (may be empty quotes)
  6. -
- - - - - - - - Note: quotes are required for this annotation - - - Discovered Annotations
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/Base/src/main/java/ghidra/app/util/viewer/field/ExecutableTaskStringHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ExecutableTaskStringHandler.java deleted file mode 100644 index 2952bf5b42..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ExecutableTaskStringHandler.java +++ /dev/null @@ -1,198 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.util.viewer.field; - -import java.io.*; -import java.util.*; - -import docking.widgets.fieldpanel.field.AttributedString; -import ghidra.app.nav.Navigatable; -import ghidra.framework.plugintool.ServiceProvider; -import ghidra.program.model.listing.Program; -import ghidra.util.Msg; - -public class ExecutableTaskStringHandler implements AnnotatedStringHandler { - private static final String INVALID_SYMBOL_TEXT = - "@execute annotation must have an executable name"; - private static final String[] SUPPORTED_ANNOTATIONS = { "execute" }; - - @Override - public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text, - Program program) throws AnnotationException { - if (text.length <= 1) { - throw new AnnotationException(INVALID_SYMBOL_TEXT); - } - - String displayText = getDisplayText(text); - if (displayText == null) { - // some kind of error - throw new AnnotationException(INVALID_SYMBOL_TEXT); - } - - return new AttributedString(displayText, prototypeString.getColor(0), - prototypeString.getFontMetrics(0), true, prototypeString.getColor(0)); - } - - private String getDisplayText(String[] text) { - // - // We currently support two modes of: 3 parameters or 1. The user can leave off the - // executable's parameter and display string OR they can have all three. - // - if (text.length == 4) { - return text[3]; // 4 items means they have display text - } - else if (text.length != 2) { - throw new AnnotationException( - "Invalid number of inputs - " + (text.length - 1) + " found - 1 or 3 required"); - } - - // otherwise, no display text, just use the executable name - String programInfo = text[1]; - return getDisplayTextForFilePathOrName(programInfo); - } - - private String getDisplayTextForFilePathOrName(String fileString) { - File file = new File(fileString); - if (file.isAbsolute() && file.exists()) { - return file.getName(); - } - return fileString; - } - - @Override - public String getDisplayString() { - return "Execute"; - } - - @Override - public String getPrototypeString() { - return "{@execute \"executable_path_and_name\" \"arg1 arg2\" \"Display Text\"}"; - } - - @Override - public String[] getSupportedAnnotations() { - return SUPPORTED_ANNOTATIONS; - } - - @Override - public boolean handleMouseClick(String[] annotationParts, Navigatable sourceNavigatable, - ServiceProvider serviceProvider) { - - String executableName = annotationParts[1]; - - List command = new ArrayList<>(); - command.add(executableName); - - if (annotationParts.length > 2) { - String commandParameterString = annotationParts[2]; - StringTokenizer tokenizer = new StringTokenizer(commandParameterString, " "); - while (tokenizer.hasMoreTokens()) { - command.add(tokenizer.nextToken()); - } - } - - new ProcessThread(command).start(); - - return true; - } - -//================================================================================================== -// Inner Classes -//================================================================================================== - - private static class ProcessThread extends Thread { - - private final List command; - - ProcessThread(List command) { - super("Process Runner - " + command.get(0)); - this.command = command; - } - - @Override - public void run() { - ProcessBuilder processBuilder = new ProcessBuilder(command); - processBuilder = processBuilder.redirectErrorStream(true); - - IOThread ioThread = null; - StringBuilder buffer = new StringBuilder(); - int exitValue = 1; - InputStream inputStream = null; - Process process = null; - String executableName = command.get(0); - try { - Msg.info(this, "Launching process: " + executableName); - process = processBuilder.start(); - inputStream = process.getInputStream(); - ioThread = new IOThread(buffer, inputStream); - ioThread.start(); - exitValue = process.waitFor(); - ioThread.join(); - inputStream.close(); - } - catch (Exception e) { - Msg.showError(this, null, "Error Launching Executable", - "Unexpected exception trying to launch process: " + executableName, e); - - } - finally { - if (inputStream != null) { - try { - inputStream.close(); - } - catch (IOException e) { - // ignore; we tried - } - } - } - - if (exitValue != 0) { - Msg.warn(this, "Process \"" + executableName + "\" exited abnormally with value: " + - exitValue); - } - } - } - - private static class IOThread extends Thread { - private BufferedReader shellOutput; - private StringBuilder buffer; - - IOThread(StringBuilder buffer, InputStream input) { - super("IO Thread - Executable Annotation Task"); - this.buffer = buffer; - shellOutput = new BufferedReader(new InputStreamReader(input)); - } - - @Override - public void run() { - String line = null; - try { - while ((line = shellOutput.readLine()) != null) { - buffer.append(line).append('\n'); - } - } - catch (Exception e) { - Msg.error(this, "Exception reading output for executable annotation", e); - buffer = null; - } - } - } - - @Override - public String getPrototypeString(String displayText) { - return "{@execute " + displayText.trim() + "}"; - } -} 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