Merge tag 'Ghidra_12.1.2_build' into stable

This commit is contained in:
Ryan Kurtz
2026-06-05 12:20:04 -04:00
14 changed files with 129 additions and 42 deletions

View File

@@ -1,3 +1,14 @@
# Ghidra 12.1.2 Change History (June 2026)
### Improvements
* _MachineLearning_. Upgraded the Machine Learning extension's olcut jars to 5.3.1. (GP-6894)
* _Multi-User_. Data buffers transferred to and from a Ghidra Server are now compressed by default. The default was previously inconsistent for the two directions. (GP-6915)
### Bugs
* _Decompiler_. Fixed infinite loop in Decompiler analysis that was triggered by bitfield data-types. (GP-6850, Issue #9185)
* _Importer:ELF_. Improved ELF GNU Hash table bounds-checking to avoid potential hang. (GP-6887)
* _Multi-User_. Corrected severe regression error with Ghidra Server 12.1.1 block-stream compression. (GP-6903)
# Ghidra 12.1.1 Change History (May 2026)
### Improvements

View File

@@ -1,5 +1,5 @@
MODULE FILE LICENSE: lib/olcut-config-protobuf-5.2.0.jar BSD-2-ORACLE
MODULE FILE LICENSE: lib/olcut-core-5.2.0.jar BSD-2-ORACLE
MODULE FILE LICENSE: lib/olcut-config-protobuf-5.3.1.jar BSD-2-ORACLE
MODULE FILE LICENSE: lib/olcut-core-5.3.1.jar BSD-2-ORACLE
MODULE FILE LICENSE: lib/tribuo-classification-core-4.3.2.jar Apache License 2.0
MODULE FILE LICENSE: lib/tribuo-classification-tree-4.3.2.jar Apache License 2.0
MODULE FILE LICENSE: lib/tribuo-common-tree-4.3.2.jar Apache License 2.0

View File

@@ -26,8 +26,8 @@ def protobufVersion = getProperty("ghidra.protobuf.java.version")
dependencies {
api project(':Base')
api "com.oracle.labs.olcut:olcut-config-protobuf:5.2.0" //{exclude group: "com.google.protobuf", module: "protobuf-java"}
api ("com.oracle.labs.olcut:olcut-core:5.2.0") {exclude group: "org.jline"}
api "com.oracle.labs.olcut:olcut-config-protobuf:5.3.1" //{exclude group: "com.google.protobuf", module: "protobuf-java"}
api ("com.oracle.labs.olcut:olcut-core:5.3.1") {exclude group: "org.jline"}
api "org.tribuo:tribuo-classification-core:4.3.2"
api "org.tribuo:tribuo-classification-tree:4.3.2"
api "org.tribuo:tribuo-common-tree:4.3.2"

View File

@@ -697,7 +697,7 @@ public class ElfHeader implements StructConverter {
// p_vaddr to find it relative to a PT_LOAD segment
long vaddr = dynamicHeaders[0].getVirtualAddress();
if (vaddr == 0 || dynamicHeaders[0].getFileSize() == 0) {
Msg.warn(this, "ELF Dynamic table appears to have been stripped from binary");
logError("ELF Dynamic table appears to have been stripped from binary");
return;
}
@@ -931,7 +931,7 @@ public class ElfHeader implements StructConverter {
!dynamicTable.containsDynamicValue(ElfDynamicType.DT_SYMENT) ||
dynamicHashType == null) {
if (dynamicStringTable != null) {
Msg.warn(this, "Failed to parse DT_SYMTAB, missing dynamic dependency");
logError("Failed to parse DT_SYMTAB, missing dynamic dependency");
}
return null;
}
@@ -1012,14 +1012,26 @@ public class ElfHeader implements StructConverter {
private int deriveGnuHashDynamicSymbolCount(long gnuHashTableOffset) throws IOException {
int numBuckets = reader.readInt(gnuHashTableOffset);
int symbolBase = reader.readInt(gnuHashTableOffset + 4);
int bloomSize = reader.readInt(gnuHashTableOffset + 8);
long bloomSize = reader.readUnsignedInt(gnuHashTableOffset + 8);
// int bloomShift = reader.readInt(gnuHashTableOffset + 12);
int bloomWordSize = is64Bit() ? 8 : 4;
long bloomWordSize = is64Bit() ? 8 : 4;
long bucketsOffset = gnuHashTableOffset + 16 + (bloomWordSize * bloomSize);
// Identify restricted region which contains GNU hash table (arbitrary min-length)
long maxOffset = getMaxOffsetForLoadedRegionContaining(gnuHashTableOffset, 12);
if (maxOffset <= 0) {
logError("Failed to idenitify loaded GNU Hash table");
return 0;
}
long bucketOffset = bucketsOffset;
int maxSymbolIndex = 0;
for (int i = 0; i < numBuckets; i++) {
if (bucketOffset < gnuHashTableOffset || bucketOffset > maxOffset) {
logError("Error occured while inspecting GNU Hash table");
return 0;
}
int symbolIndex = reader.readInt(bucketOffset);
if (symbolIndex > maxSymbolIndex) {
maxSymbolIndex = symbolIndex;
@@ -1032,6 +1044,10 @@ public class ElfHeader implements StructConverter {
++maxSymbolIndex;
long chainOffset = bucketOffset + (4 * chainIndex); // chains immediately follow buckets
while (true) {
if (chainOffset < gnuHashTableOffset || chainOffset > maxOffset) {
logError("Error occured while inspecting GNU Hash table");
return 0;
}
int chainValue = reader.readInt(chainOffset);
if ((chainValue & 1) != 0) {
break;
@@ -1042,6 +1058,25 @@ public class ElfHeader implements StructConverter {
return maxSymbolIndex;
}
private long getMaxOffsetForLoadedRegionContaining(long offset, long minSize) {
long maxOffset = -1;
if (e_shnum != 0) {
ElfSectionHeader sectionContaining =
getSectionHeaderContainingFileRange(offset, minSize);
if (sectionContaining != null) {
maxOffset = sectionContaining.getOffset() + sectionContaining.getSize() - 1;
}
}
else {
ElfProgramHeader containingSegment =
getProgramLoadHeaderContainingFileOffset(offset);
if (containingSegment != null) {
maxOffset = containingSegment.getOffset() + containingSegment.getFileSize() - 1;
}
}
return maxOffset;
}
/**
* Walk DT_GNU_XHASH table to determine dynamic symbol count
* @param gnuHashTableOffset DT_GNU_XHASH table file offset
@@ -1296,6 +1331,7 @@ public class ElfHeader implements StructConverter {
}
}
catch (IOException e) {
logError("Elf prelink read failure (see log)");
Msg.error(this, "Elf prelink read failure", e);
}
return preLinkImageBase;

View File

@@ -115,6 +115,15 @@ BitFieldTransform::BitFieldTransform(Funcdata *f,Datatype *dt,int4 off)
isBigEndian = f->getArch()->getDefaultDataSpace()->isBigEndian();
}
const vector<uint4> BitFieldInsertTransform::allowedFinalWrites = {
CPUI_COPY, CPUI_INT_EQUAL, CPUI_INT_NOTEQUAL, CPUI_INT_SLESS, CPUI_INT_SLESSEQUAL,
CPUI_INT_LESS, CPUI_INT_LESSEQUAL, CPUI_INT_ZEXT, CPUI_INT_SEXT, CPUI_INT_ADD, CPUI_INT_CARRY,
CPUI_INT_SCARRY, CPUI_INT_XOR, CPUI_INT_AND, CPUI_INT_OR, CPUI_INT_LEFT, CPUI_INT_RIGHT,
CPUI_INT_SRIGHT, CPUI_INT_MULT, CPUI_BOOL_NEGATE, CPUI_BOOL_XOR, CPUI_BOOL_AND, CPUI_BOOL_OR,
CPUI_FLOAT_EQUAL, CPUI_FLOAT_NOTEQUAL, CPUI_FLOAT_LESS, CPUI_FLOAT_LESSEQUAL, CPUI_FLOAT_NAN,
CPUI_SUBPIECE
};
/// If the state is for a partial field whose storage location is overwritten
/// later in the same basic block, return \b true
/// \param state is the field
@@ -794,10 +803,17 @@ BitFieldInsertTransform::BitFieldInsertTransform(Funcdata *f,PcodeOp *op,Datatyp
outvn = op->getIn(0);
if (!outvn->isWritten()) return;
finalWriteOp = outvn->getDef(); // But use the op feeding the INDIRECT as the finalWriteOp
if (!mappedVn->isAddrTied())
return;
// Check that op feeding INDIRECT is in the allowed list
if (!binary_search(allowedFinalWrites.begin(),allowedFinalWrites.end(),finalWriteOp->code()))
return;
}
else {
outvn = finalWriteOp->getOut();
mappedVn = outvn;
if (!mappedVn->isAddrTied())
return;
}
containerSize = outvn->getSize();
originalValue = (Varnode *)0;
@@ -1678,13 +1694,8 @@ int4 RuleBitFieldStore::applyOp(PcodeOp *op,Funcdata &data)
void RuleBitFieldOut::getOpList(vector<uint4> &oplist) const
{
uint4 list[]={ CPUI_COPY, CPUI_INT_EQUAL, CPUI_INT_NOTEQUAL, CPUI_INT_SLESS, CPUI_INT_SLESSEQUAL,
CPUI_INT_LESS, CPUI_INT_LESSEQUAL, CPUI_INT_ZEXT, CPUI_INT_SEXT, CPUI_INT_ADD, CPUI_INT_CARRY,
CPUI_INT_SCARRY, CPUI_INT_XOR, CPUI_INT_AND, CPUI_INT_OR, CPUI_INT_LEFT, CPUI_INT_RIGHT,
CPUI_INT_SRIGHT, CPUI_INT_MULT, CPUI_BOOL_NEGATE, CPUI_BOOL_XOR, CPUI_BOOL_AND, CPUI_BOOL_OR,
CPUI_FLOAT_EQUAL, CPUI_FLOAT_NOTEQUAL, CPUI_FLOAT_LESS, CPUI_FLOAT_LESSEQUAL, CPUI_FLOAT_NAN,
CPUI_INDIRECT, CPUI_SUBPIECE };
oplist.insert(oplist.end(),list,list+30);
oplist.insert(oplist.end(),BitFieldInsertTransform::allowedFinalWrites.begin(),BitFieldInsertTransform::allowedFinalWrites.end());
oplist.push_back(CPUI_INDIRECT);
}
int4 RuleBitFieldOut::applyOp(PcodeOp *op,Funcdata &data)

View File

@@ -112,6 +112,8 @@ public:
BitFieldInsertTransform(Funcdata *f,PcodeOp *op,Datatype *dt,int4 off); ///< Construct from a terminating op
bool doTrace(void); ///< Trace bitfields backward from the terminating op
void apply(void); ///< Transform recovered expressions into INSERT operations
static const vector<uint4> allowedFinalWrites; ///< List of opcodes allowed for finalWriteOp (must be sorted)
};
/// \brief Class that converts bitfield pull expressions into explicit ZPULL and SPULL operations

View File

@@ -1806,18 +1806,16 @@ void RuleDoubleSub::getOpList(vector<uint4> &oplist) const
int4 RuleDoubleSub::applyOp(PcodeOp *op,Funcdata &data)
{
PcodeOp *op2;
Varnode *vn;
int4 offset1,offset2;
vn = op->getIn(0);
Varnode *vn = op->getIn(0);
if (!vn->isWritten()) return 0;
op2 = vn->getDef();
PcodeOp *op2 = vn->getDef();
if (op2->code() != CPUI_SUBPIECE) return 0;
offset1 = op->getIn(1)->getOffset();
offset2 = op2->getIn(1)->getOffset();
Varnode *inVn = op2->getIn(0);
if (inVn->isFree()) return 0;
int4 offset1 = op->getIn(1)->getOffset();
int4 offset2 = op2->getIn(1)->getOffset();
data.opSetInput(op,op2->getIn(0),0); // Skip middleman
data.opSetInput(op,inVn,0); // Skip middleman
data.opSetInput(op,data.newConstant(4,offset1+offset2), 1);
return 1;
}

View File

@@ -141,8 +141,14 @@ public class RemoteInputBlockStreamHandle extends RemoteBlockStreamHandle<InputB
try (OutputStream out = getBlockInputStream(socket)) {
copyBlockData(inputBlockStream, out);
// Done with compressed stream, force compressed data to flush
// before final handshake occurs
if (out instanceof RemoteDeflaterOutputStream deflatorOut) {
deflatorOut.finish();
}
// perform final handshake before close (uncompressed)
// Perform final handshake before close (uncompressed)
writeStreamEnd(socket);
readStreamEnd(socket, false);
}

View File

@@ -33,7 +33,7 @@ public class DataBuffer implements Buffer, Externalizable {
"db.buffers.DataBuffer.compressedOutput";
private static boolean enableCompressedSerializationOutput =
Boolean.parseBoolean(System.getProperty(COMPRESSED_SERIAL_OUTPUT_PROPERTY, "false"));
Boolean.parseBoolean(System.getProperty(COMPRESSED_SERIAL_OUTPUT_PROPERTY, "true"));
public static void enableCompressedSerializationOutput(boolean enable) {
System.setProperty(COMPRESSED_SERIAL_OUTPUT_PROPERTY, Boolean.toString(enable));

View File

@@ -4,9 +4,9 @@
* 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.
@@ -15,12 +15,13 @@
*/
package db.buffers;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;
import java.io.*;
import java.util.Arrays;
import java.util.Random;
import org.junit.After;
import org.junit.Test;
import generic.test.AbstractGenericTest;
@@ -35,6 +36,12 @@ public class DataBufferTest extends AbstractGenericTest {
super();
}
@After
public void tearDown() throws Exception {
// restore default compression: enabled
DataBuffer.enableCompressedSerializationOutput(true);
}
private void transferData(boolean useRandomFill) throws Exception {
for (int k = 0; k < 20; k++) {

View File

@@ -367,7 +367,7 @@ public abstract class LocalFolderItem implements FolderItem {
File dataDir = getDataDir();
File chkDir = new File(dataDir.getParentFile(), dataDir.getName() + ".delete");
FileUtilities.deleteDir(chkDir);
if (useDataDir && dataDir.exists() && !dataDir.renameTo(chkDir)) {
if (dataDir.exists() && !dataDir.renameTo(chkDir)) {
throw new FileInUseException(getName() + " is in use");
}
boolean success = false;
@@ -380,15 +380,13 @@ public abstract class LocalFolderItem implements FolderItem {
}
finally {
if (!success) {
if (useDataDir && !dataDir.exists() && chkDir.exists() &&
if (!dataDir.exists() && chkDir.exists() &&
propertyFile.exists()) {
chkDir.renameTo(dataDir);
}
}
else {
if (useDataDir) {
FileUtilities.deleteDir(chkDir);
}
FileUtilities.deleteDir(chkDir);
log("file deleted", user);
}
}
@@ -785,13 +783,16 @@ public abstract class LocalFolderItem implements FolderItem {
* Returns the appropriate instantiation of a LocalFolderItem
* based upon a specified property file which resides within a
* LocalFileSystem.
* <p>
* {@link LocalUnknownFolderItem} will be returned for unknown/unsupported content.
*
* @param fileSystem local file system which contains property file
* @param propertyFile property file which identifies the folder item.
* @return folder item
* @return folder item or null if invalid item.
*/
static LocalFolderItem getFolderItem(LocalFileSystem fileSystem,
ItemPropertyFile propertyFile) {
int fileType = propertyFile.getInt(FILE_TYPE, UNKNOWN_FILE_TYPE);
int fileType = propertyFile.getInt(FILE_TYPE, Integer.MIN_VALUE);
try {
if (fileType == DATAFILE_FILE_TYPE) {
return new LocalDataFileItem(fileSystem, propertyFile);
@@ -802,9 +803,15 @@ public abstract class LocalFolderItem implements FolderItem {
else if (fileType == LINK_FILE_TYPE) {
return new LocalTextDataItem(fileSystem, propertyFile);
}
else if (fileType == UNKNOWN_FILE_TYPE) {
log.error("Folder item has unspecified file type: " + new File(
else if (fileType == Integer.MIN_VALUE) {
// Item not properly created and in bad state
// Use badItem instance to remove all related storage
LocalUnknownFolderItem badItem =
new LocalUnknownFolderItem(fileSystem, propertyFile);
badItem.delete(LATEST_VERSION, "REPAIR");
log.error("Removing folder item with unspecified file type: " + new File(
propertyFile.getParentStorageDirectory(), propertyFile.getStorageName()));
return null; // triggers storage deallocation
}
else {
log.error("Folder item has unsupported file type (" + fileType + "): " + new File(

View File

@@ -95,9 +95,9 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
private static final String SHOW_TOOLTIPS_OPTION_NAME = "Show Tooltips";
private static final String BLINKING_CURSORS_OPTION_NAME = "Allow Blinking Cursors";
// TODO: Experimental Option !!
private static final String ENABLE_COMPRESSED_DATABUFFER_OUTPUT =
"Use DataBuffer Output Compression";
private static final Boolean ENABLE_COMPRESSED_DATABUFFER_OUTPUT_DEFAULT = true;
private static final String RESTORE_PREVIOUS_PROJECT_NAME = "Restore Previous Project";
private boolean shouldRestorePreviousProject;
@@ -356,7 +356,8 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
options.registerOption(SHOW_TOOLTIPS_OPTION_NAME, true, help,
"Controls the display of tooltip popup windows.");
options.registerOption(ENABLE_COMPRESSED_DATABUFFER_OUTPUT, false, help,
options.registerOption(ENABLE_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)");
@@ -381,7 +382,8 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
DockingUtils.setGlobalTooltipEnabledOption(showToolTips);
boolean compressDataBuffers =
options.getBoolean(ENABLE_COMPRESSED_DATABUFFER_OUTPUT, false);
options.getBoolean(ENABLE_COMPRESSED_DATABUFFER_OUTPUT,
ENABLE_COMPRESSED_DATABUFFER_OUTPUT_DEFAULT);
DataBuffer.enableCompressedSerializationOutput(compressDataBuffers);
shouldRestorePreviousProject = options.getBoolean(RESTORE_PREVIOUS_PROJECT_NAME, true);

View File

@@ -29,6 +29,7 @@ import javax.rmi.ssl.SslRMIClientSocketFactory;
import org.apache.commons.lang3.RandomStringUtils;
import db.buffers.DataBuffer;
import generic.hash.HashUtilities;
import generic.test.*;
import ghidra.framework.Application;
@@ -92,6 +93,8 @@ public class ServerTestUtil {
private static final int SERVER_STARTUP_MAXWAIT_MS = 20000;
public static boolean enableCompressionOnServerStart = true;
private static IOThread cmdOut;
private static IOThread cmdErr;
private static Process serverProcess;
@@ -413,6 +416,9 @@ public class ServerTestUtil {
boolean enableAltLoginName, boolean enableSSHAuthentication,
boolean enableAnonymousAuthentication) throws IOException {
// Set client-side compression to match server
DataBuffer.enableCompressedSerializationOutput(enableCompressionOnServerStart);
if (port == 0) {
port = GHIDRA_TEST_SERVER_PORT;
}
@@ -442,6 +448,7 @@ public class ServerTestUtil {
argList.add("-Xdebug");
argList.add("-Xnoagent");
argList.add("-Djava.compiler=NONE");
argList.add("-Ddb.buffers.DataBuffer.compressedOutput=" + enableCompressionOnServerStart);
argList.add("-D" + DefaultTrustManagerFactory.GHIDRA_CACERTS_PATH_PROPERTY + "=" +
getTestPkiCACertsPath());
argList.add("-D" + DefaultKeyManagerFactory.KEYSTORE_PATH_PROPERTY + "=" +

View File

@@ -1,5 +1,5 @@
application.name=Ghidra
application.version=12.1.1
application.version=12.1.2
application.release.name=DEV
application.layout.version=3
application.gradle.min=8.5