Skip to content
This repository has been archived by the owner on Apr 15, 2020. It is now read-only.

lxc/cgmanager

Repository files navigation

# Deprecated

Please note that the CGManager project has been deprecated in favor of
using the kernel's CGroup Namespace or lxcfs' simulated cgroupfs.

See https://s3hh.wordpress.com/2016/06/18/whither-cgmanager/ for details.

=== Intro ===

This is a motivation, description and explanation of the cgmanager
design.  The original design RFC was described here:

http://lwn.net/Articles/575672/
http://lwn.net/Articles/575683/

And much of it still holds (and is cut-pasted, though edited, here).

===  Cgmanager Design ===

One of the driving goals is to enable nested lxc as simply and safely as
possible.  If this project is a success, then a large chunk of code can
be removed from lxc.  I'm considering this project a part of the larger
lxc project, but given how central it is to systems management that
doesn't mean that I'll consider anyone else's needs as less important
than our own.

This document consists of two parts.  The first describes how I
intend the daemon (cgmanager) to be structured and how it will
enforce the safety requirements.  The second describes the commands 
which clients will be able to send to the manager.  The list of
controller keys which can be set is very incomplete at this point,
serving mainly to show the approach I was thinking of taking.

=== Summary ===

Each 'host' (identified by a separate instance of the linux kernel) has
exactly one running daemon to manage control groups.  This daemon
answers cgroup management requests over a dbus socket, located at
/sys/fs/cgroup/cgmanager/sock.  The /sys/fs/cgroup/cgmanager directory
can be bind-mounted into various containers, so that one daemon can support the
whole system.  (Bind-mounting the directory rather than the socket itself
allows a container to proceed if the cgmanager is restarted, creating a
new socket.)

Outline:
  . A single manager, cgmanager, is started on the host, very early
    during boot.  It has very few dependencies, and requires only
    /proc, /run, and /sys to be mounted, with /etc ro.  It mounts
    the cgroup hierarchies in a private namespace and set defaults for
    clone_children and use_hierarchy.  It opens a Unix socket at
    /sys/fs/cgroup/cgmanager/sock.
  . A client (requestor 'r') can make cgroup requests over
    /sys/fs/cgroup/cgmanager/sock using dbus calls.  Detailed privilege
    requirements for r are listed below.
  . The client request will pertain an existing or new cgroup A.  r's
    privilege over the cgroup must be checked.  r is said to have
    privilege over A if A is owned by r's uid, or if A's owner is mapped
    into r's user namespace, and r is root in that user namespace.
  . The client request may pertain a victim task v, which may be moved
    to a new cgroup.  In that case r's privilege over both the cgroup
    and v must be checked.  r is said to have privilege over v if v
    is mapped in r's pid namespace, v's uid is mapped into r's user ns,
    and r is root in its userns.  Or if r and v have the same uid
    and v is mapped in r's pid namespace.
  . r's credentials will be taken from socket's peercred, ensuring that
    pid and uid are translated.
  . A request to chown a cgroup requires a uid U and gid G.
  . If r is in the same pid and user namespaces as the cgmanager, then
    v, U and G can be passed as integer arguments over the D-Bus requests.
  . If r is not in the same namespaces as the cgmanager, then V, U and G
    must be passed as SCM_CREDENTIALs so that the cgmanager receives the
    translated global pid/uid/gid.  Since D-Bus does not support
    sending SCM_CREDENTIALs as part of a D-Bus message, the D-Bus arguments
    include a file descriptor.  The SCM_CREDENTIALs are sent over the
    file descriptor after the D-Bus transaction completes, and the final
    result is sent over the same file descriptor.
  . It is desirable that all transactions can be accomplished with simple
    D-Bus transactions.  Therefore a cgroup manager proxy (cgproxy) is
    provided.  This will move /sys/fs/cgroup/cgmanager to
    /sys/fs/cgroup/cgmanager.lower, then serve as a proxy translating
    D-Bus requests received on /sys/fs/cgroup/cgmanager/sock into
    SCM-enhanced D-Bus requests on /sys/fs/cgmanager/cgmanager.lower/sock.
  . In plain D-Bus transactions, the requestor r's credentials are read
    from the socket.
  . In SCM-enhanced D-Bus transactions, the proxy p's credentials are read
    from the socket.  The requestor's credential is sent as an SCM_CREDENTIAL.

Privilege requirements by action:
    * Requestor of an action (r) over a socket may only make
      changes to cgroups over which it has privilege.
    * Requestors may be limited to a certain #/depth of cgroups
      (to limit memory usage).  This is not yet implemented.
    * Cgroup hierarchy is responsible for resource limits.  To this end,
      a request to chown cgroup A to uid U will only chown the directory
      itself (allowing child cgroup creation) and the tasks and cgroup.procs
      file.
    * A requestor must either be uid 0 in its userns with victim mapped
      ito its userns, or the same uid and in same/ancestor pidns as the
      victim
    * If r requests creation of cgroup '/x', /x will be interpreted
      as relative to r's cgroup.  r cannot make changes to cgroups not
      under its own current cgroup.
    * Root in the cgmanager's pid namespace may 'escape' to the cgmanager's
      cgroup with a special MovePidAbs command.
    * A proxy may move a task over which it has privilege to the proxy's
      own cgroup.  This allows the proxy to mimic the cgmanager's special
      root-may-escape semantics in its own container.
    * If r requests creation of cgroup '/x', it must have write access
      to its own cgroup.
    * if r requests setting a limit under /x, then
      . either r must be root in its own userns, and UID(/x) be mapped
        into its userns, or else UID(r) == UID(/x)
      . /x must not be / (not strictly necessary, all users know to
        ensure an extra cgroup layer above '/')
      . setns(UIDNS(r)) would not work, due to in-kernel capable() checks
        which won't be satisfied.  Therefore we'll need to do privilege
        checks ourselves, then perform the write as the host root user.
        (see devices.allow/deny).  Further we need to support older kernels
        which don't support setns for pid.

Types of requests:
  * r requests creating cgroup A'/A
    . lmctfy/cli/commands/create.cc
    . Verify that UID(r) mapped to 0 in r's userns
    . R=cgroup_of(r)
    . Verify that UID(R) is mapped into r's userns
    . Create R/A'/A
    . chown R/A'/A to UID(r)
  * r requests to move task x to cgroup A.
    . lmctfy/cli/commands/enter.cc
    . r must send PID(x) as ancillary message
    . Verify that UID(r) mapped to 0 in r's userns, and UID(x) is mapped into
      that userns
      (is it safe to allow if UID(x) == UID(r))?
    . R=cgroup_of(r)
    . Verify that R/A is owned by UID(r) or UID(x)?  (not sure that's needed)
    . echo PID(x) >> /R/A/tasks
  * r requests chown of cgroup A to uid X
    . X is passed in ancillary message
      * ensures it is valid in r's userns
      * maps the userid to host for us
    . Verify that UID(r) mapped to 0 in r's userns
    . R=cgroup_of(r)
    . Chown R/A to X
  * r requests cgroup A's 'property=value'
    . Verify that either
      * A != ''
      * UID(r) == 0 on host
      In other words, r in a userns may not set root cgroup settings.
    . Verify that UID(r) mapped to 0 in r's userns
    . R=cgroup_of(r)
    . Set property=value for R/A
      * Expect kernel to guarantee hierarchical constraints
  * r requests deletion of cgroup A
    . lmctfy/cli/commands/destroy.cc (without -f)
    . same requirements as setting 'property=value'
  * r requests purge of cgroup A
    . lmctfy/cli/commands/destroy.cc (with -f)
    . same requirements as setting 'property=value'

Long-term we will want the cgroup manager to become more intelligent -
to place its own limits on clients, to address cpu and device hotplug,
etc.  Since we will not be doing that in the first prototype, the daemon
will not keep any state about the clients.

===  Another look at the safety of requests  ===

Notes:

1. In a plain D-Bus call, the proxy is the requestor.
2. If a client does an SCM call to the cgmanager socket,
   then the proxy is the requestor.
3. In any call over a proxy, the proxy won't be able to
   make changes outside its own cgroups.  If it misbehaves,
   damage is contained so it only damages itself..
4. Chained proxying is not supported.  If a proxy gets a
   request where proxy != requestor, the call is rejected.
5. The identity of the proxy (which may be the requestor) cannot
   be forged;  it is taken from the socket credential.  A more
   privileged user must not allow a less privileged task to
   have access to the opened DBus socket, as the credential will
   be that at the time of connect().

On newer kernels, cgmanager can tell whether a proxy or requestor
is in the same namespace as itself.  On older kernels, it cannot.

 . for Create, this is ok.  We have the proxy's real pid and
   can constrain create under its cgroup.
 . for getPidCgroup, we can ensure that only results under the
   parent's cgroup are returned.
   we can NOT ensure that results will make sense for plain
   DBus calls, as we cannot guarantee that proxy is in the same
   ns as cgmanager.  However, this is not unsafe.
   When we can and do detect that p is in a different pid namespace,
   then we reject the call, because the result cannot be sensible.
 . for chmod: We constrain under proxy's cgroup, so this is safe.
 . for chown: on older kernel we cannot guarantee that the
   uid/gid make sense on the host;  However
     . root on the root host - no translation necessary
     . root in a non-user-ns container: no translation necessary
     . root in a unprivileged container: won't have privilege
       to do any chown without going through a proxy.
   Therefore rejecting calls from another namespace is not
   necessary.  The worst it will do is to give -EPERM for calls
   which for root in a unprivileged container otherwise would be
   allowed to do.
 . movepid:
     . root on root host - fine
     . root in a non-user-ns container: we can only ensure that
       the victim be under the proxy's cgroup.  If that is the
       case, then root (which is also root on the host) is allowed
       to move the task.
   When we can and do detect a different pid namespace, then we
   reject the call because the results cannot make sense.
 . MovePidAbs: On an older kernel, or if the task is in a different
   namespace, then this requires a proxy.  The cgmanager will only
   allow escaping up to the level of the proxy.
     . root on root host - allowed to escape.
     . root in a non-user-ns container: allowed to escape up to the
       proxy's level.  If the host misconfigures the container so
       that the host's proxy is in the container, then root can
       escape completely.
     . if root tries to mimick a proxy, then it can only escape to
       the proxy's level - it's own.  So it cannot escape at all.