Skip to content

Commit 42e89a4

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 with proper range validation - Applies this function to all bitwise operations - Limits values to the safe integer range (±(2^53-1)) - Provides clear error messages with source location context The safe integer range limitation is necessary because IEEE 754 doubles can only precisely represent integers up to 2^53-1. Beyond this range, precision is lost, which would lead to unpredictable results in bitwise operations that depend on exact bit patterns. This change aligns with the Jsonnet specification which requires bitwise operations to convert operands to signed 64-bit integers before performing operations, while ensuring mathematical correctness. Signed-off-by: Ville Vesilehto <[email protected]>
1 parent 5a4e8e3 commit 42e89a4

File tree

2 files changed

+48
-10
lines changed

2 files changed

+48
-10
lines changed

core/vm.cpp

+29-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,21 @@ 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+
3416+
// Constants for safe double-to-int conversion
3417+
// IEEE 754 doubles can only precisely represent integers up to 2^53-1
3418+
constexpr int64_t DOUBLE_MAX_SAFE_INTEGER = (1LL << 53) - 1;
3419+
constexpr int64_t DOUBLE_MIN_SAFE_INTEGER = -((1LL << 53) - 1);
3420+
3421+
// Check if the value is within the safe integer range
3422+
if (value < DOUBLE_MIN_SAFE_INTEGER || value > DOUBLE_MAX_SAFE_INTEGER) {
3423+
throw internal::StaticError(loc, "numeric value outside safe integer range for bitwise operation");
3424+
}
3425+
return static_cast<int64_t>(value);
3426+
}
3427+
34093428
} // namespace jsonnet::internal

core/vm.h

+19
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,25 @@ 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 safe integer range (±(2^53-1))
138+
*
139+
* The safe integer range limitation is necessary because IEEE 754 double precision
140+
* floating point numbers can only precisely represent integers up to 2^53-1.
141+
* Beyond this range, precision is lost, which would lead to unpredictable results
142+
* in bitwise operations that depend on exact bit patterns.
143+
*
144+
* \param value The double value to convert
145+
* \param loc The location in source code (for error reporting)
146+
* \throws StaticError if value is not finite or outside the safe integer range
147+
* \returns The value converted to int64_t
148+
*/
149+
int64_t safeDoubleToInt64(double value, const LocationRange& loc);
150+
132151
} // namespace jsonnet::internal
133152

134153
#endif

0 commit comments

Comments
 (0)