Skip to content

Commit 16f936b

Browse files
committed
Added checks for integer overflow
1 parent 85f4e17 commit 16f936b

File tree

5 files changed

+158
-5
lines changed

5 files changed

+158
-5
lines changed

README.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ White spaces are not significant and will be ignored (except inside strings).
9696
9797
### Operators
9898
99-
- Integers and floating point numbers can be mixed in all operations. If an operation involves an integer and a floating point number, the integer number is first converted to floating point before the operation. Available operations: `+`, `-`, `*`, `/`, `%` (modulo), `**` (exponentiation/power). Integer division and modulo by zero will raise an error. Integer overflow is undefined.
99+
- Integers and floating point numbers can be mixed in all operations. If an operation involves an integer and a floating point number, the integer number is first converted to floating point before the operation. Available operations: `+`, `-`, `*`, `/`, `%` (modulo), `**` (exponentiation/power). Integer division/modulo by zero and integer overflow will raise an error.
100100
- Numbers can also be tested for equality (`==` and `!=`) and compared for ordering (`>`, `>=`, `<`, `<=`), with the same conversion rules.
101101
- Strings can be concatenated with `+`. They can be tested for equality and compared for ordering (using simple alphabetical ordering). Individual characters can be accessed with angled brackets (`'str'[0]`); the index must be an integer, and is zero-based (first character had index zero). Range access is also possible with `str[a:b]` (`a` is the index of the first character to extract, and `b` is 1 plus the index of the last character to extract, so `'abc'[0:2]` is `'ab'`; an empty string is returned if `a >= b`).
102102
- Booleans can only be tested for equality, and combined with the boolean operators `and` and `or`. The boolean operators are "short-circuiting"; namely, when evaluating `a and b` and `a` evaluates to `false`, `b` will not be evaluated. This allows bypassing evaluation of operations that would otherwise be invalid (e.g. accessing elements beyond the length of an array). Finally, booleans can also be negated (`not a`). No other operation is possible.
@@ -330,13 +330,10 @@ first_non_null(1, 1+'abc') -> 1 (second argument was invalid, but no error si
330330

331331
# Security
332332

333-
All operations allowed in the language are meant to be safe, in the sense that they should not make the host process abort or behave in an unspecified manner (e.g., through out-of-bounds read or writes, use-after-free, incorrect type accesses, read of uninitialized memory, etc.). This is tested by running the test suite with sanitizers, and by fuzzing. The underlying JSON library is also battle-tested.
333+
All operations allowed in the language are meant to be safe, in the sense that they should not make the host process abort or behave in an unspecified manner (e.g., through out-of-bounds read or writes, use-after-free, incorrect type accesses, read of uninitialized memory, signed overflow, division by zero, etc.). This is tested by running the test suite with sanitizers, and by fuzzing. The underlying JSON library is also battle-tested.
334334

335335
Furthermore, the parser has a fixed maximum recursion depth to prevent stack overflows. This depth can be changed with the CMake/compilation option `JSONEXPR_MAX_AST_DEPTH`.
336336

337-
Despite the above, the library is not 100% risk-free. In particular, the following is currently unsafe:
338-
- integer overflow and underflow in evaluated expression
339-
340337
The following would trigger an exception (or abort the process if exceptions are disabled):
341338
- running out of heap memory while parsing or evaluating an expression
342339

libjsonexpr/src/functions.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,44 @@ function_result safe_max(const T& lhs, const U& rhs) {
6767
}
6868
}
6969

70+
constexpr number_integer_t int_max = std::numeric_limits<number_integer_t>::max();
71+
constexpr number_integer_t int_min = -int_max; // We loose the normally allowed most negative int.
72+
73+
bool check_overflow_add(number_integer_t lhs, number_integer_t rhs) {
74+
if (lhs >= 0) {
75+
if (rhs > int_max - lhs) {
76+
return true;
77+
}
78+
} else {
79+
if (rhs < int_min - lhs) {
80+
return true;
81+
}
82+
}
83+
84+
return false;
85+
}
86+
87+
bool check_overflow_sub(number_integer_t lhs, number_integer_t rhs) {
88+
return check_overflow_add(lhs, -rhs);
89+
}
90+
91+
bool check_overflow_mul(number_integer_t lhs, number_integer_t rhs) {
92+
return std::abs(lhs) > int_max / std::abs(rhs);
93+
}
94+
7095
#define MATHS_OPERATOR(NAME, OPERATOR) \
7196
template<typename T, typename U> \
7297
function_result safe_##NAME(const T& lhs, const U& rhs) { \
7398
if constexpr (std::is_floating_point_v<T> || std::is_floating_point_v<U>) { \
7499
return static_cast<number_float_t>(lhs) OPERATOR static_cast<number_float_t>(rhs); \
75100
} else { \
101+
if constexpr (std::is_arithmetic_v<T> && std::is_arithmetic_v<U>) { \
102+
if (check_overflow_##NAME(lhs, rhs)) { \
103+
return unexpected(std::string( \
104+
"integer overflow in '" + std::to_string(lhs) + (" " #OPERATOR " ") + \
105+
std::to_string(rhs) + "'")); \
106+
} \
107+
} \
76108
return lhs OPERATOR rhs; \
77109
} \
78110
}

libjsonexpr/src/parse.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,17 @@ try_parse_literal(depth_counter depth, std::span<const token>& tokens) {
351351
if (parsed.type() == json::value_t::discarded) {
352352
return unexpected(abort_parse(t, "could not parse literal"));
353353
}
354+
355+
if (parsed.type() == json::value_t::number_unsigned) {
356+
const auto unsigned_value = parsed.get<json::number_unsigned_t>();
357+
if (unsigned_value > static_cast<json::number_unsigned_t>(
358+
std::numeric_limits<number_integer_t>::max())) {
359+
return unexpected(
360+
abort_parse(t, "integer overflow in '" + std::string(t.content) + "'"));
361+
}
362+
363+
parsed = static_cast<json::number_integer_t>(unsigned_value);
364+
}
354365
}
355366

356367
tokens = tokens.subspan(1);

tests/data/errors.txt

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7376,6 +7376,9 @@ error: could not parse literal
73767376
1 0
73777377
^
73787378
error: unexpected content in expression
7379+
9223372036854775808
7380+
^~~~~~~~~~~~~~~~~~~
7381+
error: integer overflow in '9223372036854775808'
73797382
1 e-2
73807383
^
73817384
error: unexpected content in expression
@@ -7409,6 +7412,63 @@ error: expected unary expression
74097412
--
74107413
^
74117414
error: expected unary expression
7415+
9223372036854775807+1
7416+
^~~~~~~~~~~~~~~~~~~~~
7417+
error: integer overflow in '9223372036854775807 + 1'
7418+
9223372036854775807+2
7419+
^~~~~~~~~~~~~~~~~~~~~
7420+
error: integer overflow in '9223372036854775807 + 2'
7421+
-9223372036854775807+(-1)
7422+
^~~~~~~~~~~~~~~~~~~~~~~~~
7423+
error: integer overflow in '-9223372036854775807 + -1'
7424+
-9223372036854775807+(-2)
7425+
^~~~~~~~~~~~~~~~~~~~~~~~~
7426+
error: integer overflow in '-9223372036854775807 + -2'
7427+
1+9223372036854775807
7428+
^~~~~~~~~~~~~~~~~~~~~
7429+
error: integer overflow in '1 + 9223372036854775807'
7430+
2+9223372036854775807
7431+
^~~~~~~~~~~~~~~~~~~~~
7432+
error: integer overflow in '2 + 9223372036854775807'
7433+
-1+(-9223372036854775807)
7434+
^~~~~~~~~~~~~~~~~~~~~~~~~
7435+
error: integer overflow in '-1 + -9223372036854775807'
7436+
-2+(-9223372036854775807)
7437+
^~~~~~~~~~~~~~~~~~~~~~~~~
7438+
error: integer overflow in '-2 + -9223372036854775807'
7439+
9223372036854775807+9223372036854775807
7440+
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7441+
error: integer overflow in '9223372036854775807 + 9223372036854775807'
7442+
-9223372036854775807-1
7443+
^~~~~~~~~~~~~~~~~~~~~~
7444+
error: integer overflow in '-9223372036854775807 - 1'
7445+
-9223372036854775807-2
7446+
^~~~~~~~~~~~~~~~~~~~~~
7447+
error: integer overflow in '-9223372036854775807 - 2'
7448+
9223372036854775807-(-1)
7449+
^~~~~~~~~~~~~~~~~~~~~~~~
7450+
error: integer overflow in '9223372036854775807 - -1'
7451+
9223372036854775807-(-2)
7452+
^~~~~~~~~~~~~~~~~~~~~~~~
7453+
error: integer overflow in '9223372036854775807 - -2'
7454+
1-(-9223372036854775807)
7455+
^~~~~~~~~~~~~~~~~~~~~~~~
7456+
error: integer overflow in '1 - -9223372036854775807'
7457+
2-(-9223372036854775807)
7458+
^~~~~~~~~~~~~~~~~~~~~~~~
7459+
error: integer overflow in '2 - -9223372036854775807'
7460+
-1-9223372036854775807
7461+
^~~~~~~~~~~~~~~~~~~~~~
7462+
error: integer overflow in '-1 - 9223372036854775807'
7463+
-2-9223372036854775807
7464+
^~~~~~~~~~~~~~~~~~~~~~
7465+
error: integer overflow in '-2 - 9223372036854775807'
7466+
9223372036854775807-(-2)
7467+
^~~~~~~~~~~~~~~~~~~~~~~~
7468+
error: integer overflow in '9223372036854775807 - -2'
7469+
-9223372036854775807-9223372036854775807
7470+
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7471+
error: integer overflow in '-9223372036854775807 - 9223372036854775807'
74127472
1*
74137473
^
74147474
error: expected unary expression
@@ -7433,6 +7493,21 @@ error: integer division by zero
74337493
0/0
74347494
^~~
74357495
error: integer division by zero
7496+
9223372036854775807*2
7497+
^~~~~~~~~~~~~~~~~~~~~
7498+
error: integer overflow in '9223372036854775807 * 2'
7499+
9223372036854775807*9223372036854775807
7500+
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7501+
error: integer overflow in '9223372036854775807 * 9223372036854775807'
7502+
9223372036854775807*-2
7503+
^~~~~~~~~~~~~~~~~~~~~~
7504+
error: integer overflow in '9223372036854775807 * -2'
7505+
9223372036854775807*-9223372036854775807
7506+
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7507+
error: integer overflow in '9223372036854775807 * -9223372036854775807'
7508+
-9223372036854775808/-1
7509+
^~~~~~~~~~~~~~~~~~~
7510+
error: integer overflow in '9223372036854775808'
74367511
1%
74377512
^
74387513
error: expected unary expression

tests/src/number.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ TEST_CASE("number literal", "[maths]") {
44
SECTION("bad") {
55
CHECK_ERROR("01");
66
CHECK_ERROR("1 0");
7+
CHECK_ERROR("9223372036854775808");
78
CHECK_ERROR("1 e-2");
89
CHECK_ERROR("1e -2");
910
CHECK_ERROR("1e- 2");
@@ -16,6 +17,7 @@ TEST_CASE("number literal", "[maths]") {
1617
CHECK(evaluate("10") == "10"_json);
1718
CHECK(evaluate("0.1") == "0.1"_json);
1819
CHECK(evaluate("123456789") == "123456789"_json);
20+
CHECK(evaluate("9223372036854775807") == "9223372036854775807"_json);
1921
CHECK(evaluate("1e2") == "1e2"_json);
2022
CHECK(evaluate("1E2") == "1E2"_json);
2123
CHECK(evaluate("1e-2") == "1e-2"_json);
@@ -68,6 +70,31 @@ TEST_CASE("adsub", "[maths]") {
6870
CHECK(evaluate("1 ++1") == "2"_json);
6971
CHECK(evaluate("1++ 1") == "2"_json);
7072
}
73+
74+
SECTION("int overflow") {
75+
CHECK(evaluate("9223372036854775807+0") == "9223372036854775807"_json);
76+
CHECK_ERROR("9223372036854775807+1");
77+
CHECK_ERROR("9223372036854775807+2");
78+
CHECK_ERROR("-9223372036854775807+(-1)");
79+
CHECK_ERROR("-9223372036854775807+(-2)");
80+
CHECK_ERROR("1+9223372036854775807");
81+
CHECK_ERROR("2+9223372036854775807");
82+
CHECK_ERROR("-1+(-9223372036854775807)");
83+
CHECK_ERROR("-2+(-9223372036854775807)");
84+
CHECK_ERROR("9223372036854775807+9223372036854775807");
85+
86+
CHECK(evaluate("-9223372036854775807-0") == "-9223372036854775807"_json);
87+
CHECK_ERROR("-9223372036854775807-1");
88+
CHECK_ERROR("-9223372036854775807-2");
89+
CHECK_ERROR("9223372036854775807-(-1)");
90+
CHECK_ERROR("9223372036854775807-(-2)");
91+
CHECK_ERROR("1-(-9223372036854775807)");
92+
CHECK_ERROR("2-(-9223372036854775807)");
93+
CHECK_ERROR("-1-9223372036854775807");
94+
CHECK_ERROR("-2-9223372036854775807");
95+
CHECK_ERROR("9223372036854775807-(-2)");
96+
CHECK_ERROR("-9223372036854775807-9223372036854775807");
97+
}
7198
}
7299

73100
TEST_CASE("muldiv", "[maths]") {
@@ -89,6 +116,17 @@ TEST_CASE("muldiv", "[maths]") {
89116
CHECK_ERROR("0/0");
90117
}
91118

119+
SECTION("int overflow") {
120+
CHECK(evaluate("9223372036854775807*1") == "9223372036854775807"_json);
121+
CHECK_ERROR("9223372036854775807*2");
122+
CHECK_ERROR("9223372036854775807*9223372036854775807");
123+
CHECK(evaluate("9223372036854775807*-1") == "-9223372036854775807"_json);
124+
CHECK_ERROR("9223372036854775807*-2");
125+
CHECK_ERROR("9223372036854775807*-9223372036854775807");
126+
127+
CHECK_ERROR("-9223372036854775808/-1");
128+
}
129+
92130
SECTION("float") {
93131
CHECK(evaluate("2.0*4.0") == "8.0"_json);
94132
CHECK(evaluate("4.0/2.0") == "2.0"_json);

0 commit comments

Comments
 (0)