-
Notifications
You must be signed in to change notification settings - Fork 1.8k
reject out-of-range integers in JsonReader::ParseInt #16240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |
| #ifndef FIRESTORE_CORE_SRC_UTIL_JSON_READER_H_ | ||
| #define FIRESTORE_CORE_SRC_UTIL_JSON_READER_H_ | ||
|
|
||
| #include <limits> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
|
|
@@ -99,6 +100,35 @@ class JsonReader : public util::ReadContext { | |
| template <typename IntType> | ||
| IntType ParseInt(const nlohmann::json& value, JsonReader& reader) { | ||
| if (value.is_number_integer()) { | ||
| // nlohmann stores integers as int64_t or uint64_t, so `get<IntType>()` | ||
| // 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<uint64_t>() > | ||
| static_cast<uint64_t>(std::numeric_limits<IntType>::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<int64_t>(); | ||
| if (std::numeric_limits<IntType>::is_signed) { | ||
| if (val < static_cast<int64_t>(std::numeric_limits<IntType>::min()) || | ||
| val > static_cast<int64_t>(std::numeric_limits<IntType>::max())) { | ||
| reader.Fail("Integer value out of range: " + value.dump()); | ||
| return 0; | ||
| } | ||
| } else if (val < 0 || | ||
| static_cast<uint64_t>(val) > | ||
| static_cast<uint64_t>( | ||
| std::numeric_limits<IntType>::max())) { | ||
| reader.Fail("Integer value out of range: " + value.dump()); | ||
| return 0; | ||
| } | ||
| } | ||
|
Comment on lines
+108
to
+131
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation duplicates the error handling logic ( We can refactor this by using a boolean flag bool out_of_range = false;
if (value.is_number_unsigned()) {
if (value.get<uint64_t>() >
static_cast<uint64_t>(std::numeric_limits<IntType>::max())) {
out_of_range = true;
}
} 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<int64_t>();
if (std::numeric_limits<IntType>::is_signed) {
if (val < static_cast<int64_t>(std::numeric_limits<IntType>::min()) ||
val > static_cast<int64_t>(std::numeric_limits<IntType>::max())) {
out_of_range = true;
}
} else if (val < 0 ||
static_cast<uint64_t>(val) >
static_cast<uint64_t>(
std::numeric_limits<IntType>::max())) {
out_of_range = true;
}
}
if (out_of_range) {
reader.Fail("Integer value out of range: " + value.dump());
return 0;
}
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Folded into the same change, error handling is now a single
Comment on lines
+108
to
+131
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation of bounds checking can trigger compiler warnings (such as We can avoid these warnings and simplify the code by calculating the limits safely as bool out_of_range = false;
if (value.is_number_unsigned()) {
out_of_range = value.get<uint64_t>() >
static_cast<uint64_t>(std::numeric_limits<IntType>::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.
const int64_t val = value.get<int64_t>();
const int64_t min_limit =
std::numeric_limits<IntType>::is_signed
? static_cast<int64_t>(std::numeric_limits<IntType>::min())
: 0;
const uint64_t max_limit =
static_cast<uint64_t>(std::numeric_limits<IntType>::max());
if (std::numeric_limits<IntType>::is_signed) {
out_of_range = val < min_limit ||
(val > 0 && static_cast<uint64_t>(val) > max_limit);
} else {
out_of_range = val < 0 || static_cast<uint64_t>(val) > max_limit;
}
}
if (out_of_range) {
reader.Fail("Integer value out of range: " + value.dump());
return 0;
}
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. The bounds are now computed up front as |
||
| return value.get<IntType>(); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current implementation unconditionally rejects parsing any signed integer representation into an unsigned
IntType(e.g., parsing a positive integer stored as a signedint64_tintouint32_t). Innlohmann::json, integers can be stored as signedint64_teven if they are positive (for example, if constructed from a signed variable or initializer list).Instead of failing immediately when
!std::numeric_limits<IntType>::is_signedis true on the signed branch, we should allow non-negative values and check if they fit within the range of the unsignedIntType.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Reworked the signed branch so a non-negative value stored as
int64_tdecodes when it fits an unsignedIntType(e.g.version/totalDocumentsasuint32_t), instead of rejecting every signed representation. Negatives into unsigned and genuinely out-of-range values still fail. Pushed in 5e2228f.