microg/GmsCore · error · RuntimeException

Overread allowed size end=%d

Error message

Overread allowed size end=%d

What it means

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.

Source

Thrown at safe-parcel-processor/src/main/kotlin/org/microg/safeparcel/SafeParcelProcessor.kt:171

                            $variableDeclarations
                            $setVariablesDefault
                            while (parcel.dataPosition() < end) {
                                int header = $SafeParcelReader.readHeader(parcel);
                                int fieldId = $SafeParcelReader.getFieldId(header);
                                switch (fieldId) {
                                    $readVariablesFromParcel
                                    default:
                                        $Log.d("SafeParcel", String.format("Unknown field id %d in %s, skipping.", fieldId, "$fullName"));
                                        $SafeParcelReader.skip(parcel, header);
                                }
                            }
                            $invokeConstructor
                            $setFieldsFromVariables
                        } catch (Exception e) {
                            throw new RuntimeException(String.format("Error reading %s", "$fullName"), e);
                        }
                        if (parcel.dataPosition() > end) {
                            throw new RuntimeException(String.format("Overread allowed size end=%d", end));
                        }
                        return object;
                    }

                    @Override
                    public void writeToParcel($fullName object, $Parcel parcel, int flags) {
                        int start = $SafeParcelWriter.writeObjectHeader(parcel);
                        try {
                            $variableDeclarations
                            $setVariablesFromFields
                            $writeVariableToParcel
                        } catch (Exception e) {
                            throw new RuntimeException(String.format("Error writing %s", "$fullName"), e);
                        }
                        $SafeParcelWriter.finishObjectHeader(parcel, start);
                    }

                    @Override

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Treat the incoming parcel as untrusted: validate size headers before passing to the creator, or reject the payload
  2. Align library versions between writer and reader so field layouts match
  3. Re-source the data (re-fetch / re-persist) instead of decoding the corrupt blob
  4. If the class schema legitimately changed, update both sides and bump the parcel format consistently

Example fix

// before: blindly unparceling untrusted data
val obj = CREATOR.createFromParcel(parcel)
// after: validate declared size against remaining bytes
val end = parcel.dataPosition() + parcel.readInt()
require(end <= parcel.dataSize()) { "Truncated parcel" }
val obj = CREATOR.createFromParcel(parcel)
Defensive patterns

Strategy: validation

Validate before calling

// sanity-check declared size against remaining parcel bytes before reading
val header = parcel.dataPosition()
val size = parcel.readInt()
if (size <= 0 || header + size > parcel.dataSize()) throw SecurityException("Bad parcel size")

Type guard

fun Parcel.hasValidObjectHeader(): Boolean {
  val pos = dataPosition()
  val size = readInt()
  setDataPosition(pos)
  return size > 0 && pos + 4 + size <= dataSize()
}

Try / catch

try {
  val obj = CREATOR.createFromParcel(parcel)
} catch (e: RuntimeException) {
  if (e.message?.startsWith("Overread allowed size") == true) {
    Log.w(TAG, "Parcel overread — corrupt/untrusted payload")
    obj = null
  } else throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/c708a42c68b9e255. Report an issue: GitHub.