Skip to content

fix: make serializer id 0 reject non-empty bytes and manifests - #3751

Merged
pjfanning merged 2 commits into
apache:mainfrom
pjfanning:fix-null-serializer-non-empty-input
Nov 6, 2026
Merged

pjfanning merged 2 commits into
apache:mainfrom
pjfanning:fix-null-serializer-non-empty-input

Conversation

@pjfanning

@pjfanning pjfanning commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

Motivation

Since #3495, Serialization.deserialize(bytes, 0, manifest) with a non-empty manifest returns Success(null), whatever the bytes contain. Id 0 is a common default for a missing int value, so a corrupt stored record can load as a null payload instead of failing, and callers that rely on a failure to fall back never get one.

Modification

  • NullSerializer.fromBinary throws NotSerializableException for non-empty input. toBinary only ever produces an empty array, so such input cannot have come from this serializer.
  • Serialization.deserializeByteArray throws NotSerializableException for NullSerializer with a non-empty manifest. Serializers.manifestFor always gives a serialized null an empty manifest. The serializer never sees the manifest, so this check has to be in Serialization.
  • Document the behaviour of serializer id 0 in serialization.md.

Result

Call with serializer id 0 2.0.0-M4 2.0.0 This PR
garbage bytes, "not-a-class" manifest Failure Success(null) Failure
garbage bytes, "" manifest Success(null) Success(null) Failure
empty bytes, "not-a-class" manifest Failure Success(null) Failure
empty bytes, loadable class as manifest Success(null) Success(null) Failure
empty bytes, "" manifest (a real serialized null) Success(null) Success(null) Success(null) (unchanged)

Every Failure above is a NotSerializableException, and deserializeByteBuffer throws the same exception in those cases.

#3495 stays in place and no class loading is added: the new check only tests whether the manifest is empty.

Two rows are stricter than 2.0.0-M4, so anything relying on them now gets a failure, which needs a line in the 2.0.1 release notes:

Tests

  • sbt "actor-tests/testOnly org.apache.pekko.serialization.*": 172 passed. Both new rejection tests failed before their fixes.
  • sbt "remote/testOnly org.apache.pekko.remote.classic.transport.PekkoProtocolSpec org.apache.pekko.remote.artery.ActorSelectionQueueDistributionSpec": 25 passed (these build messages with serializer id 0). Run before the manifest check was added, not re-run after.
  • sbt actor/mimaReportBinaryIssues: no issues.
  • scalafmt --mode diff-ref=upstream/main and git diff --check: run.
  • sbt docs/paradox: not run locally, left to CI.
  • Scala 3 and the full test matrix: not run locally, left to CI.

References

Fixes #3749
Fixes #3752

@He-Pin
He-Pin requested a balanced review from Copilot November 6, 2026 17:38
He-Pin pushed a commit that referenced this pull request Nov 6, 2026
Motivation:
sbt/setup-sbt v1.5.13 fetches its signature verification policy from
the default branch of carabiner-dev/policies. That policy now has a
meta.name field the ampel version used by the action can't parse, so
any job that misses the sbt download cache fails in Install sbt with
"compiling policy: unable to parse a policy, group or policySet from
data".

Modification:
Update all sbt/setup-sbt references to v1.5.14, which pins the policy
to a compatible commit. v1.5.14 is on the ASF allow list.

Result:
Jobs can install sbt on a cache miss again.

Tests:
- Not run locally - CI runs the updated workflows on this PR

References:
Refs #3751, Refs #3753

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues are reported for the serializer id 0 validation changes.

No open issues 🎉

What changed in this PR

Tightens serializer id 0 validation so malformed serialized records fail instead of silently becoming null, while preserving #3495’s avoidance of manifest class loading.

Changes:

  • Rejects non-empty bytes and non-empty string manifests for NullSerializer with NotSerializableException.
  • Adds regression tests for valid null round trips and invalid inputs through byte-array and byte-buffer APIs.
  • Documents serializer id 0’s expected representation and rejection behavior.
File Description
docs/​src/​main/​paradox/​serialization.md Documents id 0 validation and malformed-record behavior.
actor/​src/​main/​scala/​org/​apache/​pekko/​serialization/​Serializer.scala Rejects non-empty input in NullSerializer.
actor/​src/​main/​scala/​org/​apache/​pekko/​serialization/​Serialization.scala Rejects non-empty manifests without class loading.
actor-tests/​src/​test/​scala/​org/​apache/​pekko/​serialization/​SerializeSpec.scala Tests null round trips and rejection through both deserialization APIs.

Review effort: Balanced

Motivation:
Since apache#3495, Serialization.deserialize(bytes, 0, manifest) with a
non-empty manifest returns Success(null), whatever the bytes contain.
Id 0 is a common default for a missing int value, so a corrupt stored
record can load as a null payload instead of failing.

Modification:
NullSerializer.fromBinary throws NotSerializableException for non-empty
input. toBinary only ever produces an empty array, so such input cannot
have come from this serializer. Document the behaviour of serializer
id 0.

Result:
Deserializing non-empty bytes with serializer id 0 fails, with an empty
or non-empty manifest. Empty bytes still deserialize to null, and apache#3495
stays in place with no class loading added.

Tests:
- sbt "actor-tests/testOnly org.apache.pekko.serialization.*": 171 passed
  (the new rejection test failed before the fix)
- sbt "remote/testOnly ...PekkoProtocolSpec ...ActorSelectionQueueDistributionSpec":
  25 passed
- sbt actor/mimaReportBinaryIssues: no issues
- docs/paradox: not run locally, left to CI

References:
Fixes apache#3749
Motivation:
Since apache#3495, Serialization.deserialize(empty, 0, "not-a-class") returns
Success(null). 2.0.0-M4 failed with NotSerializableException. A real
serialized null always has an empty manifest, so this input cannot have
come from a conforming sender.

Modification:
Serialization.deserializeByteArray throws NotSerializableException for
NullSerializer with a non-empty manifest. The check only tests whether
the manifest is empty, so apache#3495 stays in place and nothing is loaded.

Result:
An unresolvable manifest with serializer id 0 fails as it did in
2.0.0-M4. A loadable manifest also fails, where 2.0.0-M4 returned
Success(null), because matching that would need the class lookup that
apache#3495 removed.

Tests:
- sbt "actor-tests/testOnly org.apache.pekko.serialization.*": 172 passed
  (the new test failed before the fix)
- sbt actor/mimaReportBinaryIssues: no issues
- docs/paradox: not run locally, left to CI

References:
Fixes apache#3752
@pjfanning
pjfanning force-pushed the fix-null-serializer-non-empty-input branch from 21fb4b2 to e69cbc0 Compare November 6, 2026 18:32
@pjfanning pjfanning added this to the 2.0.1 milestone Nov 6, 2026
@pjfanning
pjfanning merged commit f70bfb6 into apache:main Nov 6, 2026
11 checks passed
@pjfanning
pjfanning deleted the fix-null-serializer-non-empty-input branch November 6, 2026 19:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants