Skip to content
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

fix(json): int64 overflow in JSON numop #2340

Closed
7 changes: 7 additions & 0 deletions src/common/bit_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ inline size_t RawPopcount(const uint8_t *p, int64_t count) {

return bits;
}
inline bool Int64OperationOverFlow(int64_t a, int64_t b, int64_t result, uint8_t operation) {
if (operation == 1) {
return __builtin_add_overflow(a, b, &result);
} else {
return __builtin_mul_overflow(a, b, &result);
}
}
Copy link
Member

@PragmaTwice PragmaTwice May 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function looks very weird. What's operation? What's 1 here? Why mul is executed if operation is not 1?

Please remove it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this function is totally wrong since the result is passed by value.


template <typename T = void>
inline int ClzllWithEndian(uint64_t x) {
Expand Down
15 changes: 15 additions & 0 deletions src/types/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <limits>
#include <string>

#include "common/bit_util.h"
#include "common/string_util.h"
#include "jsoncons_ext/jsonpath/jsonpath_error.hpp"
#include "status.h"
Expand Down Expand Up @@ -575,6 +576,11 @@ struct JsonValue {
result->value.push_back(jsoncons::json::null());
return;
}
if (CheckJsonInt64OverFlow(number.value) || CheckJsonInt64OverFlow(origin)) {
status = {Status::RedisExecErr, "number out of range"};
return;
}

if (number.value.is_double() || origin.is_double()) {
double v = 0;
if (op == NumOpEnum::Incr) {
Expand All @@ -588,6 +594,12 @@ struct JsonValue {
}
origin = v;
} else {
int64_t v = 0;
if (util::Int64OperationOverFlow(origin.as_integer<int64_t>(), number.value.as_integer<int64_t>(), v,
static_cast<uint8_t>(op))) {
status = {Status::RedisExecErr, "number out of range"};
return;
}
if (op == NumOpEnum::Incr) {
origin = origin.as_integer<int64_t>() + number.value.as_integer<int64_t>();
} else if (op == NumOpEnum::Mul) {
Expand All @@ -601,6 +613,9 @@ struct JsonValue {
}
return status;
}
static bool CheckJsonInt64OverFlow(const jsoncons::json &check_value) {
return (check_value.is_number() && (!check_value.is_int64() && !check_value.is_double()));
}

JsonValue(const JsonValue &) = default;
JsonValue(JsonValue &&) = default;
Expand Down
12 changes: 12 additions & 0 deletions tests/gocase/unit/type/json/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,18 @@ func TestJson(t *testing.T) {
EqualJSON(t, `[1]`, rdb.Do(ctx, "JSON.NUMINCRBY", "a", "$.foo", 1).Val())
EqualJSON(t, `[1]`, rdb.Do(ctx, "JSON.GET", "a", "$.foo").Val())
EqualJSON(t, `[3]`, rdb.Do(ctx, "JSON.NUMINCRBY", "a", "$.foo", 2).Val())
//args overflow
require.Error(t, rdb.Do(ctx, "JSON.NUMINCRBY", "a", "$.foo", "22222222222222222222222222222222222222222222222222222").Err())
require.Error(t, rdb.Do(ctx, "JSON.NUMMULTBY", "a", "$.foo", "22222222222222222222222222222222222222222222222222222").Err())
//origin overflow
require.NoError(t, rdb.Do(ctx, "JSON.SET", "over_flow", "$", `{ "foo":9223372036854775809, "bar": "baz" }`).Err())
require.Error(t, rdb.Do(ctx, "JSON.NUMINCRBY", "over_flow", "$.foo", 2).Err())
require.Error(t, rdb.Do(ctx, "JSON.NUMMULTBY", "over_flow", "$.foo", 2).Err())
//result overflow 9223372036854775806<std::numeric_limits::max()
require.NoError(t, rdb.Do(ctx, "JSON.SET", "over_flow", "$", `{ "foo":9223372036854775806, "bar": "baz" }`).Err())
require.Error(t, rdb.Do(ctx, "JSON.NUMINCRBY", "over_flow", "$.foo", 20).Err())
require.Error(t, rdb.Do(ctx, "JSON.NUMMULTBY", "over_flow", "$.foo", 2).Err())

EqualJSON(t, `[3.5]`, rdb.Do(ctx, "JSON.NUMINCRBY", "a", "$.foo", 0.5).Val())

// wrong type
Expand Down
Loading