NationalSecurityAgency/ghidra · error · LSHException
LSHException fatal error
Error message
LSHException fatal error
What it means
When re-inserting an executable whose md5 already exists, compareMetadata returns a nonzero flags bit and constructFatalError is non-null for a 'fatal' bit: architecture (METADATA_ARCH), compiler (METADATA_COMP), library flag (METADATA_LIBR), or repository (METADATA_REPO). These are treated as incompatible and throw an LSHException (hard stop), because comparing functions across a different arch/compiler/repository is meaningless. The message is the constructFatalError text.
Source
Thrown at Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/elastic/ElasticDatabase.java:1632
* @throws ElasticException for communication problems with the server
* @throws LSHException if update differences cannot reconciled
* @throws DatabaseNonFatalException for non-fatal updates that can't be executed
*/
private boolean insertExe(DescriptionManager manager, ExecutableRecord exeRecord)
throws ElasticException, LSHException, DatabaseNonFatalException {
RowKeyElastic eKey = updateKey(manager, exeRecord);
String exeId = eKey.generateExeIdString();
if (!insertExecutableRecord(exeRecord, exeId)) { // Try to insert the executable
JsonObject exeObj = queryMd5ExeMatch(exeRecord.getMd5());
if (exeObj != null) { // Try to retrieve the previous version
ExecutableRecord oldrec = makeExecutableRecordTemp(exeObj);
int cmp = oldrec.compareMetadata(exeRecord);
if (cmp != 0) {
String fatalerror =
FunctionDatabase.constructFatalError(cmp, exeRecord, oldrec);
if (fatalerror != null) {
throw new LSHException(fatalerror);
}
throw new DatabaseNonFatalException(
FunctionDatabase.constructNonfatalError(cmp, exeRecord, oldrec));
}
throw new DatabaseNonFatalException(
exeRecord.getNameExec() + " is already ingested");
}
return false; // Indicate this executable already inserted
}
int newIds = 0;
long baseId = 0;
Iterator<FunctionDescription> iter = manager.listFunctions(exeRecord);
while (iter.hasNext()) { // Count the functions to insert for this executable
iter.next();
newIds += 1;
}
baseId = allocateFunctionIndexSpace(newIds); // Allocated the ids we will need
iter = manager.listFunctions(exeRecord);View on GitHub (pinned to d5f144c24d)
Solutions
- If the change is intentional, drop the existing executable first (QueryDelete / dropexec) then re-ingest.
- Align architecture/compiler/repository/library metadata with the originally ingested record before re-inserting.
- Use a separate BSim database for the variant so its md5 does not collide.
Example fix
// before
// re-ingest same md5 built with a new toolchain -> LSHException
// after
if (db.hasExecutableForMd5(md5)) {
db.dropExecutable(md5); // remove incompatible prior record
}
db.insert(insertRequest); // ingest fresh, compatible metadata Defensive patterns
Strategy: validation
Validate before calling
// Before inserting, compare metadata against any existing record for this md5.
ExecutableRecord existing = db.findExecutableByMd5(md5);
if (existing != null) {
int fatalFlags = existing.compareMetadata(newRecord)
& (METADATA_ARCH | METADATA_COMP | METADATA_LIBR | METADATA_REPO);
if (fatalFlags != 0) {
throw new IllegalStateException("md5 collides with incompatible record; drop first");
}
} Type guard
// True if re-inserting newRecord under an existing md5 would hit a fatal metadata conflict.
boolean fatalConflict = Optional.ofNullable(db.findExecutableByMd5(md5))
.map(o -> (o.compareMetadata(newRecord) & FATAL_MASK) != 0)
.orElse(false); Try / catch
try {
db.insert(req);
} catch (LSHException e) {
if (e.getMessage().contains("already ingested with different")) {
db.dropExecutable(md5); db.insert(req);
} else throw e;
} Prevention
- Keep arch/compiler/repo metadata deterministic across rebuilds of the same binary.
- Treat md5 + toolchain as the identity unit; vary the database when either changes.
When it happens
Trigger: Ingesting the same md5 with a different architecture/compiler string, a toggled library flag, or from a different repository override than the original ingest of that md5.
Common situations: Rebuilding a binary with a different toolchain and re-ingesting under the same md5; mixing a stripped vs non-stripped build; using --repo_override differently on the second ingest; cross-arch variants colliding on md5 (rare).
Related errors
- Already inserted
- is already ingested
- Already inserted
- Duplicate md5 hash, different metadata
- Overwriting existing executable id
AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14).
Data as JSON: /api/errors/703f122485973801.
Report an issue: GitHub.