Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions Ghidra/Extensions/MachineLearning/Module.manifest
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions Ghidra/Extensions/MachineLearning/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ private void parseDynamicTable() throws IOException {
// 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;
}

Expand Down Expand Up @@ -931,7 +931,7 @@ private ElfSymbolTable parseDynamicSymbolTable() throws IOException {
!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;
}
Expand Down Expand Up @@ -1012,14 +1012,26 @@ else if (dynamicHashType == ElfDynamicType.DT_GNU_XHASH) {
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;
Expand All @@ -1032,6 +1044,10 @@ private int deriveGnuHashDynamicSymbolCount(long gnuHashTableOffset) throws IOEx
++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;
Expand All @@ -1042,6 +1058,25 @@ private int deriveGnuHashDynamicSymbolCount(long gnuHashTableOffset) throws IOEx
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
Expand Down Expand Up @@ -1296,6 +1331,7 @@ private long getPreLinkImageBase() {
}
}
catch (IOException e) {
logError("Elf prelink read failure (see log)");
Msg.error(this, "Elf prelink read failure", e);
}
return preLinkImageBase;
Expand Down
25 changes: 18 additions & 7 deletions Ghidra/Features/Decompiler/src/decompile/cpp/bitfield.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions Ghidra/Features/Decompiler/src/decompile/cpp/bitfield.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 7 additions & 9 deletions Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,14 @@ void serveBlockStream(Socket socket, BlockStream blockStream) throws IOException
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
13 changes: 10 additions & 3 deletions Ghidra/Framework/DB/src/test/java/db/buffers/DataBufferTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Expand All @@ -35,6 +36,12 @@ public DataBufferTest() {
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++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ final void deleteContent(String user) throws IOException {
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;
Expand All @@ -380,15 +380,13 @@ final void deleteContent(String user) throws IOException {
}
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);
}
}
Expand Down Expand Up @@ -785,13 +783,16 @@ public void clearCheckout() throws IOException {
* 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);
Expand All @@ -802,9 +803,15 @@ else if (fileType == DATABASE_FILE_TYPE) {
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -356,7 +356,8 @@ private void initFrontEndOptions() {

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

Expand All @@ -381,7 +382,8 @@ private void initFrontEndOptions() {
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);
Expand Down
Loading