So I've successfully written a metaclass that gives python the functionality of private attributes in classes.
Q: But what about inst.__private
?
A:
>>> class A(object):
__slots__ = ['__private']
def __new__(cls):
inst = object.__new__(cls)
inst.__private = 10
return inst
def showprivate(inst):
return inst.__private
>>> inst = A()
>>> inst.showprivate()
10
>>> inst._A__private = 20 # security through obscurity never works
>>> inst.showprivate() # sorry not private
20
With my metaclass, private attributes are inaccessible outside native and super-native namespaces:
class A(object, metaclass=privatetype):
__slots__ = ['A']
__private__ = ['B'] # NOTE: __slots__ is enforced for security regardless of local definition
def __new__(cls): # native
inst = object.__new__(cls)
inst.B = 10 # can only be accessed here
return inst
class B(A):
# NOTE: __slots__ = ['B'] is not allowed
__private__ = A.__private__ # inheritance would add insecurity
# ^ with this we can restrict specific private attributes to superclasses
def method(inst):
return inst.B # or here (including further subclasses)
# NOTE: you can not add methods after class creation and expect to access private attributes.
So how does it work?
The answer is frame validation, to make sure we're in a native namespace before looking up private attributes from an external dictionary.
Since the code for this is currently written in python,
it's currently exploitable by accessing __closure__[1].cell_contents
of either B.__getattribute__
or B.__setattr__
(both are the same function).
This will give you access to a mappingproxy (read-only dict) of ['attr'](inst, *val) functions,
the functions return either static values, or the result of a member_descriptor (__get__
/__set__
) call.
What's __closure__[0].cell_contents
?
It's a frozenset (read-only set) containing native code objects the function uses to validate frame f_code
objects with.
This would only be useful to a hacker if they could modify it, allowing them to add "native" functions to classes to manipulate private attributes.
So there's 2 things I want to ask here before I show the code for the metaclass.
1: is it possible to restrict access to the mappingproxy to close the final backdoor and prevent external access to manipulating private attributes??
2: I'm having an issue with super-native functions where calling a super-native while supplying the class/instance operates on super-class private attributes...
To explain this a bit further:
class A(object, metaclass=privatetype):
__private__ = ['B']
B = {'A':10, 'B':20} # static
def __new__(cls):
inst = object.__new__(cls)
for k,v in inst.B.items():
print( '%s = %s'%(k,v) )
return inst
class B(A):
__private__ = A.__private__
B = {'A':30, 'B':40, 'C':50} # static
def __new__(cls):
return A.__new__(cls)
When we call inst = B()
the result prints this: (hash order ignored)
A = 10
B = 20
This is because we're calling A.__new__
which has the private context of class A
instead of B
as expected
How can I use the context of class B
without compromising security??
For the metaclass code, keep in mind this isn't final, so it's still a bit messy:
from sys import _getframe, _functools import reduce
class _c(object): __slots__=['_a']; _m=lambda i: None
mappingproxy = _c.__dict__.__class__; method = _c()._m.__class__; del _c # yeeted
getstatic = lambda value: lambda inst, *val: None if val else value # getset for static items
newtype = type.__new__
class privatetype(type):
def __new__( typ, name, bases, NS ):
# won't be so hacky in C
def __getsetattr__(inst,attr,*val):
# return typical methods from super-class (extended security for preventing access here)
if attr == '__setattr__': return None if val else super(cls,inst).__setattr__
if attr == '__getattribute__': return None if val else super(cls,inst).__getattribute__
try:
f = _getframe(1)
return privateattrs[attr](inst,*val) if f.f_code in nativecodes else( # getset private attribute
super(cls,inst).__setattr__(attr,*val) if val else super(cls,inst).__getattribute__(attr) ) # normal attribute
finally: del f
NS['__getattribute__'] = NS['__setattr__'] = __getsetattr__
oldslots = NS.get('__slots__',frozenset()) # backup
# check for subclass globalization of private attributes
superprivateslots = reduce(frozenset.union, (frozenset(getattr(cls,'__private__', frozenset())) for cls in bases))
for attr in oldslots:
if attr in superprivateslots:
raise AttributeError("can't make private attribute '%s' public."%attr)
# remove private static attributes from NS
nativecodes = { None, __getsetattr__.__code__ }; addnativecode = nativecodes.add
privateattrs = {}
privateslots = set(NS.get('__private__', set()))
for privateattr in privateslots:
if privateattr in NS: # make static
item = NS.pop(privateattr)
privateattrs[privateattr] = getsetstatic(item)
addnativecode(getattr(item, '__code__', None)) # private methods are native too
# create private members
NS['__slots__'] = frozenset(oldslots).union(frozenset(privateslots.difference(privateattrs))) # exclude static
cls = newtype(typ, name, bases, NS)
# remove remaining private items and add super-natives
for attr in dir(cls): # dir() to get ALL public items, not just cls.__dict__ local items
item = getattr(cls,attr)
if isinstance(item, staticmethod): item = item.__func__
if isinstance(item, property):
for a in ('fget','fset','fdel'): addnativecode(getattr(getattr(item,a), '__code__', None))
else: addnativecode(getattr(item, '__code__', None))
if attr in privateslots:
delattr(cls, attr)
privateattrs[attr] = method(lambda dsc, inst,*val: dsc.__set__(inst,*val) if val else dsc.__get__(inst), item) # getset method
# freeze to prevent modification (won't be so easy to access once written in C)
nativecodes = frozenset(nativecodes)
privateattrs = mappingproxy(privateattrs) # not sure why this isn't builtin
# note that private mutable objects can still be modified
cls.__slots__ = oldslots
return cls
No I will not follow PEP8, I'm sorry if you're offended.