From 43ebd20f80c5c43e4cc55985bc67bd957737a647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Mon, 24 Aug 2026 10:11:01 +0200 Subject: [PATCH] AVRO-3194: [c++] Throw instead of segfaulting on GenericDatum::value() type mismatch GenericDatum::value() returned *std::any_cast(&value_). When the requested C++ type T does not match the type actually stored in the datum, std::any_cast on a pointer returns nullptr, so dereferencing it is an unchecked null-pointer dereference (CWE-476) that leads to undefined behaviour and a segmentation fault (e.g. const GenericRecord r = datum.value() when the datum does not hold a GenericRecord). Guard both the mutable and const overloads: throw an avro::Exception on a type mismatch instead of dereferencing the null pointer. --- lang/c++/include/avro/GenericDatum.hh | 20 ++++++++++++++++---- lang/c++/test/unittest.cc | 21 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/lang/c++/include/avro/GenericDatum.hh b/lang/c++/include/avro/GenericDatum.hh index 1b1c3d9af87..b18acd31e03 100644 --- a/lang/c++/include/avro/GenericDatum.hh +++ b/lang/c++/include/avro/GenericDatum.hh @@ -537,14 +537,26 @@ inline LogicalType GenericDatum::logicalType() const { template T &GenericDatum::value() { - return (type_ == AVRO_UNION) ? std::any_cast(&value_)->datum().value() - : *std::any_cast(&value_); + if (type_ == AVRO_UNION) { + return std::any_cast(&value_)->datum().value(); + } + T *ptr = std::any_cast(&value_); + if (ptr == nullptr) { + throw Exception("Invalid type. Requested C++ type does not match the datum type {}", toString(type_)); + } + return *ptr; } template const T &GenericDatum::value() const { - return (type_ == AVRO_UNION) ? std::any_cast(&value_)->datum().value() - : *std::any_cast(&value_); + if (type_ == AVRO_UNION) { + return std::any_cast(&value_)->datum().value(); + } + const T *ptr = std::any_cast(&value_); + if (ptr == nullptr) { + throw Exception("Invalid type. Requested C++ type does not match the datum type {}", toString(type_)); + } + return *ptr; } inline size_t GenericDatum::unionBranch() const { diff --git a/lang/c++/test/unittest.cc b/lang/c++/test/unittest.cc index b0cb44c5b9f..353b155eefe 100644 --- a/lang/c++/test/unittest.cc +++ b/lang/c++/test/unittest.cc @@ -24,6 +24,7 @@ #include "Compiler.hh" #include "Decoder.hh" #include "Encoder.hh" +#include "GenericDatum.hh" #include "Node.hh" #include "Parser.hh" #include "Schema.hh" @@ -1066,6 +1067,25 @@ void testNestedMapSchema() { BOOST_CHECK_EQUAL(expected, actual.str()); } +// Regression test for AVRO-3194: GenericDatum::value() with a mismatched +// C++ type used to dereference the null pointer returned by std::any_cast, +// causing a segmentation fault. It must now throw an avro::Exception instead. +static void testGenericDatumValueTypeMismatch() { + GenericDatum datum(std::string("hello")); + BOOST_CHECK_EQUAL(datum.type(), AVRO_STRING); + + // Correct type still works. + BOOST_CHECK_EQUAL(datum.value(), std::string("hello")); + + // Mismatched type must throw, not segfault. + BOOST_CHECK_THROW(datum.value(), avro::Exception); + BOOST_CHECK_THROW(datum.value>(), avro::Exception); + + // Same guarantee through the const overload. + const GenericDatum &constDatum = datum; + BOOST_CHECK_THROW(constDatum.value(), avro::Exception); +} + boost::unit_test::test_suite * init_unit_test_suite(int /*argc*/, char * /*argv*/[]) { using namespace boost::unit_test; @@ -1086,6 +1106,7 @@ init_unit_test_suite(int /*argc*/, char * /*argv*/[]) { boost::make_shared())); test->add(BOOST_TEST_CASE(&testNestedArraySchema)); test->add(BOOST_TEST_CASE(&testNestedMapSchema)); + test->add(BOOST_TEST_CASE(&testGenericDatumValueTypeMismatch)); return test; }