Skip to content

Commit 715e678

Browse files
committed
fix: add safe double-to-int64 conversion for bitwise operations
Implements proper safety checks for all bitwise operations (<<, >>, &, ^, |) when converting double values to int64_t. This prevents undefined behavior and potential security issues from integer overflow or invalid values. The implementation: - Adds safeDoubleToInt64 function to safely convert doubles to int64_t - Applies this function to all bitwise operations in vm.cpp - Validates that values are finite (not NaN or Infinity) - Ensures values are within valid int64_t range - Provides proper error reporting with source location context This change aligns with the Jsonnet specification which states that bitwise operations should first convert operands to signed 64-bit integers before performing operations. The previous implementation performed unchecked casts that could lead to undefined behavior with extreme values. Signed-off-by: Ville Vesilehto <[email protected]>
1 parent 5a4e8e3 commit 715e678

File tree

2 files changed

+36
-10
lines changed

2 files changed

+36
-10
lines changed

core/vm.cpp

+22-10
Original file line numberDiff line numberDiff line change
@@ -2559,36 +2559,38 @@ class Interpreter {
25592559
case BOP_SHIFT_L: {
25602560
if (rhs.v.d < 0)
25612561
throw makeError(ast.location, "shift by negative exponent.");
2562-
int64_t long_l = lhs.v.d;
2563-
int64_t long_r = rhs.v.d;
2562+
2563+
int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location);
2564+
int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location);
25642565
long_r = long_r % 64;
25652566
scratch = makeNumber(long_l << long_r);
25662567
} break;
25672568

25682569
case BOP_SHIFT_R: {
25692570
if (rhs.v.d < 0)
25702571
throw makeError(ast.location, "shift by negative exponent.");
2571-
int64_t long_l = lhs.v.d;
2572-
int64_t long_r = rhs.v.d;
2572+
2573+
int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location);
2574+
int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location);
25732575
long_r = long_r % 64;
25742576
scratch = makeNumber(long_l >> long_r);
25752577
} break;
25762578

25772579
case BOP_BITWISE_AND: {
2578-
int64_t long_l = lhs.v.d;
2579-
int64_t long_r = rhs.v.d;
2580+
int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location);
2581+
int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location);
25802582
scratch = makeNumber(long_l & long_r);
25812583
} break;
25822584

25832585
case BOP_BITWISE_XOR: {
2584-
int64_t long_l = lhs.v.d;
2585-
int64_t long_r = rhs.v.d;
2586+
int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location);
2587+
int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location);
25862588
scratch = makeNumber(long_l ^ long_r);
25872589
} break;
25882590

25892591
case BOP_BITWISE_OR: {
2590-
int64_t long_l = lhs.v.d;
2591-
int64_t long_r = rhs.v.d;
2592+
int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location);
2593+
int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location);
25922594
scratch = makeNumber(long_l | long_r);
25932595
} break;
25942596

@@ -3406,4 +3408,14 @@ std::vector<std::string> jsonnet_vm_execute_stream(Allocator *alloc, const AST *
34063408
return vm.manifestStream(string_output);
34073409
}
34083410

3411+
inline int64_t safeDoubleToInt64(double value, const internal::LocationRange& loc) {
3412+
if (std::isnan(value) || std::isinf(value)) {
3413+
throw internal::StaticError(loc, "numeric value is not finite");
3414+
}
3415+
if (value < static_cast<double>(INT64_MIN) || value > static_cast<double>(INT64_MAX)) {
3416+
throw internal::StaticError(loc, "numeric value out of range for integer operation");
3417+
}
3418+
return static_cast<int64_t>(value);
3419+
}
3420+
34093421
} // namespace jsonnet::internal

core/vm.h

+14
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,20 @@ std::vector<std::string> jsonnet_vm_execute_stream(
129129
double gc_min_objects, double gc_growth_trigger, const VmNativeCallbackMap &natives,
130130
JsonnetImportCallback *import_callback, void *import_callback_ctx, bool string_output);
131131

132+
/** Safely converts a double to an int64_t, with range and validity checks.
133+
*
134+
* This function is used primarily for bitwise operations which require integer operands.
135+
* It performs two safety checks:
136+
* 1. Verifies the value is finite (not NaN or Infinity)
137+
* 2. Ensures the value is within the valid range for int64_t
138+
*
139+
* \param value The double value to convert
140+
* \param loc The location in source code (for error reporting)
141+
* \throws StaticError if value is not finite or out of range
142+
* \returns The value converted to int64_t
143+
*/
144+
int64_t safeDoubleToInt64(double value, const LocationRange& loc);
145+
132146
} // namespace jsonnet::internal
133147

134148
#endif

0 commit comments

Comments
 (0)