sandbox python via module loader

T

timprepscius

Greetings, in the past I wrote a sandboxing module loader for c++/
python.

I am moving away from python.. I can't stand it actually. Call me
blasphemous... I'm immune..
So this code is going to just find the trash..

Maybe it will be useful to someone else. Can't post it all, however,
if you are trying to integrate python into a system in which you need
to restrict access to "safe" modules (meaning, non-native code), and
wish also to restrict a script's ability to access other scripts, may
be useful to have a look at.

Basically, it is modeled after java. Each instantiated script in your
system will have a ModuleLoader. That module loader is in charge of
enforcing restrictions.

Sorry for not being able to post a full compilable segment.
But this sure would have helped me to look at before I wrote it.

-tim

p.s. Man I hope this code is formatted okay after posting.. Will see
I guess. Anyway. Cheers.

-- .h

/**
* legal header - public domain
*
*
============================================================================
*
* @author Timothy Prepscius
*/

#ifndef __SnowCrash_Script_Python_Internal_PScriptModuleLoader_h__
#define __SnowCrash_Script_Python_Internal_PScriptModuleLoader_h__

#include <Utilities/URL.h>
#include <Utilities/Transporter.h>
#include <Common/Script/Signature.h>
#include <set>
#include <map>
#include <list>

#define BOOST_PYTHON_STATIC_LIB
#include <boost/python.hpp>

namespace SnowCrash {
namespace Script {
namespace Python {
namespace pInternal {

class PModuleLoader
{
protected:
static boost::python::eek:bject mainDictionary;
static bool importLock;

protected:
typedef std::set<Utilities::URL> ModuleSources;
ModuleSources moduleSources;

typedef std::map<std::wstring, PyObject *> ModuleMap;
typedef std::list<boost::python::eek:bject> ModuleList;
ModuleMap moduleMap;

// this is to ensure that modules are destroyed in reverse order of
construction
// because python doesn't seem to keep module references within
modules
ModuleList moduleList;

PyObject *getCachedModule (const Utilities::URL &url);
void setCachedModule (const Utilities::URL &url, PyObject *);

PyObject *returningModule (PyObject *);
PyObject *loadModule (Utilities::Transporter::BufferPtr buffer);
Utilities::Transporter::BufferPtr loadCodeString (const
Utilities::URL &url);
PyObject *getModule(const Utilities::URL &name);
PyObject *findModule(const std::wstring &name);

typedef std::list<Utilities::URL> ModuleURLList;
ModuleURLList modulesLoading;

public:
PModuleLoader ();
virtual ~PModuleLoader();

void addAvailableSource (const Utilities::URL &);

PyObject *loadModule (const char *name);
PyObject *loadModule (const Common::Script::Signature &);

static PyObject *__import__ (
const char *name,
PyObject *globals = NULL,
PyObject *locals = NULL,
PyObject *fromlist = NULL,
PyObject *level = NULL
);

static void setMainDictionary (boost::python::eek:bject);

static void setImportLock (bool lock);
} ;

} // namespace pInternal
} // namespace Python
} // namespace Script
} // namespace SnowCrash

#endif

-- .cpp

/**
* legal header - public domain
*
*
============================================================================
*
* @author Timothy Prepscius
*/

#include "Utilities/BaseInclude/CppInclude.h"
#include "PModuleLoader.h"
#include <Utilities/VFS.h>
#include "Global/Utilities.h"

#include "Script/Instance.h"
#include "../../Monitor.h"
#include "../pScript/PScript.h"
#include "../Exception.h"

#include <algorithm>

// for PyAPI_FUNC(PyObject *) PyMarshal_ReadObjectFromString(char *,
Py_ssize_t);
#include "marshal.h"

//-----------------------------------------------------------------------------
using namespace SnowCrash::Script::python::pInternal;
using namespace SnowCrash::Script::python;
using namespace SnowCrash::Script;
using namespace boost::python;

//
=============================================================================

object PModuleLoader::mainDictionary;
bool PModuleLoader::importLock = true;

void PModuleLoader::setMainDictionary (object object)
{
PModuleLoader::mainDictionary = object;
}

PyObject *PModuleLoader::loadModule (Utilities::Transporter::BufferPtr
buffer)
{
PyObject *m=NULL, *co=NULL, *v=NULL;

try
{
// read the object
// this failed once! must investigate
co = PyMarshal_ReadObjectFromString (buffer->getData()+8, buffer-
getSize()-8);

if (!co)
throw Python::Exception ();

m = PyModule_New("_private_");

if (!m)
throw Python::Exception ();

// get the dictionary and fill in the neccessary values.. arbitrary
to us.
// notice the incref on the dictionary, if it is not there garbage
collections dies a
// miserable death
object dict = object(handle<>(incref(PyModule_GetDict(m))));
dict["__builtins__"] = mainDictionary["__builtins__"];

object fname = object(handle<>(((PyCodeObject *)co)->co_filename));
std::string s = extract<std::string>(fname);
dict["__file__"] = fname;

// evaluate the code, I wonder what this returns.
v = PyEval_EvalCode((PyCodeObject *)co, dict.ptr(), dict.ptr());

if (v)
{
decref (v);
}
else
{
throw Python::Exception();
}
}
catch (Python::Exception &e)
{
std::string pe = handleException (e);
LogRelease (PModuleLoader::loadModule, "caught exception " << pe);
}
catch (...)
{
LogRelease (PModuleLoader::loadModule, "caught unknown exception.");
}

return m;
}

PyObject *PModuleLoader::returningModule (PyObject *module)
{
if (module)
{
moduleList.push_back (object(detail::borrowed_reference(module)));
incref (module);
return module;
}

Py_RETURN_NONE;
}

Utilities::Transporter::BufferPtr PModuleLoader::loadCodeString (const
Utilities::URL &url)
{
std::wstring extension = L".pyc";
std::wstring urlString = url;

if (urlString.find (extension) == -1)
urlString += extension;

Utilities::URL fileURL (urlString);

Utilities::VFS::InFilePtr
inFile = Utilities::VFS::SystemSingleton->getInFile (
Global::getVFSDataPath(fileURL)
);

if (inFile)
{
int size = inFile->size();
char *buffer = new char[size];
inFile->read (buffer, size);

return new Utilities::Transporter::AllocatorBuffer (buffer, size);
}

return NULL;
}

PModuleLoader::pModuleLoader ()
{
}

PModuleLoader::~PModuleLoader ()
{
moduleMap.clear();
while (!moduleList.empty())
moduleList.pop_front();
}

void PModuleLoader::addAvailableSource (const Utilities::URL &url)
{
moduleSources.insert (url);
}

PyObject *PModuleLoader::getCachedModule (const Utilities::URL &url)
{
std::wstring moduleName = url;

// quick return if we have it
ModuleMap::iterator i = moduleMap.find(moduleName);
if (i != moduleMap.end())
return i->second;

return NULL;
}

void PModuleLoader::setCachedModule (const Utilities::URL &url,
PyObject *module)
{
std::wstring moduleName = url;
moduleMap[moduleName] = module;
}

PyObject *PModuleLoader::getModule (const Utilities::URL &url)
{
// see if we have a cached module
PyObject *module = getCachedModule (url);
if (module)
return module;

// else try to load the codestring
Utilities::Transporter::BufferPtr codeString = loadCodeString (url);
if (codeString)
{
// try to load the module
modulesLoading.push_front (url);
module = loadModule (codeString);
modulesLoading.pop_front ();
}

setCachedModule (url, module);
return module;
}

PyObject *PModuleLoader::findModule (const std::wstring &name)
{
// first try to load the relative path from the module
if (!modulesLoading.empty())
{
const Utilities::URL &url = modulesLoading.front();
Utilities::URL urlWithName (url, name);
PyObject *module = getModule (urlWithName);
if (module)
return module;
}

ModuleSources::iterator i;
for (i=moduleSources.begin(); i!=moduleSources.end(); ++i)
{
const Utilities::URL &url = *i;
Utilities::URL urlWithName = Utilities::URL::join(url, name);

PyObject *module = getModule(urlWithName);
if (module)
return module;
}

return NULL;
}

PyObject *PModuleLoader::loadModule (const Common::Script::Signature
&signature)
{
// path/file/class
std::wstring invokation = signature.getInvokation();

// path/file
std::wstring path = Utilities::File::getDirectory (invokation,
false);

// zipfile.zip/path/file
Utilities::URL zipPath = Utilities::URL::join(signature.getURL
(),path);

return returningModule (getModule (zipPath));
}


PyObject *PModuleLoader::loadModule (const char *name)
{
std::wstring path = Utilities::String::convert (name);
std::replace (path.begin(), path.end(), L'.', L'/');

return returningModule(findModule (path));
}

PyObject *PModuleLoader::__import__ (
const char *name,
PyObject *globals,
PyObject *locals,
PyObject *fromlist,
PyObject *level
)
{
LogDebug (Script::python, "__import__ " << name);

try
{

static char *allowedBuiltinModules[] = {
"dD.*",
"dD_*",
NULL
} ;

bool allowed = !importLock;

// check if it matches a given pattern
int i;
for (i=0; !allowed && allowedBuiltinModules!=NULL; ++i)
{
char *check = allowedBuiltinModules;
int checkLength = strlen(check);

// if there is a star at the end, match all thing that have the
substring at the beginning
if (check[checkLength-1] == '*')
{
if (strncmp (name, check, checkLength-1)==0)
allowed = true;
}
else
{
if (strcmp (name, check)==0)
allowed = true;
}
}

// only import if it is one of ours, else, must be pure python
code,
// downloaded with the script
if (allowed)
{
PyObject *module = PyImport_ImportModuleEx ((char *)name, globals,
locals, fromlist);

if (module)
{
incref (module);
return module;
}
}
}
catch (Python::Exception &e)
{
handleException (e);
}

Script::Instance *script =
Script::MonitorSingleton->getExecutingScript ();

if (!script)
Py_RETURN_NONE;

Python::pScript::pScript *pscript =
DynamicCastPtr(Python::pScript::pScript, script);

return pscript->getModuleLoader()->loadModule (name);
}

void PModuleLoader::setImportLock (bool lock)
{
importLock = lock;
}


--- Also, for these things to be triggered, you need to override them
in the main dictionary

/**
* legal header - public domain
*
*
============================================================================
* initials date comments
*
* @author Timothy Prepscius
*/

#include "Utilities/BaseInclude/CppInclude.h"
#include "PPackage.h"
#include "PAccessor.h"
#include "PModuleLoader.h"
#include "PRestricted.h"
#include "PLog.h"
#include "PSystem.h"
#include "../Defines.h"

using namespace SnowCrash::Script::python::pInternal;
using namespace boost::python;

BOOST_PYTHON_MODULE(dD_internal)
{
class_<PLog>("Log", init<std::string>())
.def ("println", &PLog::println);

class_<PSystem>("System", no_init)
.def ("getClientTimeMS", &PSystem::getClientTimeMS)
.staticmethod ("getClientTimeMS")
.def ("getMetaverseTimeMS", &PSystem::getMetaverseTimeMS)
.staticmethod ("getMetaverseTimeMS");
}

BOOST_PYTHON_FUNCTION_OVERLOADS(import_overloads,
PModuleLoader::__import__, 1, 5);
BOOST_PYTHON_FUNCTION_OVERLOADS(compile_overloads,
PRestricted::throwPermissionDeniedException, 3, 5);
BOOST_PYTHON_FUNCTION_OVERLOADS(open_overloads,
PRestricted::throwPermissionDeniedException, 1, 3);

bool PPackage::registerNativeMethods ()
{
if (! (
(PyImport_AppendInittab("dD_internal", initdD_internal) != -1) &&
PyImport_ImportModule ("dD_internal")
))
return false;

IMPLEMENT_PYTHON_CONVERTER (PAccessor);

PModuleLoader::setImportLock (false);
{
object main = import("__main__");
object dict(main.attr("__dict__"));
PModuleLoader::setMainDictionary (dict);

object builtins (dict["__builtins__"]);
scope within(builtins);
def ("__import__", &PModuleLoader::__import__, import_overloads());
def ("compile", &PRestricted::throwPermissionDeniedException,
compile_overloads());
def ("exit", &PRestricted::throwPermissionDeniedException,
open_overloads());
def ("execfile", &PRestricted::throwPermissionDeniedException,
open_overloads());
def ("file", &PRestricted::throwPermissionDeniedException,
open_overloads());
def ("open", &PRestricted::throwPermissionDeniedException,
open_overloads());
}
PModuleLoader::setImportLock (true);

return true;
}

bool PPackage::deregisterNativeMethods ()
{
return true;
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top