Skip to content

kdump plugin: unconfined --path plus symlinked vmcore-dmesg.txt yields root file disclosure (monitoring user to root LPE)

Moderate
markuslf published GHSA-q8c8-wxhc-3h4c Sep 14, 2026

Software

Linuxfabrik/monitoring-plugins

Affected versions

= 7.0.0

Patched versions

>= 8.0.0

Description

Summary

The kdump check plugin (check-plugins/kdump/kdump) is shipped in the project's sudoers policy so that the unprivileged monitoring account (icinga on RHEL, nagios on Debian) may run it as root with no argument restrictions. The plugin accepts a caller-supplied --path crash-dump directory and uses it verbatim as the scan root, then reads a captured dmesg file from inside a discovered dump directory with a bare, symlink-following open() and prints its head back to the caller. A local monitoring user can therefore stage a directory containing a dummy vmcore marker file plus a vmcore-dmesg.txt symlink pointing at any root-readable file (for example /etc/shadow) and have the plugin disclose that file's contents while running as root, escalating from the monitoring account to full root.

Root Cause

The plugin trusts the caller-supplied --path value as a location it may already legitimately read and never confines the directory it scans or the file it ultimately opens. resolve_dump_path() returns the explicit --path before any distribution or configuration logic runs, with no canonicalization, allowlist, or containment check. Dump discovery (dir_has_vmcore(), scan_dumps()) and dmesg selection (find_dmesg_file()) use symlink-following primitives (os.access, glob, os.path.isdir, os.path.isfile), and the final read goes through lib.disk.read_file(), a generic bare open(filename, 'r') with no O_NOFOLLOW, no realpath, and no is_within containment. The vmcore-dmesg.txt filename constraint only binds the attacker-chosen symlink name, not the symlink target, so it imposes no restriction on which file is read.

Affected component: kdump check plugin
- check-plugins/kdump/kdump: resolve_dump_path (returns --path verbatim),
  find_dmesg_file (os.path.isfile follows the symlink), analyze_dump (calls the read sink)
- lib/disk.py: read_file (shared, unconfined, symlink-following open())

Impact

An attacker who already has the local unprivileged monitoring account (icinga/nagios) and the shipped sudoers grant, both of which are the intended default for a monitoring install, can read the head of any root-readable file on the host as root. Disclosing the first line of /etc/shadow yields root's password hash, which is always inside the preview window the plugin prints, enabling offline cracking and full root compromise; other high value targets include private TLS/SSH keys and credential files. The demonstrated primitive is a read; the monitoring account cannot read these files on its own, so exploitation crosses a genuine privilege boundary (monitoring user to root). A plausible secondary effect (not demonstrated) is memory exhaustion of the root process if the symlink points at a very large or unbounded file, since the read is unbounded.

Proof of Concept

Requires the shipped sudoers grant for the monitoring account and the kdump plugin installed at the sudoers path (for example /usr/lib64/nagios/plugins/kdump). Run the staging and invocation as the unprivileged monitoring user; --test is left unset so the production code path is exercised.

  1. As the monitoring user, create a scratch directory with a dump subdirectory holding an empty vmcore marker and a vmcore-dmesg.txt symlink to the target file.
  2. Invoke the plugin through sudo with --path pointing at the scratch directory.
  3. The plugin, running as root, follows the symlink and prints the head of the target file.
D=$(mktemp -d); mkdir -p "$D/dump.1"; : > "$D/dump.1/vmcore"
ln -sf /etc/shadow "$D/dump.1/vmcore-dmesg.txt"
sudo /usr/lib64/nagios/plugins/kdump --path="$D"
resolve_dump_path verbatim: True
scan_dumps found: ['dump.1']
dmesg_file: <scratch>/dump.1/vmcore-dmesg.txt        # symlink followed
---- analysis shown to caller ----
root:$6$deadbeef$ROOTHASH.aaaa...:19000:0:99999:7:::
...
---- end ----
ROOT HASH DISCLOSED: True

Attack Chain

  1. Exposure: The unprivileged monitoring user (icinga/nagios) is granted sudo for the kdump binary with no pinned arguments (assets/sudoers/RedHat.sudoers, assets/sudoers/Debian.sudoers; the alias entry runs as root), so the user invokes kdump --path=<dir> as root.
  2. Control: The attacker fully controls the --path value and the contents of the directory it names, including a planted vmcore marker and a vmcore-dmesg.txt symlink whose target is arbitrary.
  3. Path: resolve_dump_path() returns --path verbatim; scan_dumps() accepts the planted dump directory because the empty vmcore matches the dump glob; find_dmesg_file() returns the vmcore-dmesg.txt symlink via os.path.isfile; analyze_dump() passes it to lib.disk.read_file().
  4. Guard: There is no containment on the resolved directory and no symlink or path restriction on the read; read_file() is a bare open() with no O_NOFOLLOW or realpath/is_within check, so the symlink is followed as root.
  5. Primitive: An unbounded, root-privileged read of an attacker-chosen file, whose head is returned to the caller.
  6. Result: The monitoring user obtains root-owned file contents (for example root's /etc/shadow hash), escalating from the monitoring account to root.

Bypass Evidence

This is a novel, previously unfixed code path rather than a bypass of an existing fix, and that was verified directly. The prior fix for the seed advisory lives in lib/lftest.py::_read_fixture, which anchors fixture reads under <realpath(dirname(argv[0]))>/unit-test/; that code is reached only through the --test branch and is never invoked on the production --path path used here. The adjacent logfile disclosure fix confines its caller through lib.logsource/lib.disk.is_within allowlists scoped to logfile plugins, and does not touch kdump. A grep of the kdump plugin for is_within, read_file, _read_fixture, and realpath shows only the single unconfined read_file call and the --test-only lftest usage, confirming no guard exists on the production read path. Refutations were attempted and defeated: the sudoers grant is not argument-pinned (only the librenms validator and Debian's apt-get update --quiet 2 are pinned); --test is not required because the args.TEST is None production branch honours --path; os.path.isfile and open() both follow symlinks; and the output truncation preserves the first lines, so root's hash (line 1 of /etc/shadow) is always disclosed. The behavior was reproduced dynamically end to end against the real plugin functions.

Affected Versions

  • Ecosystem: other (GitHub release distribution; no package-registry coordinate)
  • Package: linuxfabrik/monitoring-plugins
  • Confirmed affected range: = 7.0.0
  • Fix status: fixed in v8.0.0

The kdump plugin was introduced in release v7.0.0 (it is absent from v6.0.0 and earlier), so v7.0.0 is the only affected release. v8.0.0 confines the dmesg read to the crash-dump directory and refuses symlinked dmesg files (monitoring-plugins commit 9d8484c). The project distributes plugins as versioned GitHub releases rather than through a language package registry.

Suggested Fix

Enforce a containment invariant on both the directory scanned and the file actually read so that the plugin can only read within a trusted, canonicalized crash-dump root even when it runs as root. Concretely, canonicalize the resolved dump directory with realpath, reject or refuse to follow any dmesg candidate whose real path escapes that root (for example via lib.disk.is_within(realpath(candidate), [realpath(path)])), and open the dmesg file with symlink following disabled (O_NOFOLLOW). The check must cover every discovery and read edge (resolve_dump_path, find_dmesg_file, and the lib.disk.read_file call in analyze_dump), not just one, and must reject symlinked entries rather than merely validating the fixed filename. As an interim mitigation, restrict the sudoers grant for kdump so the monitoring user cannot pass an arbitrary --path (for example by pinning allowed arguments), which reduces exposure without a code change.


Reported by zx (GitHub: @manus-pi).

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Local
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

CVE ID

No known CVE

Weaknesses

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory. Learn more on MITRE.

Improper Link Resolution Before File Access ('Link Following')

The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource. Learn more on MITRE.

Improper Privilege Management

The product does not properly assign, modify, track, or check privileges for an actor, creating an unintended sphere of control for that actor. Learn more on MITRE.

Credits