Skip to content

Security: fix(spanner), implement look-ahead deserialization filtering in CloudClientExecutor#12308

Open
missoum1307 wants to merge 2 commits intogoogleapis:mainfrom
missoum1307:fix/spanner-executor-deserialization
Open

Security: fix(spanner), implement look-ahead deserialization filtering in CloudClientExecutor#12308
missoum1307 wants to merge 2 commits intogoogleapis:mainfrom
missoum1307:fix/spanner-executor-deserialization

Conversation

@missoum1307
Copy link
Copy Markdown

@missoum1307 missoum1307 commented Mar 30, 2026

This PR addresses an insecure deserialization vulnerability in the CloudClientExecutor unmarshalling logic. By replacing the default ObjectInputStream with a hardened SecureObjectInputStream, we now enforce a strict allowlist of classes permitted for de-serialization.

The unmarshall helper method previously deserialized ByteString inputs (such as Transaction IDs and Partition tokens) without validating the underlying class types. This could allow the instantiation of unauthorized classes if a malicious payload was provided via the gRPC interface.

Changes:
Introduced SecureObjectInputStream, a subclass of ObjectInputStream that overrides resolveClass.
Implemented ALLOWED_CONF_CLASSES, a strictly defined Set of permitted classes including BatchTransactionId and Partition.
Updated unmarshall to use the new filtered stream.

Regression Testing: Confirmed that legitimate Spanner operations, specifically Partitioned Reads/Queries, remain functional as com.google.cloud.spanner.Partition and its dependencies are explicitly allowed.

@missoum1307 missoum1307 requested a review from a team as a code owner March 30, 2026 15:19
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a secure deserialization mechanism in CloudClientExecutor by implementing a look-ahead filter with a custom SecureObjectInputStream and an allowlist of authorized classes. The review feedback identifies critical issues: the class names in the allowlist use incorrect casing (e.g., partitionOptions instead of PartitionOptions), which would cause legitimate deserialization to fail, and the unmarshall method signature was unnecessarily broadened to throw Exception instead of specific checked exceptions.

Comment on lines +3805 to +3806
"com.google.cloud.spanner.partitionOptions",
"com.google.cloud.spanner.partition",
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.

critical

The class names com.google.cloud.spanner.partitionOptions and com.google.cloud.spanner.partition in the ALLOWED_CLASSES set appear to be incorrect. Based on standard Java naming conventions and the pull request description mentioning com.google.cloud.spanner.Partition, these should likely start with an uppercase letter (e.g., Partition and PartitionOptions). If these names are wrong, legitimate deserialization for partitioned reads/queries will fail with an InvalidClassException, breaking existing functionality.

Suggested change
"com.google.cloud.spanner.partitionOptions",
"com.google.cloud.spanner.partition",
"com.google.cloud.spanner.PartitionOptions",
"com.google.cloud.spanner.Partition",

throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(input.newInput());
return (T) objectInputStream.readObject();
private <T extends Serializable> T unmarshall(ByteString input) throws Exception {
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.

high

The unmarshall method's signature was changed to throw Exception, which is too broad. This forces callers to catch a generic Exception, obscuring the specific exceptions that can be thrown and potentially hiding other issues. The previous signature throws IOException, ClassNotFoundException was more precise. The new implementation can throw IOException (from input.newInput(), SecureObjectInputStream constructor, ois.readObject(), and InvalidClassException) and ClassNotFoundException (from ois.readObject()). Please revert to a more specific exception signature.

Suggested change
private <T extends Serializable> T unmarshall(ByteString input) throws Exception {
private <T extends Serializable> T unmarshall(ByteString input) throws IOException, ClassNotFoundException {

Copy link
Copy Markdown
Author

@missoum1307 missoum1307 left a comment

Choose a reason for hiding this comment

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

Fixed the casing for Partition and PartitionOptions and narrowed the exception signature to IOException, ClassNotFoundException as suggested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant