Skip to content

Commit 58b2ce9

Browse files
committed
fix(spanner): catch recursion and decode errors in proto parsing to prevent DoS
1 parent 614a3d0 commit 58b2ce9

File tree

2 files changed

+60
-3
lines changed

2 files changed

+60
-3
lines changed

packages/google-cloud-spanner/google/cloud/spanner_v1/_helpers.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from google.api_core.exceptions import Aborted
2929
from google.cloud._helpers import _date_from_iso8601_date
3030
from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper
31-
from google.protobuf.message import Message
31+
from google.protobuf.message import DecodeError, Message
3232
from google.protobuf.struct_pb2 import ListValue, Value
3333
from google.rpc.error_details_pb2 import RetryInfo
3434

@@ -603,8 +603,14 @@ def _parse_proto(value_pb, column_info, field_name):
603603
default_proto_message = column_info.get(field_name)
604604
if isinstance(default_proto_message, Message):
605605
proto_message = type(default_proto_message)()
606-
proto_message.ParseFromString(bytes_value)
607-
return proto_message
606+
try:
607+
proto_message.ParseFromString(bytes_value)
608+
return proto_message
609+
except (DecodeError, RecursionError):
610+
log.warning(
611+
"Warning: Field could not be parsed as Proto due to excessive nesting/corruption. Returning raw bytes."
612+
)
613+
return bytes_value
608614
return bytes_value
609615

610616

packages/google-cloud-spanner/tests/unit/test__helpers.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,57 @@ def test_w_proto_message(self):
771771
self._callFUT(value_pb, field_type, field_name, column_info), VALUE
772772
)
773773

774+
def test_w_proto_message_decode_error(self):
775+
import base64
776+
from unittest import mock
777+
778+
from google.protobuf.message import DecodeError
779+
from google.protobuf.struct_pb2 import Value
780+
781+
from google.cloud.spanner_v1 import Type, TypeCode
782+
783+
from .testdata import singer_pb2
784+
785+
VALUE = singer_pb2.SingerInfo()
786+
field_type = Type(code=TypeCode.PROTO)
787+
field_name = "proto_message_column"
788+
raw_bytes = VALUE.SerializeToString()
789+
value_pb = Value(string_value=base64.b64encode(raw_bytes))
790+
column_info = {"proto_message_column": singer_pb2.SingerInfo()}
791+
792+
# Mock ParseFromString to raise DecodeError
793+
with mock.patch(
794+
"google.protobuf.message.Message.ParseFromString",
795+
side_effect=DecodeError("Mock Decode Error"),
796+
):
797+
result = self._callFUT(value_pb, field_type, field_name, column_info)
798+
# Should return raw bytes
799+
self.assertEqual(result, raw_bytes)
800+
801+
def test_w_proto_message_recursion_error(self):
802+
import base64
803+
from unittest import mock
804+
805+
from google.protobuf.struct_pb2 import Value
806+
807+
from google.cloud.spanner_v1 import Type, TypeCode
808+
809+
from .testdata import singer_pb2
810+
811+
VALUE = singer_pb2.SingerInfo()
812+
field_type = Type(code=TypeCode.PROTO)
813+
field_name = "proto_message_column"
814+
raw_bytes = VALUE.SerializeToString()
815+
value_pb = Value(string_value=base64.b64encode(raw_bytes))
816+
column_info = {"proto_message_column": singer_pb2.SingerInfo()}
817+
818+
with mock.patch(
819+
"google.protobuf.message.Message.ParseFromString",
820+
side_effect=RecursionError("Mock Recursion Error"),
821+
):
822+
result = self._callFUT(value_pb, field_type, field_name, column_info)
823+
self.assertEqual(result, raw_bytes)
824+
774825
def test_w_proto_enum(self):
775826
from google.protobuf.struct_pb2 import Value
776827

0 commit comments

Comments
 (0)