CVE-2026-104795
Insights-runtime-extractor: insights-runtime-extractor: container escape via privileged binary execution
The insights-runtime-extractor runs as a privileged DaemonSet (hostPID: true)
that scans container processes for runtime fingerprinting. When scanning a
container, the coordinator:
- fork()s a child process
- setns() into the target container's PID and mount namespaces
- fchdir() back to an orphaned FD pointing at the extractor's own root
filesystem (where
/crio.sockis hostPath-mounted) - Calls only
seteuid(target_uid)— changing the effective UID but leaving the real UID and saved-set-UID at 0 and retaining the permitted capability set - The
check_no_privileged_perms()guard inspects only the Effective capability set (so it passes), and is skipped entirely when the target runs as uid 0 - The
VersionExecutablefingerprint invokesfpr_kind_executablewithprocess.command_line[0]— the target's argv[0], read from/proc/<pid>/cmdlineand fully controlled by the workload fpr_kind_executablecallsexec.Command(executable, "--version").Output()— executing the workload's own binary inside the workload's mount namespace while ruid=0 in the host user namespace
The executed process can call setuid(0) to restore full root with
CAP_SYS_ADMIN, and inherits the orphaned cwd which resolves into the extractor
container's filesystem containing the host CRI-O socket.
Affected Code
1. Insufficient privilege drop (Rust — extractor coordinator)
File: extractor/src/insights_runtime_extractor.rs
// Lines 139-146 — seteuid-only drop, guard skipped for root containers
join_process_namespaces(process.pid)?;
change_dir(current_dir);
switch_user(*process.uid)?;
if *process.uid != 0 {
perms::check_no_privileged_perms()
.expect("Must not have privileged permissions to run fingerprints");
}
fingerprint::run_fingerprints(&config, out_dir, &process);
// Lines 182-185 — only effective UID changed; ruid/suid stay 0
fn switch_user(uid: u32) -> Result<(), ScannerError> {
debug!("... Becoming user: {uid}");
seteuid(uid.into()).map_err(|e| ScannerError::Errno(e))
}
2. Execution of untrusted binary (Go — fingerprint)
File: fingerprints/pkg/utils/file.go
// Lines 75-80 — executes a path taken verbatim from workload's argv[0]
func GetExecutableVersionOutput(executable string) (string, error) {
out, err := exec.Command(executable, "--version").Output()
if err != nil {
return "", err
}
return string(out), nil
}
3. Workload-controlled path forwarded (Rust — version_executable)
File: version_executable.rs
// Lines 29-34 — workload-controlled command_line[0] forwarded to fingerprint
return Some(vec![
fpr_kind_executable,
out_dir.to_string(),
String::from(&process.command_line[0]),
String::from(&version_executable.runtime_kind_name),
]);
Attack Pattern
Attacker: Any tenant able to schedule a pod (unprivileged, any namespace) Victim: The node running the insights-runtime-extractor DaemonSet Trigger: Automatic — the DaemonSet scans all container processes on the node
Attack sequence:
- Attacker creates a pod whose process name matches a configured
versioned-executable pattern (
node,python,python3,MainThread, or containsjava) - The pod's main binary (at the path matching argv[0]) is a malicious
executable that calls
setuid(0)to restore root - When the insights operator triggers a scan, the DaemonSet forks, enters the attacker's mount namespace, and executes the attacker's binary with ruid=0
- The attacker's code regains full root with
CAP_SYS_ADMINin the host user namespace - The inherited cwd contains the host CRI-O socket → full node compromise
Attack profile
How easy it is to exploit
- Attack vector
- Local
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
8.8
Insights-runtime-extractor: insights-runtime-extractor: container escape via privileged binary execution
The insights-runtime-extractor runs as a privileged DaemonSet (hostPID: true)
that scans container processes for runtime fingerprinting. When scanning a
container, the coordinator:
- fork()s a child process
- setns() into the target container's PID and mount namespaces
- fchdir() back to an orphaned FD pointing at the extractor's own root
filesystem (where
/crio.sockis hostPath-mounted) - Calls only
seteuid(target_uid)— changing the effective UID but leaving the real UID and saved-set-UID at 0 and retaining the permitted capability set - The
check_no_privileged_perms()guard inspects only the Effective capability set (so it passes), and is skipped entirely when the target runs as uid 0 - The
VersionExecutablefingerprint invokesfpr_kind_executablewithprocess.command_line[0]— the target's argv[0], read from/proc/<pid>/cmdlineand fully controlled by the workload fpr_kind_executablecallsexec.Command(executable, "--version").Output()— executing the workload's own binary inside the workload's mount namespace while ruid=0 in the host user namespace
The executed process can call setuid(0) to restore full root with
CAP_SYS_ADMIN, and inherits the orphaned cwd which resolves into the extractor
container's filesystem containing the host CRI-O socket.
Affected Code
1. Insufficient privilege drop (Rust — extractor coordinator)
File: extractor/src/insights_runtime_extractor.rs
// Lines 139-146 — seteuid-only drop, guard skipped for root containers
join_process_namespaces(process.pid)?;
change_dir(current_dir);
switch_user(*process.uid)?;
if *process.uid != 0 {
perms::check_no_privileged_perms()
.expect("Must not have privileged permissions to run fingerprints");
}
fingerprint::run_fingerprints(&config, out_dir, &process);
// Lines 182-185 — only effective UID changed; ruid/suid stay 0
fn switch_user(uid: u32) -> Result<(), ScannerError> {
debug!("... Becoming user: {uid}");
seteuid(uid.into()).map_err(|e| ScannerError::Errno(e))
}
2. Execution of untrusted binary (Go — fingerprint)
File: fingerprints/pkg/utils/file.go
// Lines 75-80 — executes a path taken verbatim from workload's argv[0]
func GetExecutableVersionOutput(executable string) (string, error) {
out, err := exec.Command(executable, "--version").Output()
if err != nil {
return "", err
}
return string(out), nil
}
3. Workload-controlled path forwarded (Rust — version_executable)
File: version_executable.rs
// Lines 29-34 — workload-controlled command_line[0] forwarded to fingerprint
return Some(vec![
fpr_kind_executable,
out_dir.to_string(),
String::from(&process.command_line[0]),
String::from(&version_executable.runtime_kind_name),
]);
Attack Pattern
Attacker: Any tenant able to schedule a pod (unprivileged, any namespace) Victim: The node running the insights-runtime-extractor DaemonSet Trigger: Automatic — the DaemonSet scans all container processes on the node
Attack sequence:
- Attacker creates a pod whose process name matches a configured
versioned-executable pattern (
node,python,python3,MainThread, or containsjava) - The pod's main binary (at the path matching argv[0]) is a malicious
executable that calls
setuid(0)to restore root - When the insights operator triggers a scan, the DaemonSet forks, enters the attacker's mount namespace, and executes the attacker's binary with ruid=0
- The attacker's code regains full root with
CAP_SYS_ADMINin the host user namespace - The inherited cwd contains the host CRI-O socket → full node compromise
- Record
CVE-2026-104795- CVSS 3.1
- 8.8 High
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H - Weakness
- CWE-250
- Updated
- 2026-11-02
EUVDEUVD-2026-100981
8.8
The insights-runtime-extractor runs as a privileged DaemonSet (hostPID: true)
that scans container processes for runtime fingerprinting. When scanning a
container, the coordinator:
- fork()s a child process
- setns() into the target container's PID and mount namespaces
- fchdir() back to an orphaned FD pointing at the extractor's own root
filesystem (where
/crio.sockis hostPath-mounted) - Calls only
seteuid(target_uid)— changing the effective UID but leaving the real UID and saved-set-UID at 0 and retaining the permitted capability set - The
check_no_privileged_perms()guard inspects only the Effective capability set (so it passes), and is skipped entirely when the target runs as uid 0 - The
VersionExecutablefingerprint invokesfpr_kind_executablewithprocess.command_line[0]— the target's argv[0], read from/proc/<pid>/cmdlineand fully controlled by the workload fpr_kind_executablecallsexec.Command(executable, "--version").Output()— executing the workload's own binary inside the workload's mount namespace while ruid=0 in the host user namespace
The executed process can call setuid(0) to restore full root with
CAP_SYS_ADMIN, and inherits the orphaned cwd which resolves into the extractor
container's filesystem containing the host CRI-O socket.
Affected Code
1. Insufficient privilege drop (Rust — extractor coordinator)
File: extractor/src/insights_runtime_extractor.rs
// Lines 139-146 — seteuid-only drop, guard skipped for root containers
join_process_namespaces(process.pid)?;
change_dir(current_dir);
switch_user(*process.uid)?;
if *process.uid != 0 {
perms::check_no_privileged_perms()
.expect("Must not have privileged permissions to run fingerprints");
}
fingerprint::run_fingerprints(&config, out_dir, &process);
// Lines 182-185 — only effective UID changed; ruid/suid stay 0
fn switch_user(uid: u32) -> Result<(), ScannerError> {
debug!("... Becoming user: {uid}");
seteuid(uid.into()).map_err(|e| ScannerError::Errno(e))
}
2. Execution of untrusted binary (Go — fingerprint)
File: fingerprints/pkg/utils/file.go
// Lines 75-80 — executes a path taken verbatim from workload's argv[0]
func GetExecutableVersionOutput(executable string) (string, error) {
out, err := exec.Command(executable, "--version").Output()
if err != nil {
return "", err
}
return string(out), nil
}
3. Workload-controlled path forwarded (Rust — version_executable)
File: version_executable.rs
// Lines 29-34 — workload-controlled command_line[0] forwarded to fingerprint
return Some(vec![
fpr_kind_executable,
out_dir.to_string(),
String::from(&process.command_line[0]),
String::from(&version_executable.runtime_kind_name),
]);
Attack Pattern
Attacker: Any tenant able to schedule a pod (unprivileged, any namespace) Victim: The node running the insights-runtime-extractor DaemonSet Trigger: Automatic — the DaemonSet scans all container processes on the node
Attack sequence:
- Attacker creates a pod whose process name matches a configured
versioned-executable pattern (
node,python,python3,MainThread, or containsjava) - The pod's main binary (at the path matching argv[0]) is a malicious
executable that calls
setuid(0)to restore root - When the insights operator triggers a scan, the DaemonSet forks, enters the attacker's mount namespace, and executes the attacker's binary with ruid=0
- The attacker's code regains full root with
CAP_SYS_ADMINin the host user namespace - The inherited cwd contains the host CRI-O socket → full node compromise
- Record
EUVD-2026-100981- CVSS 3.1
- 8.8 High
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-98f3-7g2j-289q
8.8
The insights-runtime-extractor runs as a privileged DaemonSet (`hostPID: true`) that scans...
The insights-runtime-extractor runs as a privileged DaemonSet (hostPID: true)
that scans container processes for runtime fingerprinting. When scanning a
container, the coordinator:
- fork()s a child process
- setns() into the target container's PID and mount namespaces
- fchdir() back to an orphaned FD pointing at the extractor's own root
filesystem (where
/crio.sockis hostPath-mounted) - Calls only
seteuid(target_uid)— changing the effective UID but leaving the real UID and saved-set-UID at 0 and retaining the permitted capability set - The
check_no_privileged_perms()guard inspects only the Effective capability set (so it passes), and is skipped entirely when the target runs as uid 0 - The
VersionExecutablefingerprint invokesfpr_kind_executablewithprocess.command_line[0]— the target's argv[0], read from/proc/<pid>/cmdlineand fully controlled by the workload fpr_kind_executablecallsexec.Command(executable, "--version").Output()— executing the workload's own binary inside the workload's mount namespace while ruid=0 in the host user namespace
The executed process can call setuid(0) to restore full root with
CAP_SYS_ADMIN, and inherits the orphaned cwd which resolves into the extractor
container's filesystem containing the host CRI-O socket.
Affected Code
1. Insufficient privilege drop (Rust — extractor coordinator)
File: extractor/src/insights_runtime_extractor.rs
// Lines 139-146 — seteuid-only drop, guard skipped for root containers
join_process_namespaces(process.pid)?;
change_dir(current_dir);
switch_user(*process.uid)?;
if *process.uid != 0 {
perms::check_no_privileged_perms()
.expect("Must not have privileged permissions to run fingerprints");
}
fingerprint::run_fingerprints(&config, out_dir, &process);
// Lines 182-185 — only effective UID changed; ruid/suid stay 0
fn switch_user(uid: u32) -> Result<(), ScannerError> {
debug!("... Becoming user: {uid}");
seteuid(uid.into()).map_err(|e| ScannerError::Errno(e))
}
2. Execution of untrusted binary (Go — fingerprint)
File: fingerprints/pkg/utils/file.go
// Lines 75-80 — executes a path taken verbatim from workload's argv[0]
func GetExecutableVersionOutput(executable string) (string, error) {
out, err := exec.Command(executable, "--version").Output()
if err != nil {
return "", err
}
return string(out), nil
}
3. Workload-controlled path forwarded (Rust — version_executable)
File: version_executable.rs
// Lines 29-34 — workload-controlled command_line[0] forwarded to fingerprint
return Some(vec![
fpr_kind_executable,
out_dir.to_string(),
String::from(&process.command_line[0]),
String::from(&version_executable.runtime_kind_name),
]);
Attack Pattern
Attacker: Any tenant able to schedule a pod (unprivileged, any namespace) Victim: The node running the insights-runtime-extractor DaemonSet Trigger: Automatic — the DaemonSet scans all container processes on the node
Attack sequence:
- Attacker creates a pod whose process name matches a configured
versioned-executable pattern (
node,python,python3,MainThread, or containsjava) - The pod's main binary (at the path matching argv[0]) is a malicious
executable that calls
setuid(0)to restore root - When the insights operator triggers a scan, the DaemonSet forks, enters the attacker's mount namespace, and executes the attacker's binary with ruid=0
- The attacker's code regains full root with
CAP_SYS_ADMINin the host user namespace - The inherited cwd contains the host CRI-O socket → full node compromise
- Record
GHSA-98f3-7g2j-289q- CVSS 3.1
- 8.8 High
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H - Weakness
- CWE-250
- Updated
- 2026-11-02
NVD
8.8
The insights-runtime-extractor runs as a privileged DaemonSet (hostPID: true)
that scans container processes for runtime fingerprinting. When scanning a
container, the coordinator:
- fork()s a child process
- setns() into the target container's PID and mount namespaces
- fchdir() back to an orphaned FD pointing at the extractor's own root
filesystem (where
/crio.sockis hostPath-mounted) - Calls only
seteuid(target_uid)— changing the effective UID but leaving the real UID and saved-set-UID at 0 and retaining the permitted capability set - The
check_no_privileged_perms()guard inspects only the Effective capability set (so it passes), and is skipped entirely when the target runs as uid 0 - The
VersionExecutablefingerprint invokesfpr_kind_executablewithprocess.command_line[0]— the target's argv[0], read from/proc/<pid>/cmdlineand fully controlled by the workload fpr_kind_executablecallsexec.Command(executable, "--version").Output()— executing the workload's own binary inside the workload's mount namespace while ruid=0 in the host user namespace
The executed process can call setuid(0) to restore full root with
CAP_SYS_ADMIN, and inherits the orphaned cwd which resolves into the extractor
container's filesystem containing the host CRI-O socket.
Affected Code
1. Insufficient privilege drop (Rust — extractor coordinator)
File: extractor/src/insights_runtime_extractor.rs
// Lines 139-146 — seteuid-only drop, guard skipped for root containers
join_process_namespaces(process.pid)?;
change_dir(current_dir);
switch_user(*process.uid)?;
if *process.uid != 0 {
perms::check_no_privileged_perms()
.expect("Must not have privileged permissions to run fingerprints");
}
fingerprint::run_fingerprints(&config, out_dir, &process);
// Lines 182-185 — only effective UID changed; ruid/suid stay 0
fn switch_user(uid: u32) -> Result<(), ScannerError> {
debug!("... Becoming user: {uid}");
seteuid(uid.into()).map_err(|e| ScannerError::Errno(e))
}
2. Execution of untrusted binary (Go — fingerprint)
File: fingerprints/pkg/utils/file.go
// Lines 75-80 — executes a path taken verbatim from workload's argv[0]
func GetExecutableVersionOutput(executable string) (string, error) {
out, err := exec.Command(executable, "--version").Output()
if err != nil {
return "", err
}
return string(out), nil
}
3. Workload-controlled path forwarded (Rust — version_executable)
File: version_executable.rs
// Lines 29-34 — workload-controlled command_line[0] forwarded to fingerprint
return Some(vec![
fpr_kind_executable,
out_dir.to_string(),
String::from(&process.command_line[0]),
String::from(&version_executable.runtime_kind_name),
]);
Attack Pattern
Attacker: Any tenant able to schedule a pod (unprivileged, any namespace) Victim: The node running the insights-runtime-extractor DaemonSet Trigger: Automatic — the DaemonSet scans all container processes on the node
Attack sequence:
- Attacker creates a pod whose process name matches a configured
versioned-executable pattern (
node,python,python3,MainThread, or containsjava) - The pod's main binary (at the path matching argv[0]) is a malicious
executable that calls
setuid(0)to restore root - When the insights operator triggers a scan, the DaemonSet forks, enters the attacker's mount namespace, and executes the attacker's binary with ruid=0
- The attacker's code regains full root with
CAP_SYS_ADMINin the host user namespace - The inherited cwd contains the host CRI-O socket → full node compromise
- Record
CVE-2026-104795- CVSS 3.1
- 8.8 High
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H - Weakness
- CWE-250
- Updated
- 2026-11-02
Affected products
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-104795 is a high vulnerability (CVSS 8.8) in Red Hat Openshift Container Platform 4.19, published October 14, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.18%, the 7th percentile. No source names a fixed version.
References
access.redhat.com/errata/RHSA-2026:83702
access.redhat.com/errata/RHSA-2026:89198
access.redhat.com/errata/RHSA-2026:89341
access.redhat.com/errata/RHSA-2026:89655
access.redhat.com/security/cve/CVE-2026-104795
bugzilla.redhat.com/show_bug.cgi?id=2543917
nvd.nist.gov/vuln/detail/CVE-2026-104795
github.com/advisories/GHSA-98f3-7g2j-289q