Skip to content

Under some circumstances, mappingproxy will convert to normal dict while keeping the reference. #119041

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
gresm opened this issue May 14, 2024 · 3 comments
Labels
type-bug An unexpected behavior, bug, or error

Comments

@gresm
Copy link

gresm commented May 14, 2024

Bug report

Bug description:

def mutable(tp: type):
    return tp.__dict__ == type("",(),{"__eq__":lambda s,o:o})()


mutable(tuple)["hi"] = "Hello World!"

print(().hi)  # Outputs: "Hello World!"

Or

def mt(dct):
    return dct == type("",(),{"__eq__":lambda s,o:o})()


mapping = type(tuple.__dict__)
safe = mapping({"flag": True})

print(repr(safe))  # Outputs: "mappingproxy({'flag': True})"

mt(safe)["flag"] = False

print(repr(safe))  # Outputs: "mappingproxy({'flag': False})"

Two things to note:

  1. Order matter, so type("",(),{"__eq__":lambda s,o:o})() == dct won't work.
  2. I've found legitimate use of this bug: this and it seems like the author of this package have known about this for quite a while.

I don't know if I even want this to be patched, but I want to get it at least somewhat documented.

CPython versions tested on:

3.10, 3.12

Operating systems tested on:

No response

@gresm gresm added the type-bug An unexpected behavior, bug, or error label May 14, 2024
@serhiy-storchaka
Copy link
Member

This is a duplicate of #88004. It was decided that this is not worth fixing.

@serhiy-storchaka serhiy-storchaka closed this as not planned Won't fix, can't repro, duplicate, stale May 14, 2024
@Tcll
Copy link

Tcll commented Aug 5, 2024

I've tested the exploit all the way back to 2.7.18 and have verified it's on literally everything, which means I can actively modify the internal functionality of any version of python from 2.7.18+

currently working on achieving:

def func(**kw):
    print( kw.item )

have already achieved better inlining for dict updates: :)

class access(object): __slots__=(); __eq__=lambda i,o: o

d = dict.__dict__ == access() # internal memory

_init = dict.__init__ # updates memory for d so we don't return None from d.update()
_update = d.pop('update') or None # ensures our memory exists for d.update when popped

d['update'] = lambda d,*a,**kw: _update(d,*a,**kw) or _init(d) or d

a = {}.update( key1 = 'value1' ).update( key2 = 'value2' ).update( key3 = 'value3' )

print(a)

I intend to also use the exploit to patch the exploit, since clearly this needs to be fixed :)

@Tcll
Copy link

Tcll commented Aug 15, 2024

here's a patch using the exploit that restores the intended functionality for property()

class access(object): __slots__=(); __eq__=lambda i,o: o

pd = property.__dict__ == access()
pd.pop('__init__') # fix property() so attributes are actually readonly

class A(object):
    __slots__ = frozenset()
    a = property(lambda i: 'test')

a = A()
#A.a.__init__(lambda i: 'failure')

print(a.a)

basically the patch shoves property.__init__ back into C space, since the function can't be GCd

what's returned instead when you access the attribute is object.__init__, which means you can't supply arguments to overwrite the readonly attributes fget, fset, and fdel. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

3 participants