sandbox python via module loader

timprepscius timprepscius at gmail.com
Mon Nov 23 21:22:02 CET 2009


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::object 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::object> 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::object);

		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[i]!=NULL; ++i)
		{
			char *check = allowedBuiltinModules[i];
			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;
}



More information about the Python-list mailing list