NOTE:  This document explains most of the theory behind the Lima Mudlib's
       security system.  Dysfunctional Mud's system is based on the theory
       presented here, however it should be noted that the implementation
       details presented in this document do not necessarily apply to DM's
       system, as it was created from scratch.
       (This is here to act as a guide to the system until someone writes
       one.)
---------------
Lima Mudlib Security

     ed. note: this is a work in progress. It is more a description of
     the system rather than how to run the system. Working with the
     "admtool" program and the background presented here should help
     quite a bit. As always, come to Lima Bean for more details, but
     ensure that you've read this whole document before asking
     questions about security :-)

There are many elements that work together to create the security system
with a Lima mud. Here are the various pieces that you need to be aware of to
fully understand Lima's security system:

   * SECURE_D
   * privileges
   * protections
   * stack-based security concepts
   * the user object
   * wizards
   * domains
   * admins
   * the unguarded() function
   * other security-related functions

This document will also cover how to maintain a secure mud and how to look
for holes in your security. One of the benefits of the Lima security system
is a simple and direct way to look for problems.

Other topics:

   * The Admtool System
   * Glossary

SECURE_D

This daemon, located at /secure/daemons/secure_d.c is the heart of the Lima
security system. It manages the core set of data that is responsible for
maintaining proper security.

At a high level, it maintains the follow items of information:

   * what privileges exist, and who "has" those privileges
   * what domains exist, and who is within those domains (and who are the
     "lords" of a domain)
   * what protections have been applied to directories
   * which users are wizards

The daemon defines functions for manipulating, querying, and verifying these
pieces of information. There are a few support files to provide additional
high level capability; these are M_ACCESS (which is the
/secure/modules/access.c file) and /secure/simul_efun/security.c.

In standard practice, you will never need to interact directly with the
SECURE_D, but rather through the interface functions in M_ACCESS and the
sefuns. These functions are summarized later.

Privileges

The security revolves around defining and using sets of privileges. These
privileges are assigned to objects (and via a user object, to a particular
user when they connect to the mud). Privileges are applied to directories,
where they are called "protections". A particular object must have enough
privilege to overcome the protection on a directory before it can read/write
to that directory.

In its simplest form, a privilege is a string of alphabetic, lower case
characters. There are few additional forms that are used:

  1. A lower case alphabetic string, followed by a colon, optionally
     followed by another lower case alphabetic string. For example:
     deathblade:objects
  2. Simple privileges or those with a colon in them (as in (1)) that have
     their first letter capitalized.
  3. An @ character followed by alphabetic characters. For example: @secure.
  4. The integers 0 and 1.

Now, a discussions of the privilege hierarchy and ordering among them is
needed, before the above forms can be fully understood.

1 is the "highest" privilege of all. All other privileges are "lower" than
this privilege. Comparisons are made often between privileges to determine
if a given privilege is equal to or higher than another. 1 will always be
higher (or equal).

0 is the lowest privilege of all. It is the exact opposite of 1. All other
privileges are (equal to or) higher than 0.

Named privileges are, orderwise, between 0 and 1. Two named privileges are
neither higher nor lower than each other. They are "disjoint" and a
comparison will always fail.

A small graphic portrays this very well:

          1
         /|\
        / | \
       /  |  \
    foo  bar  @blah
       \  |  /
        \ | /
         \|/
          0

This graphic shows how 1 is the highest, foo/bar/@blah are between 1 and 0
(but have no order compared to each other), and all are higher than 0.

Privileges may have "subprivileges". The subprivileges have a colon after
their "owner privilege", possibly followed by a subprivilege name. The
privileges and subprivileges are organized as follows:

   1  -->  foo  -->  foo:  -->  foo:bar  ->  foo:barlonger -> 0
                           -->  foo:blatz  ->  0

The privilege "foo" is called the "control privilege". Having that privilege
allows you to define the subprivileges. It is not as high as 1, but is
higher than all subprivileges of itself. The "foo" subprivileges are called
"data privileges". The "foo" privilege may also be called an "owner
privilege", as it "owns" the subprivileges.

Privileges such as "foo" are created when the user "foo" becomes a wizard
(the "foo:" subprivilege is also defined at wizard creation time). There is
a one to one correspondence between wizards and these privileges; there is
no way to create these control privileges except through creating wizards.
Take note that wizard privileges are entirely in lower case. The wizards
"user object" will receive their privilege when they connect to the mud (see
the USER OBJECT section). As it is the control privilege, the wizard may
define subprivileges as needed. Why this is handy will be discussed in a
bit.

Administrators may also define privileges that begin with "@". These can be
applied as protections to a directory or as the privilege of a certain
object. Note that it is impossible for a wizard to have a name beginning
with "@", so it would not be possible to spoof the privilege. As with
wizard-based privileges, subprivileges may be created as needed.

Lastly, there are domain privileges. These are automatically created when a
domain is created. They are like wizard privileges, but have their first
letter capitalized (thus preventing a wizard spoofing the privilege). When a
domain is created, two associated privileges are created for it: the control
privilege and a data privilege. For example, let's say you create the domain
"telaria". The two privileges that are created are: "Telaria" and
"Telaria:".

Now comes the complicated part. Providing additional privileges to a wizard.
This is done by linking parts of the privilege graph together. This
definitely calls for a graphic:

           1
          / \
         /   \
       joe   sue
        | \   |
        |   \ |
       joe:  sue:
         \   /
          \ /
           0

The wizard Joe now has access to directories with that are protected by the
"sue:" privilege. Note that you do not want to give the control privilege
"sue" to Joe, because that would allow him to create, remove, or alter
subprivileges of the wizard Sue. He could even use the control privilege to
allow other wizards access to Sue's directories.

One other caveat: when the security system is considering whether an object
has the privilege to access something protected by "sue:", it will require
that the object has a privilege higher than "sue:" ("sue" or 1), or that the
object's privilege is on the list of privileges "sue:" has been opened up
to. Note that it requires these assigned privileges to match exactly.
Therefore, even if the "joe:" privilege was added to the list for "sue:",
the "joe" privilege won't match (even though it "should" since it is higher
than "joe:").

For this reason, when assigning privileges to another (like "joe" to
"sue:"), you need to consider what objects will need to access the protected
directories (protected by "sue:" in this example). In this example, We want
the wizard Joe to have access, so we use Joe's privilege. If we wanted one
of Joe's objects (typically "joe:" or another subprivilege of that) to
access "sue:" then we would open that specific subprivilege.

Lastly, any privilege can be opened to any other. Typically, these
capabilities will be used to provide a wizard access to specific parts of
the mudlib. Domains are a more general mechanism, so investigate using that
before considering the opening of specific privileges.

Protections

A directory can have a privilege associated with it. Any objects within that
directory are limited to that as a maximum privilege attainable. In
addition, an object must have at least that privilege to write into the
directory.

Directories can also have read privileges assigned to them to limit what
objects can read files in the directory.

Stack-Based Security Concepts

The Lima security system uses a method called "stack-based security". Simply
put, when a privileged operation needs to occur, every object on the current
execution stack will be examined to ensure that all of them have the
necessary privileges to perform the operation.

As an example, let's say that the wizard Joe wants to write a file into
Sue's directory (/wiz/sue). We'll also say the directory has a protection of
"sue:" (which Joe has had opened up to him). Joe will use a command called
"writefile" which is in a directory that has a protection of 1.

Okay: when Joe uses the command, the stack might have the following objects
on it:

   /secure/user    priv=joe
   /bin/writefile  priv=1
   /secure/master  priv=1    (the valid_write() call)

valid_write() is going to verify that the current privilege is enough to
overcome the protection of "sue:" on the /wiz/sue directory. SECURE_D is
called to check the privilege. It steps backwards through the stack, finding
privilege 1 a couple times. This is higher than "sue:" so SECURE_D continues
back through the stack. It finds the /secure/user object and that it has
privilege "joe". "joe" is on the access list for "sue:" so it passes. The
stack has been verified, so the SECURE_D makes one final check and verifies
that this_user() has enough privileges. this_user() is Joe, with the "joe"
privilege, and it, again, succeeds. The result is that Joe and the writefile
command are allowed to write into /wiz/sue.

For another example, let's say that the wizard John tries to write into
Sue's directory with the writefile command. When the stack is checked, it
would find the privilege "john" which is NOT open to "sue:" and so the
operation would be denied.

Lastly, how about the case where an evil wizard tries to code an object that
calls writefile. He then gives that to an admin, hoping that the admin's
privileges would allow the operation to succeed. A simple examination of the
stack reveals this won't happen, though:

   /secure/user     priv=1   (the admin)
   /wiz/evil/mytoy  priv=evil:  (wiz directories are always name:)
   /bin/writefile   priv=1
   /secure/master   priv=1

As soon as the stack system saw the "evil:" it would cause the operation to
fail. It is also very interesting to note, now, that the evil wizard doesn't
have any privileges higher than "evil" and, therefore, cannot place his
mytoy object into a priv=1 directory (causing it to pass inspection).

The User Object

As you may have seen from the discussions above, the user object is rather
central to the security system. It defines the privilege that a particular
user has, and therefore limits their operations.

When a user connects to the mud and logs in successfully, the user object
will then be assigned a privilege. For players, this will be 0 (no
privileges at all). From there on, players are restricted to only the most
basic of operations. Wizards will be assigned their own privilege (the
privilege named after them). Admins will receive privilege 1 (the highest).
The SECURE_D is quite involved in determining what a user actually is
(player, wizard, admin); the user object will use that information to decide
on the privilege.

Note that the /secure/user object is tightly locked up to prevent any
potential alteration of its privilege or faking out what it will be set to.

Wizards

The SECURE_D daemon records which users have been marked as wizards. There
is no other record of this fact, so there is no other way to become a
wizard, other than convincing the SECURE_D that you are one. Of course, to
convince it of this, you must be an admin (which implies you are a wizard
already).

There is a simul-efun named wizardp() that should be used to detect if
somebody is a wizard. This helps isolate you from the details of how to ask
the SECURE_D and it also provides a bit more flexibility. wizardp() takes a
single argument: a user object, or a string containing the user's name in
lower case.

When a wizard is created, their associated privilege is created along with
the subprivilege "name:" (where is the wizard's name). If the wizard will
also receive a directory, then that directory has the "name:" protection
applied to it. It is very important that the subprivilege is used for the
directory. You do not want the wizard's objects to have the control
privilege! Only the wizard himself should have that privilege.

Domains

Domains are a mechanism that simplifies assigning (groups of) privileges to
wizards and protections to directories.

Also, since the creation of domains and the inclusion of members in those
domains is tightly controlled by SECURE_D, domains and their membership can
be trusted in all contexts. This trust is used in certain parts of the
mudlib to classify wizards and the commands and options available to them.
More on this later.

Each domain has a name, a set of "domain lords", and a set of members.
Typically, it will also have a directory associated with it, such as
/domains/name/. The name must be lower case, alphabetic characters. The
domain lords are the wizards who allow/revoke membership in the domain and
have the control privilege for the domain. The domain members simply have
domain data privileges.

Admins

Admins are defined solely by being a member of the "admin" domain. Members
of this domain will have privilege 1 assigned to their user object, meaning
they have the highest possible privilege.

The unguarded() Function

An obvious problem appears with a stack-based security system. How is it
possible for a player (with privilege 0) to cause entries to appear in the
/log directory (protection 1) ? The short answer is that it is impossible
for a user to reach privilege 1. On the other hand, if the player can ask a
high privilege object to do it for him and then somehow avoid being seen in
the stack, then it would work.

The unguarded() function is used for just this purpose. When an object uses
unguarded(), then the call stack tracing used by the security system stops
at that point and ignores all previous callers. A simple example should
demonstrate:

   /secure/user            priv=0   the player
   /secure/daemons/log_d   priv=1   theoretical logging daemon
   unguarded()
   /secure/daemons/log_d   priv=1   func used in the unguarded()
   /secure/master          priv=1

In this example, the security system will only look back as far as the
unguarded(), ignoring that the prior object had a privilege of 0.

Now, the one big, major, super-duper, never forget, tattoo it to your
forehead, inscribe on your toes, end-all of all rules:

--> Use unguarded() VERY VERY carefully < --

Why? unguarded() is the ONLY (*) way that you can create a security hole in
your mudlib. This is by virtue of the fact that this is how a low-privilege
user can gain a high-privilege status to execute certain functions.
Therefore, you must take particular care to how it is used. The parameters
to unguarded are:

unguarded(mixed privilege, function func)
     Evaluate the specified function at the specified privilege

The function that is passed to unguarded() should be extremely limited and
if it is parameterized at all, ensure that your parameters are validated.
For example:

  unguarded(1, (: save_object, SAVE_FILE :));

This is the "best" kind of use of unguarded(): the function does one thing
and one thing only. The file that it uses is hard-coded. Another example:

  unguarded(1, (: write_file, LOG_DIR + fname, contents :))

This is tricky: before using something like this, you must ensure that
somebody cannot get this line of code to be executed with an fname of
something like "../data/secure/access.o". So... before this is called, it
would be very important to ensure fname is one of a few legal values
(/std/reporter.c does something like this) or that it contains no "."
characters in it (or alternatively, only alphabetic characters).

Here is an absolute WORST thing to do. This is the perfect security hole:

void security_hole(string fname, string contents)
{
  unguarded(1, (: write_file, fname, contents :));
}

This allows anybody to write anything to any file in any directory.
Generally, not a good idea. :-)

Therefore, it is usually a good idea to scan your mudlib every now and then
and examine your uses of unguarded() to see if there any are potential
holes. The Lima Team does this, so unless you are coding with unguarded()
yourself (shouldn't be necessary), then you won't have to worry about this.

(*) Okay: technically, there are a couple other ways you can create a
security hole. One is to have a high privilege object inherit a low
privilege object. Second is to have a high privilege object #include a low
privilege file. A simple example: let's say /secure/user did a #include
"/tmp/somefile.h". Any Joe Wizard could rewrite somefile.h to include the
security_hole() function from above. This would then become part of your
high security object, and is then usable by anybody to execute high
privilege operations.

At this point, the Lima Team investigating a couple ways to prevent the
inherit/include problem to avoid accidents on the behalf of admins (note
that only admins can edit priv 1 objects and thereby create this hole).

Other Security-Related Functions

check_privilege() [ SIMUL_EFUN ]
check_previous_privilege() [ SIMUL_EFUN ]
get_protection() [ SIMUL_EFUN ]
get_privilege() [ SIMUL_EFUN ]
query_domain_members() [ SECURE_D ]
query_domains() [ SECURE_D ]
set_privilege() [ M_ACCESS ]

The Admtool System

Glossary

admin
administrative privilege
control privilege
data privilege
domain
domain privilege
owner privilege
privilege
protection
subprivilege
wizard
wizard privilege

[Image]Return to the LIMA Home Page.
----------------------------------------------------------------------------
Please direct any questions, comments, or problems with this page to
wwwadmin@lima.imaginary.com or lima@lima.imaginary.com
