Skip to content

Commit 5387713

Browse files
committed
Add safe execution environment base code
1 parent adec91a commit 5387713

File tree

1 file changed

+107
-0
lines changed

1 file changed

+107
-0
lines changed

source/be.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,113 @@ def idfu() -> int:
531531
self.key = key
532532
self.id = idfu
533533

534+
class VObject:
535+
"""
536+
A virtual object, proxying another object with limited permissions, set at creation.
537+
538+
This way of doing things is private variables for CircuitPython.
539+
However note that CPython's `inspect` module can still find it.
540+
"""
541+
542+
def __init__(
543+
self,
544+
target, # The real object
545+
attr_whitelist, # If None, the other filters apply
546+
attr_blacklist, # Same
547+
writes: bool, # Permit calling functions and setting attributes
548+
):
549+
_target = target # Private variables (not accessible via self)
550+
_whitelist = set(attr_whitelist) if attr_whitelist else None
551+
_blacklist = set(attr_blacklist) if attr_blacklist else None
552+
_writes = writes
553+
554+
class _Proxy:
555+
def __getattr__(_, name):
556+
if (_whitelist and name not in _whitelist) or (
557+
_blacklist and name in _blacklist
558+
):
559+
raise AttributeError(
560+
f'Access to "{name}" is restricted.'
561+
)
562+
563+
attr = getattr(_target, name)
564+
if callable(attr): # Proxy method calls
565+
if not _writes:
566+
raise AttributeError(
567+
f'Calls to "{name}" are restricted.'
568+
)
569+
return lambda *args, **kwargs: attr(*args, **kwargs)
570+
return attr
571+
572+
def __setattr__(_, name: str, value) -> None:
573+
if (
574+
(_whitelist and name not in _whitelist)
575+
or (_blacklist and name in _blacklist)
576+
or not _writes
577+
):
578+
raise AttributeError(
579+
f"Access to '{name}' is restricted."
580+
)
581+
setattr(_target, name, value)
582+
583+
def __dir__(_) -> list:
584+
return dir(_target)
585+
586+
self._proxy = _Proxy()
587+
588+
def __getattr__(self, name: str):
589+
return getattr(
590+
self._proxy, name
591+
) # Delegate attributes to the internal proxy.
592+
593+
def __setattr__(self, name: str, value) -> None:
594+
return setattr(self._proxy, name, value) # Same
595+
596+
def __dir__(self):
597+
return dir(self._proxy) # Delegate `dir()` to the internal proxy.
598+
599+
class SafeExec:
600+
def __init__(self):
601+
self._globals = {}
602+
603+
def add_object(
604+
self,
605+
obj,
606+
name=None, # Variable name in scope override
607+
whitelist=None, # Reference VObject comments
608+
blacklist=None,
609+
writes: bool = False,
610+
):
611+
"""Adds a restricted object inside exec()."""
612+
if name is None:
613+
try:
614+
name = obj.__name__
615+
except:
616+
raise RuntimeError("No name on nameless object")
617+
self._globals[name] = VObject(obj, whitelist, blacklist, mode)
618+
619+
def add_variable(self, **kwargs):
620+
"""Adds variables that can be modified inside exec but don't affect outside."""
621+
for key, value in kwargs.items():
622+
if isinstance(value, (int, float, str, bool, type(None))):
623+
self._globals[key] = value # Immutable types are fine
624+
elif isinstance(value, list):
625+
self._globals[key] = list(value)
626+
elif isinstance(value, tuple):
627+
self._globals[key] = tuple(value)
628+
elif isinstance(value, set):
629+
self._globals[key] = set(value)
630+
elif isinstance(value, dict):
631+
self._globals[key] = dict(value)
632+
else:
633+
raise TypeError(
634+
f"Type: {type(value).__name__}, cannot be copied"
635+
)
636+
637+
def run(self, code):
638+
"""Executes the given code in the constructed isolated environment."""
639+
exec(code, self._globals, {})
640+
534641
def getvar(var: str):
535642
"""
536643
Get a system user variable without mem leaks

0 commit comments

Comments
 (0)