
DaemonSet для митигации уязвимости CVE-2026-64564 (SCTPhantom)
Automatic application of mitigation for the CVE-2026-64564 (SCTPhantom) vulnerability in the Linux kernel on all worker nodes of a Yandex Managed Kubernetes cluster.
CVE identifier (CVE ID): CVE-2026-64564
CVE link: https://nvd.nist.gov/vuln/detail/CVE-2026-64564
Original report:
9b2854f86f0b in net/sctp/sm_make_chunk.cBrief description:
SCTPhantom is a use-after-free in the SCTP Dynamic Address Reconfiguration (ASCONF, RFC 5061) subsystem of the Linux kernel that allows an unprivileged local user to gain superuser (root) privileges.
The root cause is an identity mismatch when processing an ASCONF chunk: the DEL-IP operation check is performed against the IPv4 packet source address (S), while further processing uses the transport selected via the Address Parameter (L). Because of this, the ordered sequence
[ Address Parameter L ] [ DEL-IP L ] [ DEL-IP 0.0.0.0 ]
passes the checks and removes transport(L), after which the wildcard operation DEL-IP 0.0.0.0 reuses the already freed pointer as a "persistent path". As a result, asoc->peer.primary_path and asoc->peer.active_path remain dangling pointers to the freed struct sctp_transport, and a subsequent call to getsockopt(SCTP_STATUS) dereferences them.
The vulnerable logic was introduced in Linux 2.6.25 (2007, commit 42e30bf3463c), meaning it has been present in the kernel for about 18 years.
Attack:
CAP_NET_ADMIN or CAP_SYS_ADMIN, works with the default seccomp profile active; ASCONF and AUTH are enabled per-socket via SCTP_ASCONF_SUPPORTED / SCTP_AUTH_SUPPORTED, so the net.sctp.addip_enable sysctl does not need to be changedcall_usermodehelper_exec(), which launches a process in the host's initial namespaceskernel panic, making detection hardercommit_creds()), without shellcode or classic ROPAffected technologies:
net/sctp subsystem (sctp module), ASCONF handling in net/sctp/sm_make_chunk.csctp_diag module (depends on sctp), used for inspecting SCTP socketsThe vulnerability is exploitable only when SCTP is available: if the sctp module is not loaded and its autoloading is blocked, the vector is unavailable.
Targets confirmed by the authors (root obtained):
| Distribution | Kernel |
|---|---|
| Research kernel | Linux 7.2-rc2 |
| OpenCloudOS-family | 6.6.119 |
| Debian 13 | 6.12.95+deb13-amd64 |
| Rocky Linux 9 / RHEL 9 | 5.14 vendor kernel (with the sctp module loaded) |
| Ubuntu 24.04 | 6.8.0-134-generic |
Fixed kernel versions:
| Branch | First fixed version | Stable fix |
|---|---|---|
| 6.6.y | 6.6.148 | fedeb4468987 |
| 6.12.y | 6.12.101 | 74e8f3e7114f |
| 6.18.y | 6.18.42 | 85aca407c560 |
| 7.1.y | 7.1.6 | d136b29bf91d |
| mainline | 7.2-rc5 | 9b2854f86f0b |
Vendor kernels may contain a backport of the fix with an older version string — the kernel version alone is not a sufficient indicator of vulnerability; rely on the vendor's advisory or sources.
Attack vector and severity according to CVSS v4.0:
Base score: 8.5 (HIGH)
Vector: CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
The DaemonSet automatically, on every worker node of the cluster:
sctp and sctp_diag are loaded and with what refcnt. The check is intentionally passive: the DaemonSet does not open an SCTP socket before installing the blacklist, so as not to trigger autoloading of the module on a node where it is not yet loaded.refcnt and live records in /proc/net/sctp/assocs and /proc/net/sctp/eps. Kubernetes itself does not use SCTP, but user workloads may declare protocol: SCTP in a Service/Pod./etc/modprobe.d/blacklist-sctp.conf with install and blacklist rules for sctp and sctp_diag.rmmod for sctp_diag, then sctp (order matters: sctp_diag depends on sctp). If live SCTP connections are found, unloading is skipped unless FORCE_APPLY=true is explicitly set.modprobe sctp is rejected, and that creating an SCTP socket (socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP)) no longer succeeds.The mitigation consists of two independent parts, and on some nodes only one of them is applied.
1. Blacklist (always applied, reliable). After /etc/modprobe.d/blacklist-sctp.conf is created, the sctp module can no longer be loaded — neither automatically on socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP), nor by an explicit modprobe. This closes the vector on nodes where the module has not yet been loaded (the typical state: SCTP is not used by Kubernetes and is loaded only on demand).
2. Unloading the module from memory (not always possible). If sctp is already loaded, unloading it will most often fail. On tested Yandex Managed Kubernetes nodes (Ubuntu 22.04, kernel 5.15.0-181-generic), a freshly loaded sctp module that is not used by anyone already has refcnt=6 with an empty holders list and empty /proc/net/sctp/{assocs,eps}, and rmmod returns ERROR: Module sctp is in use. The counter does not decrease over time.
Hence two implications:
refcnt is not an indicator that SCTP is in use — the DaemonSet outputs it for informational purposes only, and decides whether to unload based on live records in /proc/net/sctp/assocs and /proc/net/sctp/eps.sctp is already resident, the DaemonSet honestly reports ⚠ mitigation applied PARTIALLY. The blacklist is in place there (the module will not return after a reboot), but until reboot the node remains vulnerable. To fully close the vector, such nodes need to be rebooted or the node group recreated.To find such nodes after rollout:
kubectl logs -n kube-system -l app=cve-2026-64564-fix -c apply-fix --prefix | grep -E 'PARTIALLY|STILL LOADED'
Important: the mitigation completely disables SCTP on the node.
Kubernetes and network plugins (Cilium, Calico) do not use SCTP for their own operation, so for the vast majority of clusters the mitigation is safe. However, if the cluster has workloads that use SCTP (for example, telecom applications, VoIP/SS7/Diameter signaling, Service or NetworkPolicy with protocol: SCTP), their traffic will stop working.
Check whether such objects exist in the cluster before rollout:
# Service / Pod с protocol: SCTP
kubectl get svc -A -o json | jq -r '.items[] | select(.spec.ports[]?.protocol=="SCTP") | "\(.metadata.namespace)/\(.metadata.name)"'
kubectl get pods -A -o json | jq -r '.items[] | select(.spec.containers[].ports[]?.protocol=="SCTP") | "\(.metadata.namespace)/\(.metadata.name)"'
# NetworkPolicy с protocol: SCTP
kubectl get netpol -A -o json | jq -r '.items[] | select((.spec.ingress[]?.ports[]?.protocol=="SCTP") or (.spec.egress[]?.ports[]?.protocol=="SCTP")) | "\(.metadata.namespace)/\(.metadata.name)"'
If SCTP is used on the node, the DaemonSet by default does not unload the module from memory, but only installs the blacklist (the module will not return after a node reboot) and writes a warning to the logs. To force unloading and break existing SCTP connections, set in the manifest:
env:
- name: FORCE_APPLY
value: "true"
wget https://raw.githubusercontent.com/yandex-cloud-examples/yc-mk8s-sctphantom-mitigation/main/sctphantom-mitigation-daemonset.yaml
Or clone the repository:
git clone https://github.com/yandex-cloud-examples/yc-mk8s-sctphantom-mitigation.git
cd yc-mk8s-sctphantom-mitigation
kubectl apply -f sctphantom-mitigation-daemonset.yaml
# Проверить статус DaemonSet
kubectl get daemonset -n kube-system cve-2026-64564-fix
# Посмотреть на скольких нодах применен фикс
kubectl get pods -n kube-system -l app=cve-2026-64564-fix -o wide
# Логи initContainer (применение фикса)
kubectl logs -n kube-system -l app=cve-2026-64564-fix -c apply-fix
# Логи основного контейнера (мониторинг)
kubectl logs -n kube-system -l app=cve-2026-64564-fix -c monitor
kubectl logs -n kube-system -l app=cve-2026-64564-fix -c apply-fix --prefix | grep -E 'PARTIALLY|STILL LOADED|Skipping'
=========================================
SCTPhantom (CVE-2026-64564) mitigation for Yandex Managed K8s
Node: demo-ru-central1-a-1
Date: Mon Aug 10 14:00:00 UTC 2026
FORCE_APPLY: false
=========================================
Step 1: Checking modules state before fix...
[LOADED] sctp (refcnt=0) - node is exposed
[UNLOADED] sctp_diag
Step 2: Checking whether SCTP is in use on this node...
✓ No SCTP usage detected (expected: Kubernetes itself does not use SCTP)
Step 3: Applying the mitigation...
Created /etc/modprobe.d/blacklist-sctp.conf
Step 4: Unloading vulnerable modules...
sctp_diag was not loaded (OK)
sctp unloaded
Step 5: Validating the configuration...
Configuration file is in place:
install sctp /bin/false
install sctp_diag /bin/false
blacklist sctp
blacklist sctp_diag
Step 6: Validating that the modules can no longer be loaded...
modprobe sctp_diag is blocked ✓
modprobe sctp is blocked ✓
SCTP socket creation is blocked ✓
Step 7: Validating modules state after fix...
[UNLOADED] sctp_diag ✓
[UNLOADED] sctp ✓
=========================================
✓ CVE-2026-64564 mitigation applied successfully
=========================================
Step 1: Checking modules state before fix...
[UNLOADED] sctp_diag
[LOADED] sctp (refcnt=6) - node is exposed
Step 2: Checking whether SCTP is in use on this node...
sctp is loaded, refcnt=6 (informational only)
✓ No SCTP usage detected (expected: Kubernetes itself does not use SCTP)
Step 4: Unloading vulnerable modules...
sctp_diag was not loaded (OK)
⚠ could not unload sctp (see the note about refcnt below)
Step 6: Validating that the modules can no longer be loaded...
modprobe sctp_diag is blocked ✓
sctp is still resident, skipping the modprobe test
⚠ SCTP socket still available: the sctp module is still resident and could not be unloaded
Step 7: Validating modules state after fix...
[UNLOADED] sctp_diag ✓
[STILL LOADED] sctp (refcnt=6) - WARNING: module could not be unloaded
=========================================
⚠ CVE-2026-64564 mitigation applied PARTIALLY
...
In that case reboot the node or recreate the node group to close the vector.
=========================================
Such a node needs to be rebooted: the blacklist will no longer allow the module to load again.
The most demonstrative check is to reproduce the attacker's position: an unprivileged pod with dropped capabilities, as in the container escape chain.
NODE=<имя ноды>
kubectl run sctp-check --rm -i --restart=Never --image=python:3-slim \
--overrides="{\"spec\":{\"nodeName\":\"$NODE\",\"containers\":[{\"name\":\"c\",\"image\":\"python:3-slim\",\"securityContext\":{\"allowPrivilegeEscalation\":false,\"capabilities\":{\"drop\":[\"ALL\"]}},\"command\":[\"python3\",\"-c\",\"import socket\ntry:\n socket.socket(socket.AF_INET, socket.SOCK_STREAM, 132).close()\n print('SCTP REACHABLE -> EXPLOITABLE')\nexcept OSError as e:\n print('SCTP BLOCKED:', e)\"]}]}}"
Expected output on a protected node:
SCTP BLOCKED: [Errno 93] Protocol not supported
On an unprotected node, the output will be SCTP REACHABLE -> EXPLOITABLE, and the check itself will cause the sctp module to be autoloaded on that node (after which unloading it will most likely be impossible — see the section above). Do not run it on unprotected nodes unless necessary.
You can check the node state manually. Connect to the node via SSH and run:
# Загружены ли уязвимые модули
lsmod | grep -E '^sctp'
# sctp 447488 0 <- refcount 0, можно выгружать
# Доступен ли SCTP-сокет (основной вектор автозагрузки модуля)
python3 -c 'import socket; socket.socket(socket.AF_INET, socket.SOCK_STREAM, 132); print("SCTP available - VULNERABLE")'
# Если выводит "SCTP available - VULNERABLE" - система уязвима
# Если выдает ошибку - система защищена
Caution: running this check on a node where the module is not yet loaded will itself trigger its autoloading. Run it only after applying the mitigation, or deliberately.
Check the configuration:
# Проверить наличие конфигурации блокировки
cat /etc/modprobe.d/blacklist-sctp.conf
# Ожидаемый вывод:
# install sctp /bin/false
# install sctp_diag /bin/false
# blacklist sctp
# blacklist sctp_diag
Check that the vulnerable modules are not loaded:
lsmod | grep -cE '^sctp'
# Ожидаемый вывод: 0
If you need to apply the mitigation on regular hosts, a one-liner:
sudo sh -c 'printf "install sctp /bin/false\ninstall sctp_diag /bin/false\nblacklist sctp\nblacklist sctp_diag\n" > /etc/modprobe.d/blacklist-sctp.conf; rmmod sctp_diag 2>/dev/null; rmmod sctp 2>/dev/null; if lsmod | grep -qE "^sctp "; then echo "STILL-LOADED refcnt=$(cat /sys/module/sctp/refcnt)"; else echo "UNLOADED-OK"; fi'
If you need to remove the DaemonSet:
kubectl delete -f sctphantom-mitigation-daemonset.yaml
Important: Deleting the DaemonSet will not remove the configuration files from the nodes. The file /etc/modprobe.d/blacklist-sctp.conf will remain in place and will continue to protect the system.
To completely remove the fix from the nodes, connect to each node via SSH and manually delete the file:
rm /etc/modprobe.d/blacklist-sctp.conf
Permissions used:
hostPID: true — for access to host processes via nsenterprivileged: true — for writing to /etc and unloading kernel modules/ — for access to the host filesystemImage: ubuntu:22.04
Resources:
Namespace: kube-system
Why the config has both install and blacklist: blacklist blocks loading by alias (including autoloading on socket(..., IPPROTO_SCTP)), but does not prevent an explicit modprobe sctp. The install sctp /bin/false line closes that path too.
Why the mitigation is not a substitute for a kernel update: blocking the module removes the vector, but the bug itself remains in the kernel. The permanent solution is updating the kernel to a fixed version (see the table above) or updating node images and recreating the node group.
Apache License 2.0
See LICENSE for details.
If you encounter any issues, create an issue in the repository.