Skip to content

JerryScript: Proxy getOwnPropertyDescriptor invariant check misses descriptor defaults #5297

@shi-bohao

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions