Summary
A type confusion vulnerability exists in the Elk JavaScript engine's NaN boxing implementation. The IEEE 754 representation of positive Infinity is incorrectly interpreted as an internal object type pointing to offset 0, which is the global scope object. This allows attackers to read, shadow, or hijack any global variable or function from JavaScript code.
Root Cause
Elk uses NaN boxing to encode JavaScript values into 64-bit doubles. The type is encoded in bits 48-51 when the value is a NaN (exponent bits all 1s):
// elk.c line 138-140
static jsval_t mkval(uint8_t type, uint64_t data) {
return ((jsval_t) 0x7ff0U << 48U) | ((jsval_t) (type) << 48) | (data & 0xffffffffffffUL);
}
static bool is_nan(jsval_t v) { return (v >> 52U) == 0x7ffU; }
static uint8_t vtype(jsval_t v) { return is_nan(v) ? ((v >> 48U) & 15U) : (uint8_t) T_NUM; }
The problem is that IEEE 754 +Infinity has the bit pattern 0x7FF0000000000000, which:
- Passes the
is_nan() check (exponent bits are all 1s)
- Has type bits = 0, which equals
T_OBJ (object type)
- Has data bits = 0, which points to offset 0 in the JS heap
Offset 0 is where the global scope object is allocated during js_create():
// elk.c line 1333
js->scope = mkobj(js, 0); // Create global scope at offset 0
Proof of Concept
// Create Infinity - this is misinterpreted as T_OBJ pointing to global scope
let inf = 1e300 * 1e300;
// Verify the type confusion
print("typeof Infinity:", typeof(inf)); // Outputs: "object" (should be "number")
Expected output
"typeof Infinity:" "object"
Impact
Function Hijacking: Intercept and modify any global function calls, including security-critical C functions imported by the host
Security Bypass: Override authentication/authorization check functions
Logic Tampering: Modify application behavior by shadowing global variables
Information Disclosure: Read values of global variables
Suggested Fix
Add an explicit check for Infinity in the vtype() function:
// Option 1: Check for Infinity before NaN boxing interpretation
static uint8_t vtype(jsval_t v) {
// Infinity (0x7FF0000000000000) should be treated as T_NUM, not T_OBJ
if (v == 0x7FF0000000000000ULL) return T_NUM; // +Infinity
return is_nan(v) ? ((v >> 48U) & 15U) : (uint8_t) T_NUM;
}
// Option 2: Reserve type 0 and shift all type values
// This requires more extensive changes but is more robust
Alternative fix - modify the NaN boxing scheme to avoid collision:
// Use a different bit pattern that doesn't collide with IEEE 754 special values
// For example, use 0x7FF1 as the NaN marker instead of 0x7FF0
static jsval_t mkval(uint8_t type, uint64_t data) {
return ((jsval_t) 0x7ff1U << 48U) | ((jsval_t) (type) << 48) | (data & 0xffffffffffffUL);
}
static bool is_nan(jsval_t v) { return (v >> 52U) == 0x7ffU && ((v >> 48U) & 0xF) != 0; }
Summary
A type confusion vulnerability exists in the Elk JavaScript engine's NaN boxing implementation. The IEEE 754 representation of positive
Infinityis incorrectly interpreted as an internal object type pointing to offset 0, which is the global scope object. This allows attackers to read, shadow, or hijack any global variable or function from JavaScript code.Root Cause
Elk uses NaN boxing to encode JavaScript values into 64-bit doubles. The type is encoded in bits 48-51 when the value is a NaN (exponent bits all 1s):
The problem is that IEEE 754
+Infinityhas the bit pattern0x7FF0000000000000, which:is_nan()check (exponent bits are all 1s)T_OBJ(object type)Offset 0 is where the global scope object is allocated during
js_create():Proof of Concept
Expected output
Impact
Function Hijacking: Intercept and modify any global function calls, including security-critical C functions imported by the host
Security Bypass: Override authentication/authorization check functions
Logic Tampering: Modify application behavior by shadowing global variables
Information Disclosure: Read values of global variables
Suggested Fix
Add an explicit check for
Infinityin thevtype()function:Alternative fix - modify the NaN boxing scheme to avoid collision: