PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Reader\Exception

Parameter pos=$pos is invalid.

Error message

Parameter pos=$pos is invalid.

What it means

Thrown by OLERead::getInt4d() when asked to read a 4-byte integer at a negative offset into the OLE data buffer. Offsets in a valid container are non-negative; a negative one means header fields (block counts, start blocks) decoded to nonsense values earlier, which is the signature of a corrupted or hostile file.

Source

Thrown at src/PhpSpreadsheet/Shared/OLERead.php:312

            }

            // Additional Document Summary information
            if ($name == chr(5) . 'DocumentSummaryInformation') {
                $this->documentSummaryInformation = count($this->props) - 1;
            }

            $offset += self::PROPERTY_STORAGE_BLOCK_SIZE;
        }
    }

    /**
     * Read 4 bytes of data at specified position.
     */
    private static function getInt4d(string $data, int $pos): int
    {
        if ($pos < 0) {
            // Invalid position
            throw new ReaderException('Parameter pos=' . $pos . ' is invalid.');
        }

        $len = strlen($data);
        if ($len < $pos + 4) {
            $data .= str_repeat("\0", $pos + 4 - $len);
        }

        // FIX: represent numbers correctly on 64-bit system
        // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
        // Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
        $_or_24 = ord($data[$pos + 3]);
        if ($_or_24 >= 128) {
            // negative number
            $_ord_24 = -abs((256 - $_or_24) << 24);
        } else {
            $_ord_24 = ($_or_24 & 127) << 24;
        }

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Re-obtain the workbook from the source and retry; verify integrity (checksum vs origin) if available.
  2. Screen uploads before parsing: non-empty, plausible size, and correct OLE magic bytes, so damaged files fail fast with your own error message.
  3. Catch Reader\Exception around Xls loads and map it to a user-facing 'corrupt file' response instead of a server error.
  4. Update PhpSpreadsheet — bounds/robustness fixes for the legacy reader are released regularly.

Example fix

// before
$spreadsheet = (new \PhpOffice\PhpSpreadsheet\Reader\Xls())->load('input.xls');
// Parameter pos=-4 is invalid.

// after
try {
    $spreadsheet = (new \PhpOffice\PhpSpreadsheet\Reader\Xls())->load('input.xls');
} catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
    return ['error' => 'The .xls file is corrupt and cannot be read. Please re-export it.'];
}
Defensive patterns

Strategy: try-catch

Validate before calling

// cheap structural pre-check for legacy workbooks:
$size = filesize($file);
$head = (string) @file_get_contents($file, false, null, 0, 512);
if ($size === 0 || !str_starts_with($head, "\xD0\xCF\x11\xE0")) {
    throw new RuntimeException('Empty or non-OLE workbook');
}

Try / catch

try { $spreadsheet = $reader->load($file); }
catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
    if (str_contains($e->getMessage(), 'is invalid.') || str_contains($e->getMessage(), 'OLE')) {
        return ['error' => 'Corrupt .xls file — please re-export it.'];
    }
    throw $e;
}

Prevention

When it happens

Trigger: During Reader\Xls loading: constants like NUM_BIG_BLOCK_DEPOT_BLOCKS_POS or ROOT_START_BLOCK_POS combine with sizes/pointers from a damaged header so that a computed position goes negative, and getInt4d's `if ($pos < 0)` guard fires. Only reachable with malformed input — well-formed .xls files never produce negative positions.

Common situations: Truncated or bit-flipped .xls uploads; files patched by broken conversion tools; fuzzing payloads aimed at the binary reader.

Related errors


AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17). Data as JSON: /api/errors/b755f14ee777b73b. Report an issue: GitHub.