Custom Safety Policies in SSErl

DRAFT - 7 Oct 97


Dr Lawrie Brown
School of Computer Science, Australian Defence Force Academy, Canberra, Australia
Email: Lawrie.Brown@adfa.edu.au

Last updated: 27 Jun 97


Abstract

This report describes an approach to checking conformance of mobile code to a specified security policy. It is based on executing the mobile code in a constrained environement, trusted to prevent direct access to any external resources, and then applying filters to messages between the mobile code and server processes which mediate the access to resources. This has been implemented in the Safer Erlang prototype system, which provides an appropriately constrained environment for programs written in the functional language Erlang, and also has a general server manager which can be easily modified to perform the filtering function.

Introduction

One of the major issues in supporting mobile code (that is code sourced from a remote, less trusted, site) is to provide a suitable level of System Safety, which provides appropriate controls on accesses to resources by the executing code so the security of the execution system is not compromised, Connolly[Conn96ih]. Ideally it would be desirable to be able to formally verify that the imported code will not violate the systems security policy, however in practise this is currently, and likely to remain, infeasible.

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.

Imposing Policy using a Message Check Function

The central element in this approach to providing System Safety is the use of a message check function which is both the security policy specification, and the means used to verify that messages sent from the executing mobile code to server processes conform to that policy.

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.

safe_gen_server

Currently Erlang supports a generic client-server mechanism using the 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:

proc_rights()
returns max list of rights for process capabilities, which will be constrained to at most those of the parent node.
aliases()
returns the list of aliases to be used in the new node. Usually these will map to modified versions of standard library modules.
init_servers()
calls any server init routines servicing the new node, these servers are run in the current node context, and returns these in the list of registered names to be used in the newnode.
check(Mod,Typ,Msg)
server message safety check function, which will be called by safe_gen_server to check server messages received, when configured in option list (by init_servers()).

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

Creating a Node Implementing a Policy

To create a new node which implements the policy in a policy module, a utility function 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.

An Example - safe_rpc

This mechanism has been used in the safe_rpc server. The server executes calls to it in a restricted subnode. The 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.

Limitations

There are currently some limitations with this approach.

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.

Conclusions

This paper describes an approach to implementing a desired security policy by filtering messages between mobile code executing in a constrained environment and server processes which provide mediated access to external resources. This approach has been trialed using the SSErl safe Erland prototype, where the code executes in a node whose constraints are specified using a policy module which includes a message check function to check all server messages.

Acknowledgements

The SSErl prototype and this paper were written during my special studies program in 1997, whilst visiting SERC in Melbourne, and NTNU in Trondheim, Norway. I'd like to thank my colleagues at these institutions, and at the Ericsson Computer Science Laboratory in Stockholm for their discussions and support.

References

BrSa97
L. Brown, D. Sahlin, "Extending Erlang for Safe Mobile Code Execution", Australian Defence Force Academy, Canberra, Australia, Technical Report, No CS03/97, Nov 1997. http://lpb.canb.auug.org.au/adfa/papers/tr9703.ps.gz.
Conn96ih
Dan Connolly, "Issues in the Development of Distributed Hypermedia Applications", W3C, Dec 1996. http://www.w3.org/OOP/HyperMediaDev.

The latest version of this paper may be found at: http://lpb.canb.auug.org.au/adfa/papers/ssp97/sserl97e.html.
Last updated: 5 Nov 1997.