Skip to content

Fix guarded_getattr ImplPython/ImplC assertion-key parity#165

Merged
dataflake merged 1 commit into
masterfrom
guarded-getattr-impl-parity
Jun 7, 2026
Merged

Fix guarded_getattr ImplPython/ImplC assertion-key parity#165
dataflake merged 1 commit into
masterfrom
guarded-getattr-impl-parity

Conversation

@jensens

@jensens jensens commented Jun 5, 2026

Copy link
Copy Markdown
Member

Closes #166.

What

guarded_getattr derived its container-assertion lookup key from different objects in the two implementations:

  • ImplPython keyed on type(value.__self__) (src/AccessControl/ImplPython.py).
  • cAccessControl keys on type(inst) (src/AccessControl/cAccessControl.c, whose own comment reads assertion = Containers(type(inst))).

When an attribute of an object that has no container assertion is a bound method whose __self__ is an allow-listed simple type (tuple, bytes, range, the BTree *Items views, dict_keys/dict_values/dict_items, or any allow_type(T, 1) type), the pure-Python engine took the truthy-assertion short-circuit and returned the value without calling validate(), while the C engine routed through validate(). So the two engines could reach different access decisions, and under PURE_PYTHON / PyPy the permission check was skipped.

Fix

Key the assertion short-circuit in ImplPython.guarded_getattr on type(inst), matching cAccessControl. (The value.__self__ derivation was the only difference; the rest of the function already mirrors the C logic.)

Test

Added TestGuardedGetattr.test_validates_bound_method_of_allow_listed_type, which accesses an attribute that is a bound method of an allow-listed tuple on an object with no container assertion under a rejecting SecurityManager, and asserts Unauthorized is raised and validate() is called.

  • Without the fix, under PURE_PYTHON=1: the new test fails (Unauthorized not raised by guarded_getattr).
  • With the fix: passes under both engines.

Full suite green on CPython 3.14.3:

  • C engine: Ran 320 tests with 0 failures, 0 errors.
  • PURE_PYTHON=1: Ran 299 tests with 0 failures, 0 errors and 21 skipped.

The pure-Python ``guarded_getattr`` keyed the container assertion on
``type(value.__self__)`` while the C implementation keys on ``type(inst)``.
As a result, under ``PURE_PYTHON`` a bound method whose ``__self__`` is an
allow-listed simple type (``tuple``/``bytes``/``range``/...) took the
truthy-assertion short-circuit and was returned without calling
``validate()``, diverging from the C engine.

Key the assertion lookup on ``type(inst)`` to match ``cAccessControl``, and
add a regression test (red under ``PURE_PYTHON`` before the fix, green
after; the C engine was already correct).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@dataflake dataflake left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch. How did you even find this?

@jensens

jensens commented Jun 6, 2026

Copy link
Copy Markdown
Member Author

Nice catch. How did you even find this?

Actually I was hunting something completely different - with agentic help - with focus on a report addressed to the Plone security team. And while doing so the agents deep analysis came up with this one too.

@dataflake dataflake merged commit c18cdbb into master Jun 7, 2026
61 checks passed
@dataflake dataflake deleted the guarded-getattr-impl-parity branch June 7, 2026 07:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

guarded_getattr: ImplPython and ImplC derive the assertion key from different objects (validate() skipped under PURE_PYTHON)

2 participants