Skip to content

JerryScript: Proxy getOwnPropertyDescriptor invariant check misses descriptor defaults #5297

Description

@shi-bohao

Summary

ecma_op_to_complete_property_descriptor does not apply the default values required by ECMA-262 CompletePropertyDescriptor. It ensures that a descriptor is a data or accessor descriptor, but it does not mark missing writable, enumerable, and configurable attributes as defined with value false.

As a result, Proxy getOwnPropertyDescriptor invariant checks can approve a trap result that should be rejected. The returned descriptor shown to JavaScript is completed later, but the internal invariant checks have already run on incomplete flags.

Security impact

  • Suggested severity: Low / specification conformance bug
  • Suggested CWE: none assigned; this is primarily an ECMAScript Proxy invariant-validation error
  • Attack surface: pure JavaScript
  • Observed impact: ECMA-262 Proxy invariant violation; no crash observed

Root cause

At jerry-core/ecma/operations/ecma-objects-general.c:734-748, ecma_op_to_complete_property_descriptor does not complete missing boolean attributes:

void
ecma_op_to_complete_property_descriptor (ecma_property_descriptor_t *desc_p)
{
  if (!(desc_p->flags & (JERRY_PROP_IS_GET_DEFINED | JERRY_PROP_IS_SET_DEFINED)))
  {
    desc_p->flags |= JERRY_PROP_IS_VALUE_DEFINED;
  }
  else
  {
    desc_p->flags |= (JERRY_PROP_IS_GET_DEFINED | JERRY_PROP_IS_SET_DEFINED);
  }
}

ECMA-262 requires:

  • generic/data descriptors missing [[Value]] to default to undefined;
  • generic/data descriptors missing [[Writable]] to default to false;
  • accessor descriptors missing [[Get]] or [[Set]] to default to undefined;
  • all descriptors missing [[Enumerable]] or [[Configurable]] to default to false.

Since the boolean fields are not marked as defined before Proxy invariant checks, the engine can miss an invalid descriptor transition.

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
python3 tools/build.py --clean

Debug build also shows the same incorrect behavior:

python3 tools/build.py --clean --debug
Build log

Not a build problem. Build completed successfully.

Test case

poc_proxy_invariant_descriptor.js:

var target = {};
Object.defineProperty(target, 'p', {
  value: 1,
  writable: true,
  enumerable: true,
  configurable: false
});

var handler = {
  getOwnPropertyDescriptor: function(t, prop) {
    if (prop === 'p') {
      return { value: 1 };
    }
  }
};

var proxy = new Proxy(target, handler);

try {
  var desc = Object.getOwnPropertyDescriptor(proxy, 'p');
  print("desc =", JSON.stringify(desc));
} catch (e) {
  print("error:", e.message);
}
Execution platform

Same as the build platform.

Execution steps
build/bin/jerry poc_proxy_invariant_descriptor.js
Output

Current JerryScript output:

desc = {"value":1,"writable":false,"enumerable":false,"configurable":false}
Backtrace

No crash occurs.

Expected behavior

This should throw a TypeError.

The trap returns { value: 1 }. After CompletePropertyDescriptor, this is equivalent to:

{ value: 1, writable: false, enumerable: false, configurable: false }

The target property is non-configurable but writable:

{ value: 1, writable: true, enumerable: true, configurable: false }

For Proxy [[GetOwnProperty]], reporting a non-configurable, non-writable data descriptor for a target property that is non-configurable and writable violates the Proxy invariants and should be rejected.

Suggested fix

Complete all missing descriptor fields before invariant checks. The fix must distinguish data/generic descriptors from accessor descriptors: accessor descriptors must not receive JERRY_PROP_IS_WRITABLE_DEFINED.

Under JerryScript's descriptor representation, this assumes that descriptor value/getter/setter fields and boolean value bits are already initialized to their undefined / false defaults, and the *_DEFINED flags only mark whether the field exists.

void
ecma_op_to_complete_property_descriptor (ecma_property_descriptor_t *desc_p)
{
  if (!(desc_p->flags & (JERRY_PROP_IS_GET_DEFINED | JERRY_PROP_IS_SET_DEFINED)))
  {
    /* Generic and data descriptors become complete data descriptors. */
    desc_p->flags |= (JERRY_PROP_IS_VALUE_DEFINED
                      | JERRY_PROP_IS_WRITABLE_DEFINED);
  }
  else
  {
    /* Accessor descriptors get undefined getter/setter defaults. */
    desc_p->flags |= (JERRY_PROP_IS_GET_DEFINED | JERRY_PROP_IS_SET_DEFINED);
  }

  desc_p->flags |= (JERRY_PROP_IS_ENUMERABLE_DEFINED
                    | JERRY_PROP_IS_CONFIGURABLE_DEFINED);
}

After this, Proxy invariant validation should see the same completed descriptor that is later exposed to JavaScript.

Also consider checking the return value of ecma_op_to_property_descriptor before calling ecma_op_to_complete_property_descriptor, so completion is not applied to an error result.

Regression test suggestion

Add a Proxy regression test where getOwnPropertyDescriptor returns { value: 1 } for a non-configurable, writable target property. Object.getOwnPropertyDescriptor(proxy, "p") should throw a TypeError.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions