Skip to content

Commit e01c886

Browse files
committed
GH-49155: [C++][IPC] Allow disabling extension type deserialization
Add extension_types_blocked option to IpcReadOptions to allow users to disable extension type deserialization for security/robustness. When enabled, extension types are returned as their storage types instead of calling custom deserialization code.
1 parent ae4c1b9 commit e01c886

3 files changed

Lines changed: 48 additions & 18 deletions

File tree

cpp/src/arrow/ipc/metadata_internal.cc

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -851,7 +851,9 @@ class FieldToFlatbufferVisitor {
851851
};
852852

853853
Status FieldFromFlatbuffer(const flatbuf::Field* field, FieldPosition field_pos,
854-
DictionaryMemo* dictionary_memo, std::shared_ptr<Field>* out) {
854+
DictionaryMemo* dictionary_memo,
855+
const IpcReadOptions* options,
856+
std::shared_ptr<Field>* out) {
855857
std::shared_ptr<DataType> type;
856858

857859
std::shared_ptr<KeyValueMetadata> metadata;
@@ -866,7 +868,7 @@ Status FieldFromFlatbuffer(const flatbuf::Field* field, FieldPosition field_pos,
866868
child_fields.resize(children->size());
867869
for (int i = 0; i < static_cast<int>(children->size()); ++i) {
868870
RETURN_NOT_OK(FieldFromFlatbuffer(children->Get(i), field_pos.child(i),
869-
dictionary_memo, &child_fields[i]));
871+
dictionary_memo, options, &child_fields[i]));
870872
}
871873
}
872874

@@ -899,21 +901,26 @@ Status FieldFromFlatbuffer(const flatbuf::Field* field, FieldPosition field_pos,
899901
// Look for extension metadata in custom_metadata field
900902
int name_index = metadata->FindKey(kExtensionTypeKeyName);
901903
if (name_index != -1) {
902-
std::shared_ptr<ExtensionType> ext_type =
903-
GetExtensionType(metadata->value(name_index));
904-
if (ext_type != nullptr) {
905-
int data_index = metadata->FindKey(kExtensionMetadataKeyName);
906-
std::string type_data = data_index == -1 ? "" : metadata->value(data_index);
907-
908-
ARROW_ASSIGN_OR_RAISE(type, ext_type->Deserialize(type, type_data));
909-
// Remove the metadata, for faithful roundtripping
910-
if (data_index != -1) {
911-
RETURN_NOT_OK(metadata->DeleteMany({name_index, data_index}));
912-
} else {
913-
RETURN_NOT_OK(metadata->Delete(name_index));
904+
// Check if extension types are blocked
905+
bool should_deserialize = (options == nullptr || !options->extension_types_blocked);
906+
907+
if (should_deserialize) {
908+
std::shared_ptr<ExtensionType> ext_type =
909+
GetExtensionType(metadata->value(name_index));
910+
if (ext_type != nullptr) {
911+
int data_index = metadata->FindKey(kExtensionMetadataKeyName);
912+
std::string type_data = data_index == -1 ? "" : metadata->value(data_index);
913+
914+
ARROW_ASSIGN_OR_RAISE(type, ext_type->Deserialize(type, type_data));
915+
// Remove the metadata, for faithful roundtripping
916+
if (data_index != -1) {
917+
RETURN_NOT_OK(metadata->DeleteMany({name_index, data_index}));
918+
} else {
919+
RETURN_NOT_OK(metadata->Delete(name_index));
920+
}
914921
}
915922
}
916-
// NOTE: if extension type is unknown, we do not raise here and
923+
// NOTE: if extension type is unknown or blocked, we do not raise here and
917924
// simply return the storage type.
918925
}
919926
}
@@ -933,6 +940,13 @@ Status FieldFromFlatbuffer(const flatbuf::Field* field, FieldPosition field_pos,
933940
return Status::OK();
934941
}
935942

943+
// Backward-compatible overload without options
944+
Status FieldFromFlatbuffer(const flatbuf::Field* field, FieldPosition field_pos,
945+
DictionaryMemo* dictionary_memo, std::shared_ptr<Field>* out) {
946+
return FieldFromFlatbuffer(field, field_pos, dictionary_memo, nullptr, out);
947+
}
948+
949+
936950
flatbuffers::Offset<KVVector> SerializeCustomMetadata(
937951
FBB& fbb, const std::shared_ptr<const KeyValueMetadata>& metadata) {
938952
std::vector<KeyValueOffset> key_values;
@@ -1433,7 +1447,7 @@ Status WriteFileFooter(const Schema& schema, const std::vector<FileBlock>& dicti
14331447
// ----------------------------------------------------------------------
14341448

14351449
Status GetSchema(const void* opaque_schema, DictionaryMemo* dictionary_memo,
1436-
std::shared_ptr<Schema>* out) {
1450+
const IpcReadOptions* options, std::shared_ptr<Schema>* out) {
14371451
auto schema = static_cast<const flatbuf::Schema*>(opaque_schema);
14381452
CHECK_FLATBUFFERS_NOT_NULL(schema, "schema");
14391453
CHECK_FLATBUFFERS_NOT_NULL(schema->fields(), "Schema.fields");
@@ -1447,7 +1461,7 @@ Status GetSchema(const void* opaque_schema, DictionaryMemo* dictionary_memo,
14471461
// XXX I don't think this check is necessary (AP)
14481462
CHECK_FLATBUFFERS_NOT_NULL(field, "DictionaryEncoding.indexType");
14491463
RETURN_NOT_OK(
1450-
FieldFromFlatbuffer(field, field_pos.child(i), dictionary_memo, &fields[i]));
1464+
FieldFromFlatbuffer(field, field_pos.child(i), dictionary_memo, options, &fields[i]));
14511465
}
14521466

14531467
std::shared_ptr<KeyValueMetadata> metadata;
@@ -1460,6 +1474,12 @@ Status GetSchema(const void* opaque_schema, DictionaryMemo* dictionary_memo,
14601474
return Status::OK();
14611475
}
14621476

1477+
// Backward-compatible overload
1478+
Status GetSchema(const void* opaque_schema, DictionaryMemo* dictionary_memo,
1479+
std::shared_ptr<Schema>* out) {
1480+
return GetSchema(opaque_schema, dictionary_memo, nullptr, out);
1481+
}
1482+
14631483
Status GetTensorMetadata(const Buffer& metadata, std::shared_ptr<DataType>* type,
14641484
std::vector<int64_t>* shape, std::vector<int64_t>* strides,
14651485
std::vector<std::string>* dim_names) {

cpp/src/arrow/ipc/options.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,16 @@ struct ARROW_EXPORT IpcReadOptions {
189189
/// The lazy property will always be reset to true to deliver the expected behavior
190190
io::CacheOptions pre_buffer_cache_options = io::CacheOptions::LazyDefaults();
191191

192+
/// \brief Whether to disable deserialization of extension types
193+
///
194+
/// If true, extension types will be deserialized as their storage types instead
195+
/// of calling custom deserialization code. This can be useful for security-sensitive
196+
/// applications that want to avoid potentially buggy third-party extension type
197+
/// deserialization code.
198+
///
199+
/// Default is false (extension types are deserialized normally).
200+
bool extension_types_blocked = false;
201+
192202
static IpcReadOptions Defaults();
193203
};
194204

cpp/src/arrow/ipc/reader.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,7 @@ Status UnpackSchemaMessage(const void* opaque_schema, const IpcReadOptions& opti
810810
std::shared_ptr<Schema>* schema,
811811
std::shared_ptr<Schema>* out_schema,
812812
std::vector<bool>* field_inclusion_mask, bool* swap_endian) {
813-
RETURN_NOT_OK(internal::GetSchema(opaque_schema, dictionary_memo, schema));
813+
RETURN_NOT_OK(internal::GetSchema(opaque_schema, dictionary_memo, &options, schema));
814814

815815
// If we are selecting only certain fields, populate the inclusion mask now
816816
// for fast lookups

0 commit comments

Comments
 (0)