[Python-Dev] rexec.py unuseable

Luke Kenneth Casson Leighton lkcl at lkcl.net
Thu Dec 18 17:57:48 EST 2003


On Thu, Dec 18, 2003 at 10:33:52PM +0100, Martin v. L?wis wrote:
> Luke Kenneth Casson Leighton <lkcl at lkcl.net> writes:
> 
> >  so, with the correct codebase reordering, a simple capabilities
> >  based system can be added, the problem goes away.
> > 
> >  yes?
> 
> I still don't see how it could, 

 neither do i: i believe that the best person you should address
 that concern to is to greg ewing, who raised this point:

	 "The spirit behind my suggestion was to start thinking about
	 ways in which functionality could be separated out so that
	 this kind of special-casing for security purposes isn't
	 needed."
	
 in other words, i think he means that by restructuring the
 python codebase [and libraries? and its design?] it _may_ be
 possible to avoid a need for adding ACLs at all.

 and i believe he may be thinking along the lines of being able
 to permanently _remove_ at run-time, under python-programmer-control,
 access to __class__ from objects.

 somehow.


> and I believe I understand pretty well
> what kind of feature you propose. Restricted execution is just
> something completely different.
 
 if you believe that, then i have evidently not explained 
 myself clearly enough.

 i'll give this one more shot.  i'll be honest with you.
 if it's not obvious enough at the end of this message, i have
 to give up and stop, and say i tried but did not succeed in
 explaining this clearly enough to you, which is no failing
 on your part, but on mine.

 
 anyway.


 first i should outline the pseudo-code modifications needed
 to Python/ceval.c which are necessary, then after that i will
 outline what ACLs are needed.
 
 what i _can_ say that i haven't gone into detail on [in this
 message] is _how_ the acls are obtained from the function
 object. e.g. in GetACLforFunction.

 but it is worth mentioning that _how_ the acls are _associated_
 with the function object (or any other object) is an
 implementation-specific issue NOT a design / specification
 issue.


 static PyObject *
 call_function(PyObject ***pp_stack, int oparg)
 {
 	
	 PyObject **pfunc = (*pp_stack) - n - 1;
     PyObject *func = *pfunc;


	/* caller function name on stack below?? probably not,
	   but the principle is at least demonstrable */
	PyObject *calling_func = GetCallerFunction(*pp_stack) - n - 2;
	PyACL *acl_for_func = GetACLforFunction(func);

	...
	if (acl_for_func != NULL && !check_permissions(calling_func, acl_for_func,
	                             PERMS_EXECUTE)
	{
		return Exception("access denied");
	}

	...

 }

 
 also, to satisfy the requirements of "capabilities", a specific check
 to make it look like the object doesn't exist is added.

 it's probably possible to have a look-up table on a per-opcode basis
 to look up the permissions to be checked against, which alleviates the
 need to put a call to check_permissions in every darn switch statement.

 note the check for ACL being NULL: if it's NULL, that's assumed
 to mean "anything goes".

 it also means that there's not much performance hit involved unless
 you actually add permissions.



 case STORE_ATTR:
	if (acl_for_func != NULL && !check_permissions(calling_func, acl_for_func,
					PERMS_CAN_SEE)
	{
		return Exception("attrib doesn't exist");
	}
	if (acl_for_func != NULL && !check_permissions(calling_func, acl_for_func,
					PERMS_WRITE)
	{
		return Exception("access denied");
	}
 case DELETE_ATTR:
	if (acl_for_func != NULL && !check_permissions(calling_func, acl_for_func,
					PERMS_CAN_SEE)
	{
		return Exception("attrib doesn't exist");
	}
	if (acl_for_func != NULL && !check_permissions(calling_func, acl_for_func,
					PERMS_WRITE+PERMS_DELETE)
	{
		return Exception("access denied");
	}
 case LOAD_ATTR:

	if (acl_for_func != NULL && !check_permissions(calling_func, acl_for_func,
					PERMS_CAN_SEE)
	{
		return Exception("attrib doesn't exist");
	}
	if (acl_for_func != NULL && !check_permissions(calling_func, acl_for_func,
					PERMS_READ)
	{
		return Exception("access denied");
	}


 basically, as long as code is only executed through eval_frame(),
 then it's likely to be the only place where access control checks
 are needed to be added.


 
 basically, therefore, once calls to restrict operations are added,
 it's a matter of dropping the ACLs in at the right places.

 _that's_ how you achieve the same job as the rexec.py code.

 you place an acl on __builtins__ to restrict "all functions", DENY,
 EVERYTHING.

 you place an acl on open() to restrict "all functions", DENY,
 FILE_WRITE.

 you place an acl on open.__class__ "all functions", DENY, CAN_SEE+FILE_WRITE

 in fact you might even be able to get away with putting an acl
 on EVERY function right at the top-level.

 but that, as described previously, means that it'd be necessary
 to get the "ACL-inheritance" to work properly, in order to avoid
 having to add a DENY FILE_WRITE acl to every single file operation
 capable of writing, for example.


 so like i said, i believe it to be a relatively simple job to spec
 out how to add the means _by which_ ACLs can be usefully evaluated.

 what i CAN'T tell you is exactly WHERE every single ACL [needed to
 achieve the same results as rexec] have to be added [such that
 they will be usefully evaluated]: i simply don't
 have enough knowledge of the python codebase to do that on my own.

 [unless someone was happy to pay me for long enough to find out,
  of course].


 is this making sense at all?

 in some ways, the longer this is left, the harder it is going to
 be to retrospectively bolt on.

 there's an adage that says security cannot be easily added in, it
 has to be designed in from the start.

 fortunately, i think there are a lot of smart people about :)

 sincerely,

 l.






More information about the Python-list mailing list