mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Josef Bacik <josef@toxicpanda.com>
To: Christian Brauner <brauner@kernel.org>,
	 James Bottomley <James.Bottomley@HansenPartnership.com>,
	 Andrew Halaney <andrew@amutable.com>,
	Jeremy Bingham <jbingham@gmail.com>,
	 Tahera Fahimi <taherafahimi@linux.microsoft.com>,
	 Serge Hallyn <sergeh@kernel.org>
Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	 Josef Bacik <josef@toxicpanda.com>
Subject: [PATCH RFC v3 02/90] Documentation: add examples for cells
Date: Thu, 05 Nov 2026 14:56:53 +0000	[thread overview]
Message-ID: <20261105-b4-cells-v3-2-bc9095937a84@toxicpanda.com> (raw)
In-Reply-To: <20261105-b4-cells-v3-0-bc9095937a84@toxicpanda.com>

The design document explains what a cell is and why. Add a second page for
people who write a builder. It walks through the common cases: running a
single program, running a service from an image, devices, a network, an
init system, starting another task in a running cell, and containers that
belong together.

A cell only has what it was given, and it sets up nothing by itself.
Software that runs fine in a container often fails at first for that
reason. The most common cause is capabilities, because a cell has none
unless the builder names them. So the page lists a sensible starting set,
says what each capability is for, and shows what the failure looks like
when one is missing.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
 Documentation/userspace-api/cells-examples.rst | 551 +++++++++++++++++++++++++
 Documentation/userspace-api/index.rst          |   1 +
 2 files changed, 552 insertions(+)

diff --git a/Documentation/userspace-api/cells-examples.rst b/Documentation/userspace-api/cells-examples.rst
new file mode 100644
index 000000000000..2c0c64dca906
--- /dev/null
+++ b/Documentation/userspace-api/cells-examples.rst
@@ -0,0 +1,551 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===========
+Using cells
+===========
+
+Documentation/userspace-api/cells.rst says what a cell is and why it is the way it is.
+This page is for whoever writes a builder: a container runtime, a sandbox, a test
+harness.  It goes through what a builder commonly wants, and through what commonly goes
+wrong the first time.
+
+One rule explains most of it.  **A cell has what it was given, and it sets up nothing by
+itself.**  It mounts nothing, it configures no network, and being its root comes with no
+capability.  What a container today does for itself on its way up, the builder of a cell
+does from the outside, before the first task runs.
+
+Error handling is left out of the examples.  There are no wrappers for the system calls
+in the C library yet, so ``cell_create()`` and the others stand for ``syscall(2)``.
+
+.. contents:: :local:
+
+
+A program and nothing else
+==========================
+
+The smallest cell takes no privilege.  It runs one program as the user who builds it,
+which can see the tree it was given and reach nothing else: no network, no other task,
+no capability.
+
+.. code-block:: c
+
+   int cfd, tree, ns, pidfd, status;
+
+   cfd = cell_create(CELL_SELF, 0);
+
+   tree = open_tree(AT_FDCWD, "/home/me/sandbox", OPEN_TREE_CLONE);
+   ns = open_tree(tree, "", AT_EMPTY_PATH | AT_RECURSIVE | OPEN_TREE_NAMESPACE);
+   cell_configure(cfd, CELL_SET_FD, "root", NULL, ns);
+   cell_configure(cfd, CELL_SET_FD, "budget", NULL, cgroup_dirfd);
+   cell_configure(cfd, CELL_CMD_SEAL, NULL, NULL, 0);
+
+   pidfd = pidfd_open(cfd, PIDFD_EMPTY);
+   pidfd_configure(pidfd, PIDFD_SET_STRING, "exec", "/bin/convert", 0);
+   pidfd_configure(pidfd, PIDFD_SET_BINARY, "argv", "convert\0-\0png:-", 16);
+   pidfd_configure(pidfd, PIDFD_SET_FD, "0", NULL, in_fd);
+   pidfd_configure(pidfd, PIDFD_SET_FD, "1", NULL, out_fd);
+   pidfd_configure(pidfd, PIDFD_CMD_START, NULL, NULL, 0);
+
+   read(cfd, &status, sizeof(status));
+
+The ``ids`` slot was left empty, so this is a one-id cell.  A builder without privilege
+does the mounting in a user and mount namespace of its own, since ``open_tree()`` with
+``OPEN_TREE_CLONE`` takes ``CAP_SYS_ADMIN`` over the mount namespace of the caller.  The
+cgroup has to be one that it owns and could move itself into, such as one that
+``systemd --user`` has delegated to it.
+
+The new task has the three files it was given and no others.  There is nothing to close
+and nothing to forget.
+
+
+Which capabilities
+==================
+
+.. note::
+
+   If software that runs fine in a container fails in a cell with ``EPERM``, this is the
+   first place to look.
+
+A container runtime starts from everything and takes away.  Docker leaves fourteen
+capabilities by default.  A cell starts from nothing, and **uid 0 of a cell is an id like
+any other** until the builder says otherwise:
+
+.. code-block:: c
+
+   __u64 caps = 1ULL << CAP_CHOWN | 1ULL << CAP_DAC_OVERRIDE | 1ULL << CAP_FOWNER |
+                1ULL << CAP_SETUID | 1ULL << CAP_SETGID | 1ULL << CAP_KILL;
+
+   cell_configure(cfd, CELL_SET_BINARY, "caps", &caps, sizeof(caps));
+
+Those six are what it takes to be root over the ids of the cell, and they are a good
+place to start for anything that expects to be started as root: an init system, a
+package manager, a daemon that drops privileges.  All of them are about the cell's own
+ids, files and tasks.  None of them reaches anything else.
+
+======================  ==========================================================
+Capability              What stops working without it
+======================  ==========================================================
+``CAP_SETUID``,         Becoming another user: ``setuid()``, ``setgid()``,
+``CAP_SETGID``          ``setgroups()``.  A daemon that drops privileges, ``su``
+                        and ``runuser`` run by root, ``User=`` in a unit.
+``CAP_CHOWN``           Giving a file to another id: unpacking an archive with its
+                        owners, ``install -o``, ``systemd-tmpfiles``, delegating a
+                        cgroup.
+``CAP_FOWNER``          ``chmod()``, ``utimes()`` and the like on a file that
+                        belongs to another id, including one that was just given
+                        away.
+``CAP_DAC_OVERRIDE``    Reading, writing and creating where the mode says no to
+                        uid 0, such as in the home or the state directory of
+                        another id.
+``CAP_KILL``            Signalling a task that runs under another id.  An init
+                        cannot stop such a service.
+======================  ==========================================================
+
+A program that runs as one id from start to end needs none of them.  That is the common
+case for a single service, and the best one: start it as the id it is to run as, with the
+``cred`` key of ``pidfd_configure()``, and leave the slot empty.
+
+.. code-block:: c
+
+   struct pidfd_cred cred = { .uid = 33, .gid = 33 };      /* ids of the cell */
+
+   pidfd_configure(pidfd, PIDFD_SET_BINARY, "cred", &cred, sizeof(cred));
+
+Others that come up:
+
+``CAP_NET_ADMIN``, ``CAP_NET_RAW``
+  Good for nothing by themselves.  They say who in the cell may use the rights over its
+  network that the builder has named, see `A network`_.  Listening on port 80 and ``ping``
+  do not take a capability at all.
+
+``CAP_SYS_CHROOT``
+  For ``chroot()``, which some daemons use on themselves.
+
+``CAP_SYS_PTRACE``
+  For debugging, and reading ``/proc/<pid>`` of, a task that runs under another id.
+
+``CAP_SYS_ADMIN``
+  Over a cell this is far less than the name suggests.  It does not let a cell mount or
+  make a namespace.  It is for ``sethostname()`` and a few more.  Give it only to what
+  asks for it, see `An init system`_.
+
+``CAP_SETPCAP``
+  Not needed to shrink the bounding set, which in a cell takes nothing.
+
+``CAP_SETFCAP``
+  Refused.  Nothing that a cell writes imparts privilege.
+
+``CAP_MKNOD``, ``CAP_SYS_MODULE``, ``CAP_SYS_RAWIO``, ``CAP_SYS_TIME`` ...
+  Can be named and do nothing.  What they guard asks for them over the user namespace of
+  the host.
+
+What it looks like when one is missing:
+
+=====================================================  ========================
+Seen                                                   Wants
+=====================================================  ========================
+``setgroups: Operation not permitted``,                ``CAP_SETGID``,
+systemd: ``status=216/GROUP``                          ``CAP_SETUID``
+``chown: Operation not permitted``,                    ``CAP_CHOWN``
+``fchownat() of ... failed``
+``fchmod() of ... failed: Operation not permitted``    ``CAP_FOWNER``
+right after a ``chown``
+``Permission denied`` for root                         ``CAP_DAC_OVERRIDE``
+systemd: ``Failed to kill main process``               ``CAP_KILL``
+systemd: ``status=217/USER`` for a unit with           ``CAP_SYS_ADMIN``, or
+``PrivateTmp=`` and the like                           drop the options
+=====================================================  ========================
+
+``grep Cap /proc/self/status`` in the cell and ``cell_info()`` with ``CELL_INFO_CAPS``
+outside of it tell what there is.
+
+The slot is for the whole cell, and it is an upper limit.  A task can let go of what it
+does not need, for itself and for what it starts, and nothing brings it back.
+
+
+A service from an image
+=======================
+
+An image is stored with on-disk ids from 0 up.  A block of ids and an idmapped mount make
+those the ids of the cell, for this cell and for as long as it lives.  Several cells can
+use one image at a time.  None of them sees the ids of another, and nothing is written to
+the image that says which cell wrote it.
+
+.. code-block:: c
+
+   unsigned int ids = 65536;
+   struct mount_attr attr = { .attr_set = MOUNT_ATTR_IDMAP };
+
+   cfd = cell_create(CELL_SELF, 0);
+   cell_configure(cfd, CELL_SET_BINARY, "ids", &ids, sizeof(ids));
+
+   attr.userns_fd = cfd;           /* a cellfd stands for the ids of the cell */
+   tree = open_tree_attr(AT_FDCWD, "/images/web", OPEN_TREE_CLONE, &attr, sizeof(attr));
+
+The ``ids`` slot has to be filled before a mount can be mapped for the cell.  Every
+writable mount of a filesystem that can be idmapped has to be, or the seal fails.
+
+For a read-only image with a writable layer, idmap the overlay and leave the layers as
+they are.
+
+What else goes into the tree is mounted on it before it becomes a mount namespace.  A
+filesystem for the cell to write to is mapped in the same way:
+
+.. code-block:: c
+
+   fs = fsopen("tmpfs", 0);
+   fsconfig(fs, FSCONFIG_SET_STRING, "mode", "1777", 0);
+   fsconfig(fs, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
+   mnt = fsmount(fs, 0, MOUNT_ATTR_NOSUID | MOUNT_ATTR_NODEV);
+   mount_setattr(mnt, "", AT_EMPTY_PATH, &attr, sizeof(attr));
+   move_mount(mnt, "", tree, "tmp", MOVE_MOUNT_F_EMPTY_PATH);
+
+``/proc`` is one for the pid namespace of the cell, which the builder gets from the cell
+while it is being built:
+
+.. code-block:: c
+
+   pidns = cell_open(cfd, CELL_OPEN_SLOT, "pids", NULL, 0);
+   fs = fsopen("proc", 0);
+   fsconfig(fs, FSCONFIG_SET_FD, "pidns", NULL, pidns);
+   fsconfig(fs, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
+   mnt = fsmount(fs, 0, MOUNT_ATTR_NOSUID | MOUNT_ATTR_NODEV | MOUNT_ATTR_NOEXEC);
+   move_mount(mnt, "", tree, "proc", MOVE_MOUNT_F_EMPTY_PATH);
+
+.. warning::
+
+   A procfs that is mounted the plain way shows the tasks of the builder's pid namespace,
+   and a sysfs the network devices of the network namespace it was mounted from.  See
+   `An init system`_ for sysfs.
+
+
+Devices
+=======
+
+A device is its number.  The builder makes a node in a filesystem that is mapped for the
+cell, and who in the cell may open it is a matter of the owner, group and mode of that
+node, in the ids of the cell.
+
+.. code-block:: c
+
+   dev = fsmount(fs, 0, MOUNT_ATTR_NOSUID);        /* a tmpfs, not yet mapped */
+   mknodat(dev, "null", S_IFCHR | 0666, makedev(1, 3));
+   fchmodat(dev, "null", 0666, 0);                 /* mknod() goes by the umask */
+   mknodat(dev, "kvm", S_IFCHR | 0660, makedev(10, 232));
+   fchownat(dev, "kvm", 0, 36, 0);                 /* group kvm of the image */
+   mount_setattr(dev, "", AT_EMPTY_PATH, &attr, sizeof(attr));
+   move_mount(dev, "", tree, "dev", MOVE_MOUNT_F_EMPTY_PATH);
+
+Binding the node of the host's ``/dev`` does not do it.  That one belongs to ids of the
+host, which nobody in a cell has, its root included, so what is left is what the mode has
+for everybody.  A device can also be handed over open, as a file of a new task.
+
+A cell makes no device nodes, whatever its capabilities.
+
+
+A network
+=========
+
+What is in the ``net`` slot decides, see "The network" in
+Documentation/userspace-api/cells.rst.  Most builders want ``"new"``: a network namespace
+that is made for the cell, belongs to the builder, and is set up by the builder.
+
+.. code-block:: c
+
+   cell_configure(cfd, CELL_SET_STRING, "net", "new", 0);
+   net = cell_open(cfd, CELL_OPEN_SLOT, "net", NULL, 0);
+
+   /*
+    * Move one end of a veth pair there, by IFLA_NET_NS_FD.  Then, in a helper that
+    * has called setns(net, CLONE_NEWNET): addresses, routes, and the links up.
+    */
+
+Nothing is done for the builder.  Until it brings ``lo`` up, connecting to 127.0.0.1 fails
+with ``ENETUNREACH``.
+
+No module is loaded because a cell asked for it.  A protocol, a tunnel or a packet filter
+that the cell is to use and that is built as a module has to be loaded on the host
+beforehand, or ``socket()`` fails with ``EAFNOSUPPORT`` in the cell where it works outside.
+
+What a container commonly has a capability for is a setting of the namespace here, which
+the helper makes while it is in there:
+
+.. code-block:: sh
+
+   sysctl -w net.ipv4.ip_unprivileged_port_start=80     # instead of CAP_NET_BIND_SERVICE
+   sysctl -w net.ipv4.ping_group_range="0 4294967294"   # instead of CAP_NET_RAW, for ping
+
+.. note::
+
+   The range is in the gids of whoever writes it, and the gids of a cell are those of its
+   block, from 2147483648 up.  The customary ``0 2147483647`` ends one short of the first
+   of them, and ``ping`` does not work.  Read from inside of the cell the setting
+   shows as ``65534 65534`` either way.
+
+The builder can go back in at any time, so the network can change while the cell runs:
+devices come and go, addresses and routes change.  Once the cell is sealed the way in is
+the file that ``cell_open()`` returned, or ``/proc/<pid>/ns/net`` of any of its tasks.
+
+**CNI plugins** work as they are.  They take a path for the namespace, and either will do:
+
+.. code-block:: sh
+
+   # a namespace that was made and pinned beforehand, as container runtimes do
+   ip netns add cni-1234
+   CNI_COMMAND=ADD CNI_NETNS=/run/netns/cni-1234 CNI_IFNAME=eth0 ... ptp < conf
+   # then: cell_configure(cfd, CELL_SET_FD, "net", NULL, open("/run/netns/cni-1234"))
+
+   # or the one of the cell, "new", by way of the file that cell_open() returned
+   CNI_COMMAND=ADD CNI_NETNS=/proc/$builder/fd/$net CNI_IFNAME=eth0 ... ptp < conf
+
+The second needs no pin, so nothing is left behind if the runtime goes away.  **Keep that
+file.**  ``cell_open()`` returns it only while the cell is being built, and a cell that dies
+takes its network along.  With the file the network comes down in the customary order,
+tasks first:
+
+.. code-block:: c
+
+   cell_kill(cfd, 0);
+   poll(&(struct pollfd){ .fd = cfd, .events = POLLIN }, 1, -1);
+   /* CNI_COMMAND=DEL CNI_NETNS=/proc/$builder/fd/$net ...: all there, nobody in it */
+   close(net);
+
+Without it the namespace is gone by the time anybody notices that the init of the cell has
+exited, and a plugin can undo only what it finds without.  ``ptp`` then still succeeds and
+gives the address back, and the veth pair went with the namespace, but the masquerading
+rules that it made on the host stay behind.
+
+A rule that goes by a uid or a gid means the ids of whoever owns the network namespace, and
+that is the builder.  To it, uid 1337 of the cell is not 1337.  ``cell_info()`` tells what
+it is, from when the ``ids`` slot is filled:
+
+.. code-block:: c
+
+   struct cell_info info = { .mask = CELL_INFO_IDS_FIRST };
+
+   cell_info(cfd, &info, sizeof(info), 0);
+   /* nft add rule ... meta skuid (info.ids_first + 1337) ...
+    * ip rule add uidrange ..., iptables -m owner --uid-owner ... */
+
+The bit is not set in ``mask`` on return if the caller has no name for those ids, as in a
+user namespace that maps nothing but the caller.  To such a builder everybody in the cell
+is the overflow uid, and it cannot tell them apart in a rule.
+
+What a dataplane hangs on the outside applies to a cell as to any container, and the cell
+can neither see nor undo it: filters on the host's end of a veth or netkit pair, and BPF
+programs on a cgroup.  One that is attached to the budget holds for every cgroup that the
+cell makes below.
+
+A device of the host's can be moved in as well, a NIC for one.  It goes back to the host
+when the namespace goes.  With ``"new"`` that is when the cell is dead, provided that
+nobody else holds the namespace: no file, no bind mount.  A pinned namespace keeps what was
+moved there until it is unpinned, whatever has become of the cell.
+
+A cell that is to change something about its network is given the right to, by name.  The
+namespace stays the builder's: a CNI plugin sets it up as ever, and the cell does what it
+was given the right to on top of that.
+
+.. code-block:: c
+
+   /* It adds rules to the packet filter, and nothing else. */
+   __u64 rights = CELL_NET_FILTER;
+   __u64 caps = 1ULL << CAP_NET_ADMIN;
+
+   cell_configure(cfd, CELL_SET_STRING, "net", "new", 0);        /* first */
+   cell_configure(cfd, CELL_SET_BINARY, "net-rights", &rights, sizeof(rights));
+   cell_configure(cfd, CELL_SET_BINARY, "caps", &caps, sizeof(caps));
+
+It takes both.  The right says what the cell may do to its network, and the capability says
+who in the cell may do it: ``CAP_NET_RAW`` for ``CELL_NET_RAW``, ``CAP_NET_ADMIN`` for all
+others.  What sets up the rules can let go of the capability before it runs the rest.
+
+===============================================  ====================================
+What runs in the cell                            Rights
+===============================================  ====================================
+``iptables``, ``nft``                            ``FILTER``; ``iptables-legacy`` also
+                                                 ``RAW``, for the socket it uses
+a VPN client                                     ``TUN``, ``LINK``, ``ADDR``, ``ROUTE``
+a VMM with a TAP device and a bridge             ``TUN``, ``LINK``, ``LINK_NEW``
+``tcpdump``, ``ping`` the old way                ``RAW``
+a transparent proxy                              ``SOCKOPT``, with rules and routes that
+                                                 the builder can just as well make
+a DHCP client                                    ``RAW``, ``ADDR``, ``ROUTE``
+what runs the network of the host, given that    what it does there, and the builder
+for a ``net`` slot                               needs to be able to do that itself
+===============================================  ====================================
+
+A request that takes a right which the cell does not have fails as it does for anybody
+without the capability, with ``EPERM``, or ``EACCES`` below ``/proc/sys/net``.
+
+A cell that was given the network namespace of the host can use the network of the host, and
+listen on a port from ``ip_unprivileged_port_start`` up.
+
+A program that wants a TAP device, a VMM for one, needs ``/dev/net/tun`` in its tree and
+one of these:
+
+* ``CELL_NET_TUN``, and it makes the device, or attaches to one that the builder has made;
+* the device already open, as a file of the new task.
+
+Naming an owner for the device does not do it.  The owner and the group of a tun device
+count in the cell that named them and in no other, so what the builder sets with
+``TUNSETOWNER`` means nothing in the cell, whichever id it is.  Within the cell they work as
+ever: a task that has the right can make a device for another that has not.
+
+
+An init system
+==============
+
+An init expects to find a system, or to put one together.  In a cell it cannot do the
+latter, so the builder does.  This is what systemd 255 takes to boot to
+``multi-user.target``, run units under other users with limits of their own, and shut
+down in order.
+
+Mounted on the tree, besides the root itself:
+
+=====================  ===============================================================
+Where                  What
+=====================  ===============================================================
+``/proc``              procfs for the pid namespace of the cell
+``/sys``               sysfs, read-only, see below
+``/sys/fs/cgroup``     the budget: ``open_tree(AT_FDCWD, budget, OPEN_TREE_CLONE)``
+``/dev``               a tmpfs that is mapped for the cell, with ``null``, ``zero``,
+                       ``full``, ``random``, ``urandom``, ``tty`` and ``console``, and
+                       the links ``fd``, ``stdin``, ``stdout``, ``stderr``, ``ptmx``
+``/dev/pts``           devpts with ``newinstance,ptmxmode=0666``
+``/dev/shm``,          a tmpfs each, mapped for the cell
+``/run``, ``/tmp``
+``/run/lock``          likewise.  Debian and Ubuntu do not boot without it
+=====================  ===============================================================
+
+systemd leaves alone what is mounted already.  What is not, it tries to mount, and gives
+up with "Failed to mount API filesystems".
+
+A sysfs shows the network devices of the network namespace that its mounter was in.  For
+``"new"`` the builder mounts it from in there:
+
+.. code-block:: c
+
+   old = open("/proc/self/ns/net", O_RDONLY);
+   setns(net, CLONE_NEWNET);
+   fs = fsopen("sysfs", 0);
+   fsconfig(fs, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
+   mnt = fsmount(fs, 0, MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID | MOUNT_ATTR_NODEV |
+                        MOUNT_ATTR_NOEXEC);
+   setns(old, CLONE_NEWNET);
+
+Without a network, ``unshare(CLONE_NEWNET)`` in place of the first ``setns()`` makes for an
+empty one.
+
+The environment says that this is a container, which keeps systemd from what is only for
+real hardware:
+
+.. code-block:: c
+
+   pidfd_configure(pidfd, PIDFD_SET_STRING, "exec", "/usr/lib/systemd/systemd", 0);
+   pidfd_configure(pidfd, PIDFD_SET_BINARY, "argv", "systemd", 8);
+   pidfd_configure(pidfd, PIDFD_SET_BINARY, "envp", "container=cell", 15);
+
+The six capabilities of `Which capabilities`_ are enough.  systemd goes by the bounding set
+of pid 1 to tell what is worth trying, so it skips debugfs, tracefs and the mqueue
+filesystem by itself.
+
+It manages the cgroups below the budget as it does anywhere: it moves itself to
+``init.scope``, enables controllers, and makes a cgroup with limits for every unit.  The
+limits of the budget itself are not the cell's to change.
+
+When pid 1 exits, by ``SuccessAction=exit`` of a unit for one, its wait status is what
+``read()`` on the cellfd returns.
+
+.. note::
+
+   ``PrivateTmp=``, ``ProtectSystem=``, ``ProtectHome=``, ``PrivateDevices=`` and the other
+   options that are done with a mount namespace do not work, since a cell makes no
+   namespaces.  What systemd makes of that depends on ``CAP_SYS_ADMIN``.  With it, it tries,
+   fails, and **starts the unit without them**, which it logs at debug level only.  Without
+   it, it tries a user namespace first, and the unit fails with ``status=217/USER``.
+   ``NoNewPrivileges=``, ``SystemCallFilter=`` and what else is done with seccomp work.
+   The cell is the sandbox.  What needs to be kept apart goes into cells of its own.
+
+
+Another task in a running cell
+==============================
+
+There is no entering a cell.  What takes the place of ``nsenter`` and ``docker exec`` is
+another spawn, by whoever holds the cellfd:
+
+.. code-block:: c
+
+   pidfd = pidfd_open(cfd, PIDFD_EMPTY);
+   pidfd_configure(pidfd, PIDFD_SET_STRING, "exec", "/bin/sh", 0);
+   pidfd_configure(pidfd, PIDFD_SET_FD, "0", NULL, pty);
+   pidfd_configure(pidfd, PIDFD_SET_FD, "1", NULL, pty);
+   pidfd_configure(pidfd, PIDFD_SET_FD, "2", NULL, pty);
+   ret = pidfd_configure(pidfd, PIDFD_CMD_START, NULL, NULL, 0);
+
+The task starts in the budget.  If the init of the cell manages cgroups that fails with
+``EBUSY``: it has enabled controllers on the budget, and a cgroup that passes a controller
+on holds no task.  The builder then says where, and the cgroup that the init is in will
+always do.  A start can be tried only once, so this takes a new pidfd:
+
+.. code-block:: c
+
+   /* "0::/cell0/init.scope", as the host sees it */
+   snprintf(path, sizeof(path), "/proc/%d/cgroup", init_pid);
+   ...
+   cg = open("/sys/fs/cgroup/cell0/init.scope", O_RDONLY | O_DIRECTORY);
+   pidfd_configure(pidfd, PIDFD_SET_FD, "cgroup", NULL, cg);
+
+It has to be the budget or lie below it.  It takes no permission on the cgroup, which
+belongs to ids that nobody outside of the cell has.  Should the cell have removed it in
+the meantime the start fails, with ``ENODEV``, and the builder looks again.
+
+The new task has the capabilities in the ``caps`` slot, as every task that is started in
+the cell.  Its parent is the spawner, but nobody has to wait for it: the pidfd tells how it
+ended.
+
+
+Containers that belong together
+===============================
+
+Containers that trust each other, as those of a pod do, are tasks of one cell.  They share
+its pid namespace and network, can signal each other and pass each other files.  The
+builder makes a cgroup below the budget for each, sets its limits, and starts each where
+it belongs:
+
+.. code-block:: c
+
+   mkdirat(budget, "pause", 0755);
+   mkdirat(budget, "app", 0755);
+   mkdirat(budget, "sidecar", 0755);
+   /* the first task is the init of the cell: when it exits, so does the cell */
+   start(cfd, "/pause", openat(budget, "pause", O_RDONLY | O_DIRECTORY));
+   write_at(budget, "cgroup.subtree_control", "+memory +pids");
+   write_at(budget, "app/memory.max", "1G");
+   write_at(budget, "sidecar/memory.max", "128M");
+   start(cfd, "/app/run", openat(budget, "app", O_RDONLY | O_DIRECTORY));
+   start(cfd, "/sidecar/run", openat(budget, "sidecar", O_RDONLY | O_DIRECTORY));
+
+The seal hands the budget to the ids of the cell, so from then on this takes a builder that
+is not held back by the owner of a file.  They all start at the root of the cell for now.
+What does not trust each other goes into cells of its own.
+
+
+The end
+=======
+
+A cell is dead when its first task has exited and everything else in it is gone.  The
+cellfd then polls readable, and ``read()`` returns the wait status of the first task.
+
+.. code-block:: c
+
+   struct pollfd p = { .fd = cfd, .events = POLLIN };
+
+   if (!poll(&p, 1, 30000))
+           cell_kill(cfd, 0);      /* everything in it, wherever it has got to */
+   read(cfd, &status, sizeof(status));
+
+``cell_kill()`` does not wait, the ``read()`` does.  Closing the last cellfd kills the cell
+too, so a builder that dies takes its cells along.
+
+The cgroups that the cell made below the budget are removed with it, and the budget is
+given back to whoever owned it.
diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst
index b9d2e4bef24c..f11307aa8970 100644
--- a/Documentation/userspace-api/index.rst
+++ b/Documentation/userspace-api/index.rst
@@ -18,6 +18,7 @@ System calls
 
    unshare
    cells
+   cells-examples
    futex2
    ebpf/index
    ioctl/index

-- 
2.55.0


  parent reply	other threads:[~2026-11-05 14:58 UTC|newest]

Thread overview: 93+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-05 14:56 [PATCH RFC v3 00/90] cells: a container as one kernel object Josef Bacik
2026-11-05 14:56 ` [PATCH RFC v3 01/90] Documentation: add a design document for cells Josef Bacik
2026-11-05 17:06   ` Serge E. Hallyn
2026-11-05 18:23     ` Josef Bacik
2026-11-05 14:56 ` Josef Bacik [this message]
2026-11-05 14:56 ` [PATCH RFC v3 03/90] cells: reserve a window of ids Josef Bacik
2026-11-05 14:56 ` [PATCH RFC v3 04/90] user_namespace: let a cell own a user namespace Josef Bacik
2026-11-05 14:56 ` [PATCH RFC v3 05/90] user_namespace: refuse setns() into a cell Josef Bacik
2026-11-05 14:56 ` [PATCH RFC v3 06/90] capability: give the owner no say over " Josef Bacik
2026-11-05 14:56 ` [PATCH RFC v3 07/90] ucount: count the id blocks a user holds Josef Bacik
2026-11-05 14:56 ` [PATCH RFC v3 08/90] user_namespace: let the kernel map the ids of a cell Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 09/90] user_namespace: keep legacy maps out of the window Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 10/90] user: add uid_range_in_use() Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 11/90] keys: add key_user_range_in_use() Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 12/90] fs: add kuid_has_fsmapping() and kgid_has_fsmapping() Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 13/90] fs: never store an id that was lent to a cell Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 14/90] fs: take no id of a cell for a mount option Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 15/90] fs: let an idmapping pin the ids of a cell Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 16/90] cgroup: let a cgroup become the budget " Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 17/90] cgroup: add cgroup_cell_chown() Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 18/90] cgroup: give the budget of a cell back Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 19/90] cgroup: add cgroup_cell_kill() Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 20/90] cgroup: root a cgroup namespace at a given cgroup Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 21/90] fs: seal a mount namespace for a cell Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 22/90] nsfs: make open_namespace() available outside of fs/ Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 23/90] nsproxy: build the nsproxy the tasks of a cell share Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 24/90] cells: add the cell object and cell_create() Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 25/90] cells: add cell_kill() Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 26/90] cells: add cell_configure() Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 27/90] cells: add the "budget" slot Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 28/90] cells: add the "root" slot Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 29/90] cells: add the "ids" slot, for a single id Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 30/90] cells: lend a cell a block of ids Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 31/90] cells: reserve the "spawn" slot Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 32/90] cells: add CELL_CMD_SEAL Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 33/90] cells: add cell_open() Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 34/90] pid: allocate a struct pid ahead of its numbers Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 35/90] fork: let copy_process() place a task in a cell Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 36/90] cgroup: fork a task of a cell into the budget Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 37/90] x86: give a task without an mm no user state Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 38/90] uprobes: cope with a new task that has no mm Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 39/90] fork: add tasks that start out with nothing Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 40/90] pidfd: add PIDFD_EMPTY Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 41/90] pidfd: add pidfd_configure() Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 42/90] pidfd: let pidfd_configure() place files Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 43/90] pidfd: let pidfd_configure() set credentials Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 44/90] pidfd: let pidfd_configure() name a cgroup to start in Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 45/90] ptrace: keep everybody off an embryo Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 46/90] cells: end a cell when its init exits Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 47/90] cells: refuse a task of a cell a change of namespaces Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 48/90] fs: refuse a task of a cell a new mount namespace Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 49/90] nsproxy: refuse to enter a namespace of a cell Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 50/90] cgroup: keep a task of a cell within the budget Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 51/90] cgroup: let nothing but a spawn into a budget Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 52/90] kernfs: let a filesystem refuse a change to a node Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 53/90] cgroup: let a cell change nothing outside its budget Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 54/90] cgroup: leave the limits of a budget to who is above Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 55/90] fs: let a filesystem hold ids that go with the cell Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 56/90] cgroup: let a cell make cgroups below its budget Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 57/90] resctrl: let a cell change nothing Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 58/90] cred: let no id count across cells Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 59/90] ptrace: refuse PTRACE_TRACEME " Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 60/90] keys: ignore an equal uid or gid " Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 61/90] fs: accept a cellfd for MOUNT_ATTR_IDMAP Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 62/90] fs: let a cell set no set-id bits Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 63/90] fs: drop privileges from what a cell may write to Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 64/90] cells: add the "caps" slot Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 65/90] capabilities: take nothing from a file in a cell Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 66/90] capabilities: let a cell shrink its bounding sets Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 67/90] af_unix: deliver ancillary data only within a cell Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 68/90] af_unix: tell a sender that control data will not cross Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 69/90] net: do not identify a peer in another cell Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 70/90] seccomp: let a listener put no file into a cell Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 71/90] umh: run no program for a task of " Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 72/90] coredump: let no core dump leave " Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 73/90] cells: add the "net" slot Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 74/90] net: give a cell without a network no sockets Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 75/90] net: add net_may() Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 76/90] rtnetlink: use net_may() for requests that change state Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 77/90] net: use net_may() in the configuration ioctls Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 78/90] netfilter: use net_may() for configuration Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 79/90] tun: use net_may() to create and attach to devices Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 80/90] net: use net_may() for the net sysctls Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 81/90] net: use net_may() for privileged socket options Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 82/90] net: use net_may() for raw and packet sockets Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 83/90] cells: add the "net-rights" slot Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 84/90] net: honour the rights of a cell in net_may() Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 85/90] tun: ignore an equal uid or gid across cells Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 86/90] cells: add cell_info() Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 87/90] cells: add CONFIG_CELLS Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 88/90] arch: wire up the cell system calls Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 89/90] MAINTAINERS: add an entry for cells Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 90/90] selftests: add tests " Josef Bacik

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261105-b4-cells-v3-2-bc9095937a84@toxicpanda.com \
    --to=josef@toxicpanda.com \
    --cc=James.Bottomley@HansenPartnership.com \
    --cc=andrew@amutable.com \
    --cc=brauner@kernel.org \
    --cc=jbingham@gmail.com \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=sergeh@kernel.org \
    --cc=taherafahimi@linux.microsoft.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®