gh-152405: Prevent mappingproxy operator dispatch from exposing underlying mappings#152449
gh-152405: Prevent mappingproxy operator dispatch from exposing underlying mappings#152449zainnadeem786 wants to merge 1 commit into
Conversation
|
To me the copy looks expensive and |
| mappingproxy_copy_mapping(PyObject *mapping) | ||
| { | ||
| PyObject *copy_method; | ||
| int res = PyObject_GetOptionalAttr(mapping, &_Py_ID(copy), ©_method); |
There was a problem hiding this comment.
I don't think that it is correct: see my proposed alternative #152483
There was a problem hiding this comment.
Thanks for the review @sobolevn and for putting together the alternative approach.
I agree that relying on .copy() for arbitrary mappings is not ideal since it is not part of the Mapping interface. I’ll leave my PR as-is for now and follow the discussion on #152483.
If you would prefer, I can close this PR in favor of #152483, or help with tests/review there.
|
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase |
Summary
Closes gh-152405.
This PR prevents
mappingproxyrich comparison (==,!=) and union (|) operations from exposing the underlying proxied mapping to arbitrary reflected operator methods.Previously, reflected methods such as
__eq__and__ror__could receive the original internal mapping object, allowing user code to bypass the read-only guarantees ofmappingproxy. In the case of built-in type dictionaries (for examplevars(list)), this made it possible to mutate normally immutable type dictionaries and could ultimately lead to interpreter crashes.Problem
mappingproxyexists to provide a read-only view of another mapping.However, the current implementation forwards the underlying mapping directly into generic operator dispatch.
For equality:
For union:
If the right-hand operand implements reflected methods such as:
those methods receive the original proxied mapping object instead of the
mappingproxy.For example:
returns the internal dictionary backing
list.That dictionary can then be modified directly:
bypassing the intended immutability provided by
mappingproxy.During investigation I also confirmed that the same encapsulation issue exists in
mappingproxy_or().Root Cause
The implementation passed the original proxied mapping directly into generic operator dispatch.
This violates the design intent documented in
Objects/descrobject.c, where mappingproxy operations are expected not to expose the underlying mapping.Because reflected operator dispatch executes arbitrary Python code, user-defined methods could obtain references to internal dictionaries.
Fix
This change avoids passing the original proxied mapping into operator dispatch.
Instead, mappingproxy operands are delegated through safe temporary operands before invoking:
PyObject_RichCompare()PyNumber_Or()As a result:
Mappings that do not provide an appropriate copy path safely fall back without exposing the underlying mapping.
Compatibility
The implementation is intentionally conservative.
It preserves the existing behaviour for normal operations such as comparisons with:
ChainMapwhile preventing arbitrary reflected operator methods from obtaining the original internal mapping.
No public Python API changes were introduced.
Tests
Added focused regression coverage in
Lib/test/test_types.pyfor:The regression tests intentionally avoid crashing the main test process.
Validation
The following focused test suites were executed successfully:
Additionally:
completed successfully with no whitespace errors.
Notes
During investigation I confirmed the issue on a local debug build by reproducing mutation of built-in type dictionaries and observing an interpreter crash after the mutated internal state was exercised.
The implementation intentionally avoids relying on crash-based regression tests and instead validates the encapsulation guarantees through focused behavioural tests.