Hi,
we've had some interesting discussion around my proposal for a privilege
system on the way back from the meeting, regarding using capabilities. I
thought it would be good idea to capture my current idea for a privilege
system as it stands, in writing. Please find it attached.
This should help put the discussions on a more solid ground. I tried to
describe the system in such a way that it can be tested (without an actual
implementation) and compared to another approach. Next I would like to look
into the Genode book introduction, as suggested by Jakub, try to come up
with an alternate, capability-based approach and then try to compare
different approaches.
Any feedback is highly appreciated.
Best regards,
Jiri
Hierarchical Privilege System
*****************************
Author: Jiří Svoboda, 2017
Purpose of this document
========================
This document presents a generic OS privilege control mechanism and applies
it to HelenOS tasks. The purpose is to provide have a (as much as possible)
complete design that can be discussed, evaluated and compared to other
options (such as capability-based approaches).
Motivation
==========
Currently any task in HelenOS is able to modify the system in an
unrestricted way. This presents both safety and security risks. It applies
both to system services and user sessions.
If the web server is subverted, it can, for example, overwrite any file,
write to a block device, unmount a filesystem, etc.
Similarly, a user can unwittingly write to the block device containing the
root filesystem or kill the naming service, rendering the system unusable.
An RBAC-based security model has been presented in [1], but it primarily
deals with introducing mutiple-user concept to HelenOS and defining the
authorizations of each user. It is quite heavyweight and it is very unwieldy
to try to use it for a fundamentally different purpose, which is restricting
the privileges of each task.
Goals and non-goals
===================
The goal is to limit the privileges of individual tasks and thus the damage
that they can do either unwittingly or maliciously (e.g. if they are
subverted). The privilege mechanism should be simple and easy to integrate
with the existing system.
There is no goal to introduce the concept of multiple users to the system
(this is largely an orthogonal problem).
Conventions
===========
T ≡ the set of all tasks
P ≡ the set of all privileges
∀ t ∊ T, P(t) ≡ the set of privileges of task t
Basics
======
Every task t ∊ T has a set of privileges P(t) stored in a secure place.
Privileges are associated with a running task and do not live past the task
lifetime (i.e. they are not persistent).
Any other task can look up this information, but cannot modify it. When a
task t is spawning a child c, it can give it any subset of its own
privileges, i.e. P(c) ⊆ P(t). This naturally enables privilege delegation.
If a client c is requesting a service from a server s, the server's
implementation may require a certain privilege p to provide this service.
The server s will look up the security DB to see if p ∊ P(s).
Example
-------
There could be a privilege p_bd to access block devices. When starting
system servers only file system servers would be given p_bd. Any block
device service would, upon receiving any incoming connection, check that the
caller task has p_bd.
Security DB implementation
==========================
We have a single security attribute P(t) (set of privileges) that needs to
be stored for each task.
Currently per-task information is managed in the kernel task_t structure and
in ns - although there is no dedicated user-space server with a task
attribute DB.
Options for storing the task security attributes are thus:
1. Kernel (hanging of task_t)
2. With the generic userspace task attribute DB
3. In a dedicated userspace security DB task
Option 1 goes against the microkernel principle. It has the merit of making
it easy for the kernel to use privileges for (dis)allowing connections
betweeen tasks. This approach might be useful for an OS with a
monolithic-kernel design.
For HelenOS option 3 seems like a good choice.
Privilege representation
========================
HelenOS kernel already has a similar mechanism, the task permissions.
Permissions are represented as a 32-bit/64-bit bit mask. Using such a
system would limit the number of permissions and the ability to dynamically
extend the system.
Requirements
------------
We'd like a system that allows arbitrary number of privileges. We'd like a
system that allows adding privileges to the system dynamically. At the same
time we'd like the privileges to be light-weight and simple to use. They
should not have any associated state or life cycle, not needing any
registration.
The basic way of identifying privileges is using a (hierarchical) string.
E.g. /sys/svc/net/tcp could identify the privileges to talk to the 'net/tcp'
service.
(Note: we could use a URI notation with the scheme priv: for privileges and
svc: for sevices, thus priv:/sys/svc/net would allow talking to
svc:/net/tcp).
As per requirement above, privilege names are just identifiers and
privileges can be granted or verified just by passing the privilege URI as a
string. (Cf. [1] where all security principals, users and object groups,
must be registered with the RBAC database).
This scheme allows mapping other hierarchical spaces into the privilege
space, such as service names, file names, etc.
Example:
priv:/sys/svc/<service-name> .. privilege to talk to a service
priv:/sys/file/read/<path> .. privilege to read files under a certain directory
Mapping to Activities and Resources
===================================
If we tried to map this to the classic security matrix model, activities are
HelenOS tasks. One can either consider privilege names to be the resources
(they are formalized, but they don't have associated state or life cycle,
just identity). Or one can say that the resources are not explicit (they are
implicit in the code that checks privileges).
Privilege Hierarchy
===================
To make the system more flexible, we make the privilege names hierarchical.
Thus: priv:/a ⊇ priv:/a/<anything>
So if a task has
priv:/a
It also has
priv:/a/b
priv:/a/c
priv:/a/d/e
etc.
This means a privilege name always corresponds to a set of privileges that
can be further subdivided.
Example
-------
A service restarter can be given priv:/sys/svc. This allows it to talk to
any service. It also allows it to grant any of it children (services) the
privileges to talk to any particular subset of services. Each service could
have an associated manifest containing a set of services it is allowed to
talk to (more broadly a set of privileges). The restarter would be able to
grant these privileges since they are a subset of priv:/sys/svc.
Ex.: svc:/net/tcp could be granted priv:/sys/svc/inet
Privilege set representation
============================
Let the set of privileges defined by a single privilege privilege name/URI
be called a 'primitive' privilege set.
A 'simple' privilege set is a union of a finite number of primitive
privilege sets. It can be represented (and displayed) simply as a list of
privilege URIs.
Example:
{priv:/} = P, the set of all privileges
{priv:/sys/svc/inet,priv:/sys/svc/tcp}
It can be useful for a task to define the privileges of its children as a
modification of its own privilege set. There's a catch if it were to use set
operations, though:
Effect of set operations on simple privilege sets:
{priv:/a} ∪ {priv:/b} = {priv:/a,priv:/b}
{priv:/a} ∪ {priv:/a/b} = {priv:/a}
{priv:/a} ∩ {priv:/b} = {}
{priv:/a} ∩ {priv:/a/b} = {priv:/a/b}
{priv:/a} - {priv:/a/b} = not a simple privilege set
Set subtraction can produce a privilege set that is not simple. However, if
we restrict our selves to only removing privileges that are explicitly
listed in the list (or their prefixes), then simple privilege sets are
sufficient.
An equivalent, more compact, representation of a simple privilege set is a
(prefix) tree.
Extensions
==========
Complex privilege sets
----------------------
It is possible to define the complex privilege set as the closure of simple
privilege sets over set operations. It is possible to represent a complex
privilege set as a prefix tree where, in addition, each node has an
attribute whether it's 'negative' or not. A negative node /a/b means that
the privilege set contains all privileges under /a/b except those explicitly
listed (i.e. represented by the children of /a/b).
The problem with complex privilege sets is that they are more -- complex --
when displayed and they are more difficult for a human to interpret.
Therefore we believe sticking to simple privilege sets is preferable.
Getting additional privileges
-----------------------------
If a user task requires additional privileges (e.g. administrative action)
we may need to allow increasing its privileges in a secure way. One way is
to allow a parent to grant additional privileges (it owns) to its child
after it's been created.
This way the task could ask its session manager to grant these privileges.
The session manager (who has secure channel to the user) could
confirm/authorize the action with the user (such as with a simple
confirmation, password, etc.)
File access control
-------------------
For restricting access to files one can map the file system namespace into
the permission namespace. This allows restricting file access by location.
Alternatively, one could provide a DAC-like feature for files by adding an
ACL to the file. (E.g. permission needed to read the file, permission needed
to write the file).
Example:
File /cfg/userdb has ACL: {r,w:priv:/sys/access/top-secret}
On the other hand, the author believes that file access control should be
primarily based on location. A secure system should not allow DAC (i.e. a
situation where there is no way of stopping activities from gratuitously
granting access to resources they own to anyone else).
Application
-----------
Currently services are started by the init task (srv_start function). We
could add an extra argument to srv_start with the set of privileges granted
to the service.
Ex.:
init <- {priv:/sys/svc}
/srv/tcp <- {priv:/sys/svc/inet}
/srv/inet <- {priv:/sys/svc/category/iplink}
....
If a proper service manager is introduced, the privileges would be
listed in a service manifest.
Before a task is allowed to make use of a service foo, the system needs to
check that the task has priv:/sys/svc/foo. This check can be added in the
location service, in the service itself, or both. (This decision has some
implications.)
A session manager would grant a particular set of privileges to the leading
session task.
For applications, the application launcher script could further restrict the
privileges of an application compared to those of the user. For example,
restrict write access of a web browser just to the downloads and cache
directories.
An application that is decomposed into muliple tasks could further limit
privileges of its subtasks. For example. if a web browser runs tabs in
individual tasks, it could revoke their access to the file system completely
and act as a proxy, providing a limited set of operations.
Relationship to kernel permissions
----------------------------------
HelenOS kernel permissions can be easily mapped to privileges. If the
security manager recognized these mapped privileges as special, one could
manage kernel permissions through the privilege mechanism.
E.g.
priv:/sys/perm/perm
priv:/sys/perm/mem-manager
priv:/sys/perm/io-manager
priv:/sys/perm/irq-req
Similar and related mechanisms
==============================
HelenOS kernel associates a 32-bit or 64-bit bitmask of PERM_xxx permissions
with each task. A task having PERM_PERM can grant or revoke any permission
to any task.
To replace the traditional UNIX EUID==0 check, the Linux kernel, since
version 2.2 introduced so-called capabilities [2]. It associates capability
sets with each thread [2]. There are several sets: Permitted, Inheritable,
Effective. This allows a thread to enable and disable some capabilities at
will. This could be considered more of a safety than security mechanism,
since a compromised thread can easily activate the extra capabilities, if
needed.
Solaris introduced similar functionality with the Least Privilege for
Solaris project [3]. Individual privileges replace the EUID==0 check. They
are associated with processes and there are also different sets to allow
temporarily turning privileges off, handling inheritance and and extensive
framework for ensuring backward compatiblity with privilege non-aware
processes.
In all cases above the set of privileges is more or less a small (finite),
fixed set with no internal structure. Mapping other namespaces (e.g. file
namespace) into the privilege namespace would not be possible.
Further Work
============
It would be useful to be able to compare effectiveness of the privilege
control mechanism not only in protecting services, but also other objects
(such as files).
Specific file access control mechanisms would need to be more developed to
provide solid ground for comparison with alternative approaches.
It would be nice to come up with a capability-style privilege limitation
mechanism and compare the approaches, in terms of simplicity, ease of use
and effectiveness. It would be nice to consider different threats / attack
scenarios and test the behavior of the mechanisms. This document should
provide deailed enough description so that the mechanism can be tested
without having an actual implementation.
Acknowledgements
================
I would like to thank Štěpán Henek for his work on HelenOS security and
access rights. Even though I may disagree with individual decisions he made
in his implementation, I keep referring to his work as the most complete
attempt to tackle security in HelenOS to this date.
Conclusion
==========
The Hierarchical Privilege System provides a conceptually simple, yet
powerful and flexible, privilege control mechanism. It is easy to understand
and easy to integrate into the existing HelenOS system.
Adding H.P.S. to HelenOS introduces a new security mechanism, distinct from
the one already provided naturally via HelenOS IPC. We should try to devise
a IPC-based (i.e. capability-style) privilege control mechanism and compare
both approaches.
References
==========
1. Štepán Henek: Security containers and access rights in HelenOS, MFF UK, 2011
http://www.helenos.org/doc/theses/sh-thesis.pdf
2. Linux Kernel Capabilities man page
http://man7.org/linux/man-pages/man7/capabilities.7.html
3. Least Privilege for Solaris (2002/188), Casper Dik ([email protected]),
February 20, 2003
https://us-east.manta.joyent.com/jmc/public/opensolaris/ARChive/PSARC/2002/188/final.materials/priv.pdf
_______________________________________________
HelenOS-devel mailing list
[email protected]
http://lists.modry.cz/listinfo/helenos-devel