Challenge Solution: Card Keys, capabilities, counters Gregory Frascadore (gaf@objectGuild.com)
Sun, 22 Mar 1998 13:40:39 -0700

Jonathan,

Here is my proposed solution to the challenges.

>
>Scenario 1:
>
>Consider two processes A and B. A is a client application, and B is a
>supporting component that A wishes to create and then use. If you
>like, imagine that B is something in the style of an ActiveX control.
>
>Imagine that A has authority to manipulate objects W, X, and Y (our
>sample user hasn't created many files yet). It wishes to grant to B
>the right to access X, but not W or Y. In addition, it wishes to
>ensure that B never gains access to subsequently created objects Z,
>Z', Z'', Z''' etc.
>
>
>Challenge 1: Describe a conceptually sound solution to this problem
> using only ACLs. [This] cannot be solved in the pure ACL model,
> but *can* be solved with relatively minor surgery to the ACL model.

Just to give myself some raw materials to work with, I'll make the following assumptions.

o The environment is one similar to the Java virtual machine where encapsulation really works and object references cannot be forged.

o The processes A and B will be representable as Java threads and it is acceptable for A and B to implement the java Runnable interface (i.e. the method A.run() is the "main program" for A)

o The phrase "A wishes to create and then use" is a) an essential part of the problem b) there is no arbitrary upper limit on the number of Bs that A may want to create c) that creation means to create an instance from some Bclass.

o The objects W, X, and Y are of the same class (WXYclass) They have one invocable method "foobar()" and the objective is that B can successfully invoke X.foobar() but not W.foobar() or similarly for Y. Similarly for Zi.

o The term "gains access" means to be able to invoke methods against the object upon which access is granted without receiving a permission exception. All private data is assumed encapsulated and accessible only through methods.

o An unsuccessful invocation is designated by throwing an exception from W or Y: NoPermissionException. Similarly for Zi.

Remarks

i. The main problem I faced was one establishing the identity of the caller of foobar(). Once foobar() knew the identity of the caller, it was easy to check the ACL.

ii. I don't know the exact definition of "the pure ACL model", but I assumed that Java security manager tricks that perform scanning of the call stack are NOT allowed.

And now the proposed solution. This is described in two parts, the first just deals with solving the "identity of the caller" problem within Java. The second part is the proposed solution.

PART 1: Establishing the identity of the caller

  1. Define a Process class. Each instance has a unique ID. The IDs are monotonically increasing. There is a static attribute 'nextId' that maintains the value for the next ID to be assigned.
  2. An instance of Process encapsulates a Java Thread. Invoking start() on a Process invokes start() on the underlying Thread. The Process instance assigns the id of the encapsulated thread to be "ProcessN" where N is the process ID.

(Side note: the only reason converting the id intro a string is that Java thread ids are strings and I wanted the Process id to agree)

3. There is a method against a Process called getId() that returns values of the form "ProcessN" There is a static Process method getProcessId() that returns the value Thread.currentThread().getName()

4. The Process constructor takes a Runnable as an argument. The instance of Runnable is the object executing in the new Process.

So now we can describe how A creates process B:

        {
            B b = new B();
            Process p = new Process(b);
            p.start();
            ...
        }

Assuming p was the first Process instance, upon completion of p.start() the above code has b executing within a process with id == 1 and threadid == "Process1" and hence:

p.getId().equals("Process1");

Even better, any code executed in the Thread started by p will find that:

             Process.getProcessId().equals("Process1");
            

and this is how the problem of determining "who called me" is going to be solved."

PART 2: Solving the Challenge

  1. Each non-Process object W, X, Y, Zi has an ACL. The ACL is a set of id's. The presence of an id in the ACL designates permission to invoke the foobar() method. The code for foobar is below in #2.
  2. Each non-Process object W, X, Y, Zi has an owner as specified to its constructor. Only the owner may change the object's ACL.

class WXYclass {

                 Hashtable acl = new Hashtable();
                 String owner;

                public WXYclass(String owner) {
                    this.owner = owner;
                }

                public void addAccess(String id) {
                    if (!Process.getProcessId().equals(owner))
                        throw new NoPermissionException();
                    acl.put(id, id);
                }

                void foobar() {
                    if (!acl.containsKey(Process.getProcessId()))
                        throw new NoPermissionException();
                }
        }                

3. An instance of A creates a B and an instance of WXYclass and makes it accessible to B as follows.

class A implements Runnable {

        // There is a global memory where instances of
        // WXY will be "published" so that references to
        // foobar() may be attempted.

        public static Vector global = new Vector();

        void run() {

            // Create an instance of B and execute
            // it within a new process

            B b = new B();
            Process bproc = new Process(b);
            b.start();

            // Create an instances and have them owned
            // by the current instance of A, only the owner
            // may change acl entries

            WXYclass x = new WXYclass(Process.getProcessId());
            global.addElement(x);

            WXYclass y = new WXYclass(Process.getProcessId());
            global.addElement(y);

            // Add access for  a and b (really the process executing these)

            x.addAccess(Process.getProcessId());
            x.addAccess(bproc.getId());

            // According the the challenge description, only a has
            // access to y

            y.addAccess(Process.getProcessId());
            
            At this point:
                    - any a, b, or future b could invoke foobar
                        against any member of global
                    - if a or b invokes x.foobar() they will succeed.
                    - if b invokes y.foobar() it will receive NoPermissionException.
                    - if a future bi invokes x or y.foobar() it will receive NoPermissionException
}

4. For completeness, an instance of A is started as follows:

        class MainProgram {
            public static void main(String args[]) {
                A a = new A();
                Process aproc = new Process(a);
                aproc.start();
            }
        }



>Challenge 2: Design an *efficient* primitive mechanism to implement
> the key element(s) of your solution.

Although I didn't set out to work on Challenge 2, some mechanisms used in the above code seem to be:

  1. User identity is bound to the process/thread executing code on behalf of a user.
  2. In this case, the Java virtual machine enforces integrity of identities and code that checks ACLs
  3. ACLs are hashtables keyed by identity strings.
  4. Protected objects have owners. At first, the owner is the creator. Only the owner may modify the acl entries.

>
>Challenge 1 cannot be solved in the pure ACL model, but *can* be
>solved with relatively minor surgery to the ACL model.
>
>Challenge 2 appears to be intractable. Dynamic allocation of kernel
>data structures to solve the problem leads to kernel deadlock, and is
>therefore not acceptable.

Sorry this was so long.

-Greg