NationalSecurityAgency/ghidra · error · IOException

pos cannot be less than zero

Error message

pos cannot be less than zero

What it means

GRandomAccessFile.seek(long pos) calls checkOpen() then explicitly rejects pos < 0 with this IOException, rather than letting the underlying RandomAccessFile.seek throw. A negative position is nonsensical for a file offset, so it indicates a computed offset went wrong upstream (underflow, bad arithmetic, endianness).

Source

Thrown at GPL/DMG/src/dmg/java/mobiledevices/dmg/ghidra/GRandomAccessFile.java:133

	/**
	 * Sets the file-pointer offset, measured from the beginning of this 
	 * file, at which the next read or write occurs.  The offset may be 
	 * set beyond the end of the file. Setting the offset beyond the end 
	 * of the file does not change the file length.  The file length will 
	 * change only by writing after the offset has been set beyond the end 
	 * of the file. 
	 * @param      pos   the offset position, measured in bytes from the 
	 *                   beginning of the file, at which to set the file 
	 *                   pointer.
	 * @throws IOException 
	 * @exception  IOException  if <code>pos</code> is less than 
	 *                          <code>0</code> or if an I/O error occurs.
	 */
	public void seek(long pos) throws IOException {
		checkOpen();

		if (pos < 0) {
			throw new IOException("pos cannot be less than zero");
		}

		if (pos < bufferFileStartIndex || pos >= bufferFileStartIndex + BUFFER_SIZE) {
			// check if the last buffer contained it, and swap in if necessary
			swapInLast();
			if (pos < bufferFileStartIndex || pos >= bufferFileStartIndex + BUFFER_SIZE) {
				// not in either, gotta get a new one
				buffer = EMPTY;
				bufferOffset = 0;
				bufferFileStartIndex = pos;
			}
		}
		bufferOffset = pos - bufferFileStartIndex;
	}

	/**
	 * This method reads a byte from the file, starting from the current file pointer. 
	 * <p>

View on GitHub (pinned to d5f144c24d)

Solutions

  1. Log pos and its components before seek to identify which offset went negative.
  2. Verify the offset fields feeding pos are read with the correct width and endianness.
  3. Bounds-check pos against [0, length()) before seeking.
  4. Confirm the parsed structure providing the offset is intact (re-check the parent parse).

Example fix

// before
raf.seek(base + relative);

// after
long pos = base + relative;
if (pos < 0 || pos > raf.length()) {
    throw new IOException("Bad seek target " + pos + " (base=" + base + ", rel=" + relative + ")");
}
raf.seek(pos);
Defensive patterns

Strategy: validation

Validate before calling

if (pos < 0) {
    throw new IOException("Refusing negative seek: " + pos + " (computed offset is corrupt)");
}
if (pos > raf.length()) {
    throw new IOException("Seek target " + pos + " past EOF " + raf.length());
}
raf.seek(pos);

Type guard

private static boolean isSeekable(long pos, long fileLen) {
    return pos >= 0 && pos <= fileLen;
}

Try / catch

try {
    raf.seek(pos);
} catch (IOException e) {
    if (e.getMessage() != null && e.getMessage().contains("less than zero")) {
        throw new IOException("Negative seek computed (likely corrupt offset field)", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling seek(pos) where pos is negative. pos is usually computed from parsed offsets (base + relative, or a record's start); a corrupt/wrong relative offset or an arithmetic underflow can drive it below zero.

Common situations: A parsed offset field read with wrong endianness yielding a huge value that wraps negative when narrowed or combined; subtracting a base offset that exceeds the position; truncated input where an offset field is missing (0) combined with a negative adjustment; off-by-one in computing a record's start.

Related errors


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