HomeContentsContents

Introduction

The development of a system of core security is crucial to the safe development and maintenance of a core in ColdC. Fortunately, object data encapsulation and some of the features and functions available in ColdC make the development of core security a relatively painless task.

As with networking, the actual functionality of core security will vary from core to core. ColdC does not provide a one-size-fits-all set of functions with which to secure your core. The information presented here is intended to offer an example of how a system of security might be developed for a ColdC core. This example is taken from the system used for The Dreaming City. It assumes a core that is intended to be used for a multiplayer virtual world (MUD) and that is accessed by multiple individuals who have the ability to make changes to the data in the core (creators).

Top

Why Bother?

There are two primary reasons for maintaining a robust system of core security. The more obvious reason is the prevention of the deliberate destruction or vandalism of the core by malicious individuals who have access to the core (a.k.a disgruntled creators). Some administrators may feel that their teams are trustworthy and that it is not necessary to develop routines to limit the ability of the team members to manipulate the core. This, however, overlooks the second reason for core security: the unintentional destruction or damage of objects crucial to the operation of the core or the unintentional loss or revision of data which may result in unpredictable behavior by objects in the core. For example, while it is a no-brainer to prevent creators from being able to deliberately access and change one another's passwords, it should also be apparent that a system should be in place that prevents a creator (or even the core administrator) from accidentally changing or deleting user passwords and thus, perhaps, unintentionally blocking user access to the services provided by the core.

Top

Object-Based Permissions

One basic means of limiting the ability to manipulate data on objects in the core is to check any requests to manipulate data to ensure that the object or user attempting to manipulate the data has permission to do so.

Here is an example of the .perms() method that is found on $root in the DreamCore. This method throws an error if the necessary permissions are not found for the object being evaluated. The comments provide information on how every step in the method helps determine whether or not the object or user designated by the argument what has permission to access the method that called .perms():


        public $root.perms() : nooverride
        {
            arg what, @args;
        
            // .perms() is a public method. Security-critical calls to .perms() should always
            // come from inside the object requiring a permissions check, but we may sometimes
            // want to make a non-security-critical call to .perms() from elsewhere (such as a
            // command that may want to run a preliminary check to see if a creator has
            // permission to alter an object before actually attempting to make the alteration
            // (during which a secure call to .perms() would be made.

            // .perms() is nooverride. This ensures that its operation on objects will always
            // be consistent and predictable.

            // $sys is treated as a system secure object. It will generally have permission to
            // access and manipulate any object it needs to. The method .is_system() checks to
            // to see if 'what' is a member of the list of other system secure objects which
            // is stored on $sys.
            if ($sys.is_system(what))
                return;

            // The optional args argument announces the type of permissions that 'what' is
            // requesting for the calling method. This argument will default to 'writer perms
            // (the lowest level of access-by-class in the DreamCore).
            if (!args)
                args = ['writer];

            // If args is a symbol, the calling method is granting access-by-class permission.
            // The DreamCore has four levels of access-by-class permission:
            //     system - Full permissions to manipulate objects.
            //    manager - Near-system-level permissions for the object being managed. This
            //              is the level of permission generally required to design and
            //              develop an object.
            //    trusted - Permissions generally granted to a group of objects which must
            //              regularly access and modify the object granting permission.
            //     writer - Permissions granted to objects that need to make usually
            //              non-security-critical modifications to objects. Generally
            //              granted to creators who need to modify text on in-game-world
            //              rooms, items, and NPCs.
            if (type(args[1]) == 'symbol) {
                switch (args[1]) {
                    case 'system:

                        // The calling method is granting access only to system secure objects.
                        // Since we have already checked to see if 'what' is system secure and
                        // would not have reached this point in the method if it had been, we
                        // can go ahead and throw an error.
                        throw(~perm, strfmt("Permission Denied: %s is not system secure", what));
                    case 'manager:

                        // The calling method is granting access only to the object's manager.
                        if (what != .manager())
                            throw(~perm, strfmt("Permission Denied: %s is not object manager", what));
                    case 'trusts:

                        // The calling method is granting access only to objects that are
                        // trusted by this object. Permission is granted to objects which are
                        // members of the list returned by the method .trusts().
                        if (!.trusts(what))
                            throw(~perm, strfmt("Permission Denied: %s is not a trusted object.", what));
                    default:

                        // The calling method is granting access only to objects that have
                        // write permission on this object. Permission is granted to objects
                        // which are members of the list returned by the method .is_writable_by().
                        if (!.is_writable_by(what))
                            throw(~perm, strfmt("Permission Denied: %s is not a writer.", what));
                }
            } else if (type(what) == 'objnum) {

                // The calling method is seeking access-by-instance permissions. Permission
                // will only be granted to an object if it is one of the objects included in
                // the list 'args'. The .flatten() method is used to break up lists within
                // 'args' into their individual elements (thus, if $my_group.group() returns
                // [$foo_3, $foo_4], then [$foo_1, $foo_2, $my_group.group()] becomes [$foo_1,
                // $foo_2, $foo_3, $foo_4].
                args = args.flatten();

                // If 'wha't isn't in the list 'args', throw an error and deny permission.
                if (!(what in args))
                    throw(~perm, strfmt("%s is not member of [%s]", what, args.to_english("", " or ")));
            } else
                throw(~perm, "Invalid permission request. First argument must be an object.");

            // If we get here, the object returns without error and permission is granted. The
            // above line is to capture any improperly formatted calls to .perms().
        }
    

This method grants two types of permission: access-by-class and access-by-instance. Access-by-class permissions grant permission to any object which meets a particular criteria. Such criteria include being system secure, being an object manager, being a trusted object, or being an object with write permissions. Access-by-instance permissions grant permission to any object which is a member of a particular list of objects. For example, a method may only be accessible by objects that are a part of the group list for $group_can_do_stuff. If an object is a member of the list returned by $group_can_do_stuff.group(), then it will have permission to access the method.

Top

sender() vs. caller()

The method .perms() tests the object indicated by the argument what. As seen in the example, what must be an object reference. In practice, this means that what will generally be either sender() or caller(). Which of the two it is can significantly affect the considerations that must be given to any particular use of the .perms() method.

The function sender() is the least problematic of the two, as it returns the object that is calling the method for which permissions are requested. If we want a method to only be called by system secure objects, then we can regulate access to the method with the line:


        (> .perms(sender(), 'system) <);
    

Note the use of the error propagation expression so that the calling method will properly receive a ~perm error. We often want to propogate security-based errors to the calling method in case we want to handle them in a special manner (such as logging any failed attempts to access the method).

This code will only allow the continued execution of the method if the calling object is system secure (access-by-class). Otherwise it will throw a ~perm error. In this case, it doesn't matter where the code calling the method originated, the object doing the calling must itself be system secure. As long as our system secure objects are properly secured (and most public methods on such objects should require .perms() checks) then access to this method will be tightly regulated.

Similarly, if we want to restrict access to a specific object or group of objects (access-by-instance), we can restrict access with the line:


        (> .perms(sender(), [$cmd_quit]) <);
    

This line, for example, could be used to prevent any object but the quit command from calling the logoff routines in a user object (thus preventing creators from deliberately or accidentally forcing other creators or administrators to log off).

Suppose, however, we have a method that we want to be able to delete channels that a user object is listening to:


        $user_conn.del_channel() {
        channels = setremove(channels || [], sender());
        return channels;
        }
    

In this case, we don't want just anybody or any object to be able to, say, remove a channel that broadcasts security alerts from the list of channels an administrator is listening to. Therefore, we would like to limit access to the .del_channel() method to the object for the channel that we want to remove from the list. The channel objects make this call with the method


        $channels.del_user {
            arg cc;

            user_register = setremove(user_register, cc);
            cc.del_channel(this());
            return 1;
        }
    

Now, in this case


        (> .perms(sender(), [$channels]) <);
    

won't work because it will only permit access to $channels itself, when what we need is the ability for all the channel objects which inherit $channels to have access to the .del_channel() method. Thus, we would want to check the perms of caller() instead:


        $user_conn.del_channel() {
            (> .perms(caller(), [$channels]) <);
            channels = setremove(channels || [], sender());
            return channels;
        }
    

This code will allow any object inheriting $channels to call .del_channel() on the $user_conn object.

There is a very important security consideration to keep in mind when using caller() in a call to .perms(). In the above example, only the credentials of caller() are checked. The actual object making the call (sender()) is not checked and can be any object that inherits caller(). For this reason, caller() should only be used for a perms check if we know that the ability of other objects to inherit it are restricted. Otherwise, a creator would need only to spawn an object which inherits $channels and he will then have the ability to delete channels from any user he wishes to. (In the case of DreamCore, $channels is a core object which may only be inherited by objects created by a top-level administrator. It is, therefore, safe to use it to provide the credentials for a perms check.)

Top

Function Binding

ColdC presents a powerful set of tools that allow administrators and creators to develop code which will do just about anything, including reading and writing files, executing external programs, establishing network connections, and shutting down the Genesis server. You do not want this functionality available to every single object in your core. Obviously, a malicious creator with an unfettered ability to write to files and establish network connections would represent a very serious security risk. But you also do not want it to be possible to create an object which could inadvertantly shut down the server.

The bind_function() function is provided so that access to those ColdC functions which pose potential security risks can be restricted. In order to preserve integrity it is strongly suggested that the database builds a hierarchy of trusted objects. Bind administrative level functions to these objects using bind_function() and restrict access to them. In most cases, this will mean limiting access to these functions to $sys or $root. A good way to bind functions is to call bind_function() from $sys.startup() (this is a special method that, if present, will be called everytime the Genesis driver starts up):


        $sys.startup() {
            arg args;
            var x;

            catch any {
                for x in (bindings) {
                (| bind_function(@x) |);
            } with {
                (| (tb = traceback().compile_traceback()) |);
                (| $sys.error_handler(tb, "startup") |);
            }
        }
    

In the above code, the variable bindings is a dictionary with all the functions that will be bound as keys and the objects they will be bound to as values. It is suggested that the following functions always be bound and secured:

atomic()cancel()create()
backup()shutdown()set_heartbeat()
bind_port()unbind_port()open_connection()
fopen()fstat()fchmod()
fmkdir()frmdir()files()
fremove()frename()fclose()
fseek()feof()fwrite()
fread()execute()fflush()
bind_function()unbind_function()

It is also suggested that any additional functions which modify objects or which alter/retrieve data on objects be bound.

Top

Method Flags and Access

Method flags and access can be used to help bolster your core's security.

The method flag nooverride prevents a method from being overridden by methods in an object's descendants. This is particularly useful for many of the methods which will incorporate bound functions on $root (such as the routines to destroy objects, change parents, and add/delete methods and variables). The nooverride flag is a good way to ensure that these routines always function as expected and that a creator cannot create an object which, for example, could resist any attempts by an administrator to manipulate or destroy it.

The locked flag is a blunt-force instrument for protecting vital methods. When a method is locked with this flag, its flags and access cannot be changed from within the core, and its code cannot be recompiled. This very strength, however, can make the locked flag impractical for many core developers. Because the flag prevents any changes from being made to a method during runtime, a core administrator will be required to shut the core down and edit the textdump if she finds it necessary to alter the method in question. With a mature core, however, this flag is an extremely secure means to protect the most important parts of your core code (as the above .perms() method, for example).

The proper implementation of method access both improves the security of your core and makes good object-oriented programming sense.

Methods which are only called from within the object on which they are defined should always have their access set to private. Methods which are only called from within the object on which they are defined or on descendants of that object should always have their access set to protected. Doing so limits the number of portals through which an object's methods and variables can be accessed, thus making the job of securing that object much easier.

Methods which are called by the driver (.parse(), .disconnect(), $sys.startup(), etc.) should either have their access set to driver or have their access otherwise regulated by the core's internal security.

The root access setting should be used for any methods which should only be called from methods defined in $root. This is often useful for methods called by the $root methods in which security-sensitive functions have been bound.

Top

Contents Previous Next Functions

Valid XHTML 1.0 Strict