Basis Lab

What a Container Actually Is

The Boundary Is a View, and the Kernel Underneath It Is Shared

Last timeHow It Talks to Anything

A container's isolation is a set of values one shared kernel checks. What that leaves possible, what answers each class of weakness, and when the boundary has to be a kernel of its own.

What the separation is actually made of

Everything taken apart so far has the same shape, and it is worth saying once in

plain terms before asking what it is worth.

A container is an ordinary process. Attached to that process are some values the

kernel keeps about it: which set of host names it belongs to, which set of

process numbers, which set of mounted filesystems, which network stack, which

mapping of user numbers, which accounting group, which privilege units it still

holds. When the process asks for something, the kernel looks at the relevant

value and answers accordingly. That is the whole mechanism. There is no guard

standing between the process and the kernel, no second program inspecting

requests, no wall in any physical sense.

This has a consequence that is easy to state and unpleasant to absorb. The

thing doing the confining is the same program that is answering the process's

requests. A boundary made of decisions is exactly as good as the thing making

them, and here the thing making them is a general purpose kernel of some tens of

millions of lines, serving every process on the machine from the same code. The

separation is real and it is useful. What it is not is independent of the

kernel, and almost every wrong belief about container security is a belief that

some part of it is.

FIG 1Where the checking happens, and what all the boxes have in common
Read the arrows into H and you have the four things a careful operator configures. Read the arrows out of C and you have the one thing nobody can configure, which is that every box on the host is behind the same instance of the same program. The first list is where the work goes and the second list is where the ceiling is.

One kernel, one surface

The shared kernel is usually described as a risk. It is more useful as a number.

The interface a process reaches the kernel through is a set of entry points,

somewhere around 350 of them on a current system, though the exact count depends

on the kernel version and the processor it was built for. An ordinary web

service, the 180 MB five-layer image used throughout here, needs about 60 of

them to do its work: open a file, read it, write to a socket, wait, allocate,

and so on. Narrowing what the process may ask for to roughly those 60 leaves

60/350=0.17160 / 350 = 0.171 of the surface reachable, about a sixth, and that is a real

reduction worth making. It reduces the number of places a flaw can be reached

from. It does nothing whatever to the consequence of reaching one.

The consequence is the part that does not shrink. A flaw behind one of those

entry points is a flaw in the decision maker itself, so it is not one container's

boundary failing. It is every container behind that kernel failing at once, which

on a host running ten containers from that image is ten boundaries for one

defect. Ten virtual machines in the comparison fixed at the start of all this,

1.2 GB each and 12 GB in total, have ten kernels between them, so the same defect

costs one. That ratio, ten against one, is not improved by any amount of care

taken inside the ten boxes, because none of the care is applied by anything other

than the thing that is broken.

Four classes of weakness, and what answers each

With that established, the classes can be named honestly. There are four, they

are not equally likely, and the most common one is not an attack at all.

A view handed back at start-up. The container is given the host's process

view, or the host's network stack, or a large part of the host's filesystem

mounted inside it, or the undivided privilege set. Nothing is broken in these

cases. The separation was never created. This is the overwhelming majority of

what gets reported as a container escape, and every instance of it was granted

by somebody with a real problem to solve: a monitoring agent that needs to see

the host's processes, a build that needs the machine's own socket, a tool that

needs a device. The reason it keeps happening is that each grant is reasonable

in isolation and the document that records what was granted is never the document

that records what was intended.

A privilege that reaches the kernel. The superuser's power is not one thing,

it is a set of named units, and they can be held or dropped individually. A few

of them allow a process to change the kernel on behalf of the whole machine

rather than on behalf of itself. A process holding one of those is not confined

by anything in the picture above, because the values the kernel checks are

themselves things it can reach. The defence is the one Saltzer and Schroeder

wrote down in 1975: start from nothing and add back by name, with a sentence

attached to each addition.

An identity that means something outside. Without a mapping of user numbers,

the administrator inside the box is the administrator outside it, and the only

thing standing between that identity and the host's files is the filesystem view.

Map the numbers, so that the process which believes it is user 0 is some

unprivileged number on the host, and a view that is widened by accident is

widened onto files that identity cannot read anyway. This is the cheapest of the

four defences and the most frequently skipped.

A flaw in the shared kernel. The residual. Narrowing the request list and

applying mandatory access control both reduce how reachable such a flaw is, and

keeping the kernel current reduces how many are known, and none of the three

changes the blast radius when one is found.

The lesson stops here

5 more paragraphs to go

You have read the opening. The rest of the argument, the problems that check whether it landed, and the lines worth keeping at the end all come with a plan.

The first lesson of every course in the library reads the whole way through, free, so you can see exactly what the rest of them are.

See the planThe contents

This is the reading half

Starting the course gives you your own copy of it. Every idea on every page has problems standing under it, marked with a reason rather than a tick, and any sentence you do not believe can be opened and argued with. None of that can happen on a page nobody owns.

The contents

The rest of this course

  1. 01One Kernel, Many Processes, and No Boot Sequence Anywhere
  2. 02Six Views a Process Can Be Given Instead of Yoursopening only
  3. 03A Different Root Directory, and Why It Is Not a Wallopening only
  4. 04Half a Processor and 512 MB, and Why Only One of Them Killsopening only
  5. 05Layers, and Why One Line Rebuilds Sixty Megabytesopening only
  6. 06Why the File It Wrote Is Owned by Rootopening only
  7. 07Follow One Request In, and Name Every Translation on the Wayopening only
  8. 08The Boundary Is a View, and the Kernel Underneath It Is Sharedyou are here

Read alongside