Skip to content

lineinfile template generates regexp: '', so remediation overwrites the last line of the target file #15034

Description

@arushai

Description of problem:

Every rule built on the lineinfile template emits an Ansible task with regexp: ''.
ansible.builtin.lineinfile uses regexp to find the line to replace, and an empty pattern
matches at position 0 of every line — so the module takes the last line of the file as its
match and overwrites it, instead of finding and updating the setting the rule manages.

On the first run this silently destroys a line of the target file. On every subsequent run it
appends another duplicate of the managed setting, without bound.

Root cause. shared/templates/lineinfile/ansible.template calls the macro without a regex
argument:

{{{ ansible_lineinfile(msg='', path=PATH, new_line=TEXT, create='yes', state='present',
                       insert_after='', insert_before='', rule_title=rule_title) -}}}

regex therefore falls back to its default of '' in shared/macros/10-ansible.jinja:31, and
line 39 emits it verbatim as regexp: '{{{ regex }}}'.

Of the 14 call sites of ansible_lineinfile in the repository, this is the only one that omits
regex — every other caller passes an anchored pattern (e.g.
regex='^\s*Include\s+\/usr\/etc\/ssh\/sshd_config\.d/\*\.conf' in
shared/templates/sshd_lineinfile/ansible.template).

Affected rules — all 7 rules using template: lineinfile on current master (df060c67):

Rule File whose last line is overwritten
accounts_password_pam_enforce_root pwquality.conf
accounts_password_pam_enforce_local pwquality.conf
accounts_password_pam_enforcing pwquality.conf
accounts_password_pam_pwhistory_enforce_for_root /etc/security/pwhistory.conf
set_firewalld_default_zone /etc/firewalld/firewalld.conf
sssd_enable_certmap /etc/sssd/sssd.conf
verify_use_mappers /etc/pam_pkcs11/pam_pkcs11.conf

SCAP Security Guide Version:

Reproduced against scap-security-guide 0.1.81 (Rocky Linux 9.8 appstream package).
Root cause confirmed still present on master at df060c67.
ansible-core 2.21.3.

Operating System Version:

Rocky Linux 9.8 (Blue Onyx), aarch64, kernel 5.14.0-687.10.1.el9_8.0.1.aarch64.
Fresh install, no prior remediation.

Steps to Reproduce:

  1. Generate the Ansible remediation for profile cis_server_l1
    (/usr/share/scap-security-guide/ansible/rl9-playbook-cis_server_l1.yml).
  2. Record the stock contents of /etc/security/pwquality.conf and
    /etc/security/pwhistory.conf — in particular their final lines.
  3. Run the playbook against a fresh host.
  4. Run the identical playbook against the same host twice more, and diff both files after each run.

Actual Results:

The generated task for accounts_password_pam_enforce_root:

- name: Ensure PAM Enforces Password Requirements - Enforce for root User
  ansible.builtin.lineinfile:
    path: /etc/security/pwquality.conf
    create: true
    regexp: ''            # ← matches at position 0 of every line
    line: enforce_for_root
    state: present

After three consecutive applies:

stock after 3 applies
/etc/security/pwquality.conf lines 79 82
/etc/security/pwhistory.conf lines 21 24
occurrences of enforce_for_root in each 0 3
stock final line of pwquality.conf # local_users_only destroyed on run 1
stock final line of pwhistory.conf # file = /etc/security/opasswd destroyed on run 1

Run 1 overwrites the file's last line. Runs 2 and 3 each append another enforce_for_root,
because the pattern still fails to find the setting it just wrote. Applied on a schedule the
duplicates grow linearly — a weekly run for a year leaves 52 copies.

Expected Results:

The task finds an existing enforce_for_root setting and updates it in place, or appends it if
absent. No pre-existing line is modified, and a second run reports ok rather than changed.

Additional Information/Debugging Steps:

Why this is easy to miss. PAM reads the last occurrence of a setting, so the end state is
compliant. The compliance score does not move (97.4 on all three passes here), the OVAL check
passes, and no before/after scan delta reports anything. It is only visible by running the
remediation twice and reading the file.

On a pristine box the destroyed line is a comment. On a system an administrator has edited — the
usual state of a real host — the last line of pwquality.conf is as likely to be a setting they
added.

Two further consequences worth noting:

  • Three of the seven target the same file (pwquality.conf). In a profile selecting more than
    one, each task overwrites whatever the previous one appended, so they destroy each other's work
    rather than only the stock content. I could not find a user-facing profile in this state — the
    only profiles selecting two of the three are the internal hidden: true default profiles for
    Fedora, Ubuntu 22.04 and Ubuntu 24.04, and I did not trace rule selection through control files.
    So this is a latent hazard rather than a reproduced one.
  • sssd_enable_certmap and set_firewalld_default_zone write to service configuration, where
    losing the final line is a plausible outage rather than a lost comment.

Suggested fix. Give the template an anchored pattern so the module finds the setting it
manages — roughly:

{{{ ansible_lineinfile(msg='', path=PATH, new_line=TEXT, regex=REGEX,
                       create='yes', state='present', ...) -}}}

with a per-rule regex var defaulting to an anchored match on the managed key.
sssd_enable_certmap already passes escape_text: false and a regex-shaped text, so it likely
needs handling of its own.

I have not opened a PR because the right default (derive the pattern from text, versus require an
explicit regex var on each of the 7 rules) is a judgement call for maintainers, and any fix
should be checked against the Bash and OVAL backends too. Happy to submit one if you tell me which
shape you would prefer.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions