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

# Core Dump Analysis

Measuring the channel whose run cost is zero: a core dump brings back 3 of the 14 facts the strict bundle closes, the open surface becomes 13, and all four channels together bring back 8 facts, taking the return ratio to 0.5714; the dump's cost sits not in the run but in the sensitive data it carries.

Three channels have been measured, and all three ran during the run: system call tracing at
34 percent, in-kernel tracing at 6 percent, the sampling profile at 3 percent. The fourth
channel sits outside this pattern. A core dump is written **after** a process has crashed; it
charges the running process nothing, its run cost is **0 percent**.

A channel with zero cost is expected to be harmless. The measurement says the opposite: the
dump is among the top two in the number of facts it brings back — **3** — and among what it
brings are user ID mapping and the capability set. This lesson resolves that contradiction —
zero cost is not paid in the run, it is paid in **retention** — and closes the topic.

## The Channel That Looks After the Run Ends

When a process ends on a fatal signal, the kernel can write the process's memory image to a
file. This file is not an event stream; it is a single **snapshot**, carrying the process's
final state: memory contents, privilege state, identity fields, traces of open resources.

Assumptions continue. The dump is written when the process ends and charges nothing during the
run (**OD22**). The four facts the dump carries are the list in the code (**OD23**). Costs are
**percentages in the model, not a real measurement**; the baseline run is 1000 time units
(**OD24**). The dump is a single instant: for a fact that changes over the run, only its final
value is visible (**OD25**). The dump file is persistent; the facts it carries stay visible as
long as it can be read (**OD26**). All four channels can be on at once, and their run costs sum
(**OD27**). The dump's size and retention duration are not in the model (**OD28**), and this is
the real cost that falls outside the measurement.

```text
# example dump , has not been run
$ cat /proc/sys/kernel/core_pattern
<dump-path-template>
$ ulimit -c
<dump-size-limit>
$ ls -l <dump-directory>
-rw-------  1 <owner> <group>  <size>  <time>  core.<process-name>.<no>
```

The block has not been run and its values are mock. That the file's permission bits are narrow
is not a coincidence; the reason is measured below.

```python
# M03/K05 common definition -- this lesson builds all four channels.
FACTS = [
    ("other-process-list", "process"), ("own-process-number", "process"),
    ("ancestor-process-chain", "process"),
    ("root-filesystem-tree", "mount"), ("other-mounts", "mount"),
    ("shared-temp-directory", "mount"),
    ("host-interfaces", "network"), ("host-routing-table", "network"),
    ("listening-ports", "network"),
    ("user-id-mapping", "user"), ("file-ownership", "user"),
    ("hostname", "host"),
    ("cpu-share", "resource"), ("memory-limit", "resource"),
    ("memory-used", "resource"), ("cpu-count", "resource"),
    ("kernel-version", "kernel"), ("kernel-settings", "kernel"),
    ("system-load", "kernel"), ("clock", "kernel"),
    ("capability-set", "privilege"), ("file-permissions", "privilege"),
    ("security-label", "label"), ("policy-rules", "label"),
]
MECHANISM = {
    "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": ["host"], "left_open": []},
    "control-group":     {"axis": ["resource"], "left_open": ["cpu-count"]},
    "capability-dropping": {"axis": ["privilege"], "left_open": ["file-permissions"]},
    "mandatory-label":   {"axis": ["label"], "left_open": ["security-label"]},
}
STRICT_BUNDLE = ["pid-namespace", "mount-namespace", "network-namespace", "uts-namespace",
                 "control-group", "capability-dropping", "user-namespace", "mandatory-label"]
CHANNEL = {
    "system-call-tracing": ["root-filesystem-tree", "other-mounts", "file-ownership",
                             "capability-set", "clock"],
    "sampling-profile":     ["cpu-share", "cpu-count", "system-load"],
    "in-kernel-tracing":   ["other-process-list", "ancestor-process-chain",
                             "listening-ports", "kernel-settings", "system-load"],
    "core-dump":           ["memory-used", "capability-set", "security-label",
                             "user-id-mapping"],
}
# OD24: costs are MODEL percentages , not a real measurement
CHANNEL_COST = {"system-call-tracing": 34, "sampling-profile": 3,
                "in-kernel-tracing": 6, "core-dump": 0}
BASELINE_TIME = 1000


def visible(mechanisms, facts=FACTS):
    closed = set()
    for d in mechanisms:
        v = MECHANISM[d]
        for e in v["axis"]:
            closed |= {o for o, x in facts if x == e}
        closed -= set(v["left_open"])
    return [o for o, _ in facts if o not in closed]


def returned(mechanisms, channels, facts=FACTS):
    closed = {o for o, _ in facts} - set(visible(mechanisms, facts))
    back = set()
    for k in channels:
        back |= set(CHANNEL[k]) & closed
    return sorted(back)


def surface(mechanisms, channels=(), facts=FACTS):
    g = set(visible(mechanisms, facts))
    back = set(returned(mechanisms, channels, facts))
    return {"visible": len(g), "returned": len(back), "open_surface": len(g | back),
            "open_axes": len({e for o, e in facts if o in (g | back)})}


g = set(visible(STRICT_BUNDLE))
closed = len(FACTS) - len(g)
ys = surface(STRICT_BUNDLE, ["core-dump"])
print("facts:", len(FACTS), "| axes:", len({e for _, e in FACTS}),
      "| strict bundle: visible", len(g), "closed", closed)
print()
print("fact carried by dump        axis       status under strict bundle")
for o in CHANNEL["core-dump"]:
    print(f"  {o:26s}{dict(FACTS)[o]:11s}"
          f"{'already visible' if o in g else 'RETURNS'}")
print()
print("channel                 returned  open surface  open axes  cost  run")
for k in CHANNEL:
    y = surface(STRICT_BUNDLE, [k])
    b = CHANNEL_COST[k]
    print(f"  {k:22s} {y['returned']:4d}  {y['open_surface']:10d}  {y['open_axes']:10d}"
          f"  {b:5d}  {BASELINE_TIME * (100 + b) // 100:5d}")
h = surface(STRICT_BUNDLE, list(CHANNEL))
bh = sum(CHANNEL_COST.values())
print(f"  {'ALL FOUR CHANNELS':22s} {h['returned']:4d}  {h['open_surface']:10d}"
      f"  {h['open_axes']:10d}  {bh:5d}  {BASELINE_TIME * (100 + bh) // 100:5d}")
print()
print("facts the strict bundle closes:", closed)
print("returned by four channels     :", h["returned"])
print("return ratio                  :", round(h["returned"] / closed, 4))
print("brought back by no channel    :",
      sorted(set(o for o, _ in FACTS) - g - set(returned(STRICT_BUNDLE, list(CHANNEL)))))
print()
print("FACT SET SWEEP (all four channels)")
changes = [o for o, _ in FACTS
           if surface(STRICT_BUNDLE, list(CHANNEL),
                      [x for x in FACTS if x[0] != o])["open_surface"] != h["open_surface"]]
grows = [e for e in sorted({e for _, e in FACTS})
         if surface(STRICT_BUNDLE, list(CHANNEL),
                    FACTS + [("new-fact", e)])["open_surface"] > h["open_surface"]]
print("  removal: open surface", h["open_surface"], "| changes it", len(changes),
      "| does not", len(FACTS) - len(changes))
print("  addition: axis growing the surface", grows, "-> open surface", h["open_surface"] + 1)
```

```
facts: 24 | axes: 9 | strict bundle: visible 10 closed 14

fact carried by dump        axis       status under strict bundle
  memory-used               resource   RETURNS
  capability-set            privilege  RETURNS
  security-label            label      already visible
  user-id-mapping           user       RETURNS

channel                 returned  open surface  open axes  cost  run
  system-call-tracing       2          12           7     34   1340
  sampling-profile          1          11           7      3   1030
  in-kernel-tracing         3          13           8      6   1060
  core-dump                 3          13           7      0   1000
  ALL FOUR CHANNELS         8          18           8     43   1430

facts the strict bundle closes: 14
returned by four channels     : 8
return ratio                  : 0.5714
brought back by no channel    : ['host-interfaces', 'host-routing-table', 'hostname', 'memory-limit', 'policy-rules', 'shared-temp-directory']

FACT SET SWEEP (all four channels)
  removal: open surface 18 | changes it 18 | does not 6
  addition: axis growing the surface ['kernel'] -> open surface 19
```

## Oracle, Channel, Returning Fact

Three numbers sit side by side. **Oracle:** the process's 24 facts, over nine axes.
**Channel:** the core dump carries four facts. **Returning fact and open surface:** **3** of
the four are facts the strict bundle closed and they return; the open surface climbs from
**10 to 13**, open axes stays at **7**.

The returning trio is `memory-used`, `capability-set`, and `user-id-mapping`. All three are
facts three separate mechanisms close: the control group, capability dropping, and the user
namespace, respectively. A single file invalidates three separate isolation decisions at once.
The fourth fact, `security-label`, was already visible under the strict bundle; the mandatory
labeling mechanism deliberately leaves it open on its own axis.

Open axes staying at 7 confirms the distinction seen in the previous lesson: the dump reopens
no closed axis, it thickens three open ones. Yet it takes the open surface to **13**, and this,
together with in-kernel tracing, is the largest single surface among the four channels. The
channel with the lowest cost is one of the two channels that grow the surface the most.

## Where Zero Cost Is Paid

The run cost is zero because the dump does not touch the running process. The cost is
elsewhere, and it shows up in three forms.

The first is **persistence** (**OD26**). The other three channels give information for as long
as they are on; visibility ends once they are turned off. The dump is a **file**: once written,
it keeps showing the facts it carries until it is deleted. Tracing turned on for an hour
produces an hour of visibility; a dump written once produces visibility for as long as it is
retained.

The second is the file's **content**. The dump carries the process's memory image; whatever is
in memory at that moment is in the dump too. This is a set far beyond the four facts counted in
the measurement: processed data, session information, configuration values, and credentials can
all sit in memory. **A core dump carries sensitive data.** This course does **not** write a
procedure for extracting confidential data from a dump; what it measures is which fact the dump
makes visible, not how that visibility gets abused.

The third is **access**. The path the dump is produced to and the file's ownership determine
who can read it. A dump written to a shared directory can be readable by processes outside the
isolation bundle too.

The narrowing paths follow directly from this. Dump production can be **turned off**, or bound
to a size limit through a per-process resource limit; dump production can also be separately
blocked for privilege-escalating processes. The file is written with **narrow permissions** and
to a directory only the privileged process can read, not to shared temp directories. Dumps are
bound to a **retention policy**: they are deleted once diagnosis ends. Encryption at rest
protects the file against backups and copies too. Together, the four make the real cost of a
channel with zero run cost manageable.

## The Four Channels' Total

When all four channels are turned on together, the result is the course's third claim. The
strict bundle closed **14** of 24 facts; the four channels together bring back **8** of them.
The return ratio is **0.5714**: **more than half of what isolation closes returns through
observation.** The open surface climbs from 10 to **18**, open axes from 7 to **8**. Total run
cost is **43 percent** and the run stretches to 1430; keeping all four on at once is a rare
setup, but this line marks the setup's limit.

The six facts that do not return are instructive too: `host-interfaces`, `host-routing-table`,
`hostname`, `memory-limit`, `shared-temp-directory`, and `policy-rules`. These are on no
channel's list and stay closed. Isolation keeps working exactly where observation channels do
not look; what gets pierced is not the whole of isolation, it is **the intersection the
channels cover**.

The fact set sweep runs for the last time. On the removal side, the open surface changes with
**18** of 24 facts, does not change with **6** — the ones that do not change are exactly the
six above. On the addition side, the surface again grows only on the **`kernel`** axis (18 to
19). The same result came up in all four lessons: an axis with no closer writes every fact
added to the list straight to the surface.

## Summary

- The core dump carries four facts; **3** return (`memory-used`, `capability-set`,
  `user-id-mapping`) and the open surface becomes **13**.
- Run cost is **0 percent**; the cost sits in persistence, the file's content, and its access.
  The dump **carries sensitive data** and its retention and access must be restricted.
- A single file invalidates three separate mechanisms' decisions at once — the control group,
  capability dropping, the user namespace.
- All four channels together: returned **8**, open surface **18**, open axes **8**, total cost
  **43 percent**; return ratio **0.5714**.
- Six facts return through no channel; isolation keeps working where the channels do not look.
- Narrowing: turning off dump production or binding it to a size limit, a narrow-permission
  unshared directory, a retention policy, and encryption at rest.

## Course Wrap-Up

The course ran on a single rule: **an isolation's count is not the surface it closes, it is
the surface it leaves open; an isolation whose still-visible facts go unwritten counts as
unmeasured.** The table below sets nine lessons side by side under the same measure. The
`gozlem` topic's four rows are the numbers measured in this topic; the `izolasyon` topic's five
rows are filled with those lessons' own numbers.

| Lesson | Oracle | Isolation or channel | Open surface / returned |
|---|---|---|---|
| Namespaces | 24 facts / 9 axes | five namespaces | **15** visible; network and uts namespace are the only pair that closes its own axis **fully** |
| Control Groups | 24 facts / 9 axes | + control group | **12** visible; 3 facts close, `cpu-count` **stays open** |
| Capabilities | 24 facts / 9 axes | + capability dropping | **11** visible; only one of five capability sets closes, **file permissions stay open** |
| Mandatory Access Control | 8 of 36 access tuples required | + mandatory labeling | **10** visible (strict bundle); with no policy, **28 excess permissions**, a rough policy **10**, a narrow policy **1**; in permissive mode, surface is **11** instead of 10 |
| Container Runtime | 24 facts / 9 axes | common 6 mechanisms / strict 8 mechanisms | **12** versus **10**; two mechanisms narrow the surface by only **2 facts**, **open axes stays at 7** |
| System Call Tracing | 24 facts / 9 axes | channel: shows 5 facts, cost 34 percent | returned 2, open surface 12, open axes 7 |
| Performance Profiling | 24 facts / 9 axes | channel: shows 3 facts, cost 3 percent | returned 1, open surface 11, open axes 7 |
| In-Kernel Tracing | 24 facts / 9 axes | channel: shows 5 facts, cost 6 percent | returned 3, open surface 13, open axes 8 |
| Core Dump Analysis | 24 facts / 9 axes | channel: carries 4 facts, cost 0 percent | returned 3, open surface 13, open axes 7 |

The course's two topics were opposites of one another, and the measurement wrote this down in
numbers: isolation closed 14 facts, observation brought back 8 of them.

This lesson is also the end of the M03 curriculum. Five courses followed a single axis: **the
gap between the system's real state and the evidence in the operator's hand.** Introduction to
Linux measured reading names and permissions, Shell Programming turning a command into a tool,
System Administration how many times the diagnosis drawn from output is wrong, Linux Network
Administration and Troubleshooting narrowing a fault layer by layer, and Kernel Interfaces and
Isolation the surface isolation leaves open.

Two debts were paid too. Namespaces and control groups had been referred to this course twice —
in the System Administration course's service manager lesson and in the Linux Network
Administration course's network configuration section. This course built both and measured
both **together with the surface they leave open**: the pid namespace leaves its own process
number open, the control group leaves the CPU count open, and observation channels bring back
some of the facts both close.

Next comes the Computer Networks curriculum. In this course, the network passed through only as
an **axis**: three facts closed, one channel reopening them. The network itself — the path a
packet follows between two endpoints, the layers' jobs, addressing, and protocols — is built
there. The first course, How the Internet Works, begins by following the end-to-end journey of
a request typed into an address bar.
