Skip to main content

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_aead può 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.

Come usare il playbook

  1. Installa ansible
  2. 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
  1. Crea un inventory.ini da 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:

  1. Hai configurato l'autenticazione tramite chiavi SSH (preferenziale)
  2. L'utente scelto può eseguire sudo senza password richiesta (%sudo ALL=(ALL) NOPASSWD: ALL nella configurazione sudoers)
  3. 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:

  1. Crea /etc/modprobe.d/disable-algif.conf con install algif_aead /bin/false (impedisce il caricamento futuro del modulo, anche on-demand).
  2. Rimuove il modulo se attualmente in uso (rmmod), tollerando il caso "non caricato".
  3. 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"