Modeling Access Control Policy With The RW Language

                 Bradley Berg     CS196-1     May 14, 2006

1. Introduction

The RW language [1, 2, 3] was developed to express access control policies that
can be checked for properties of correctness.  In particular actions of agents
acting alone or collaborating with others can be checked for safety.  Safety
assurances show that intruders can not have unauthorized access to resources.
Additionally, permitted users must be granted access authorization.

The RW language elegantly captures the core properties of access control
systems.  That is it focuses solely on access control and cleanly separates
access from content and its usage at the application level.  Higher level
policy issues are excluded from RW.  For example the notation does not include
constructs to support distributed policies [4] or explain why permissions are
denied [5].  It might be desirable to include such aspects as part of a policy
as well.  Still their safety properties could be analyzed using RW.

The notation is simple and expressive; making it suitable for performing a
thorough policy analysis (See Appendix A).  When modeling using logic-based
languages, subtle errors are easily introduced due to incomplete case analysis
and omissions.  Interactions become more difficult to understand in
semantically rich languages such as Alloy [6].  There are more ways for
features to interact and alternative ways to implement a given property of a

A model of the Continue conference system was written in Alloy [7] and used as
a reference model.  The reference model does not fully represent the current
software for Continue as it requires some extensions.  A model of the Continue
access policy was then written using the RW language that is consistent with
the reference model (See Appendix B).

This paper considers issues encountered while rewriting the Continue policy
using the RW language.  Plausible extensions to the Continue policy are then
proposed to explore a variety of representation issues in the RW language.

2. Modeling With RW

Each Class in an RW policy denotes a type of entity.  For the purpose of model
checking the Run statement specifies how many entities of each type are to be
used in the check.  When executing the model within an application there may be
any number of entities.

The state of a model can be implemented as a collection of single bit variables.
Each predicate in the RW language corresponds to a multi-dimensional array of
state bits with dimensions determined by its arguments.  The state associated
with a predicate with 2 arguments can be represented with a two dimensional
array of bits.  The depth of each dimension is the number of entities in the
Class of each predicate parameter.

Each transition either Reads a state bit, Writes true, or Writes false.  Any
User Agent can invoke a transition and the transition will either be performed
or rejected due to an access violation.  The Read and Write clauses in each
predicate determine whether a transition is permitted or denied when requested
by a particular user agent.

2.1  Access to State Values

Predicates and state are intertwined in RW models.  They are not, as in some
other languages, statements about a separate global state space.  Two
predicates can not commingle control over any shared state.  Note too that
predicates must have at least one parameter as they determine the amount of
state that can be accessed within each predicate.

A Continue conference has several global settings that may be changed at will
by Administrators.  The Alloy reference model for Continue uses the
changeConferenceInfo predicate to represent changes to these settings as a
group.  Note that the content of resources is not part of an access model.
Only permission to access the values is modelled.  There are several ways with
the RW language to represent access to a set of values.

The simplest strategy is to just use the isAdmin predicate since the access to
it happens to be the same.  However, the knowledge that changeConferenceInfo
and isAdmin have the same access control would then be represented outside the
formal model.  The syntax could accommodate this by supporting aliases for
predicates.  As long as the access is the same then an alias suffices.

    Predicate  isAdmin(agent) Alias changeConferenceInfo, ...

If instead the access predicate is more complex a more general macro construct
is required.  Repeated instances of compound expressions can also be
encapsulated using macros.  A more general extension can combine predicate
references in a common expression to provide an external interface for
composite predicates.  It acts as a syntactic macro to expand the boolean
expression wherever the function is referenced in a predicate.

    Function changeConferenceInfo(agent)
    {   isAdmin(agent)  or  isChair(agent);

Another way to model value access is to create parallel predicates.  In this
case there would be separate isAdmin and changeConferenceInfo predicates.
Whenever an application performs a write to the isAdmin predicate, it must also
perform the same write to the changeConferenceInfo predicate.  Knowledge about
this access relationship is then pushed into the application; disjoint from the
model.  Also a check can not enforce that the parallel predicates are
transitioned atomically, so checks can not incorporate that fact.

      // Anyone can read conference information.
      read:   true;

      // Only Administrators can modify it.
      write:  isAdmin(agent);

An alternative technique uses a dummy argument to represent a single state bit.
This takes advantage of the two states returned upon access.  One is the value
of the state bit and the other is whether or not access is granted.  When write
access is allowed then the application interprets that as permission to modify
the values is granted.  The actual value of the dummy state is not significant.
In this case the dummy argument needs to be limited to a single entity by the
Run command.  During execution the value of the dummy argument is ignored.

      // Anyone can read conference information.
      read:   true;

      // Only Administrators can write the info.
      write:  isAdmin(user);

2.2  Representing Enumerations

Enumerations can be represented in an RW model using a separate predicate for
each enumerated state.  An ordered enumeration progresses through a fixed
sequence of states.  In the Continue model the phases for a conference and
papers are ordered enumerations.  Predicate signatures for conference phases
and the body of the inPreSubmission predicate are given below.

    // There is a predicate for each phase, but only one phase element.
    inInitialization(phase: Phase),
    inPreSubmission( phase: Phase),
    inSubmission(    phase: Phase),
    inBidding(       phase: Phase),
    inAssignment(    phase: Phase),
    inDiscussion(    phase: Phase),
    inNotification(  phase: Phase),
    inPublishing(    phase: Phase);

               // Authors are not allowed to find out the current phase.
        read:  ~isAuthors(user);

               // Only administrators can progress the conference phase.
       write:  isAdmin(user)  and

               // The current phase must be Initialization.
               ~inPreSubmission(phase)  and  inInitialization(phase);

The Phase parameter for the predicates is a dummy argument and the model must
be run with one Phase entity.  In this case the state in the model is
represented as eight bits, one per phase predicate.

Initially the state bit for inInitialization is set true; all others default to
false.  To advance the phase to PreSubmission, true is written to the
inPreSubmission predicate by an administrator.  It is constrained to avoid
rewriting the phase twice and to ensure the current phase is Initialization.
As the phases progress the state bit for the next phase is marked true.

Unordered enumerations can not be represented in this fashion.  Changing from
one enumerated state to another requires clearing one state bit and setting
another in a single transition.  The RW modeling language could be extended to
support enumerations.  Examples suggesting a notation is:

   Class Phase In Initialization,

The 'In' keyword represents an ordered enumeration and another keyword,
such as 'Any' could be used to designate an unordered enumeration.
Within the model predicate bodies would be much cleaner as enumerated states
could be directly referenced.  Note that for Continue phases the read access
for the Notification phase is difference than the other phases.

   phase  Phase
       // The phase can be read by authors except the notification phase.
       read:  ~(phase = notification)  or
              ((phase = notification)  and  ~isAuthor(user));

       // Only administrators can change the Conference phase.
       write:  isAdmin(user);

Instead of referencing a particular phase as:

    inSubmission(phase)  and  ~inBidding(phase)

we could say:  phase = Submission

2.3 Atomic States Using Global Constraints

In the conferencing example suppose we wanted to extend the model with a
conference Chair and allow only one Chair at a time.  When changing Chairs the
state bit for the current Chair needs to be cleared and the new Chair's set.

One way to enforce a single Chair is to constrain all other predicates.  A
global constraint is applied to all write clauses in other predicates.  The
constraint keeps any other predicate transition from occurring until the chair
is set.

    isAuthor(agent, phase)
        read:   true;

        write:  // Global constraint applied as part of the isChair predicate.
                (E a: Agent [ isChair(a) ])

                // The original constrains for isAuthor.
           and (isAdmin(user)  and  ~isAuthor(agent, phase)
           and  inSubmission(phase)  and  ~inBidding(phase) );

This approach applies additional constraints to prevent any state changes until
isAdmin is assigned.  Explicitly coding global constraints is cumbersome.  They
need to be consitantly applied to each predicate as the model is written or
modified.  To represent a global constraint in a model the syntax needs to be
extended.  A possible syntax for global constraints is given below.  Since the
constraint will be applied to any predicate except one then a reasonable place
to declare it is in the one exception.

               // Conjoin this predicate with all other writes.
       global: E a: Agent [ isChair(a) ];

               // Anyone can see who is a chair.
        read:  true;

               // Only the administrator can add new chairs.
       write:  isAdmin(user);

The global constraint also needs to be applied to the read predicate in the
isChair clause.  In the event that an another clause references the isChair
clause when the predicate is in the intermediate state with no Chairs,
permission would be denied.

When access is denied due to a global constraint it differs in that it is
temporary and not a true denial.  In the execution model referencing the
intermediate state needs to be avoided using serialized access to the model.
The serialization mechanism could implicitly perform the atomic write to
complete the transition.

When checking the model a temporary denial doesn't matter.  Unlike model
execution, no actions are performed as side effects of the denial.  The check
is looking for combinations of permitted states and the temporary denial would
be resolved as the atomic action would be forced to completion in the next step.

In the previous example note that the entities authorized to establish a Chair,
an Administrator, are disjoint entities from the Chair.  If instead of a Chair
only a single Administrator was allowed, a global constraint would not suffice.
A transition to a new Administrator would not be possible since only an
Administrator is permitted to assign another Administrator.  Once the previous
Administrator was removed there would be no entity that could establish the
next Administrator.

Performing atomic transitions using global constraints is also limited to
single predicates.  Atomic transitions may need to be applied over multiple
predicates.  To illustrate this, suppose the continue example is extended to
say, 'If an agent becomes a Reviewer they must be assigned at least one paper.'
That is when an agent becomes a reviewer they must also be assigned a paper as
an atomic transition.  The check would ensure that all reviewers are assigned
to at least one paper.

In this case we need to write true to the predicates isReviewer and
assignPaper.  Since papers can only be assigned to reviewers, first we have to
designate the new reviewer by writing true to the isReviewer predicate.  If a
global constraint was used, it would need to force assignPaper.  However, the
arguments needed for assignPaper are not available within isReviewer and
consequently can not be incorporated in the global constraint.  we can't force
the next move by applying the constraint:

   assignPaper(paper, reviewer, phase)

In order to work in any predicate global constraints must be fully qualified
and have no external references.  This approach will work when checking for
empty or full sets, but not parameterized sets.

2.4  Independent Atomic transitions

While global predicates can be implemented without extending the check
algorithm, their limitations warrant investigation of alternatives to perform
atomic transitions.  Atomic transitions can be expressed by using a
pre-condition and post-condition constructs.  Pre-conditions and
post-conditions were used by Frias [8] to extend Alloy.  This made the
resulting Alloy specifications more simple and clear; while creating
opportunities for optimization.

One approach is to model atomic transactions in a stateless composite function.
The pre-condition is checked first for access permission.  If omitted access
checks implicitly required in the post conditions are used.  The post-condition
is a conjunction of predicates denoting atomic transitions.  False is written
to negated predicates and true is written to positive predicates.  There is no
corresponding read transition for the Function.

    Function changeAdmin(from, to)
           pre:   isAdmin(from);

           post:  ~isAdmin(from)  and  isAdmin(to);

This approach could also be used to transition ordered enumerations.

    Function setBidding(phase)
         pre:  isSubmission(phase);

        post:  ~isSubmission(phase)  and  isBidding(phase);

Unordered enumerations require setting one predicate and clearing all others
in the enumeration.

    Function setBidding(phase)
        post:   inBidding(phase)
          and  ~inInitialization(phase)
          and  ~inPreSubmission(phase)
          and  ~inSubmission(phase)
          and  ~inAssignment(phase)
          and  ~inDiscussion(phase)
          and  ~inNotification(phase)
          and  ~inPublishing(phase);

Rather than declaring a separate function, the post-condition could be embedded
within a predicate.  The post-condition is expressed locally within an
initiating predicate so that a separate function doesn't need to be declared.
The limitation is that the parameters of predicates in a post-condition can
only be those of the initiating predicate.  This limitation might in fact be
desirable as passing the composite parameters through a function might result
in unnecessarily complex models.

    assignPaper(paper, reviewer, phase)
        read:  true;

        write:  // Only an administrator can assign papers to reviewers.
                // Move isReviewer to the post condition.
                isAdmin(user)  // and  isReviewer(reviewer)

                // Must be in the paper assignment phase.
           and  paperAssignment(paper, phase)  and  ~paperReviewing(paper, phase);

         post:  isReviewer(reviewer);

The case where we want only one Administrator in a conference can be
represented with a post-condition as:

               // Anyone can see who is a chair.
        read:  true;

               // Only the administrator can pass on the role.
       write:  oneAdmin(user);

               // There may be only one chair; remove the original.
        post:  ~oneAdmin(user);

Ordered enumerations still require separate predicates for each state and a
dummy argument.

               // Anyone can find out the current phase.
        read:  true;

               // We don't need to check for ~inPreSubmission(phase);
       write:  isAdmin(user)  and inInitialization(phase);

               // The post condition clears the old phase.
        post: ~inInitialization(phase);

Unordered enumerations also need separate predicates and a dummy argument.

               // Anyone can find out the current phase.
        read:  true;

               // Don't need to check the prior phase.
       write:  isAdmin(user);

               // The post condition clears the prior phase by clearing
               // all the other phases.
        post:  ~inInitialization(phase)
           and ~inSubmission(phase)
           and ~inBidding(phase)
           and ~inAssignment(phase)
           and ~inDiscussion(phase)
           and ~inNotification(phase)
           and ~inPublishing(phase);

3.0 Conclusion

The Continue conference system can be fully expressed using the RW modeling
language.  For such a simple notation the resulting model was surprisingly
clean.  Overall the RW model was easier to write and understand than the Alloy
model.  The only awkward representations were the conference phase (ordered
enumeration) and the introduction of dummy arguments.

On the other hand, a low level language like RW might make it more difficult to
compose more complex policies.  The policy writer may have to employ artifacts
not relevant to the model.  RW has no provision for expressing common idioms
and higher level abstractions.  It might be more appropriate to write policies
using higher notations.  A front-end language for RW requires a semantic
equivalent of a check.  It might be possible to automatically generate common
checks from a higher level language.

Syntactic extensions can be easily added to the RW language.  Still it might
be better to incorporate them in a higher level policy language rather than
extend RW itself.

Ordered enumerations can be supported by a front end to clean up the model.  In
the case of Continue this was the most awkward thing to represent.  Extending
the RW parser might make experimenting with RW models easier.

Unordered enumerations could be added, but they could not be checked without
extending the check algorithm.  If they were not checked an agent could set
multiple conflicting states or set no state.  Even without explicit checking
this might be useful.  Intermittent cases could never be explicitly checked and
the application programmer interface for the execution model could exclude any
case that would permit it.

Macro functions could be added to avoid repetitous patterns.  They could also
be used to represent unchecked atomic actions with the same limitations as
described above for unchecked unordered enumerations.

The RW modeling language is an effective tool for experimenting with access
control policy.  Since it is very difficult to manually check policies, the
ability to automate policy checks is essential for testing their safety.  With
extensions or a high level front-end RW provides a sound basis for implementing
production quality access policies.  As a core policy mechanism it may be
adapted in a wide variety of access control systems.


[1]  Nan Shang, Mark Ryan and Dimitar P. Guelev.  Synthesising Verified Access
     Control Systems through Model Checking.  Pre-publication April 19, 2006.

[2]  Dimitar P. Guelev, Mark Ryan, Pierr Yves Schobbens.  Model-checking Access
     Control Policies.  Seventh Information Security Conference (ISC '04),
     Computer Science, Springer-Verlag, 2004.

[3]  Nan Zhang, Mark Ryan, Dimitar P. Guelev.  Evaluating Access Control
     Policies Through Model Checking.  Eighth Information Security Conference
     (ISC `05).  Computer Science, Springer-Verlag, 2005.

[4]  Apu Kapadia, Geetanjali Sampemane, and Roy H. Campbell.  Know Why Your
     Access was Denied: Regulating Feedback for Usable Security.  CSS '04, 2004.

[5]  Blaze, Feigenbaum, and Lacy.  Decentralized Trust Management.
     Proceedings of the 1996 IEEE Symposium on Security and Privacy.

[6]  D. Jackson., Alloy: a lightweight object modeling notation.
     ACM Transactions on Software Engineering and Methodology, 2002.

[7]  Reference Model for Continue in Alloy.  Jay McCarthy.  2006.
     Only available through the internal Brown network.

[8]  M. F. Frias, J. P. Galeotti, C. G. Lopez Pombo, N. M. Aguirre.  DynAlloy:
     upgrading alloy with actions.  Proceedings of the 27th international
     conference on Software engineering, 2005, pp. 442-451.