{"record":{"id":"c708a42c68b9e255","repo":"microg/GmsCore","slug":"overread-allowed-size-end-d","errorCode":null,"errorMessage":"Overread allowed size end=%d","messagePattern":"Overread allowed size end=(.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"safe-parcel-processor/src/main/kotlin/org/microg/safeparcel/SafeParcelProcessor.kt","lineNumber":171,"sourceCode":"                            $variableDeclarations\n                            $setVariablesDefault\n                            while (parcel.dataPosition() < end) {\n                                int header = $SafeParcelReader.readHeader(parcel);\n                                int fieldId = $SafeParcelReader.getFieldId(header);\n                                switch (fieldId) {\n                                    $readVariablesFromParcel\n                                    default:\n                                        $Log.d(\"SafeParcel\", String.format(\"Unknown field id %d in %s, skipping.\", fieldId, \"$fullName\"));\n                                        $SafeParcelReader.skip(parcel, header);\n                                }\n                            }\n                            $invokeConstructor\n                            $setFieldsFromVariables\n                        } catch (Exception e) {\n                            throw new RuntimeException(String.format(\"Error reading %s\", \"$fullName\"), e);\n                        }\n                        if (parcel.dataPosition() > end) {\n                            throw new RuntimeException(String.format(\"Overread allowed size end=%d\", end));\n                        }\n                        return object;\n                    }\n\n                    @Override\n                    public void writeToParcel($fullName object, $Parcel parcel, int flags) {\n                        int start = $SafeParcelWriter.writeObjectHeader(parcel);\n                        try {\n                            $variableDeclarations\n                            $setVariablesFromFields\n                            $writeVariableToParcel\n                        } catch (Exception e) {\n                            throw new RuntimeException(String.format(\"Error writing %s\", \"$fullName\"), e);\n                        }\n                        $SafeParcelWriter.finishObjectHeader(parcel, start);\n                    }\n\n                    @Override","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/safe-parcel-processor/src/main/kotlin/org/microg/safeparcel/SafeParcelProcessor.kt#L153-L189","documentation":"The generated creator reads the parcel inside an explicitly sized block (end = payload start + object size). After deserialization it checks parcel.dataPosition() > end; if the reader consumed more bytes than the object declared, it throws RuntimeException(\"Overread allowed size end=%d\"). This guards against corrupt or hostile parcel data where field headers claim more bytes than the enclosing object contains.","triggerScenarios":"Deserializing a @SafeParcelable whose in-parcel length header is smaller than the fields actually encoded — e.g. truncated parcel, hand-crafted/malicious parcel, or a writer from a different library version that serialized extra fields while the reader assumes the old size.","commonSituations":"IPC payloads from a third-party app spoofing Play Services data; data written by a newer library version read by an older one; corrupted persisted state (Intent extras saved to disk, restored accounts) whose size prefix no longer matches contents.","solutions":["Treat the incoming parcel as untrusted: validate size headers before passing to the creator, or reject the payload","Align library versions between writer and reader so field layouts match","Re-source the data (re-fetch / re-persist) instead of decoding the corrupt blob","If the class schema legitimately changed, update both sides and bump the parcel format consistently"],"exampleFix":"// before: blindly unparceling untrusted data\nval obj = CREATOR.createFromParcel(parcel)\n// after: validate declared size against remaining bytes\nval end = parcel.dataPosition() + parcel.readInt()\nrequire(end <= parcel.dataSize()) { \"Truncated parcel\" }\nval obj = CREATOR.createFromParcel(parcel)","handlingStrategy":"validation","validationCode":"// sanity-check declared size against remaining parcel bytes before reading\nval header = parcel.dataPosition()\nval size = parcel.readInt()\nif (size <= 0 || header + size > parcel.dataSize()) throw SecurityException(\"Bad parcel size\")","typeGuard":"fun Parcel.hasValidObjectHeader(): Boolean {\n  val pos = dataPosition()\n  val size = readInt()\n  setDataPosition(pos)\n  return size > 0 && pos + 4 + size <= dataSize()\n}","tryCatchPattern":"try {\n  val obj = CREATOR.createFromParcel(parcel)\n} catch (e: RuntimeException) {\n  if (e.message?.startsWith(\"Overread allowed size\") == true) {\n    Log.w(TAG, \"Parcel overread — corrupt/untrusted payload\")\n    obj = null\n  } else throw e\n}","preventionTips":["Treat parcels from other apps as untrusted input; validate sizes before deserializing","Ensure writer and reader use the same library version and class schema","Discard and re-source data when overread is detected instead of retrying the same blob"],"tags":["android","parcel","security","data-corruption"],"backgroundTag":"protobuf-unmarshal-failed","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}