Skip to content
academia.sh

Lesson 01 / 09

Namespaces

A process's 24 observable facts spread across nine axes; five namespaces close nine of them, leave 15 open, and can fully close only two of the nine axes.

Contents

The Linux Network Administration and Troubleshooting course left a promise in its last lesson: there, a machine had a single network stack; here, multiple stacks are set up on the same machine, and every interface, every routing table, every rule chain multiplies. The System Administration course also pointed at the same address in two separate places. In the service manager lesson, a unit was an entry in the manager’s ledger, and isolating processes from one another was explicitly left to this course; in the resource limits lesson, the per-process constraint was measured, and the constraint placed on a process group’s total was likewise referred here. Both debts carry the same two names: namespace and cgroup. This lesson pays the first; the next lesson pays the second.

The way this debt is paid will be unfamiliar. Previous courses counted what a tool could not see and how many candidates a test eliminated; this course counts how much an isolation fails to close. Its rule is this: an isolation’s number is not the surface it closes, but the surface it leaves open; an isolation whose remaining visibility is not written down counts as unmeasured.

The Course’s Measure and Security Boundary

The measurement places three numbers side by side. The oracle is all of a process’s observable facts; since we wrote the fiction ourselves, the whole set is known. Isolation is the number of facts the mechanisms close. The remaining exposed surface is what is left — the facts the process can still see. The third number is this course’s real subject, and it is never skipped in any lesson.

This is a defensive measurement, and its boundary is written up front. No escape, privilege-escalation, bypass, or exploitation procedure is written anywhere in this course; no working exploit code, proof of concept, or ready-made command sequence is provided. What is measured is which fact stays visible, not how that visibility might be abused. In exchange, a narrowing path is written next to every open surface: if a gap is counted, the same lesson also shows the mechanism that shrinks it. Least privilege, access control models, and threat modeling procedure were established in the Introduction to Cybersecurity and Secure Development Lifecycle courses; only their kernel-level counterpart is measured here.

Every number in the model is a full count over the fiction, not a measurement taken on a real machine. Real commands are not run in this lesson; example dumps exist to show the form, and the identifiers inside them are fiction. No numeric claim comes from an example dump.

Namespace: A Filter Placed Over the Kernel Table

On a machine, the process table is single, the mount point list is single, the network interface list is single. A namespace does not produce a second table; it places a visibility filter over the table that already exists, and the process looks out from behind that filter. The same entry can carry two different names, an entry can be present in one view and absent from another, but it is never deleted.

The types are opened separately and are independent. The PID namespace renumbers process numbers: the first process inside gets number 1, different from its number outside. The mount namespace separates the mount point list. The network namespace separates interfaces, the routing table, and the ports listened on. The user namespace maps identities. The UTS namespace separates the machine name. Beyond these, there are also types that separate inter-process communication objects and the root of the cgroup tree; this lesson measures five types and names the rest without measuring them.

Which namespaces a process is in stands as symbolic links in the process file system. The dump below has not been executed; the identifiers are fiction, and a real machine shows different values.

# example dump — not executed
$ ls -l /proc/self/ns
cgroup -> 'cgroup:[40001]'
ipc    -> 'ipc:[40002]'
mnt    -> 'mnt:[40003]'
net    -> 'net:[40004]'
pid    -> 'pid:[40005]'
user   -> 'user:[40006]'
uts    -> 'uts:[40007]'

Whether two processes are in the same namespace is understood from the equality of the identifiers these links show. A namespace is an object: it lives as long as a process is attached to it or it is pinned to a file path, and it vanishes once both end.

A namespace is entered two ways. To create a new namespace, a process detaches itself; to join an existing namespace, the target’s link is opened. The two have separate shell-level tools:

# example dump — not executed
$ unshare --user --pid --fork --mount-proc --uts --net example-command
$ nsenter --target 8140 --pid --mount --net -- example-command
$ cat /proc/8140/uid_map
         0       1000          1

The first line creates new namespaces. There is a reason the user namespace comes first: it is the only type that lets an unprivileged user open the other types, and the identity visible inside carries no privilege outside. The mapping table on the third line shows this — the inside’s 0 corresponds to a user outside, and the mapping is written once and closed. The second line does the opposite: it enters an existing namespace from outside. This tool is not an isolation mechanism, it is an observation channel, and it requires privilege on the machine; this course’s second topic counts how much channels of this kind puncture isolation.

Twenty-Four Facts, Nine Axes

For measurement, what a process can see is reduced to a countable set. The fiction is the metrics-collector process running on a metrics server.

  • IM1 — A process has 24 observable facts, and each fact belongs to an axis: process, mount, network, user, machine, resource, kernel, privilege, label. The number of axes is 9.
  • IM2The oracle is all of a process’s facts; the side that built the fiction knows all of them.
  • IM3 — An isolation mechanism is a filter: it closes a specific axis but can leave something open on that axis.
  • IM4 — A mechanism closes only a single axis; cross-axis side effects are not modeled.
  • IM5 — All numbers are full counts; there is no randomness, and no second seed is used.
  • IM6 — Facts carry equal weight in terms of observability; one fact being more valuable than another is not modeled.
"""M03/K05 shared definition, the parts this lesson uses.

Oracle = all of a process's facts (fiction, known in advance).
Isolation = makes a subset of facts INVISIBLE.
What is measured: REMAINING EXPOSED SURFACE, i.e. the number of facts still visible.
"""

FACTS = [
    ("other-processes-list", "process"), ("own-process-number", "process"),
    ("ancestor-process-chain", "process"),
    ("root-filesystem-tree", "mount"), ("other-mounts", "mount"),
    ("shared-temp-dir", "mount"),
    ("machine-interfaces", "network"), ("machine-routing-table", "network"),
    ("listening-ports", "network"),
    ("user-id-mapping", "user"), ("file-ownership", "user"),
    ("machine-name", "machine"),
    ("cpu-share", "resource"), ("memory-limit", "resource"),
    ("used-memory", "resource"), ("cpu-count", "resource"),
    ("kernel-version", "kernel"), ("kernel-settings", "kernel"),
    ("system-load", "kernel"), ("clock", "kernel"),
    ("capability-set", "privilege"), ("file-permissions", "privilege"),
    ("access-label", "label"), ("policy-rules", "label"),
]

# Each namespace closes ONE axis and LEAVES SOMETHING OPEN on that axis.
MECHANISMS = {
    "pid-namespace":     {"axis": "process", "left_open": ["own-process-number"]},
    "mount-namespace":   {"axis": "mount", "left_open": ["root-filesystem-tree"]},
    "network-namespace": {"axis": "network", "left_open": []},
    "user-namespace":    {"axis": "user", "left_open": ["file-ownership"]},
    "uts-namespace":     {"axis": "machine", "left_open": []},
}
FIVE = list(MECHANISMS)


def visible(mechanisms, facts=None):
    """Facts still visible once the mechanisms are applied."""
    facts = FACTS if facts is None else facts
    closed = set()
    for m in mechanisms:
        v = MECHANISMS[m]
        closed |= {o for o, e in facts if e == v["axis"]}
        closed -= set(v["left_open"])
    return [o for o, _ in facts if o not in closed]


def open_axes(mechanisms, facts=None):
    facts = FACTS if facts is None else facts
    g = set(visible(mechanisms, facts))
    return sorted({e for o, e in facts if o in g})


axes = sorted({e for _, e in FACTS})
print("ORACLE  fact:", len(FACTS), "| axis:", len(axes), axes)
print()
print("namespace             in axis  closed  left open  visible")
for m in FIVE:
    g = visible([m])
    n = sum(1 for _, e in FACTS if e == MECHANISMS[m]["axis"])
    print(f"  {m:20s} {n:7d}  {len(FACTS) - len(g):7d}"
          f"  {len(MECHANISMS[m]['left_open']):10d}  {len(g):7d}")
print()
g5 = visible(FIVE)
print("five namespaces together")
print("  visible fact:", len(g5), "| open axis:", len(open_axes(FIVE)),
      open_axes(FIVE))
print("  fully closed axis:",
      [MECHANISMS[m]["axis"] for m in FIVE if not MECHANISMS[m]["left_open"]])
ORACLE  fact: 24 | axis: 9 ['kernel', 'label', 'machine', 'mount', 'network', 'privilege', 'process', 'resource', 'user']

namespace             in axis  closed  left open  visible
  pid-namespace              3        2           1       22
  mount-namespace            3        2           1       22
  network-namespace          3        3           0       21
  user-namespace             2        1           1       23
  uts-namespace              1        1           0       23

five namespaces together
  visible fact: 15 | open axis: 7 ['kernel', 'label', 'mount', 'privilege', 'process', 'resource', 'user']
  fully closed axis: ['network', 'machine']

Isolation Is Not Binary, It Is Partial

Three numbers stand side by side. Oracle: 24 facts, 9 axes. Isolation: five namespaces together close 9 facts. Remaining exposed surface: 15 facts and 7 axes.

The table’s first reading concerns the mechanisms one by one. The PID namespace closes two of three facts and leaves one open: the process cannot see the list of other processes or the ancestor chain, but it sees its own number. The mount namespace works the same way; other mounts and the shared temp directory close, the root filesystem tree itself stays visible. The user namespace closes only one of two facts: identity mapping is separated, file ownership stays open.

Only two of the nine axes fully close, and those two are the network and UTS namespaces. The network namespace closes all three facts of its own axis; the UTS namespace closes its single fact. Why it is these two is not an arbitrary choice of the model, it has to do with the structure of what they close: the network stack and the machine name are external attributes that do not belong to the process itself. The process number, the root directory, and file ownership, by contrast, sit within the process itself; a process cannot run without seeing itself.

This is the shared definition’s first reading: isolation is not binary, it is partial. Turning on a mechanism is not enough to count an axis as “closed”; the fact it leaves open must be written next to every mechanism.

Fact Set Sweep

Because the model has no randomness, the measurement cannot be repeated with a second seed. How much the result depends on the fiction is seen by sweeping the fact set: a fact is added to the list, a fact is removed, and how the exposed surface changes is read.

  • IM7 — The added fact session-id belongs to the process axis and is not among what the PID namespace leaves open.
  • IM8 — The removed file-ownership is the user axis’s single fact left open.
  • IM9 — The second removal, machine-name, is the machine axis’s only fact; once removed, the axis drops off the list too.
# --- This block builds on the previous block: FACTS, MECHANISMS, FIVE,
# visible and open_axes come from there.
ADDED = FACTS + [("session-id", "process")]
MISSING_OWNERSHIP = [(o, e) for o, e in FACTS if o != "file-ownership"]
MISSING_MACHINE = [(o, e) for o, e in FACTS if o != "machine-name"]

print("fact set sweep (five namespaces applied)")
print("set                        fact  axis  visible  open axis")
for name, facts in (("base", FACTS),
                     ("+session-id/process", ADDED),
                     ("-file-ownership", MISSING_OWNERSHIP),
                     ("-machine-name", MISSING_MACHINE)):
    g = visible(FIVE, facts)
    e = open_axes(FIVE, facts)
    print(f"  {name:24s} {len(facts):4d}  {len({x for _, x in facts}):5d}"
          f"  {len(g):7d}  {len(e):10d}")
fact set sweep (five namespaces applied)
set                        fact  axis  visible  open axis
  base                       24      9       15           7
  +session-id/process        25      9       15           7
  -file-ownership            23      9       14           6
  -machine-name              23      8       15           7

The sweep says two separate things. First: adding a fact to a closed axis does not grow the exposed surface. The process axis grew to four facts with the twenty-fifth fact, the PID namespace closed all three of them, and the visible fact count stayed at 15. Second, and more important: removing the single fact left open closes the whole axis. When file ownership drops off the list, the user namespace closes its entire axis, the open axis count falls from 7 to 6, and the visible fact count drops to 14.

This shows where the measurement is sensitive: what determines the exposed surface is not the fact count, but which facts are marked as left open. The third row confirms this from the opposite direction; when the machine name is removed, the axis drops off the list, the UTS namespace turns into a mechanism that closes nothing, and the visible fact count is 15 again. A mechanism with nothing to close does not narrow the surface, it only grows the configuration.

The Three Left Open, and Narrowing Paths

Of the 15 facts the five namespaces leave open, three belong directly to this lesson; the remaining twelve are on the resource, kernel, privilege, and label axes and are the subject of the next four lessons. A narrowing path is written next to each of the three.

Its own process number stays open and cannot be closed; a process cannot receive a signal or reap a child without knowing its own number. The narrowing here is not hiding the number, it is not leaking the number’s meaning: the process file system is remounted inside, and no path is left open to the outside numbers; which namespace the numbers written to logs belong to is recorded separately. The same process carrying 1 inside and a different number outside is not a malfunction, it is the rule of measurement.

The root filesystem tree stays open because a process must have a root. The narrowing path is to shrink what the root contains: the root is mounted read-only, writable space is handed to a separate, temporary mount, and unnecessary device nodes and helper programs are not placed in it. Of the mount options, nosuid, nodev, and noexec are this narrowing’s three cheapest steps, and they are written in the same place as the persistent mount entry from the System Administration course.

File ownership stays open because identity mapping changes the numbers a process sees but does not change the ownership fields on disk. The narrowing path is to carry the mapping through to the file system: the root filesystem tree’s ownership is normalized to the mapped range, and shared directories are either given read-only or not given at all. This lesson does not stop at counting a surface; every line also leaves behind a configuration decision that shrinks it.

Summary

  • This course measures the surface an isolation leaves open; an isolation whose remaining visibility is not written down counts as unmeasured. The measurement is a defensive measurement, and a narrowing path is written next to every open surface.
  • A namespace does not produce a second table; it places a visibility filter over the kernel table that already exists, and the process looks out from behind that filter.
  • The oracle is 24 facts and 9 axes; five namespaces together close 9 facts, and 15 facts and 7 axes stay open.
  • No namespace fully closes its own axis on its own; the network and UTS namespaces are the only pair that can. The PID namespace leaves own-process-number open, the mount namespace root-filesystem-tree, the user namespace file-ownership.
  • The fact set sweep shows where the measurement is sensitive: adding a fact to a closed axis does not grow the surface, removing the single fact left open closes the axis and drops the open axis count from 7 to 6.
  • The first half of the debt left to this course by the System Administration and Linux Network Administration and Troubleshooting courses is paid; the second half is the cgroup.

Next Step

Five namespaces narrowed visibility but never limited consumption at all: the process inside can use the machine’s CPU, memory, and I/O bandwidth as if they were its own. The resource axis therefore stands entirely open with its four facts. The next lesson takes up the mechanism that closes this axis, the cgroup: it separates resource limiting from accounting, writes its difference from the per-process constraint measured in the System Administration course, and counts why closing three of the four resource facts leaves the cpu-count fact open. That single open fact is enough to make every program that sizes its own thread pool by the machine’s CPU count silently miscalculate.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close