Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -166,17 +166,22 @@
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.Serializable;
import java.math.BigDecimal;
import java.text.ParseException;
import java.time.Duration;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
Expand Down Expand Up @@ -3795,11 +3800,38 @@ private static com.google.spanner.v1.Type cloudTypeToTypeProto(@Nonnull Type clo
}
}

/** Define the Allowlist: ONLY allow specific, safe internal classes */
private static final Set<String> ALLOWED_CLASSES = new HashSet<>(Arrays.asList(
"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",

"com.google.cloud.spanner.BatchTransactionId",
"java.util.ArrayList",
"java.lang.Number",
"java.lang.Long",
"java.lang.Integer"
));

/** Unmarshall ByteString to serializable object. */
private <T extends Serializable> T unmarshall(ByteString input)
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 {

try (InputStream is = input.newInput();
ObjectInputStream ois = new SecureObjectInputStream(is)) {
return (T) ois.readObject();
}
}

/** The "Look-ahead" Filter */
private static class SecureObjectInputStream extends ObjectInputStream {
public SecureObjectInputStream(InputStream in) throws IOException {
super(in);
}

@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!ALLOWED_CLASSES.contains(desc.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt: ", desc.getName());
}
return super.resolveClass(desc);
}
}

/** Marshall a serializable object into ByteString. */
Expand Down
Loading