Last updated: 27 Jun 97
The approach outlined here is based on executing the mobile code in a constrained environement, which is trusted to prevent direct access to any external resources by the executing mobile code, and then applying filters to messages between the mobile code and server processes which mediate the access to resources. By restricting the problem domain to just verifying that the messages conform to the desired policy, the problem should be considerably more tractable. Further, by implementing this approach in a system based on a functional language, it is possible to have the security policy specification written in this language, and then use it directly as the actual check function.
This approach has been trialed using the SSErl prototype of a safe Erlang execution environment. This system is described in [BrSa97]. It is used to create an appropriately tailored execution node for the mobile code, whose only access to resources external to that node is via messages sent to server processes, whose process identifiers are registered by name in the restricted node's names table.
Previously I considered using a check function as a security monitor on all external function calls, ie apply's. This could impose checks on the use of the client interface to various servers. Whilst such a check function would provide the necessary functionality, serious performance concerns were raised over its use. Further it was noted that much of the time it was redundant, given the functional nature of the language. What was identified as needed, was the ability to apply such a function to messages, particularly those directed to server processes. It was also noted that messages had to be checked on reception since there is no way to enforce the use of the client interface routines. Thus the evolution of the approach outlined above.
A key feature in implementing this check function is the use of Erlang's
pattern matching ability to resolve which of the many possible
messages are valid. This permits, I believe, a clean and relatively
easy to validate specification of the desired policy related to
which messages are permissible. This monitor function has the form
check(Mod,Type,Msg)
where Mod
is the name
of the server module the policy is imposed upon, Type
is
the message type (call/cast/info), and Msg
is the message
received by safe_gen_server
to be relayed to the call-back
routines in Mod
.
For example, the monitor function could look as follows:
check(file,call,get_cwd) -> ok; check(file,call,{delete,F}) -> valid_name(F), ok; check(file,call,{read_file,F}) -> valid_name(F), ok; check(file,call,{write_file,F,Bin}) -> valid_name(F), ok; ... % other clauses removed check(file,info,_) -> ok; check(Mod,Type,Msg) -> exit(policy_violation).This indicates that for the file server, get_cwd is ok; as are requests to delete, read or write a file, provided a valid name (say current dir only) is supplied. Info messages (exits etc) are allowed. All other calls will result in a policy_violation exception.
gen_server
module. With a small change to this module
to optionally impose a check function against all messages received
for the server, it is possible to run a number of instances of a
standard server, each enforcing a different security policy.
This results in a clean, and I believe easier to verify implementation,
compared to implementing a suite of custom servers.
It also results in the checking overhead only occurring when accessing
such servers.
safe_gen_server
is a modified version of gen_server
which implements the security monitor function on messages received
for the server it manages. This function is installed by specifying
the new {check, Fun}
option in the call to
safe_gen_server:start(M,A,Opts)
(and related variants).
Then, when it receives a message, it will invoke the nominated check
function first before invoking the appropriate call-back routine in
the server module. If the check function exits, an appropriate error
message can be returned to the calling client.
It is left open whether a single check function, covering all modules relevant to a particular safety policy, or whether separate functions for each module, is most appropriate. The current implementation handles both.
To use the check facility, the servers must be started with
the check option specified. This would typically require calling
safe_gen_server
explicitly, rather than using the usual
start function in the server module. Alternatively, the server module
could be modified to provide a variant of start for this. The server
modules may also need minor changes and recompilation to work in the
safe environment anyway (this is certainly true of the prototypes).
safe_gen_server
has also been modified so that an explicit
list of node capabilities or global names must be supplied for the
multi_call/cast interfaces. This is because, with the capability concept,
processes must explicitly have capabilities for any resources they
wish to use (or know registered names for those capabilities).
Policy Module
To assist in the creation and usage of safer SSErl nodes, it is
desirable that there be a clear and easy mechanism for specifying an
appropriate safety policy. This would involve the correct specification
of the options in the creation of a newnode, but would also require
some means of controlling the use of some standard servers, especially
those accessing the file system, network and window manager.
For ease of use, policy for an SSErl node may be specified by a policy module of a prescribed form. This module must contain and export a number of functions which are used to create the SSErl node with the desired security policy. These functions are:
An example of a policy module is given below. It is intended for use by the SSErl test module as a demonstration. nb. it is an excerpt:
-module(safety_pol). % required policy functions we export -export([proc_rights/0, aliases/0, init_servers/0, check/3]). %% proc_rights is the list of side-effects permitted for process in node %% from possible list of: [db,extern,open_port] proc_rights() -> []. %% aliases is the initial list of module name aliases to use %% here alias: the gen_server modules, safed servers, stdlib modules %% all oh which are compiled for the SSErl env aliases() -> [ % safed servers {file,safe_file},{os,safe_unix},{unix,safe_unix}, % extended gen_server modules with support for check function {gen,safe_gen},{gen_server,safe_gen_server},{proc_lib,safe_proc_lib}, % standard library modules just renamed to distinguish from originals {lists,safe_lists},{ordsets,safe_ordsets}, {random,safe_random},{string,safe_string} ]. %% init_servers calls any server init routines servicing new node, %% these servers are run in current node context %% returns these in a list of registered names init_servers() -> % start the safed daemons with check function option {ok, Filer} = safe_file:start([{check, fun check/3}]), [{safe_file_server,Filer}]. %% check(Mod,Typ,Msg) - server message safety check function %% called by safe_gen_server to check server msgs %% when configured in option list (by init_servers()) %% returns ok | {EXIT,{policy_violation,{Mod,Typ,Msg}} % handle checks for safe_file server check(safe_file,call,get_cwd) -> ok; check(safe_file,call,stop) -> ok; check(safe_file,call,{delete,Nam}) -> valid_name(Nam), ok; check(safe_file,call,{file_info,Nam}) -> valid_name(Nam), ok; check(safe_file,call,{read_file,Nam}) -> valid_name(Nam), ok; check(safe_file,call,{rename,Fr,To}) -> valid_name(Fr), valid_name(To), ok; check(safe_file,call,{write_file,Nam,_}) -> valid_name(Nam), ok; check(safe_file,info,_) -> ok; % everything else is rejected check(Mod,Typ,Msg) -> exit(policy_violation). ... code for valid_name/1 omitted
safety:policynode(Prent,Name,Policy)
has been created. Its current implementation is given below.
%% policynode/3 - creates subnode which implements policy module in parent policynode(Parent,Name,Policy) -> % establish various parameters from policy module PRi = apply(Policy,proc_rights,[]), % limit process rights Mods = apply(Policy,aliases,[]), % initial module alias table Names = apply(Policy,init_servers,[]), % start servers, get names % create new node with specified policy CN = newnode(Parent,Name,[{proc_rights,PRi},{names,Names},{modules,Mods}]).
The safety:policynode(NodeName,PolicyModule)
function may be called as follows to create a custom subnode:
PolNode = safety:policynode(testnode,my_policy_mod).Once it has been created, processes in it may be spawned as usual, eg.
spawn(PolNode,test,test,[]).
So with this approach, a SSErl node with a custom policy may be created by writing an appropriate policy module, and then simply using policynode to instantiate it.
safe_rpc_policy
module is similar to that shown above, except that no file server is provided,
and the [extern]
process right is granted. This module is
invoked and used by safe_rpc:start()
as follows:
RpcN = safety:policynode(rpc,safe_rpc_pol), RpcNode = safety:restrictx(RpcN,[module,newnode]), Rpc = spawn(RpcNode, safe_rpc, loop, []), register(safe_rpc, Rpc), {Rpc,RpcNode};
In turn, the safe_rpc server itself may be subject to a policy check function, limiting its use.
The prime limitation is that not all "services" that one might wish
to impose a policy upon, use the gen_server
general server
mechanism. Whilst it works for file
and for rpc
,
other services such as: pxw,sockets,unix
do not.
Work is progressing on the best approach to handling these.