GP-6408: The 'Demangler GNU' analyzer now has a timeout option

This commit is contained in:
Ryan Kurtz
2026-02-06 12:01:38 -05:00
parent 02d096d4f9
commit 6d3fcc69e4
6 changed files with 141 additions and 20 deletions

View File

@@ -451,6 +451,13 @@
<B>The GNU Demangler</B> adds the following analysis options:
<BLOCKQUOTE>
<P>
<U><B>Timeout (seconds)</B></U> -
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.
</P>
<P>
<U><B>Use Deprecated Demangler</B></U> -
By default, GCC symbols will be demangled using the most up-to-date demangler

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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" +
"}";

View File

@@ -188,6 +188,21 @@ public class GThreadPool {
public Executor getExecutor() {
return executor;
}
/**
* Returns the {@link ExecutorService} used by this thread pool.
*
* <P>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