Custom Safety Policies in Safe Erlang

Lawrie Brown

Abstract

This paper discusses an approach to specifying a safety policy governing safe execution of mobile code written in the Safe Erlang system. This policy is specified as a check function, which is applied to request messages for access to external resources (using remote procedure calls) from the mobile code executing in a restricted environment. As such, it is similar to the Security Manager used in Java, or the policies defined in SafeTCL. However we believe its application to external resource remote procedure calls by code running in a restricted subnode leads to a simpler and cleaner solution with less overhead; and its specification in a functional language such as Erlang leads to a clearer, more easily verifiable, specification of the policy.

1  Introduction

Mobile code is defined as any code sourced remotely from the system it is executed upon. Because the code is sourced remotely, it is assumed to have a lower level of trust than locally sourced code, and hence needs to be executed within some form of constrained or sandbox environment to protect the local system from accidental or deliberate inappropriate behaviour.

One of the major issues in supporting mobile code 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 [7,10]. Ideally it would be desirable to be able to formally verify that the imported code will not violate the systems security policy, however in practice this is difficult (cf. for example the work on proof carrying code requiring a hand-crafted proof for each [8]). The other key issue is Run-Time Safety, which is concerned with ensuring that the code will behave in accordance with the source specification. This is usually provided by a combination of language features in conjunction with the run-time system.

A number of approaches to providing a safe execution environment have been investigated (see survey by [10]), with much of the focus being on securing procedural languages such as Java [3,9] or Tcl/Tk [4,11]. However here we are interested in how best to extend our existing work on Safe Erlang, which provides the necessary run-time safety, in order to best support a range of Safety Policies, and be able to impose differing levels of system safety on different mobile code instances.

Safe Erlang is an extension of the functional language Erlang [6,5]. It includes extensions to Erlang to enhance its ability to support safe execution of remotely sourced code. These extensions are in three areas:

We have been working with Erlang, rather than the more traditional procedural languages, such as Java or SafeTCL, because we believe that functional languages have a very high degree of intrinsic run-time safety. This means the changes required to provide a safe execution environment are much smaller than those needed to secure a procedural language, as discussed in [6]. Erlang is a functional language, developed by Ericsson, and is currently being used for a number of production telecommunications applications [1,2].

The approach outlined in this paper for specifying a safety policy is based on executing the untrusted mobile code in a constrained environment, which is trusted to prevent direct access to any external resources by the executing untrusted code, and then applying filters to messages between the untrusted code and server processes which mediate the access to resources. Since Erlang generally uses message passing Remote Procedure Calls (RPC's) for servicing requests (including local requests), and since we can enforce this use through our Safe Erlang extensions, this provides a very convenient choke point at which to impose a safety policy. By restricting the problem domain to just verifying that the messages conform to the desired policy, the problem should be considerably more tractable, whilst still being highly extensible. Using Safe Erlang's restricted subnodes, these checks need only be applied to untrusted code running in a restricted subnode. Further, they are done only when the untrusted code is requesting a possibly insecure service provided by a server running in a more trusted subnode. This significantly reduces the overhead of performing such checks. 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.

2  Alternate Approaches to Specifying Safety Policies

A range of alternative approaches to imposing safety policies has been tried in a number of languages. In particular the approach used by Java, and that used by Safe/Tcl will be discussed and contrasted with the approach outlined in this paper.

2.1  Java Class Loader, Security Manager, and Access Manager

Probably the best known mobile code system is Java. In Java, system safety is the responsibility of the class loader in conjunction with the security or access manager, depending on which version of Java is being used (see detailed discussion of Java security in [9]).

The Java language and run-time system provide a sandbox for code to be executed in, with suitable controls on its operations. The Java Class Loader, Security Manager, and Access Manager interact to determine which particular, potentially unsafe, operations are permitted.

The Java Class Loader is responsible for loading classes as required. It alone knows the origin of the class, whether it has been signed, and hence which security or access manager should be used with it. It is also responsible for managing and enforcing namespace separation rules, which ensures that class/package names sourced from different locations (CODEBASEs) are partitioned into separate namespaces to prevent confusion/subversion due to name collisions. Different Class Loaders are used for code loaded from different sources, and trusted code is permitted to specify the use of a new Class Loader.

The Java Security Manager is called by all Applications Programmer Interface (API) code to see if a requested operation, with the arguments as supplied, is permitted for the requesting class. Only one Security Manager is defined for any instance of a Java run-time system. It is usually selected by trusted code at startup, and cannot subsequently be changed. It includes methods to resolve requests relating to file access, network access, execution of other programs on the local system, and access to system resources. These methods need to be carefully written to handle the varying decisions. They interact with the Class Loader to determine what the requesting class is permitted to do. If different policies need to be applied to different classes, then the Security Manager must have code provided to distinguish between the various cases. In practice, this has proved to be difficult.

Java 1.2 provides a new Access Manager class. It supplements and generalises the features previously provided by the Security Manager (which is maintained to support the current security API). The Access Manager may be consulted by the Security Manager in order to determine whether some action is permitted. In contrast to the Security Manager, customisation of the Access Manager is provided by specifying rules in a policy file, rather than having to modify the actual code. This significantly improves its generality.

The Java approach involves a single execution sandbox. Hence all code running must be vetted for safety - some of the code being permitted to perform ünsafe" operations (because it was sourced locally, or comes from a signed class), whilst other code must be restricted (because it is remotely sourced, untrusted, code). This necessarily complicates the policy specification - in actual code in the case of the Security Manager, or in the policy file for the Access Manager. Also, the realm of what operations are vetted is constrained by which of the API calls include a check with the Security/Access manager as to whether the operation is permitted, although the Access Manager includes an extensible Permissions class, which user applications and classes can choose to use.

2.2  Safe Tcl/Tk Interpreter and Policies

Another well known safe execution environment is that provided by SafeTCL [4,11]. This involves a master interpreter starting a SafeBase slave interpreter to run any untrusted code. The SafeBase slave interpreter has very restricted functionality (by default, no external access is provided). Potentially ünsafe" operations are supported by extending the slave with additional commands which are actually executed on the master interpreter. These are trusted, and can impose an appropriate safety policy on any requests from the slave. A range of policies can be supported, though on any particular execution, only one is used. This policy specifies when requests for file access, network access, email access, and browser access will be permitted.

Policies are specified in a file, which supplies rules (written as Tcl statements) which govern when each of the categories of potentially ünsafe" operations are permitted, and with what arguments.

The use of multiple interpreters (sandboxes) provides significant flexibility and isolation between the various mobile code modules and the main system. Further the use of a policy specification which contains Tcl commands allows a good degree of flexibility in policy specification, whilst still keeping the policy relatively straightforward. However, Tcl/Tk policies still are bound by the domain of potentially ünsafe" operations which may be vetted.

3  Imposing Policy using a Message Check Function

The key feature in this proposed approach to providing System Safety is the use of a message check function which is applied to request messages for access to resources outside the restricted environment (using remote procedure calls) from the mobile code executing in a restricted subnode. This check function is applied on receipt of the request by the general remote procedure call (RPC) handler for some trusted server executing outside the restricted environment. The message check function acts as both the security policy specification, and the means used to verify that the RPC request messages conform to that policy.

This approach shares a number of similarities with the approach supported by SafeTCL. In both cases multiple execution environments with differing trust levels are supported, along with a flexible policy written in the language used to validate potentially unsafe access requests. Where it differs is in the application of the check to RPC messages, rather than by modifying the code in the API libraries; and in the use of a functional language, which provides a clean specification of the policy.

In Erlang, a general server mechanism is supplied which acts as a wrapper around a number of general services (such as file access). It uses the efficient, lightweight, message passing mechanism supplied by Erlang and used for all inter-process communications. It is quite straightforward to adapt the general server to support the use of a message check function, and then advertising this "protected" service in the restricted environments where unsafe code is executing. Usually no further changes to the actual server code is required, other than ensuring that the server is started using the modified server mechanism, rather than the default. Thus, unlike other systems, extending the scope of which services are to be protected merely requires that the existing server be started using the new general server mechanism with an appropriate check function, rather than requiring any changes to the actual server code (provided only that the server uses the general server handler).

Writing a suitable check function, in general, defaults to looking at the list of requests supported by the server, and itemising them as always permitted, always denied, or permitted for some parameter values. This then translates fairly directly into the code for the check function. For example, file access may be restricted to files in some specified directory. This requires a policy that includes checking the supplied filename to ensure that it is appropriate.

Whether to impose a policy or not on any particular service is a decision made by the owner of the node. An execution environment can be tailored, comprising one or more restricted subnodes each with a policy imposed by limiting them to using a predefined set of servers, which execute in trusted subnodes, and which have the required check function imposed.

4  Prototype Implementation in Safe Erlang

This approach has been trialed using the SSErl prototype of a safe Erlang execution environment. This system is described in [6]. 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.

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. The check 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 particular RPC message type (call/cast/info) being checked, and Msg is the message received by safe_gen_server to be relayed to the call-back routines in the actual server module Mod. The Msg is usually a tuple naming the requested function and providing the necessary parameters.

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.

4.1  safe_gen_server

Erlang supports a general client-server mechanism to support various services, 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 with modified code for each. It also results in the checking overhead only occurring when accessing such servers. In the prototype, this modified general client-server module is called safe_gen_server.

safe_gen_server is a modified version of the existing gen_server module, 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). In either case, the effect is to impose the check function on RPC messages before they are passed to the existing server code.

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).

4.2  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 new node, 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 in 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 when creating the new node.
check(Mod,Type,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. This policy limits access to files to just those in the current directory. More specifically, the functions perform the following:

proc_rights()
limits the subnode being created to having no special rights, in particular, no right to send external messages, thus limiting message passing to the current node (with its safe servers).
aliases()
maps the generic module names, especially file to the modified, safe, versions (without requiring any change to the code running in the restricted subnode).
init_servers()
is used to start the file server with the correct policy imposed in the main trusted node, before the restricted subnode is created with this server specified in its namespace.
check(Mod,Type,Msg)
is the actual check policy imposed on messages received by the file server, specified as a parameter when the server was started.

-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, safe servers, stdlib modules
%%       all of which are compiled for the SSErl env
aliases() -> [
    % safe servers
    {file,safe_file},
    % extended gen_server modules with support for check function
    {gen,safe_gen},{gen_server,safe_gen_server},{proc_lib,safe_proc_lib},
    ].

%% 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 safe daemons with check function option
    {ok, Filer} = safe_file:start([{check, fun check/3}]),
                                  [{safe_file_server,Filer}].

%% check(Mod,Type,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,Type,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,Type,Msg) ->
    exit(policy_violation).

... code for valid_name/1 omitted, which checks that
... plain filenames only (no directories) are specified

4.3  Creating a Node Implementing a Policy

To create a new node which implements the policy in a policy module, a utility function safety:policynode(Parent,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(Parent,NodeName,PolicyModule) function may be called as follows to create a custom subnode:
    PolNode = safety:policynode(node(),testnode,safety_pol).

Once it has been created, processes in it may be spawned as usual, eg. a simple test function in the test module could be run as:

    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.

4.4  An Example - safe_rpc

This mechanism has also 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 except that no file server is provided, and the [extern] process right is granted, permitting messages to be sent outside the local node. This module is invoked and used by safe_rpc:start() as follows:

  RpcN = safety:policynode(node(),rpc,safe_rpc_policy),
  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.

5  Limitations of the Prototype

There are currently some limitations with the prototyped approach.

The prime limitation is that not all ßervices" 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.

Also, the Safe Erlang system itself is only a prototype, proof of concept. It has shown that the proposed language extension does indeed seem capable of supporting a safe environment, but as it is implemented using a modified compiler as an additional layer above standard Erlang code, it cannot actually provide a completely safe system at present. This will require a new run-time system with internal support for the safety extensions.

The general approach outlined does however, seem to function efficiently.

6  Conclusions

This paper describes an approach to implementing a desired security policy by filtering RPC requests from untrusted (including mobile) code executing in a constrained environment to server processes which provide mediated access to resources outside that environment. This approach provides a simpler and cleaner implementation, compared to other approaches. It differs from the Java approach in that it provides multiple execution environments, each with a potentially distinct policy imposed, rather than having one environment and policy which must handle all of the distinct cases required. It differs from both Java and SafeTCL in that the check is applied against RPC messages sent by code running in an untrusted node and received by trusted servers on which a policy has been imposed, rather then being incorporated in the API and being invoked on every call to the API. Its implementation in a functional language provides a clean and simple specification of the policy, which is then executed as the actual check function. The approach has been trialed using the SSErl safe Erlang 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.

7  Acknowledgements

The Safe Erlang research was started 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 continuing discussions and support.

References

[1]
J. Armstrong. Erlang - A Survey of the Language and its Industrial Applications. In INAP'96 - The 9th Exhibitions and Symposium on Industrial Applications of Prolog, Hino, Tokyo, Japan, Oct 1996. http://www.ericsson.se/cslab/erlang/publications/inap96.ps.

[2]
J. Armstrong, R. Virding, C. Wikstrom, and M. Williams. Concurrent Programming in Erlang. Prentice Hall, 2nd edition, 1996. http://www.erlang.org/download/erlang_book_toc.html.

[3]
Ken Arnold and James Gosling. The Java programming Language. Addison-Wesley, 2nd edition, 1998. ISBN 0201310066.

[4]
Steve Ball. Web Tcl Complete. Complete Series. McGraw-Hill, Apr 1999. ISBN 007913713X, http://www.zveno.com/zm.cgi/in-wtc/.

[5]
L. Brown. SSErl - Prototype of a Safer Erlang. Technical Report CS04/97, School of Computer Science, Australian Defence Force Academy, Canberra, Australia, Nov 1997. http://lpb.canb.auug.org.au/adfa/papers/tr9704.html.

[6]
L. Brown and D. Sahlin. Extending Erlang for Safe Mobile Code Execution. In Information and Communication Security, volume 1726 of Lecture Notes in Computer Science, pages 39-53. Springer-Verlag, Nov 1999. http://lpb.canb.auug.org.au/adfa/research/sserl/sserl99a.ps.

[7]
Dan Connolly. Issues in the Development of Distributed Hypermedia Applications, Dec 1996. http://www.w3.org/OOP/HyperMediaDev.

[8]
G.C. Necula and P. Lee. Research on Proof-Carrying Code on Mobile-Code Security. In Proceedings of the Workshop on Foundations of Mobile Code Security, Monterey, 1997. http://www.cs.cmu.edu/~necula/fmcs97.ps.gz.

[9]
Scott Oaks. Java Security. O'Reilly, 1998. ISBN 1565924037.

[10]
Tommy Thorn. Programming Languages for Mobile Code. ACM Computing Surveys, 29(3):213-239, Sept 1997.

[11]
J. A. Zimmer. Tcl/Tk for Programmers. IEEE Computer Society Press, Sept 1998. ISBN 0818685158, http://www.mapfree.com/sbf/tcl/book/home.html.


File translated from TEX by TTH, version 1.56.