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

  1. If the change is intentional, drop the existing executable first (QueryDelete / dropexec) then re-ingest.
  2. Align architecture/compiler/repository/library metadata with the originally ingested record before re-inserting.
  3. 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

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


AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14). Data as JSON: /api/errors/703f122485973801. Report an issue: GitHub.