{"record":{"id":"bcd4f6e48a5036d1","repo":"commaai/openpilot","slug":"invalid-wrapped-firmware-crc","errorCode":null,"errorMessage":"invalid wrapped firmware CRC","messagePattern":"invalid wrapped firmware CRC","errorType":"console","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"openpilot/system/hardware/chestnut/flash.py","lineNumber":262,"sourceCode":"    self.write_buffer(data + bytes((-len(data)) % 4))\n    self.write_enable()\n    self.transaction(0x02, addr, len(data), mode=1)\n    self.wait_write_done()\n\n\ndef validate_image(data):\n  if len(data) < 10:\n    raise ValueError(\"wrapped firmware is too short\")\n  body_len = int.from_bytes(data[:4], \"little\")\n  if body_len > MAX_CODE_SIZE:\n    raise ValueError(f\"wrapped firmware body exceeds {MAX_CODE_SIZE} bytes\")\n  if len(data) != body_len + 10 or data[4 + body_len] != 0xA5:\n    raise ValueError(\"invalid wrapped firmware length or magic\")\n  body = data[4:4 + body_len]\n  if data[5 + body_len] != sum(body) & 0xFF:\n    raise ValueError(\"invalid wrapped firmware checksum\")\n  if data[6 + body_len:] != zlib.crc32(body).to_bytes(4, \"little\"):\n    raise ValueError(\"invalid wrapped firmware CRC\")\n\n\ndef image_product(image):\n  match = re.search(rb\"custom [0-9a-f]{8}-CLEAN\", image)\n  if match is None:\n    raise ValueError(\"no product string in wrapped firmware\")\n  return match.group().decode()\n\n\ndef reconnect(flash):\n  attempt = 0\n  while True:\n    attempt += 1\n    check_budget()\n    try:\n      flash.connect()\n      flash.init()\n      return","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/commaai/openpilot/blob/516ec1e68203439a73f340f1d0b3b91eabc626ee/openpilot/system/hardware/chestnut/flash.py#L244-L280","documentation":"ValueError from validate_image(): the trailing 4 bytes of the file do not match zlib.crc32(body) computed over the firmware body. The CRC32 is the strongest of the wrapper's three integrity checks (length/magic, byte-sum, CRC32); a failure almost always means genuine data corruption rather than a format mistake.","triggerScenarios":"validate_image(data) where data[6+body_len:] != zlib.crc32(data[4:4+body_len]).to_bytes(4, 'little'). Any corruption of the body or CRC field that survived the weaker checks, or a wrapper built with a CRC over different bytes (e.g. including the header) or in big-endian order.","commonSituations":"Corrupted artifact on disk or in transit; a packaging script that CRCs the whole file instead of the body, or encodes the CRC big-endian; repeated corruption on flaky storage on the device itself.","solutions":["Restore the artifact from a known-good source (CI artifact, version control) - do not attempt to fix the CRC bytes","Fix the packaging script if you build wrappers: CRC must be zlib.crc32(body) only, little-endian, as the last 4 bytes","Validate with an independent script before flashing so bad images never reach the device","Check the storage medium if images corrupt repeatedly on the device"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import struct, zlib\n\ndef crc_ok(data: bytes) -> bool:\n    if len(data) < 10:\n        return False\n    body_len = struct.unpack_from('<I', data)[0]\n    if len(data) != body_len + 10:\n        return False\n    return data[6 + body_len:] == zlib.crc32(data[4:4 + body_len]).to_bytes(4, 'little')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Match CRC scope exactly: body only, little-endian, last 4 bytes","Treat any integrity failure as corruption - always re-fetch rather than repair","Integrate validate_image() into CI so bad wrappers never ship"],"tags":["firmware","validation","crc32","corruption"],"backgroundTag":null,"analyzedSha":"516ec1e68203439a73f340f1d0b3b91eabc626ee","analyzedAt":"2026-08-15T00:17:37.461Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}