OpenOffice.org UNO Security


Contents

Introduction

Concept and API

Overview

Dealing with Untrusted Code

Current Implementation Details

Things left to do...

Introduction

In general, security concerns the following four topics [CORBA Security Specification]:

  • Confidentiality: Information is disclosed only to users authorized to access it.

  • Integrity: Information is modified only by users who have the right, only in authorized ways, and is transferred only between intended users in intended ways.

  • Accountability: Users are accountable for their security-relevant actions.

  • Availability: Use of the system cannot be randomly or maliciously denied to authorized users.

Among this very common understanding of security, there are two distinct topics when users want to access or modify resources, or execute a program:

Authentification: The process of validating a subject to ensure that the subject (for example John Doe) is who or what it claims to be.

Authorization: The process of granting permission to access data or execute functionality.

A subject (for example the user who started an application or demands access on resources accessible via a WWW front end) has to be authenticated to an information system. Often, this first step associates the subject with a set of credentials, such as, user attributes, permissions, or different principals. A subject may be any entity, for example, a person or service. Once authenticated, a subject is populated with associated identities or principals. A subject may have many principals. For example, a person may have a name principal ("John Doe") and a social security number principal ("123-45-6789"), which distinguish it from other subjects.

Authorization has to be done any time the user demands access to sensitive data or wants to execute sensitive code. Authorization is hence of a programmatic nature and is implemented by the specific service that is handling the demand (explicitly checked by base service, but implicitly checked from service users' perspective). A common approach of authorization is handled by access control lists (ACL), that is, sets of permissions (positive) or denials (negative).

The next sections are about the UNO security API, covering authorization exclusively. Authorization is a primary goal because it affects most of the client code, such as, doing permission checks. It is assumed that users have been authenticated previously using some mechanism.

There was no new API defined that abstracted from a specific authentication mechanism, such as the Java Authentication and Authorization framework (JAAS). Although there is no defined API, you can find some draft interfaces and services in the udkapi cvs tree of the com.sun.star.security.auth package.

Concept and API

The UNO API is closely related to the Java authorization API. Permission semantics have been adopted. For details about java permissions, have a look at Permissions in the Java 2 SDK.

There is only a difference when coming to dynamic restrictions which are implicitly handled by the Java AccessController (ac). The Java ac implicitly checks permissions related to a PermissionDomain. A PermissionsDomain includes a set of permissions related to a set of classes. Java can manage this, but this feature is not practically adaptable for compiled machine code.

As a solution, these dynamic restrictions can be set explicitly using the UNO ac interface. This will install thread/task local restrictions to be used upon permission checks.

API Access

Implementing programmatic security (client code), there is an ac singleton object in the component context given to every component. The name of the object is

/singletons/com.sun.star.security.theAccessController

This object mainly exports the com.sun.star.security.XAccessController interface which has the following IDL definition:

module com {  module sun {  module star {  module security {

interface XAccessController : com::sun::star::uno::XInterface
{
    void checkPermission(
        [in] any perm )
        raises (AccessControlException);

    any doRestricted(
        [in] XAction action,
        [in] XAccessControlContext restriction )
        raises (com::sun::star::uno::Exception);

    any doPrivileged(
        [in] XAction action,
        [in] XAccessControlContext restriction )
        raises (com::sun::star::uno::Exception);

    XAccessControlContext getContext();
};

}; }; }; };

Checking Permissions

The first method is commonly called by security-related services which need to check if an action is permitted. The actual permission takes the security policy, currently in effect, into account. In general, the security policy in effect has a dynamic and static part. The dynamic part uses restrictions modified by doRestricted() and doPrivileged() calls, while the static part takes user credentials into account (commonly persistent data). Currently, four different permission types are defined in IDL and implemented in the UDK ac (module stoc/source/security):

  • com.sun.star.io.FilePermission:

    module com {  module sun {  module star {  module io {
    
    struct FilePermission
    {
        string URL;
        string Actions;
    };
    
    }; }; }; };
    

    This permission represents access to a file or directory. A FilePermission consists of a file url and a set of actions valid for that url.

    The path of the file url that ends in "/*" indicates all the files and directories contained in that directory. A path that ends with "/-" indicates (recursively) all files and subdirectories contained in that directory. A file url string consisting of the special token "<<ALL FILES>>" matches any file.

    Note: A file url string consisting of a single "*" indicates all the files in the current directory, while a string consisting of a single "-" indicates all the files in the current directory and (recursively) all files and subdirectories contained in the current directory.

    The actions to be granted are a list of one or more comma-separated keywords. The possible keywords are "read", "write", "execute", and "delete". Their meaning is defined as follows:

    • read -- read permission
    • write -- write permission
    • execute -- execute permission
    • delete -- delete permission

    The actions string is processed case-insensitive.

    Attention: Be careful when granting FilePermissions. Think about the implications of granting read and especially write access to various files and directories. The "<<ALL FILES>>" permission with write action is especially dangerous. This grants permission to write to the entire file system.

  • com.sun.star.connection.SocketPermission:

    module com {  module sun {  module star {  module connection {
    
    struct SocketPermission
    {
        string Host;
        string Actions;
    };
    
    }; }; }; };
    

    This permission represents access to a network via sockets. A SocketPermission consists of a host specification and a set of actions specifying ways to connect to that host. The host is specified as

        host = (hostname | IPaddress)[:portrange]
        portrange = portnumber | -portnumber | portnumber-[portnumber]
        

    The host is expressed as a DNS name, as a numerical IP address, or as a "localhost" (for the local machine). The wildcard "*" may be included once in a DNS name host specification. If it is included, it must be in the leftmost position, as in "*.sun.com".

    The port or portrange is optional. A port specification of the form "N-", where N is a port number, signifies all ports numbered N and above, while a specification of the form "-N" indicates all ports numbered N and below.

    The possible ways to connect to the host are

    • accept
    • connect
    • listen
    • resolve

    The "listen" action is only meaningful when used with "localhost". The "resolve" (resolve host/ip name service lookups) action is implied when any of the other actions are present.

    As an example of the creation and meaning of SocketPermissions, note that if the following permission:

    SocketPermission("foo.bar.com:7777", "connect,accept");
    

    is granted, it allows to connect to port 7777 on foo.bar.com, and to accept connections on that port.

    Similarly, if the following permission:

    SocketPermission("localhost:1024-", "accept,connect,listen");
    

    is granted, it allows that code to accept connections on, connect to, or listen on any port between 1024 and 65535 on the local host.

    Attention: Granting code permission to accept or make connections to remote hosts may be dangerous because malevolent code can then more easily transfer and share confidential data among parties who may not, otherwise, have access to the data.

  • com.sun.star.security.RuntimePermission:

    module com {  module sun {  module star {  module security {
    
    struct RuntimePermission
    {
        string Name;
    };
    
    }; }; }; };
    

    This permission grants runtime access to some named functionality. A RuntimePermission contains a name (also referred to as a target name) but no actions list; you either have the named permission or you don't.

  • com.sun.star.security.AllPermission:

    module com {  module sun {  module star {  module security {
    
    struct AllPermission
    {
    };
    
    }; }; }; };
    

    The AllPermission is a permission that implies all other permissions.

    Attention: Granting AllPermission should be done with extreme care, as it implies all other permissions. Thus, it grants code the ability to run with security disabled. Extreme caution should be taken before granting such a permission to code. This permission should be used only during testing, or in extremely rare cases where an application is completely trusted and adding the necessary permissions to the policy is prohibitively cumbersome.

When access is denied upon calling checkPermission(), an AccessControlException is thrown. An AccessControlException signals a security violation. The AccessControlException is derived from SecurityException which is derived from RuntimeException and thus can be thrown upon any call on UNO interfaces, except for calls on XInterface::acquire() and XInterface::release().

module com {  module sun {  module star {  module uno {

exception SecurityException : com::sun::star::uno::RuntimeException
{
};

}; }; }; };

module com {  module sun {  module star {  module security {  

exception AccessControlException : com::sun::star::uno::SecurityException
{
    any LackingPermission;
};

}; }; }; };

Static Restrictions
A restriction is defined by a set of permissions. It has to be assured that a demanded permission is implied by this set of permissions. Static restrictions are defined by a (commonly persistent) set of permissions granted to a user. This has been done by some admin frontend in advance. The effective set of permissions is provided by the Policy service. There is a singleton object installed into the initial component context from which an ac implementation reads:

/singletons/com.sun.star.security.thePolicy

The service supports the interface XPolicy and reads out of some storage:

module com {  module sun {  module star {  module security {

interface XPolicy : com::sun::star::uno::XInterface
{
    sequence< any > getPermissions(
        [in] string userId );
    sequence< any > getDefaultPermissions();
    void refresh();
};

service Policy
{
    interface XPolicy;
};

}; }; }; };

Implementing the XPolicy interface, it is important to separate default permissions from user permissions. That is, the ac implementation has to combine both to get the effective set of permissions. Providing these permissions separately, the ac implementation has the opportunity to optimize permission sets, that is, it only needs to read (and convert) the default permissions once, for every user.

Dynamic Restrictions
Dynamic restrictions apply to the doPrivileged() and doRestricted() calls of the XAccessController interface. These calls will directly modify a property of the current context which is called

access-control.restriction

This property is of type XAccessControlContext and taken into account additionally to the static permissions granted to the calling user.

The XAccessControlContext has only one method:

module com {  module sun {  module star {  module security {  

interface XAccessControlContext : com::sun::star::uno::XInterface
{
    void checkPermission(
        [in] any perm )
        raises (AccessControlException);
};

}; }; }; };

Users may implement this interface themselves to restrict or to get a snapshot of the currently effective security by calling XAccessController::getContext(). This has to be done when raising new threads. A child thread has to inherit an initial restriction context from its parent at the time of creation.

When calling XAccessController::doRestricted() the ac implementation combines a previous restriction with the given one, thus the intersection of permissions of both contexts becomes effective. It then installs this new restriction into the current context and calls the specified action. In essence, when doing subsequent doRestricted() calls, you chain up several restrictions over several stack layers.

When calling XAccessController::doPrivileged(), the ac implementation adds the given set of permission (defined by the ac context), installing an extended restriction. If you pass a null-ref to doPrivileged(), then all permissions are implied (no dynamic restriction). This feature may be sensible for basic service implementations which need privileges in a well-defined manner, a.k.a., they know what they do.

AccessController Switches
The ac can be run in several modes, thus influencing its runtime behavior. When instantiating the ac service, it uses the component context' property

/services/com.sun.star.security.AccessController/mode

Currently, there are five modes:

  • "on"
    This (default) mode turns dynamic and static permission checks on. Dynamic restrictions are performed evaluating the current context's access-control.restriction property. Static permissions are checked against the calling user's permissions provided by the policy service. The current user id is determined using the current context's property access-control.user-credentials.id. If neither the dynamic restriction nor the effective user's permissions grant a demanded permission, then an AccessControlException is thrown.

  • "single-user"
    This mode turns dynamic and static permission checks on. Dynamic restrictions are performed evaluating the current context's access-control.restriction property. Static permissions are checked against the user's permissions provided by the policy service. The user id is determined using the component context's property /services/com.sun.star.security.AccessController/single-user-id. If neither the dynamic restriction nor the effective user's permissions grant a demanded permission, then an AccessControlException is thrown.

  • "single-default-user"
    This mode turns dynamic and static permission checks on. Dynamic restrictions are performed evaluating the current context's access-control.restriction property. Static permissions are checked against the default permissions provided by the policy service. If neither the dynamic restriction nor the default permission grant a demanded permission, then an AccessControlException is thrown.

  • "dynamic-only"
    This mode turns dynamic permission checks on, exclusively. This is useful, if you cannot determine any specific user and don't want to build up a dummy policy granting default permissions.

Overview

Dealing with Untrusted Code

Untrusted code should, in general, run in a sandbox, that is, a restricted environment.

Untrusted Native Code
One cannot restrict native code modifying the current context, poke memory, thus influencing the security policy. I think this is not a goal.

Untrusted Scripted Code
Sandboxed script code, for example, a basic script, has to be restricted in the way that the underlying engine disallows modifying the following current context's access-control properties, i.e., all of:

access-control.*

as well as disallowing a call on the ac's doPrivileged(). These actions are security violations and have to be signalled via a SecurityException.

In addition, it has to be ensured that the script cannot raise components exchanging the ac of the component context. Therefore, the script engine has to proxy the service manager ensuring the ac property

/singletons/com.sun.star.security.theAccessController

is original, by doing security checks when XSingleComponentFactory::createInstanceWithContext() or XSingleComponentFactory::createInstanceWithArgumentsAndContext() is called.

Untrusted Java Code
In general, the same applies for Java as for scripts. In addition, the UNO policy has to be plugged under the Java AccessController restricting calls to the Java API. For example, imagine that the untrusted Java code opens a file using the Java core API. The Java AccessController has to control this access relying on its Java policy in effect.

Another problem with integrating the Java and C++/UNO access controllers are dynamic restrictions, which is not yet solved.

Untrusted Inter-Process Code
A remote user (inter-process user) is assumed to be authenticated when connected. The server code must ensure separating different users; it is not necessary that the client code needs to be trusted in terms of dynamic restrictions as demanded for untrusted scripted code. But it has to be ensured that the server-side ac implementation is always used when restricting server-side access, such as when permission checks have to be performed process-locally.

Current Implementation Details

Upon bootstrapping the UNO core system getting an initial component context, the ac implementation of stoc/source/security (sec.dll/ libsec.so) is installed as a singleton. This is most commonly done using some of cppuhelper's bootstrapping function, for example: defaultBootstrap_InitialComponentContext(). You can specify some bootstrap variables mapping directly to the above mentioned AccessController mode:

  • UNO_AC=("on" | "off" | "dynamic-only" | "single-user" | "single-default-user")
    The general ac mode

  • UNO_AC_SERVICE=service-name [optional]
    This overrides ac singleton service name to be used

  • UNO_AC_SINGLEUSER=(UserId|<nothing>) [optional]
    The initial context runs with this user id or with default policy. In this case, the UNO_AC has to be set to "single-user" or "single-default-user".

  • UNO_AC_USERCACHE_SIZE=n
    This value maps to the component context entry /implementations/com.sun.star.security.comp.stoc.AccessController/user-cache-size, denoting the number of user permission sets to be cached before calling the policy (lru cache).

  • UNO_AC_FILEPOLICY=file-url
    This value maps to the component context entry /implementations/com.sun.star.security.comp.stoc.FilePolicy/file-name denoting the file URL to read the user permission sets from. The file policy service implementation of stoc/source/security is installed.

Recurring Calls

When implementing an ac, there is a specific issue concerning bootstrapping of the ac singleton. The ac is (to be) used in every service implementation code and has to cope with the situation that it is called recursively. This can occur when the ac implementation initially raises the policy singleton object which checks permissions.

A (possible) solution for this is to let recurring checkPermission() pass unchecked. This assumes that one knows implementation details of the underlying policy, but one generally don't feel well allowing unchecked permission grants, even for a short time.

A better (safer) solution for this problem is that you shift the time a permission is checked. Any recurring checkPermission() call will be passed unchecked, but the permission and user context is saved for later execution (when everything is initialized). For a clearer distinction that an access violation occurred during bootstrapping of the ac, this violation should not be notified using an AccessControlException, but a different RuntimeException, such as, DeploymentException (yet to be specified) . The only drawback of this solution is that an error notification is not sent as soon as possible.

Marking a stack, one commonly uses thread-local storage (tls). When calling an unknown implementation (e.g., the policy singleton) a tls slot has to be marked, thus one can recognize a recursive call. When recognizing a recursive call, the demanded permission and calling user have to be queued, performing the check later and the call is passed.

Performance Considerations

The ac implementation uses caching of user permission sets (/services/com.sun.star.security.AccessController/cache-size) for the reason that a permission description has to be parsed (e.g., extracting port numbers). Parsing a permission is performed only once, so any ongoing permission check (when the chain of granted permissions is read) can be done once, then using the parsed permission description for checking.

File Policy Implementation

The file policy implementation of stoc/source/security (implementation name com.sun.star.comp.stoc.FilePolicy is a simple policy reading from policy files.

The structure of those files is very similar to those of Java policy files, e.g.:

grant user "dbo" {
permission com.sun.star.io.FilePermission
    "file:///home/dbo/-", "read,write,execute,delete";
};

grant user "jbu" {
permission com.sun.star.io.FilePermission
    "file:///home/jbu/-", "read,write,execute,delete";
};

// permission granted to everyone
grant {
permission com.sun.star.io.FilePermission
    "file:///tmp/-", "read,write,execute,delete";
};

Things left to do...

The following items are still on the to-do list:
  • [high priority]:
    The main work left is reviewing all ac relevant code and inserting permission checks. In general, this concerns resource access, for example, file io and sockets. Memory and CPU resources cannot be limited effectively. More than the above described permission types may come, entering the API, ac, and policy implementation.

    It is commonly agreed that security checks are implemented on the component level (UNO available) rather than in the base libraries, such as, sal. Checking on component level is much more flexible, which may be needed to improve performance, but this may also lead to more forgotten checks.

    A list of all client code that has to be reviewed will be published here. If you find any ac relevant code that which needs be revisited, but you don't have cvs rights to do it, eMail me so I can append it to the list and contact the module owners.

  • [mid priority]:
    At present, the current context is not inherited by child threads being raised, so the user credentials are not accessible by the ac. This has to be solved by introducing new thread C++/UNO thread classes doing this implicitly by copying the following properties:

    • access-control.user-credentials.*
    • access-control.restriction (initially the parent thread's context)
  • [low priority]:
    The current context is not transferred via the URP-UNO interprocess bridge. It has to be transferred by transferring the above properties with little lack of performance (i.e., configurable).

  • [low priority]:
    The Java ac has to be integrated with the C++/UNO ac to run untrusted Java code using the core Java API.

Author: Daniel Bölzle
Copyright 2002 Sun Microsystems, Inc., 901 San Antonio Road, Palo Alto, CA 94303 USA.