---
title: Namespaces
source: 'https://academia.sh/en/courses/kernel-interfaces/namespaces'
course: 'Kernel Interfaces and Isolation'
language: en
updated: '2026-08-17T18:09:58+00:00'
license: 'CC BY-SA 4.0'
---

# 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.

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.

```text
# 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:

```text
# 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**.
- **IM2** — **The 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.

```python
"""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.

```python
# --- 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.
