Last active
May 3, 2026 18:39
-
-
Save m3nu/c19269ef4fd6fa53b03eb388f77464da to your computer and use it in GitHub Desktop.
Ansible playbook for CVE-2026-31431 mitigation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Mitigation for CVE-2026-31431 ("Copy Fail") — algif_aead LPE | |
| # https://xint.io/blog/copy-fail-linux-distributions | |
| # Apply: ansible-playbook playbooks/cve-2026-31431.yml | |
| # | |
| # One mitigation per OS family, plus a cleanup pass for hosts that received | |
| # the (now-abandoned) systemd seccomp drop-ins in earlier runs. | |
| # | |
| # Tags: | |
| # cve-kernel RHEL/Alma 9, 10: add initcall_blacklist=algif_aead_init | |
| # to GRUB. No reboot — the arg becomes active on the | |
| # next boot. | |
| # cve-kernel-reboot Superset of cve-kernel: writes the arg AND reboots | |
| # (idempotent — skipped if already in /proc/cmdline). | |
| # cve-rmmod Debian family: install /etc/modprobe.d/disable-algif.conf | |
| # and rmmod algif_aead so the module is gone now and | |
| # cannot be reloaded. | |
| # cve-systemd-remove All hosts: remove sshd.service.d/ and [email protected]/ | |
| # cve-2026-31431.conf drop-ins, daemon-reload, restart | |
| # sshd. [email protected] is intentionally not restarted | |
| # (would kill rootless pods). | |
| # | |
| # Default (no --tags): every block runs, gated by ansible_os_family / | |
| # ansible_distribution_major_version. RHEL hosts get kernel + reboot + | |
| # systemd-remove; Debian hosts get rmmod + systemd-remove. | |
| # | |
| # Cleanup once the vendor kernel is patched: | |
| # RHEL/Alma: grubby --update-kernel=ALL --remove-args=initcall_blacklist=algif_aead_init && reboot | |
| # Debian: rm /etc/modprobe.d/disable-algif.conf | |
| --- | |
| - hosts: all | |
| gather_facts: true | |
| become: yes | |
| serial: 1 | |
| tasks: | |
| # --- kernel initcall blacklist (RHEL/Alma 9 and 10 only) --- | |
| # Prevents algif_aead_init from running at boot so the AEAD handler is | |
| # never registered with af_alg; bind(AF_ALG, ..., "aead") returns ENOENT | |
| # system-wide. algif_aead is built into vmlinux on these releases, so a | |
| # modprobe blacklist would do nothing. | |
| - name: Apply kernel initcall blacklist mitigation (RHEL/Alma 9 and 10) | |
| tags: [cve-kernel-reboot] | |
| when: | |
| - ansible_os_family == 'RedHat' | |
| - ansible_distribution_major_version in ['9', '10'] | |
| block: | |
| - name: Read current GRUB kernel args | |
| ansible.builtin.command: | |
| cmd: grubby --info=ALL | |
| register: grubby_info | |
| changed_when: false | |
| tags: [cve-kernel, cve-kernel-reboot] | |
| - name: Add initcall_blacklist=algif_aead_init to all kernels | |
| ansible.builtin.command: | |
| cmd: grubby --update-kernel=ALL --args=initcall_blacklist=algif_aead_init | |
| when: "'initcall_blacklist=algif_aead_init' not in grubby_info.stdout" | |
| tags: [cve-kernel, cve-kernel-reboot] | |
| - name: Check whether running kernel has the mitigation active | |
| ansible.builtin.command: | |
| cmd: grep -qw initcall_blacklist=algif_aead_init /proc/cmdline | |
| register: cmdline_check | |
| changed_when: false | |
| failed_when: false | |
| - name: Reboot to activate the kernel mitigation | |
| when: cmdline_check.rc != 0 | |
| block: | |
| - name: Trigger reboot | |
| ansible.builtin.shell: sleep 3; reboot | |
| ignore_errors: true | |
| changed_when: false | |
| async: 1 | |
| poll: 0 | |
| - name: Wait for server to come back after reboot | |
| ansible.builtin.wait_for_connection: | |
| timeout: 900 | |
| delay: 40 | |
| register: reboot_result | |
| - name: Reboot time | |
| ansible.builtin.debug: | |
| msg: "The system rebooted in {{ reboot_result.elapsed }} seconds." | |
| - name: Verify running kernel now includes the mitigation | |
| ansible.builtin.command: | |
| cmd: grep -qw initcall_blacklist=algif_aead_init /proc/cmdline | |
| changed_when: false | |
| # --- Debian family modprobe blacklist + unload --- | |
| # algif_aead is a loadable module on Debian/Ubuntu, so an install rule | |
| # plus rmmod removes the attack surface immediately and prevents | |
| # reloads (including auto-load via AF_ALG bind requests). | |
| - name: Apply algif_aead modprobe blacklist + unload (Debian family) | |
| tags: [cve-rmmod] | |
| when: ansible_os_family == 'Debian' | |
| block: | |
| - name: Write modprobe blacklist for algif_aead | |
| ansible.builtin.copy: | |
| dest: /etc/modprobe.d/disable-algif.conf | |
| owner: root | |
| group: root | |
| mode: '0644' | |
| content: | | |
| # CVE-2026-31431 mitigation. Prevents algif_aead from being loaded. | |
| install algif_aead /bin/false | |
| - name: Unload algif_aead if currently loaded | |
| ansible.builtin.command: rmmod algif_aead | |
| register: rmmod_result | |
| failed_when: | |
| - rmmod_result.rc != 0 | |
| - "'not currently loaded' not in (rmmod_result.stderr | default(''))" | |
| - "'is not currently loaded' not in (rmmod_result.stderr | default(''))" | |
| changed_when: rmmod_result.rc == 0 | |
| - name: Verify algif_aead is not loaded | |
| ansible.builtin.shell: "! lsmod | awk '{print $1}' | grep -qx algif_aead" | |
| changed_when: false | |
| # --- remove systemd AF_ALG drop-ins --- | |
| # Earlier iterations of this playbook installed RestrictAddressFamilies=~AF_ALG | |
| # drop-ins on sshd.service and [email protected]. The seccomp filter caused | |
| # issues for legitimate workloads and has been abandoned. Removing the | |
| # drop-ins is safe to run on hosts that never had them — file: state=absent | |
| # is a no-op there. | |
| - name: Remove systemd AF_ALG drop-ins | |
| tags: [cve-systemd-remove] | |
| block: | |
| - name: Remove sshd.service AF_ALG drop-in | |
| ansible.builtin.file: | |
| path: /etc/systemd/system/sshd.service.d/cve-2026-31431.conf | |
| state: absent | |
| register: sshd_dropin_removed | |
| - name: Remove [email protected] AF_ALG drop-in | |
| ansible.builtin.file: | |
| path: /etc/systemd/system/[email protected]/cve-2026-31431.conf | |
| state: absent | |
| register: user_dropin_removed | |
| - name: Reload systemd if drop-ins were removed | |
| ansible.builtin.systemd: | |
| daemon_reload: yes | |
| when: sshd_dropin_removed.changed or user_dropin_removed.changed | |
| # Restart sshd so the running daemon drops the now-removed | |
| # seccomp filter. Existing SSH sessions are not killed — only | |
| # the listener restarts. user@<uid>.service is intentionally | |
| # NOT restarted: that would kill rootless pods. The redundant | |
| # filter on running per-user instances clears naturally on the | |
| # next user-instance restart or host reboot. | |
| - name: Restart sshd to drop the seccomp filter | |
| ansible.builtin.systemd: | |
| name: sshd | |
| state: restarted | |
| when: sshd_dropin_removed.changed |
And don't forget to restart your slurmd service after adding the drop-in.
Author
Updated to add a second mitigation for RHEL 9/10 as mentioned here: https://news.ycombinator.com/item?id=47956504
Author
Updated to remove the systemd workaround, since it was causing issues with containers and the initcall_blacklist is cleaner. Also added Debian. For Debian no reboot is needed. For RHEL it needs a reboot because the module is built in.
It seems that RHEL 8 also use built-in module, at least v8.10.
I can confirm that rhel 8.10 works with the playbook above. @antoine-mesobfc
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks a lot for sharing this. I wanted to add a comment for anyone who may be coming here to apply this remediation to HPC clusters running the Slurm job scheduler.
The remediation needs to be applied to slurmd as well on the compute nodes. Apply the second task in this playbook ("Deny AF_ALG for sshd-spawned processes") as a drop-in under
/etc/systemd/system/slurmd.service.d/