parrotcode: Parrot's security infrastructure | |
Contents | Documentation |
docs/pdds/pdd18_security.pod - Parrot's security infrastructure
This PDD describes the safety, security, and quota infrastructure of Parrot.
{{ NOTE: This PDD is inadequate. We want to provide a much more extensive level of sandboxing. }}
There are three basic subsystems in Parrot's security system. They are:
Each of these can be enabled or disabled separately, and each has a particular purpose. Often two or more systems will be engaged at once, but this isn't required.
The purpose of a quota system is to ensure that an interpreter doesn't use up too many resources -- usually memory and CPU, but there may be other scarce resources, such as files, that need managing. In a shared environment it prevents an interpreter from hogging too many resources, either explicitly (as in a DOS attack) or implicitly (through poor programming or poorly scheduled runs), and preventing other interpreters from running.
A privilege system is used to restrict code from performing certain actions. When privilege checking is in force you may need a particular privilege to load a library, or open a file.
Each interpreter has three sets of privileges. The first set is the current privilege set, which is the set of privileges currently in force. The second set is the set of authorized privileges. These are the privileges that the interpreter is allowed to put into its current set. The third set are the sub privileges. These are the privileges that a sub has intrinsic to itself, regardless of what the interpreter privileges currently are. (Subs with privileges attached to them are called privileged subs, oddly enough).
An interpreter may drop any privilege it likes from the current set. It may also at any time enable a privilege which is in the authorized set but not in the current set. It is possible for a sub to have a privilege in its current set which isn't in its authorized set. Those privileges are, once dropped, gone.
Subroutines may also have a set of required privileges attached to them. The current interpreter must have those privileges in its current or sub set to call a subroutine so tagged. If the interpreter doesn't have the privileges then a privilege violation exception is thrown.
In normal operation the interpreter assumes that the bytecode that it executes is valid -- that is, any parameters to opcodes are sane, data structures are intact, and the world, generally, is a good place. When parameter checking is enabled, however, we assume that bytecode is not necessarily valid. The interpreter then, at runtime, makes sure that all specified register numbers are within valid range, and string and PMC structures used are valid.
Each of the three features has a separate use. Parameter checking is most useful when executing code which may come from an unsafe source, for example from the network. Quotas are most useful when running code in a managed environment such as a web, database, or game server where no one interpreter is allowed to consume too many resources and impact the system too badly. Privileges are used when running untrusted code in a trusted environment, again such as a database or game server, where Parrot can't disable certain features, but must limit their use to trusted code.
It's unlikely that any one of these features will be enabled individually, though there are certainly reasons to do so. Each feature is separately implemented, however, and as such can be taken singly and discussed.
Quota management is split into two separate parts, CPU time and everything else.
CPU time is managed by the runloop. There's a certain unavoidable overhead, but there's no way around this, at least not reliably. (We may be able to play interesting games with timer events and system event handlers. We'll see).
The rest of the quotas are enforced by code scattered across the interpreter. The memory system handles memory quotas, the IO system handles file open and pending IO count quotas, and so on. There's not a whole lot for this, though we should abstract out all the high-level operations that do things which may have quotas applied so we can wrap these functions, so as not to pay the price when quotas aren't in force.
For example, rather than having checks in the memory subsystem to see if quotas are enabled (a check that would have to be done on each memory allocation) we should instead access the memory allocation via a function pointer stored off the interpreter somewhere. This way when quotas are enabled we can swap in an alternate function pointer, one that points to a function which checks quotas before calling into the memory subsystem.
This should be relatively painless as most, if not all, of the functions which should have quotas applied to them are also functions which embedders may wish to override, and thus already need to be accessed indirectly.
Parameter checking is done with an alternate runloop, one where the opcodes first check their parameters before executing. This is fairly expensive, which is why it isn't the default mode for operations. (The JIT may, at its choice, check parameters at JIT time). Checking at load time is also somewhat problematic, as it is also somewhat expensive, means the checker needs to know the signatures for ops which may not have been loaded yet, and precludes code doing overly clever things. (Which itself probably ought be forbidden, but that's a separate problem).
The checking op variants are automatically generated by the op file preprocessor, the same way that it generates all the other oploop variants.
Maintainer: Dan Sugalski
Class: Internal
PDD Number: 18
Version: 1.0
Status: Developing
Last Modified: 29 Aug 2004
PDD Format: 1
Language: English
|