zopefoundation / RestrictedPython

A restricted execution environment for Python to run untrusted code.
http://restrictedpython.readthedocs.io/
Other
470 stars 38 forks source link

How to print the output while using safe_globals or safe_builtins #205

Closed VetriMaaran closed 3 years ago

VetriMaaran commented 3 years ago
from RestrictedPython import compile_restricted, safe_globals
from RestrictedPython.PrintCollector import PrintCollector

_print_ = PrintCollector
_getattr_ = getattr

source_code = """
print("Hello")
print("Oh my god")
print(dir())
result = printed
"""

byte_code = compile_restricted(
    source_code,
    filename='<string>',
    mode='exec',
)
exec(byte_code, safe_globals)
print(result)

This is my code but while I am using safe_globals it gives me an error.

name '_print_' is not defined
d-maurer commented 3 years ago

VetriMaaran wrote at 2020-12-14 09:42 -0800:

from RestrictedPython import compile_restricted, safe_globals
from RestrictedPython.PrintCollector import PrintCollector

_print_ = PrintCollector
_getattr_ = getattr

source_code = """
print("Hello")
print("Oh my god")
print(dir())
result = printed
"""

byte_code = compile_restricted(
   source_code,
   filename='<string>',
   mode='exec',
)
exec(byte_code, safe_globals)
print(result)

This is my code but while I am using safe_globals it gives me an error.

name '_print_' is not defined

You must put your definitions of _print_ and _getattr_ into safe_globals: e.g. safe_globals["_print_"] = _print_.

VetriMaaran commented 3 years ago

VetriMaaran wrote at 2020-12-14 09:42 -0800: from RestrictedPython import compile_restricted, safe_globals from RestrictedPython.PrintCollector import PrintCollector _print_ = PrintCollector _getattr_ = getattr source_code = """ print("Hello") print("Oh my god") print(dir()) result = printed """ byte_code = compile_restricted( source_code, filename='<string>', mode='exec', ) exec(byte_code, safe_globals) print(result) This is my code but while I am using safe_globals it gives me an error. name '_print_' is not defined You must put your definitions of _print_ and _getattr_ into safe_globals: e.g. safe_globals["_print_"] = _print_.

I did this but now I cannot see the printed statement on my console

I tried to print the result out now I get this error

NameError: name 'result' is not defined

How can I see the printed statement in my console?

d-maurer commented 3 years ago

VetriMaaran wrote at 2020-12-14 22:13 -0800:

...

You must put your definitions of _print_ and _getattr_ into safe_globals: e.g. safe_globals["_print_"] = _print_.

I did this but now I cannot see the printed statement on my console

RestrictedPython works as follows:

You have (indirectly) replaced Python's print with your _print_, an instance of PrintCollector. This does not print to the console but collects the printed data, usually accessed (from restricted code) via the name printed.

If you want that print prints to the console, you must provide a different definition of _print_. If you use Python 3 (or Python 2 with the "future" print_function), you could use safe_globals["_print_"] = print.

icemac commented 3 years ago

@VetriMaaran I fixed your code, see below.

Some comments:

from RestrictedPython import compile_restricted, safe_globals
from RestrictedPython.PrintCollector import PrintCollector

source_code = """
print("Hello")
print("Oh my god")
# print(dir())
result = printed
"""

my_globals = safe_globals.copy()
my_globals['_print_'] = PrintCollector
my_globals['_getattr_'] = getattr
my_globals['result'] = None

byte_code = compile_restricted(
   source_code,
   filename='<string>',
   mode='exec',
)
exec(byte_code, my_globals)
print(my_globals['result'])
VetriMaaran commented 3 years ago

VetriMaaran wrote at 2020-12-14 22:13 -0800: ... > You must put your definitions of _print_ and _getattr_ into safe_globals: e.g. safe_globals["_print_"] = _print_. I did this but now I cannot see the printed statement on my console RestrictedPython works as follows: the Python source code is compiled into an "Abstract Syntax Tree" ("AST") the AST is transformed to make important operations externally controllable. In particular, attribute access is transformed into a call to _getattr_, print is transformed into a call to _print_. * the application compiles the "AST" into byte code and executes this in an environment containing the necessary definitions (e.g. _getattr_, _print_) -- usually found in safe_globals. You have (indirectly) replaced Python's print with your _print_, an instance of PrintCollector. This does not print to the console but collects the printed data, usually accessed (from restricted code) via the name printed. If you want that print prints to the console, you must provide a different definition of _print_. If you use Python 3 (or Python 2 with the "future" print_function), you could use safe_globals["_print_"] = print.

from RestrictedPython import compile_restricted, safe_globals

_print_ = print
safe_globals["_print_"] = _print_

source_code = """
print("Hello")
print("Oh my god")
result = printed
"""

byte_code = compile_restricted(
    source_code,
    filename='<string>',
    mode='exec',
)
val = exec(byte_code, safe_globals)

This method does not seem to work

Error

 val = exec(byte_code, safe_globals)
  File "<string>", line 1, in <module>
TypeError: 'NoneType' object is not callable
VetriMaaran commented 3 years ago

@VetriMaaran I fixed your code, see below.

Some comments:

  • You have to put the variables into the dict used as globals as exec does not see the variables in your module. (I created a copy of the safe_globals so it does not get modified.)
  • dir is not defined so I commented it out.
  • exec does not return anything so you have to put the return variable inside the globals dict and extract it from there afterwards.
from RestrictedPython import compile_restricted, safe_globals
from RestrictedPython.PrintCollector import PrintCollector

source_code = """
print("Hello")
print("Oh my god")
# print(dir())
result = printed
"""

my_globals = safe_globals.copy()
my_globals['_print_'] = PrintCollector
my_globals['_getattr_'] = getattr
my_globals['result'] = None

byte_code = compile_restricted(
   source_code,
   filename='<string>',
   mode='exec',
)
exec(byte_code, my_globals)
print(my_globals['result'])

This works perfectly fine.

Fundi1330 commented 1 year ago

Printed contains only of global scope prints, but prints in functions are ignored

d-maurer commented 1 year ago

print = print safe_globals["print"] = print ... This method does not seem to work

Error

 val = exec(byte_code, safe_globals)
  File "<string>", line 1, in <module>
TypeError: 'NoneType' object is not callable

I have been wrong: you cannot directly use print as definition for _print_. What you define as _print_ must emulate RestrictedPython.PrintCollector.PrintCollector.

What happens when you call print in a context (either the module or a function context) is: as a preparatory step _print_ is called (in this context) with _getattr_ to define _print; this happens once in each context (calling print). The actual print calls are then transformed into _print._call_print(...). printed is transformed into the call _print().

This description suggests how you can transform RestrictedPython's PrintCollector to provide your own custom print implementation.

bskinny129 commented 6 months ago

Printed contains only of global scope prints, but prints in functions are ignored

Is there a way to include all prints? Or specify which functions to include prints from? It looks like the PrintCollector constructor gets called multiple times, so I'm considering a static variable to combine them all manually.

d-maurer commented 6 months ago

bskinny129 wrote at 2024-5-7 20:25 -0700:

Printed contains only of global scope prints, but prints in functions are ignored

Is there a way to include all prints? Or specify which functions to include prints from? It looks like the PrintCollector constructor gets called multiple times, so I'm considering a static variable to combine them all manually.

A call with parameter _getattr_ to what you provide as _print_ definition is injected at the beginning of each restricted function which uses print. Your _print_ is expected to return an object _print with methods __call__() and _call_print(*objects, **kw). The restricted compilation for the function has translated references to printed as _print() and print(...) calls to _print._call_print(...) calls.

That is what you can play with. Especially, you can ensure a single (global) _print (letting your _print_ always return the same object) or a single _print per (top level) function execution (instantiating _print once for such an execution and passing lambda unused: _print as _print_). Note that in both cases, surprises are possible if nested restricted functions use printed.

If the possibilities outlined above are not sufficient for you, you can use a different RestrictedNodeTransformer in a customized restricted_compilation implementation.

bskinny129 commented 6 months ago

Not sure I follow, but thanks for the detailed reply. I ended up making my own PrintCollector to store to a global variable (I'm running 10 Python files at a time)

`print_output = {0: [], 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: []}

class PrintCollector: """Collect written text, and return it when called."""

def __init__(self, _getattr_=None, index=None):
    self.txt = []
    self.index = index
    #print("created: ", index)
    self._getattr_ = _getattr_

def write(self, text):
    #print("WRITE for ", self.index)
    #print(text)
    print_output[self.index].append(text)
    self.txt.append(text)

def __call__(self):
    #print("called: ", self.index)
    #print("dict is: ", print_output[self.index])
    #return ''.join(self.txt)
    return ''.join(print_output[self.index])

def _call_print(self, *objects, **kwargs):
    if kwargs.get('file', None) is None:
        kwargs['file'] = self
    else:
        self._getattr_(kwargs['file'], 'write')

    print(*objects, **kwargs)`

And data = { "_print_": partial(PrintCollector, index=index), "__builtins__": { ...

Any surprises I should expect with this approach?