Summary
ecma_proxy_object_delete_property can raise two TypeErrors in sequence without returning after the first pending exception. If a Proxy deleteProperty trap returns true for a non-configurable property on a non-extensible target, both invariant checks fail. In assertion-enabled builds this hits jcontext_raise_exception with an already pending exception and aborts the process.
Release builds do not abort because the assertion is compiled out, but the second TypeError overwrites the pending exception state.
Security impact
- Suggested severity: Low
- Suggested CWE: CWE-755 (Improper Handling of Exceptional Conditions); CWE-617 (Reachable Assertion) for assertion-enabled builds
- Attack surface: pure JavaScript
- Observed impact: deterministic assertion failure / abort in assertion-enabled builds; incorrect exception propagation in release builds
Root cause
At jerry-core/ecma/operations/ecma-proxy-object.c:1442-1451, the function raises an exception for the non-configurable property invariant and then continues into the non-extensible target invariant:
if (!(target_desc.flags & JERRY_PROP_IS_CONFIGURABLE))
{
ret_value = ecma_raise_type_error (ECMA_ERR_TRAP_TRUISH_PROPERTY_NON_CONFIGURABLE);
}
ecma_value_t extensible_target = ecma_builtin_object_object_is_extensible (target_obj_p);
if (!ecma_is_value_true (extensible_target))
{
ret_value = ecma_raise_type_error (ECMA_ERR_TRAP_TRUISH_TARGET_NOT_EXTENSIBLE);
}
If both conditions are true, the second ecma_raise_type_error is called while the first exception is still pending.
JerryScript revision
Observed at JerryScript commit:
b7069350c2e52e7dc721dfb75f067147bd79b39b
Tested on: 2026-06-20.
Build platform
Ubuntu 24.04.4 LTS (Linux 6.6.87.2-microsoft-standard-WSL2 x86_64)
Build steps
Debug build:
python3 tools/build.py --clean --debug
Release build:
python3 tools/build.py --clean
Build log
Not a build problem. Build completed successfully.
Test case
poc_proxy_delete_double_error.js:
var target = {};
Object.defineProperty(target, 'foo', { value: 1, configurable: false });
Object.preventExtensions(target);
var proxy = new Proxy(target, {
deleteProperty: function() {
return true;
}
});
try {
Reflect.deleteProperty(proxy, 'foo');
print("no error");
} catch (e) {
print("error:", e.message);
}
Execution platform
Same as the build platform.
Execution steps
build/bin/jerry poc_proxy_delete_double_error.js
On a debug build:
build-debug/bin/jerry poc_proxy_delete_double_error.js
Output
Release build:
Debug build:
Representative assertion-enabled build failure:
ICE: Assertion '!jcontext_has_pending_exception ()' failed at jcontext.c(jcontext_raise_exception):88.
Error: JERRY_FATAL_FAILED_ASSERTION
Aborted
Backtrace
Representative failing call path:
jcontext_raise_exception
ecma_raise_type_error
ecma_proxy_object_delete_property
ecma_op_object_delete
vm_op_delete_prop
vm_execute
main
Expected behavior
The engine should raise exactly one TypeError for the invalid deleteProperty trap result and then return that error. It should not attempt to raise a second exception while one is already pending, and it should not abort in assertion-enabled builds.
Suggested fix
Return immediately after the first failed invariant check, or check for an existing error before raising another TypeError. Also check the result of ecma_builtin_object_object_is_extensible for ECMA_IS_VALUE_ERROR before calling ecma_is_value_true.
For example:
if (!(target_desc.flags & JERRY_PROP_IS_CONFIGURABLE))
{
ret_value = ecma_raise_type_error (ECMA_ERR_TRAP_TRUISH_PROPERTY_NON_CONFIGURABLE);
ecma_free_property_descriptor (&target_desc);
return ret_value;
}
ecma_value_t extensible_target = ecma_builtin_object_object_is_extensible (target_obj_p);
if (ECMA_IS_VALUE_ERROR (extensible_target))
{
ecma_free_property_descriptor (&target_desc);
return extensible_target;
}
Regression test suggestion
Add a regression test using Reflect.deleteProperty(proxy, "foo") where the target property is non-configurable and the target object is non-extensible. The test should verify that exactly one TypeError is reported and assertion-enabled builds do not abort.
Summary
ecma_proxy_object_delete_propertycan raise two TypeErrors in sequence without returning after the first pending exception. If a ProxydeletePropertytrap returns true for a non-configurable property on a non-extensible target, both invariant checks fail. In assertion-enabled builds this hitsjcontext_raise_exceptionwith an already pending exception and aborts the process.Release builds do not abort because the assertion is compiled out, but the second TypeError overwrites the pending exception state.
Security impact
Root cause
At
jerry-core/ecma/operations/ecma-proxy-object.c:1442-1451, the function raises an exception for the non-configurable property invariant and then continues into the non-extensible target invariant:If both conditions are true, the second
ecma_raise_type_erroris called while the first exception is still pending.JerryScript revision
Observed at JerryScript commit:
Tested on: 2026-06-20.
Build platform
Build steps
Debug build:
Release build:
Build log
Not a build problem. Build completed successfully.
Test case
poc_proxy_delete_double_error.js:Execution platform
Same as the build platform.
Execution steps
On a debug build:
Output
Release build:
Debug build:
Representative assertion-enabled build failure:
Backtrace
Representative failing call path:
Expected behavior
The engine should raise exactly one TypeError for the invalid
deletePropertytrap result and then return that error. It should not attempt to raise a second exception while one is already pending, and it should not abort in assertion-enabled builds.Suggested fix
Return immediately after the first failed invariant check, or check for an existing error before raising another TypeError. Also check the result of
ecma_builtin_object_object_is_extensibleforECMA_IS_VALUE_ERRORbefore callingecma_is_value_true.For example:
Regression test suggestion
Add a regression test using
Reflect.deleteProperty(proxy, "foo")where the target property is non-configurable and the target object is non-extensible. The test should verify that exactly one TypeError is reported and assertion-enabled builds do not abort.