GP-7328 DataBuffer size restriction and related improvements

This commit is contained in:
ghidra1
2026-09-29 10:00:10 -04:00
parent a3eade8c92
commit 7834b39e4b
8 changed files with 60 additions and 83 deletions
@@ -15,32 +15,18 @@
*/
package ghidra.server.remote;
import static ghidra.server.remote.GhidraServer.AuthMode.JAAS_LOGIN;
import static ghidra.server.remote.GhidraServer.AuthMode.NO_AUTH_LOGIN;
import static ghidra.server.remote.GhidraServer.AuthMode.PASSWORD_FILE_LOGIN;
import static ghidra.server.remote.GhidraServer.AuthMode.PKI_LOGIN;
import static ghidra.server.remote.GhidraServer.AuthMode.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.io.*;
import java.net.*;
import java.rmi.NoSuchObjectException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.server.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.*;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.rmi.ssl.SslRMIClientSocketFactory;
@@ -57,29 +43,17 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.asn1.x509.GeneralName;
import db.buffers.DataBuffer;
import generic.jar.ResourceFile;
import generic.random.SecureRandomFactory;
import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration;
import ghidra.framework.remote.GhidraObjectInputFilter;
import ghidra.framework.remote.GhidraPrincipal;
import ghidra.framework.remote.GhidraServerHandle;
import ghidra.framework.remote.RemoteRepositoryServerHandle;
import ghidra.net.DefaultKeyManagerFactory;
import ghidra.net.DefaultSSLContextInitializer;
import ghidra.net.DefaultTrustManagerFactory;
import ghidra.net.PKIUtils;
import ghidra.framework.remote.*;
import ghidra.net.*;
import ghidra.server.RepositoryManager;
import ghidra.server.UserManager;
import ghidra.server.security.AnonymousAuthenticationModule;
import ghidra.server.security.AuthenticationModule;
import ghidra.server.security.JAASAuthenticationModule;
import ghidra.server.security.Krb5ActiveDirectoryAuthenticationModule;
import ghidra.server.security.PKIAuthenticationModule;
import ghidra.server.security.PasswordFileAuthenticationModule;
import ghidra.server.security.SSHAuthenticationModule;
import ghidra.server.security.*;
import ghidra.server.stream.BlockStreamServer;
import ghidra.server.stream.RemoteBlockStreamHandle;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.DuplicateNameException;
@@ -867,8 +841,7 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
log.info(" RMI SSL port: " + ServerPortFactory.getRMISSLPort());
log.info(" Block Stream port: " + ServerPortFactory.getStreamPort());
log.info(" Block Stream compression: " +
(RemoteBlockStreamHandle.enableCompressedSerializationOutput ? "enabled"
: "disabled"));
(DataBuffer.isCompressedSerializationOutputEnabled() ? "enabled" : "disabled"));
log.info(" Root: " + serverRoot.getAbsolutePath());
log.info(" Auth: " + authMode.getDescription());
if (authMode == PASSWORD_FILE_LOGIN && defaultPasswordExpiration >= 0) {
@@ -51,9 +51,6 @@ public abstract class RemoteBlockStreamHandle<T extends BlockStream> implements
public static final long serialVersionUID = 1L;
public static boolean enableCompressedSerializationOutput = Boolean.parseBoolean(
System.getProperty(DataBuffer.COMPRESSED_SERIAL_OUTPUT_PROPERTY, "false"));
public static final String HEADER_PREFIX = "@stream:";
public static final String HEADER_SUFFIX = "@";
public static final int HEADER_LENGTH =
@@ -70,7 +67,7 @@ public abstract class RemoteBlockStreamHandle<T extends BlockStream> implements
private final int blockCount;
private final int blockSize;
protected final boolean compressed = enableCompressedSerializationOutput;
protected final boolean compressed = DataBuffer.isCompressedSerializationOutputEnabled();
private boolean connectionPending = true;
@@ -40,12 +40,18 @@ public class DataBuffer implements Buffer, Externalizable {
enableCompressedSerializationOutput = enable;
}
public static boolean usingCompressedSerializationOutput() {
public static boolean isCompressedSerializationOutputEnabled() {
return enableCompressedSerializationOutput;
}
private static int FORMAT_VERSION = 0xEA; // 0xEA is first version (avoided simple value like 0 or 1)
// Maximum support buffer size supported
public static final int MAX_BUFFER_SIZE = 65536;
// Establish safe limit for compressed buffer size
private static final int MAX_COMPRESSED_BUFFER_SIZE = MAX_BUFFER_SIZE + 1024;
/**
* NOTE: See custom serialization methods at bottom which implement compression.
*/
@@ -145,7 +151,7 @@ public class DataBuffer implements Buffer, Externalizable {
@Override
public int length() {
return data.length;
return data != null ? data.length : 0;
}
@Override
@@ -333,8 +339,7 @@ public class DataBuffer implements Buffer, Externalizable {
// Deflater must be consistent with inflateData nowrap option and should
// not be changed since client/server must match.
// NOTE: compression mode may be adjusted to optimize performance
Deflater deflate = new Deflater(Deflater.BEST_SPEED, true);
try {
try (Deflater deflate = new Deflater(Deflater.BEST_SPEED, true)) {
deflate.setInput(data, 0, data.length);
deflate.finish();
@@ -350,8 +355,6 @@ public class DataBuffer implements Buffer, Externalizable {
}
return compressedDataOffset;
} finally {
deflate.end(); // get rid of any native memory rather than waiting for GC
}
}
@@ -369,13 +372,18 @@ public class DataBuffer implements Buffer, Externalizable {
dirty = in.readBoolean();
empty = in.readBoolean();
int len = in.readInt();
data = null;
if (len >= 0) {
if (len > MAX_BUFFER_SIZE) {
throw new IOException("Unsupported buffer size: " + len);
}
data = new byte[len];
if (compressed) {
int compressedLen = in.readInt();
if (compressedLen > MAX_COMPRESSED_BUFFER_SIZE) {
throw new IOException("Unsupported compressed buffer size: " + compressedLen);
}
byte[] compressedData = new byte[compressedLen];
in.readFully(compressedData);
inflateData(compressedData, data);
@@ -414,14 +422,13 @@ public class DataBuffer implements Buffer, Externalizable {
* Inflate compressedData into a properly sized data array.
* @param compressedData array containing compressed data
* @param data target data array size to receive fully inflated data.
* @throws IOException
* @throws IOException if an IO error occurs
*/
private static void inflateData(byte[] compressedData, byte[] data) throws IOException {
// Inflater must be consistent with deflateData nowrap option and should
// not be changed since client/server must match.
Inflater inflater = new Inflater(true);
try {
try (Inflater inflater = new Inflater(true)) {
inflater.setInput(compressedData);
int off = 0;
while (!inflater.finished() && off < data.length) {
@@ -437,9 +444,6 @@ public class DataBuffer implements Buffer, Externalizable {
catch (DataFormatException e) {
throw new IOException("DataBuffer inflation failed", e);
}
finally {
inflater.end(); // get rid of any native memory rather than waiting for GC
}
}
}
@@ -42,8 +42,8 @@ public class LocalBufferFile implements BufferFile {
private static final String STRING_ENCODING = "UTF-8";
// ?? Should be changed !!
private static final int MINIMUM_BLOCK_SIZE = 128;
private static final int MINIMUM_BLOCK_SIZE = 512;
private static final int MAXIMUM_BLOCK_SIZE = DataBuffer.MAX_BUFFER_SIZE;
private static final Random random = new Random();
@@ -196,6 +196,9 @@ public class LocalBufferFile implements BufferFile {
LocalBufferFile(int bufferSize, String tmpPrefix, String tmpExtension) throws IOException {
this.bufferSize = bufferSize;
this.blockSize = bufferSize + BUFFER_PREFIX_SIZE;
if (blockSize < MINIMUM_BLOCK_SIZE || blockSize > MAXIMUM_BLOCK_SIZE) {
throw new IllegalArgumentException("Unsupported buffer size: " + bufferSize);
}
this.readOnly = false;
this.temporary = true;
file = Application.createTempFile(tmpPrefix, tmpExtension);
@@ -219,6 +222,12 @@ public class LocalBufferFile implements BufferFile {
this.file = file;
this.bufferSize = bufferSize;
this.blockSize = bufferSize + BUFFER_PREFIX_SIZE;
if (blockSize < MINIMUM_BLOCK_SIZE || blockSize > MAXIMUM_BLOCK_SIZE) {
throw new IllegalArgumentException(
"Unsupported buffer size: " + bufferSize);
}
this.readOnly = false;
raf = new RandomAccessFile(file, "rw");
@@ -452,8 +461,12 @@ public class LocalBufferFile implements BufferFile {
throw new IOException("Unrecognized file format");
}
// Read buffer size, free buffer count, and first free buffer index
// Read block size, free buffer count, and first free buffer index
blockSize = raf.readInt();
if (blockSize < MINIMUM_BLOCK_SIZE || blockSize > MAXIMUM_BLOCK_SIZE) {
throw new IOException("Unsupported block size: " + blockSize);
}
bufferSize = blockSize - BUFFER_PREFIX_SIZE;
int firstFreeBufferIndex = raf.readInt();
long len = raf.length();
@@ -686,7 +699,8 @@ public class LocalBufferFile implements BufferFile {
byte[] data = buf.data;
boolean empty = buf.isEmpty();
if (!empty && data.length != bufferSize) {
if (data != null && data.length != bufferSize) {
throw new IllegalArgumentException("Bad buffer size");
}
@@ -39,7 +39,10 @@ public class DataBufferTest extends AbstractGenericTest {
@After
public void tearDown() throws Exception {
// restore default compression: enabled
DataBuffer.enableCompressedSerializationOutput(true);
DataBuffer.enableCompressedSerializationOutput(
Boolean.parseBoolean(
System.getProperty(DataBuffer.COMPRESSED_SERIAL_OUTPUT_PROPERTY,
"true")));
}
private void transferData(boolean useRandomFill) throws Exception {
@@ -33,7 +33,6 @@ import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.XMLOutputter;
import db.buffers.DataBuffer;
import docking.*;
import docking.action.DockingAction;
import docking.action.MenuData;
@@ -93,14 +92,10 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
public static final String AUTOMATICALLY_SAVE_TOOLS = "Automatically Save Tools";
public static final String DEFAULT_TOOL_LAUNCH_MODE = "Default Tool Launch Mode";
private static final String SHOW_TOOLTIPS_OPTION_NAME = "Show Tooltips";
private static final String USE_COMPRESSED_DATABUFFER_OUTPUT =
"Use DataBuffer Output Compression";
private static final String USE_NATURAL_SORT = "Use Natural File Sort";
private static final String USE_COMBINED_ALT_GRAPH_OPTION_NAME = "Use Combined Alt Keys";
private static final String USE_ALERT_ANIMATION_OPTION_NAME = "Use Notification Animation";
private static final Boolean ENABLE_COMPRESSED_DATABUFFER_OUTPUT_DEFAULT = true;
private static final String RESTORE_PREVIOUS_PROJECT_NAME = "Restore Previous Project";
private boolean shouldRestorePreviousProject;
@@ -358,10 +353,6 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
options.registerOption(SHOW_TOOLTIPS_OPTION_NAME, true, help,
"Controls the display of tooltip popup windows.");
options.registerOption(USE_COMPRESSED_DATABUFFER_OUTPUT,
ENABLE_COMPRESSED_DATABUFFER_OUTPUT_DEFAULT, help,
"When enabled data buffers sent to Ghidra Server are compressed (see server " +
"configuration for other direction)");
options.registerOption(BLINKING_CURSORS_OPTION_NAME, true, help,
"This controls whether" + " text cursors blink when focused");
@@ -386,11 +377,6 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
boolean showToolTips = options.getBoolean(SHOW_TOOLTIPS_OPTION_NAME, true);
DockingUtils.setGlobalTooltipEnabledOption(showToolTips);
boolean compressDataBuffers =
options.getBoolean(USE_COMPRESSED_DATABUFFER_OUTPUT,
ENABLE_COMPRESSED_DATABUFFER_OUTPUT_DEFAULT);
DataBuffer.enableCompressedSerializationOutput(compressDataBuffers);
shouldRestorePreviousProject = options.getBoolean(RESTORE_PREVIOUS_PROJECT_NAME, true);
boolean blink = options.getBoolean(BLINKING_CURSORS_OPTION_NAME, true);
@@ -420,9 +406,6 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
else if (SHOW_TOOLTIPS_OPTION_NAME.equals(optionName)) {
DockingUtils.setGlobalTooltipEnabledOption((Boolean) newValue);
}
else if (USE_COMPRESSED_DATABUFFER_OUTPUT.equals(optionName)) {
DataBuffer.enableCompressedSerializationOutput((Boolean) newValue);
}
else if (RESTORE_PREVIOUS_PROJECT_NAME.equals(optionName)) {
shouldRestorePreviousProject = (Boolean) newValue;
}
+4 -7
View File
@@ -83,18 +83,15 @@ wrapper.java.additional.7=-Djdk.tls.server.cipherSuites="TLS_DHE_RSA_WITH_AES_25
#wrapper.java.additional.9=-Dghidra.keystore=
#wrapper.java.additional.10=-Dghidra.password=
# Enable/Disable use of compression for DataBuffer serialization and Block Streams
wrapper.java.additional.11=-Ddb.buffers.DataBuffer.compressedOutput=true
# Uncomment to enable remote debug support
# The debug address will listen on all network interfaces, if desired the '*' may be
# set to a specific interface IP address (e.g., 127.0.0.1) if you wish to restrict.
# During debug it will be necessary to increase timeout values to prevent the wrapper
# from restarting the server due to unresponsiveness.
#wrapper.java.additional.12=-Xdebug
#wrapper.java.additional.13=-Xnoagent
#wrapper.java.additional.14=-Djava.compiler=NONE
#wrapper.java.additional.15=-Xrunjdwp:transport=dt_socket\,server=y\,suspend=n\,address=*:18200
#wrapper.java.additional.11=-Xdebug
#wrapper.java.additional.12=-Xnoagent
#wrapper.java.additional.13=-Djava.compiler=NONE
#wrapper.java.additional.14=-Xrunjdwp:transport=dt_socket\,server=y\,suspend=n\,address=*:18200
#wrapper.startup.timeout=0
#wrapper.ping.timeout=0
@@ -631,6 +631,12 @@ public class ServerTestUtil {
TransientProjectManager.getTransientProjectManager().dispose();
// restore default compression: enabled
DataBuffer.enableCompressedSerializationOutput(
Boolean.parseBoolean(
System.getProperty(DataBuffer.COMPRESSED_SERIAL_OUTPUT_PROPERTY,
"true")));
if (serverProcess != null) {
cmdOut.dispose();