This is a bad elevator in the staff-only hallway, not an unlocked front door
CVE-2025-26597 is a heap/buffer overflow in XkbChangeTypesOfKey() in X.Org Server and Xwayland. The bug appears when the function is first driven with groups=0, shrinking the key symbols table to zero while leaving key actions sized for the old layout; a later non-zero groups call can then overflow the actions buffer. Upstream fixes landed in xorg-server 21.1.16 and xwayland 24.1.6, while distros backported patches into their own package streams such as Ubuntu xserver-xorg-core and Debian security updates.
The vendor's 7.8/HIGH score is technically fair for the memory-corruption impact, but it overstates operational urgency for most enterprises. This is a local, post-initial-access flaw that requires low-privileged code execution on a Linux host running X.Org/Xwayland, and modern deployments often run Xorg rootless or use Xwayland in the logged-in user's context, which sharply reduces blast radius from 'root LPE everywhere' to 'useful only on a subset of workstation/VDI/VNC systems.'
4 steps from start to impact.
Get code execution on a GUI-capable Linux host
- Low-privileged code execution on the target host
- Target is running Linux/Unix with X.Org or Xwayland components present
- This implies the attacker has already cleared initial access
- Headless servers and many containerized workloads do not run X.Org/Xwayland at all
Reach the XKB code path in the display stack
XkbChangeTypesOfKey() path. In practice that means a local desktop, VDI seat, kiosk, remote GUI service, or VNC/X11-enabled workflow where the attacker can talk to the display server from their session.- An accessible X.Org or Xwayland process
- Ability to send crafted XKB requests from the local context
- Wayland-heavy environments reduce direct exposure to classic rootful Xorg
- Session isolation, sandboxing, and missing display access can block low-value footholds from reaching the vulnerable server
Trigger the zero-group then non-zero-group state mismatch
XkbChangeTypesOfKey() first with groups=0 to desynchronize buffer sizes, then again with a non-zero group count to overflow the heap buffer. The primitive is memory corruption inside the display server, which can produce crashes or controlled overwrite depending on allocator state and hardening.- Precise control of XKB requests
- Target build and runtime state conducive to stable corruption
- Reliable exploitation is harder than crashing the server
- Heap behavior, distro hardening, and process sandboxing reduce exploit reliability
Execute in the display server's security context
- Successful memory-corruption exploitation
- Vulnerable server running with privileges worth escalating into
- Rootless Xorg defaults and Xwayland user-context execution often cap the impact
- Even when Xorg is installed, many enterprise Linux servers never expose a privileged graphical session
Xorg, Xwayland, VNC, kiosk builds, and Linux VDI actually exist, and do it now so you can constrain the true exposure set. For a MEDIUM finding there is no mitigation SLA, but you should complete exposure mapping early and then drive patching inside the 365-day remediation window.Xorg.wrap is not forcing needs_root_rights=yes unless there is a hardware/legacy requirement. This directly cuts the exploit payoff from root LPE to user-context compromise; validate and enforce during the next desktop engineering cycle, with patching still completed inside the 365-day remediation window.Xorg/Xwayland restarts, coredumps, and suspicious child processes spawned from display-server contexts. That helps you catch exploitation attempts on the smaller but higher-value GUI estate while patching proceeds within the 365-day remediation window.- Perimeter controls like WAFs or internet-facing NGFW rules do not help; this is not a network-exposed bug.
- MFA does nothing once the attacker already has local code execution on the box.
- Relying on KEV absence is not a control; it only tells you CISA has not listed active exploitation evidence.
- Generic vulnerability scans alone are insufficient because they usually miss the key runtime question: is Xorg/Xwayland present and running with elevated rights?
The supporting signals.
| In-the-wild status | No confirmed active exploitation found in the sources reviewed; not present in CISA KEV. |
|---|---|
| Public PoC availability | No public PoC identified from vendor/ZDI-linked material or major public exploit indexes reviewed as of this assessment. |
| EPSS | 0.00029 from the supplied intel — effectively near-floor exploit probability in the next 30 days. |
| KEV status | Not KEV-listed; CISA's KEV catalog does not include CVE-2025-26597. |
| CVSS meaning | AV:L/AC:L/PR:L/UI:N means local attacker, low complexity, low privileges, no clicks — classic post-compromise LPE scoring. |
| Affected versions | Upstream fixed releases indicate affected upstream lines are before xorg-server 21.1.16 and before xwayland 24.1.6. |
| Fixed versions | Upstream: xorg-server 21.1.16, xwayland 24.1.6. Debian and Ubuntu ship backported fixes in package versions such as Debian bookworm 2:21.1.7-3+deb12u11 and Ubuntu noble 2:21.1.12-1ubuntu1.2. |
| Exposure reality | Narrow enterprise population: relevant mainly to Linux workstations, VDI, kiosks, lab machines, and remote-GUI/VNC estates — not typical headless servers or network edge devices. |
| Privilege context amplifier/reducer | The same bug swings from root LPE on rootful Xorg to user-session compromise on rootless Xorg/Xwayland. That runtime detail is the main reason this gets downgraded. |
| Disclosure and credit | Publicly disclosed 2025-02-25; ZDI credits Jan-Niklas Sohn and notes the vendor report date of 2024-11-15. |
Sources.
Why this verdict
- Down one full band for attacker position:
AV:L+PR:Lmeans the adversary already has code execution as a local user. That implies prior compromise and removes this from the 'stop the internet' category. - Down again for reachable population: X.Org/Xwayland is concentrated in Linux desktops, VDI, kiosks, dev workstations, and some VNC estates. For most 10,000-host enterprises, that is a much smaller slice than the total server fleet.
- Down for blast-radius uncertainty: modern Xorg can drop elevated rights automatically and Xwayland commonly runs in the user's session. When the target process is rootless, the exploit is still bad but no longer a universal root escalation.
- Not dropped further because impact can still be real: on legacy/rootful Xorg, VNC-backed desktops, or older workstation images, successful exploitation can yield root with no user interaction.
- No threat-intel amplifier: no KEV listing, no active exploitation evidence, and the supplied EPSS is extremely low.
Why not higher?
A higher rating would require either broader pre-auth reachability or real-world exploitation pressure. This CVE has neither: it is not a remote entry point, it is not KEV-listed, and its exploit chain begins only after a low-privileged foothold on the host already exists. Even the worst-case outcome depends on whether the target still runs a privileged X server.
Why not lower?
This is still memory corruption in a privileged or semi-privileged display component, not a theoretical edge case. Enterprises with Linux desktops, engineering workstations, kiosk images, shared lab systems, or rootful VNC/Xorg stacks can absolutely turn this into a meaningful local escalation path, so treating it as backlog-only hygiene would undercall the risk.
Crowdsourced verification payload.
Run this on the target Linux host as a normal user; root is not required unless your package manager blocks local metadata reads. Save as check-cve-2025-26597.sh and execute with bash check-cve-2025-26597.sh. The script uses distro-aware package checks for Debian/Ubuntu where backports matter, and falls back to upstream binary versions elsewhere.
#!/usr/bin/env bash
# check-cve-2025-26597.sh
# Detect likely exposure to CVE-2025-26597 on Linux hosts.
# Output: VULNERABLE / PATCHED / UNKNOWN
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -u
status="UNKNOWN"
reason="Could not determine installed/fixed package versions"
have_cmd() { command -v "$1" >/dev/null 2>&1; }
version_ge() {
# usage: version_ge current fixed
if have_cmd dpkg; then
dpkg --compare-versions "$1" ge "$2"
return $?
fi
# rpm-ish / generic fallback using sort -V
[ "$(printf '%s\n%s\n' "$2" "$1" | sort -V | tail -n1)" = "$1" ]
}
get_os_id() {
if [ -r /etc/os-release ]; then
. /etc/os-release
echo "${ID:-unknown}:${VERSION_CODENAME:-${VERSION_ID:-unknown}}"
else
echo "unknown:unknown"
fi
}
check_debian_ubuntu() {
local os="$1"
local id="${os%%:*}"
local codename="${os#*:}"
local xorg_ver=""
local xwayland_ver=""
local xorg_fixed=""
local xwayland_fixed=""
local vulnerable=0
local unknown=0
if have_cmd dpkg-query; then
xorg_ver="$(dpkg-query -W -f='${Version}' xserver-xorg-core 2>/dev/null || true)"
xwayland_ver="$(dpkg-query -W -f='${Version}' xwayland 2>/dev/null || true)"
fi
case "$id:$codename" in
ubuntu:noble) xorg_fixed='2:21.1.12-1ubuntu1.2'; xwayland_fixed='2:23.2.6-1ubuntu0.4' ;;
ubuntu:jammy) xorg_fixed='2:21.1.4-2ubuntu1.7~22.04.13'; xwayland_fixed='2:22.1.1-1ubuntu0.17' ;;
ubuntu:focal) xorg_fixed='2:1.20.13-1ubuntu1~20.04.19'; xwayland_fixed='' ;;
ubuntu:oracular) xorg_fixed='2:21.1.13-2ubuntu1.2'; xwayland_fixed='2:24.1.2-1ubuntu0.4' ;;
ubuntu:plucky) xorg_fixed='2:21.1.16-1ubuntu1'; xwayland_fixed='2:24.1.5-1ubuntu1' ;;
debian:bookworm) xorg_fixed='2:21.1.7-3+deb12u11'; xwayland_fixed='' ;;
debian:bullseye) xorg_fixed='2:1.20.11-1+deb11u17'; xwayland_fixed='' ;;
debian:trixie) xorg_fixed='2:21.1.16-1.3+deb13u1'; xwayland_fixed='2:24.1.6-1' ;;
*) return 2 ;;
esac
if [ -n "$xorg_ver" ] && [ -n "$xorg_fixed" ]; then
if version_ge "$xorg_ver" "$xorg_fixed"; then
:
else
vulnerable=1
reason="xserver-xorg-core $xorg_ver is below fixed $xorg_fixed"
fi
elif [ -n "$xorg_fixed" ]; then
unknown=1
fi
if [ -n "$xwayland_fixed" ]; then
if [ -n "$xwayland_ver" ]; then
if version_ge "$xwayland_ver" "$xwayland_fixed"; then
:
else
vulnerable=1
reason="xwayland $xwayland_ver is below fixed $xwayland_fixed"
fi
else
unknown=1
fi
fi
if [ "$vulnerable" -eq 1 ]; then
status="VULNERABLE"
return 1
fi
if [ -n "$xorg_ver" ] || [ -n "$xwayland_ver" ]; then
status="PATCHED"
reason="Installed Debian/Ubuntu package versions meet known fixed thresholds"
return 0
fi
if [ "$unknown" -eq 1 ]; then
status="UNKNOWN"
reason="Expected package names not installed or not queryable"
return 2
fi
return 2
}
extract_upstream_ver() {
# Pull first semantic-looking version from stdin
grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' | head -n1
}
check_upstream_fallback() {
local xorg_up=""
local xwayland_up=""
local xorg_vuln=0
local xwayland_vuln=0
local found=0
if have_cmd Xorg; then
xorg_up="$(Xorg -version 2>&1 | extract_upstream_ver || true)"
if [ -n "$xorg_up" ]; then
found=1
if version_ge "$xorg_up" '21.1.16'; then
:
else
xorg_vuln=1
reason="Xorg upstream version $xorg_up is below 21.1.16"
fi
fi
fi
if have_cmd Xwayland; then
xwayland_up="$(Xwayland -version 2>&1 | extract_upstream_ver || true)"
if [ -n "$xwayland_up" ]; then
found=1
if version_ge "$xwayland_up" '24.1.6'; then
:
else
xwayland_vuln=1
reason="Xwayland upstream version $xwayland_up is below 24.1.6"
fi
fi
fi
if [ "$found" -eq 0 ]; then
status="UNKNOWN"
reason="Neither Xorg nor Xwayland binaries were found"
return 2
fi
if [ "$xorg_vuln" -eq 1 ] || [ "$xwayland_vuln" -eq 1 ]; then
status="VULNERABLE"
return 1
fi
status="PATCHED"
reason="Installed upstream binary versions meet fixed thresholds"
return 0
}
main() {
local os
os="$(get_os_id)"
if check_debian_ubuntu "$os"; then
echo "$status - $reason"
exit 0
else
rc=$?
if [ "$rc" -eq 1 ]; then
echo "$status - $reason"
exit 1
fi
fi
check_upstream_fallback
rc=$?
echo "$status - $reason"
exit "$rc"
}
main