From 29b2685432fd7b34bf2ee4d862d28eb675965ee2 Mon Sep 17 00:00:00 2001 From: alhudz Date: Mon, 8 Jun 2026 14:55:45 +0530 Subject: [PATCH 1/3] reject out-of-range integers in JsonReader::ParseInt --- Firestore/core/src/util/json_reader.h | 23 +++++++++++++++++++ .../unit/bundle/bundle_serializer_test.cc | 15 ++++++++++++ 2 files changed, 38 insertions(+) diff --git a/Firestore/core/src/util/json_reader.h b/Firestore/core/src/util/json_reader.h index f57c6aea9dd..d5a6a8bf9ff 100644 --- a/Firestore/core/src/util/json_reader.h +++ b/Firestore/core/src/util/json_reader.h @@ -17,6 +17,7 @@ #ifndef FIRESTORE_CORE_SRC_UTIL_JSON_READER_H_ #define FIRESTORE_CORE_SRC_UTIL_JSON_READER_H_ +#include #include #include @@ -99,6 +100,28 @@ class JsonReader : public util::ReadContext { template IntType ParseInt(const nlohmann::json& value, JsonReader& reader) { if (value.is_number_integer()) { + // nlohmann stores integers as int64_t or uint64_t, so `get()` + // silently wraps a value that does not fit `IntType`. Reject it instead, + // matching the range checking the string branch below gets from + // `absl::SimpleAtoi`. + if (value.is_number_unsigned()) { + if (value.get() > + static_cast(std::numeric_limits::max())) { + reader.Fail("Integer value out of range: " + value.dump()); + return 0; + } + } else if (!std::numeric_limits::is_signed) { + reader.Fail("Integer value out of range: " + value.dump()); + return 0; + } else if (value.get() < + static_cast( + std::numeric_limits::min()) || + value.get() > + static_cast( + std::numeric_limits::max())) { + reader.Fail("Integer value out of range: " + value.dump()); + return 0; + } return value.get(); } diff --git a/Firestore/core/test/unit/bundle/bundle_serializer_test.cc b/Firestore/core/test/unit/bundle/bundle_serializer_test.cc index 72d788c7832..bf372e2d766 100644 --- a/Firestore/core/test/unit/bundle/bundle_serializer_test.cc +++ b/Firestore/core/test/unit/bundle/bundle_serializer_test.cc @@ -413,6 +413,21 @@ TEST_F(BundleSerializerTest, DecodesInvalidIntegerValueFails) { VerifyJsonStringDecodeFails(json_copy); } +TEST_F(BundleSerializerTest, DecodesOutOfRangeIntegerValueFails) { + ProtoValue value; + value.set_integer_value(22222); + ProtoDocument document = TestDocument(value); + + std::string json_string; + MessageToJsonString(document, &json_string); + + // A raw (unquoted) JSON number that does not fit int64 must be rejected + // rather than silently wrapped, the same way the string-encoded form is. + auto json_copy = + ReplacedCopy(json_string, "\"22222\"", "18446744073709551615"); + VerifyJsonStringDecodeFails(json_copy); +} + TEST_F(BundleSerializerTest, DecodesDoubleValues) { for (double_t v : {-std::numeric_limits::infinity(), From 5e2228fbd5774b5ad5dba032bf847a2079259b49 Mon Sep 17 00:00:00 2001 From: alhudz Date: Tue, 16 Jun 2026 14:42:04 +0530 Subject: [PATCH 2/3] allow non-negative signed integers to decode into unsigned types --- Firestore/core/src/util/json_reader.h | 29 +++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Firestore/core/src/util/json_reader.h b/Firestore/core/src/util/json_reader.h index d5a6a8bf9ff..04cecd091b9 100644 --- a/Firestore/core/src/util/json_reader.h +++ b/Firestore/core/src/util/json_reader.h @@ -110,17 +110,24 @@ class JsonReader : public util::ReadContext { reader.Fail("Integer value out of range: " + value.dump()); return 0; } - } else if (!std::numeric_limits::is_signed) { - reader.Fail("Integer value out of range: " + value.dump()); - return 0; - } else if (value.get() < - static_cast( - std::numeric_limits::min()) || - value.get() > - static_cast( - std::numeric_limits::max())) { - reader.Fail("Integer value out of range: " + value.dump()); - return 0; + } else { + // A non-negative value may still be stored as a signed `int64_t`, so + // accept it when it fits an unsigned `IntType` rather than rejecting + // every signed representation outright. + const int64_t val = value.get(); + if (std::numeric_limits::is_signed) { + if (val < static_cast(std::numeric_limits::min()) || + val > static_cast(std::numeric_limits::max())) { + reader.Fail("Integer value out of range: " + value.dump()); + return 0; + } + } else if (val < 0 || + static_cast(val) > + static_cast( + std::numeric_limits::max())) { + reader.Fail("Integer value out of range: " + value.dump()); + return 0; + } } return value.get(); } From a1fdcc367ce3dc6bc0d503fa727a4536091cbfc1 Mon Sep 17 00:00:00 2001 From: alhudz Date: Mon, 29 Jun 2026 12:34:57 +0530 Subject: [PATCH 3/3] compute parse range bounds without narrowing IntType::max --- Firestore/core/src/util/json_reader.h | 39 +++++++++++++++------------ 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/Firestore/core/src/util/json_reader.h b/Firestore/core/src/util/json_reader.h index 04cecd091b9..3e8645a7df1 100644 --- a/Firestore/core/src/util/json_reader.h +++ b/Firestore/core/src/util/json_reader.h @@ -104,31 +104,36 @@ class JsonReader : public util::ReadContext { // silently wraps a value that does not fit `IntType`. Reject it instead, // matching the range checking the string branch below gets from // `absl::SimpleAtoi`. + bool out_of_range = false; if (value.is_number_unsigned()) { - if (value.get() > - static_cast(std::numeric_limits::max())) { - reader.Fail("Integer value out of range: " + value.dump()); - return 0; - } + out_of_range = + value.get() > + static_cast(std::numeric_limits::max()); } else { // A non-negative value may still be stored as a signed `int64_t`, so // accept it when it fits an unsigned `IntType` rather than rejecting - // every signed representation outright. + // every signed representation outright. Compute the bounds as + // `int64_t`/`uint64_t` so the comparisons never narrow `IntType::max()` + // into a signed type, which would warn for unsigned instantiations. const int64_t val = value.get(); + const int64_t min_limit = + std::numeric_limits::is_signed + ? static_cast(std::numeric_limits::min()) + : 0; + const uint64_t max_limit = + static_cast(std::numeric_limits::max()); if (std::numeric_limits::is_signed) { - if (val < static_cast(std::numeric_limits::min()) || - val > static_cast(std::numeric_limits::max())) { - reader.Fail("Integer value out of range: " + value.dump()); - return 0; - } - } else if (val < 0 || - static_cast(val) > - static_cast( - std::numeric_limits::max())) { - reader.Fail("Integer value out of range: " + value.dump()); - return 0; + out_of_range = val < min_limit || + (val > 0 && static_cast(val) > max_limit); + } else { + out_of_range = val < 0 || static_cast(val) > max_limit; } } + + if (out_of_range) { + reader.Fail("Integer value out of range: " + value.dump()); + return 0; + } return value.get(); }