NationalSecurityAgency/ghidra · error · IOException
Bad nodeSize: {}
Error message
Bad nodeSize: {} What it means
Thrown while parsing the root node of an HFS+/HFSX B-tree embedded inside a DMG. After reading the BTreeHeaderRecord the code extracts nodeSize via Short.toUnsignedInt and guards zero: a zero nodeSize would make the subsequent node-walk loop (for i = nodeSize; ...; i += nodeSize) never advance, so it is an explicit corruption guard. The value comes straight from on-disk bytes, so it indicates the header bytes are wrong rather than a misuse of the API.
Source
Thrown at GPL/DMG/src/dmg/java/mobiledevices/dmg/btree/BTreeRootNodeDescriptor.java:30
public class BTreeRootNodeDescriptor extends BTreeNodeDescriptor {
private BTreeHeaderRecord headerRecord;
private BTreeUserDataRecord userDataRecord;
private BTreeMapRecord mapRecord;
private List<BTreeNodeDescriptor> nodes = new ArrayList<BTreeNodeDescriptor>();
public BTreeRootNodeDescriptor( GBinaryReader reader ) throws IOException {
super( reader );
headerRecord = new BTreeHeaderRecord( reader );
userDataRecord = new BTreeUserDataRecord( reader );
mapRecord = new BTreeMapRecord( reader, headerRecord );
nodes.add( this );
int nodeSize = Short.toUnsignedInt(headerRecord.getNodeSize());
if (nodeSize == 0) {
throw new IOException("Bad nodeSize: " + nodeSize);
}
for ( int i = nodeSize ; i < reader.length() ; i += nodeSize ) {
reader.setPointerIndex( i );
BTreeNodeDescriptor node = new BTreeNodeDescriptor( reader );
nodes.add( node );
node.readRecordOffsets( reader, i, headerRecord );
node.readRecords( reader, i );
}
this.readRecordOffsets( reader, 0, headerRecord );
}
public BTreeHeaderRecord getHeaderRecord() {
return headerRecord;
}
public BTreeUserDataRecord getUserDataRecord() {View on GitHub (pinned to d5f144c24d)
Solutions
- Verify the DMG file is complete and uncorrupted: re-download and confirm checksum/size before parsing.
- For encrypted DMGs, confirm the decryption key is correct (wrong key produces garbage that parses to nodeSize 0).
- Inspect the bytes of the BTreeHeaderRecord at the reader's current offset to confirm endianness and that nodeSize is a sane value (commonly 4096, 512, 1024, 2048, 8192).
- If you control the input, ensure the source produces a valid HFS+/HFSX B-tree with a non-zero nodeSize in its header record.
Example fix
// before
BTreeRootNodeDescriptor root = new BTreeRootNodeDescriptor(reader);
// after - peek nodeSize before committing to the full parse
reader.mark();
BTreeHeaderRecord hdr = new BTreeHeaderRecord(reader);
reader.reset();
int nodeSize = Short.toUnsignedInt(hdr.getNodeSize());
if (nodeSize <= 0) {
throw new IOException("Refusing to parse: B-tree nodeSize is " + nodeSize + " (corrupt or wrongly decrypted image)");
}
BTreeRootNodeDescriptor root = new BTreeRootNodeDescriptor(reader); Defensive patterns
Strategy: validation
Validate before calling
// Read the header record independently first to pre-validate nodeSize
BTreeHeaderRecord probe = new BTreeHeaderRecord(reader duplicating/seeked to the header offset);
int nodeSize = Short.toUnsignedInt(probe.getNodeSize());
if (nodeSize <= 0) {
throw new IOException("Cannot construct BTreeRootNodeDescriptor: nodeSize=" + nodeSize + " indicates corrupt/undecrypted image");
} Type guard
private static boolean isValidNodeSize(BTreeHeaderRecord hdr) {
int n = Short.toUnsignedInt(hdr.getNodeSize());
return n == 512 || n == 1024 || n == 2048 || n == 4096 || n == 8192 || n == 16384 || n == 32768;
} Try / catch
try {
BTreeRootNodeDescriptor root = new BTreeRootNodeDescriptor(reader);
} catch (IOException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Bad nodeSize")) {
// Corrupt or wrongly-decrypted B-tree header; do not retry the same bytes
throw new IOException("B-tree parse failed (nodeSize guard): likely corrupt or undecrypted DMG", e);
}
throw e;
} Prevention
- Validate the DMG checksum and completeness before parsing B-tree structures.
- Confirm encrypted-DMG decryption succeeded (key correct) before entering the B-tree reader.
- Pre-read and sanity-check the header record's nodeSize against known HFS+ node sizes before constructing the root descriptor.
When it happens
Trigger: Constructing new BTreeRootNodeDescriptor(reader) on a reader positioned over a B-tree whose header record's nodeSize field is 0. This occurs when the DMG's filesystem structures are misaligned or truncated, e.g. after failed AES decryption of an encrypted DMG yields bytes that get reinterpreted as a header.
Common situations: Wrong decryption key for an encrypted DMG (decryption produces plausible-looking but wrong bytes); a truncated or partially-downloaded DMG where the header region is short or zeroed; processing a non-HFS image through the HFS B-tree path. Because nodeSize is read with Short.toUnsignedInt, a byte-swap mismatch (endianness) on a big-endian on-disk field can also surface as 0.
Related errors
- No system partitions found. Perhaps the decryption failed?
- Invalid number of elements specified: {}
- Unable to read {} bytes
- pos cannot be less than zero
- GhidraRandomAccessFile is closed
AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14).
Data as JSON: /api/errors/f31839fa17f26882.
Report an issue: GitHub.