Using Ansible to mitigate copy.fail

Though I’d share this minimal Ansible role to mitigate copy.fail for people prior to the kernel updates being available:

---
- name: Unload algif_aead kernel module
  community.general.modprobe:
    name: algif_aead
    state: absent
    persistent: absent
  register: copyfail_modprobe

- name: Reboot
  ansible.builtin.reboot:
  when: copyfail_modprobe.changed | ansible.builtin.bool
...

Create roles/copyfail/tasks/main.yml with the above content and run the role in check mode to see if anything needs doing and then run not in check mode if needed to update and reboot servers.

I was pleasantly surprised how minimal this was :slight_smile:

you can also use a handler for reboot + notify, but this works fine too

I’ve always wanted to ask. When one should use handlers? My opition - not in the roles.

This is my line of thinking - it is not good to put flush_handlers inside role because you do not know what handlers you are flushing - there might be some existing before the role. So it is better to avoid them in the roles, otherwise you in a way forcing users of the role add flush_handlers after the role execution - adn this breaks assumption that roles are “self sufficient”.

Pattern used in this example (with skip if not changed) is much more robust and clear, from my point of view, - you only force changes you know you caused. Here with reboot this might be a bit more on the edge because this is a reboot, but CVE is forcing (I suppose).

My conclusion is that handlers should not be used in the roles, but only in playbooks. This is of course if you do not want users of your role to manually flush_handlers (which might be good in some cases, like if reboot is handled this way).

What are schools thoughts are there on this topic?

you don’t NEED to use flush_handlers it is there for when you want an ‘early’ trigger. I use roles that only have handlers, to provide them to other roles/plays, sometimes a role has it’s own handlers. For rebooting i have a ‘needs_reboot’ handler, which lives in a role that also has a couple of tasks that check if a system needs a reboot (kernel change, ubuntu flag file, etc), but i also call from my ‘update packages’ plays.

I pointed at handlers as an option, but I’ll try to be perfectly clear, the when: x is changed approach is also fine, use the one that makes more sense in your context.

That is a nice trick! Thank you for sharing.

… and let the ansible-lint complain about it later )).

This is why I have this question in the first place, this ansible-lint rules looks to me as too restrictive. There are certainly use cases when it does not make sense.

linting is mostly opinions, sometimes I agree with them, sometimes they are clearly wrong!

As far as I understand, you’re just making sure that algif_aead isn’t loaded automatically. But you would have to ensure that it’s not loaded at all. The website mentions something like:

echo “install algif_aead /bin/false” > /etc/modprobe.d/disable-algif.conf

This should(?) prevent that the module can be loaded, either by a user or a software having the permissions to do it. If this is done, you’re vulnerable.

Additionally, depending on your distro it might not be a loadable module but built into the kernel. I’ve read somewhere that RHEL did this. In that case, adding initcall_blacklist=algif_aead_init or similar to your boot loader should help. Not sure about this, just copy&paste.

Don’t take my word for it, I’ve just had a cursory look at this bug. Please investigate yourself.

@kks @bcoca Please move your discussion abound handlers (and linters?) to a dedicated thread. It’s somewhat OT here IMHO.

You are right, sorry I omitted that, I hadn’t realised that any user could load the module, this is what I have updated the role to use:

--- 
- name: Prevent algif_aead kernel module from being loaded
  ansible.builtin.lineinfile:
    path: /etc/modprobe.d/disable-algif.conf
    line: "install algif_aead /bin/false"
    state: present
    create: true
    mode: "0644"
    owner: root
    group: root 
  
- name: Unload algif_aead kernel module
  community.general.modprobe:
    name: algif_aead
    state: absent
    persistent: absent
  register: copyfail_modprobe

- name: Reboot if algif_aead kernel module has been unloaded
  ansible.builtin.reboot:
  when: copyfail_modprobe.changed | ansible.builtin.bool
...

Point taken regarding using a handler, I’d forgotten that, but in this case it means the role can consist on just one file…

I’m sharing some playbook’s I’ve cooked up today. Maybe they will be of help to someone:

This one tests for vulnerability and makes a CSV report.

# cve-2026-31431-detect.yml
---
- hosts: os_linux
  gather_facts: true

  tasks:
  - name: Detect CVE-2026-31431
    shell: |-
      import socket

      AF_ALG = 38

      try:
          s = socket.socket(AF_ALG, socket.SOCK_SEQPACKET, 0)
          s.bind(("aead", "gcm(aes)"))
          print("AF_ALG AEAD is available")
      except Exception as e:
          print("AF_ALG AEAD is not available: %s" % e)
    args:
      executable: "{{ ansible_facts['python']['executable'] }}"
    register: result
    changed_when: false
    # failed_when: result['stdout'] == 'AF_ALG AEAD is available'
    # no_log: True

  - name: Set result
    set_fact:
      cve_status: "{{ result['stdout'] == 'AF_ALG AEAD is available' }}"

- hosts: localhost
  connection: local
  gather_facts: false

  tasks:
  - name: Report
    copy:
      dest: "cve-2026-31431-report.csv"
      content: |-
        Machine, Vulnerable, OS
        {% for host in hostvars %}
        {% if host != 'localhost' and 'cve_status' in hostvars[host] %}
        {{ host }}, {{ hostvars[host]['cve_status'] }}, {{ hostvars[host]['ansible_facts']['distribution'] }} {{ hostvars[host]['ansible_facts']['distribution_version'] }}
        {% endif %}
        {% endfor %}

This one does a workaround for Ubuntu and RHEL based systems:

# cve-2026-31431-patch.yml
---
- hosts: os_ubuntu
  gather_facts: false

  tasks:
  - name: Patch CVE-2026-31431
    shell: |-
      echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
      rmmod algif_aead 2>/dev/null || true
    args:
      executable: "/bin/bash"

- hosts: os_rhel_family
  gather_facts: false

  tasks:
  - name: Patch CVE-2026-31431
    shell: |-
      grubby --update-kernel=ALL --args="initcall_blacklist=algif_aead_init"
    args:
      executable: "/bin/bash"

- hosts: os_rhel_8,os_ol_8,os_rocky_8
  gather_facts: false

  tasks:
  - name: Call grub2-mkconfig
    shell: |-
      grub2-mkconfig -o /boot/grub2/grub.cfg
    args:
      executable: "/bin/bash"

I tend to use bash instead of native Ansible approach (modules) to speed things a little bit because we are always dealing with thousands of machines. Also, no restart because we have to do it manually in planned maintenance windows.