Most modern operating systems are multi-user and give processes a ‘user identity’. This is a label denoting privilege. Access to objects that may be shared is controlled by the system. The OS provides expressive mechanisms that can represent a range of policies, the specific policy is then decided by the users of the system.

Example Access Control Policy

“Alice can read Alice’s mail, but Bob can’t.” The operating system will deny the request if Bob tries to read Alice’s mail.

Considerations for Access Control

We need to consider:

  • what kind of policies are useful?
  • how do we keep mechanisms simple, flexible, expressive, and efficient?
  • when do we perform checks?
  • are (human) user identities enough for expressing policy?
  • how does this impact the design of the system call interface?
  • how does this impact how we program?
  • how do we establish identity?
  • how do we distribute everything?

Example design: Unix pathnames, inodes and file descriptors

In Unix, we open() a file by pathname:

int fd = open("/home/alice/mail", O_RDONLY);
# then read(), write() on file handles

Permission checking is only done at open().

-rw-------- 1 alice alice 450871   Mar 4 2023  /home/alice/mail
#             ^ owner is alice
#                   ^ group is alice
# rw-: owner can read, write but not execute
# ---: other members of group alice can do nothing
# ---: other users can do nothing

Permission bits are stored in a Unix filesystem data structure called the inode. After open(), having the file handle is enough and checks are not done at read() or write(). File descriptors may however be restricted at the time of open().

In the earlier example, we used O_RDONLY which means only read() and not write() will succeed on handle fd.

The design means:

  • cost of checking is amortised
  • delegation is possible: can transmit fd to other processes
  • revocation is hard: changing permissions doesn’t revoke open descriptors

Model of policies: access control matrix

We can draw a table of who has what access to which object:

subject / object/etc/passwd/vmunix/usr/bin/ls/home/alice/project
aliceread-read, executeread, write
bobread-read, executeread, write
charlieread-read, execute-
daveread-read, execute-

We want to turn this into a data structure on a running system, we generally use one of two strategies: ACLs and Capabilities

ACLsCapabilities
stored with objectsstored with subjects
check permissions on accesscheck permission on issue + validity on use
easy to revokehard to revoke
hard to delegateeasy to delegate
usually coarse-grainedeasily made fine-grained
easy to audithard to audit
easy to retrofitmust usually ‘design in’
tend to bottleneck distributiondistributes more scalably
authority is ambientauthority is designated

Access Control List (ACL)

Access Control List (ACL)

We can store each object with a list of pairs: (who, what access)

Unix’s ‘user’, ‘group’, ‘other’ permission bits are a limited ACL Full explicit ACLs can become large (when added up) and performance-sensitive (if checks are frequent).

The protect the object, at the object. Which means:

  • easy to revoke permissions
  • easy to understand and audit

However:

  • scale can become a problem if there are many users, files, actions
  • authority is ambient: based on who you are; we can’t delegate fine-grained access to some code or process
Link to original

Capabilities

Capabilities

We can give each subject a list of pairs: (which object, what access)

Each of these pairs is a capability similar to a file descriptor. We can also store capabilities with each process instead of user, enabling them to be fine-grained and dynamic.

Unforgeable token of authority on some object, possessing a valid reference is enough. Similar to keys on a keyring:

  • each key has a distinct authority - can be fine-grained\
  • to open a door you have to pick a key - designated, not ambient
  • more than 1 key might open the same door differently

The validity check can be distributed using cryptography.

Link to original