## Guide to the Secure Configuration of Red Hat Enterprise Linux 7

> with profile Standard System Security Profile for Red Hat Enterprise Linux 7
> This profile contains rules to ensure standard security baseline of a Red Hat Enterprise Linux 7 system. Regardless of your system's workload all of these checks should pass.

The SCAP Security Guide Project
https://www.open-scap.org/security-policies/scap-security-guide
This guide presents a catalog of security-relevant configuration settings for Red Hat Enterprise Linux 7. It is a rendering of content structured in the eXtensible Configuration Checklist Description Format (XCCDF) in order to support security automation. The SCAP content is is available in the config_item="scap-security-guide" package which is developed at https://www.open-scap.org/security-policies/scap-security-guide.
Providing system administrators with such guidance informs them how to securely configure systems under their control in a variety of network roles. Policy makers and baseline creators can use this catalog of settings, with its associated references to higher-level security control catalogs, in order to assist them in security baseline creation. This guide is a italics="catalog, not a checklist," and satisfaction of every item is not likely to be possible or sensible in many operational scenarios. However, the XCCDF format enables granular selection and adjustment of settings, and their association with OVAL and OCIL content provides an automated checking capability. Transformations of this document, and its associated automated checking content, are capable of providing baselines that meet a diverse set of policy objectives. Some example XCCDF italics="Profiles", which are selections of items that form checklists and can be used as baselines, are available with this guide. They can be processed, in an automated fashion, with tools that support the Security Content Automation Protocol (SCAP). The DISA STIG for Red Hat Enterprise Linux 7, which provides required settings for US Department of Defense systems, is one example of a baseline created from this guidance.

This benchmark is a direct port of a SCAP Security Guide benchmark developed for Red Hat Enterprise Linux. It has been modified through an automated process to remove specific dependencies on Red Hat Enterprise Linux and to function with CentOS. The result is a generally useful SCAP Security Guide benchmark with the following caveats:

- CentOS is not an exact copy of Red Hat Enterprise Linux. There may be configuration differences that produce false positives and/or false negatives. If this occurs please file a bug report.
- CentOS has its own build system, compiler options, patchsets, and is a community supported, non-commercial operating system. CentOS does not inherit certifications or evaluations from Red Hat Enterprise Linux. As such, some configuration rules (such as those requiring FIPS 140-2 encryption) will continue to fail on CentOS.

Members of the CentOS community are invited to participate in OpenSCAP and SCAP Security Guide development. Bug reports and patches can be sent to GitHub: https://github.com/OpenSCAP/scap-security-guide. The mailing list is at https://fedorahosted.org/mailman/listinfo/scap-security-guide.

Do not attempt to implement any of the settings in this guide without first testing them in a non-operational environment. The creators of this guidance assume no responsibility whatsoever for its use by other parties, and makes no guarantees, expressed or implied, about its quality, reliability, or any other characteristic.

Profile Title | Standard System Security Profile for Red Hat Enterprise Linux 7
Profile ID | xccdf_org.ssgproject.content_profile_standard

## Revision History

Current version: 0.1.39

- draft (as of 2018-07-26)

## Platforms

- cpe:/o:redhat:enterprise_linux:7
- cpe:/o:centos:centos:7
- cpe:/o:redhat:enterprise_linux:7::client
- cpe:/o:redhat:enterprise_linux:7::computenode

## Table of Contents

1. Services

1. Cron and At Daemons
2. Base Services

2. System Settings

1. Configure Syslog
2. Account and Access Control
3. File Permissions and Masks
4. System Accounting with <tt>auditd</tt>
5. Installing and Maintaining Software

## Checklist

contains 51 rules

### Services [ref]group

The best protection against vulnerable software is running less software. This section describes how to review the software which Red Hat Enterprise Linux 7 installs on a system and disable software which is not needed. It then enumerates the software packages installed on a default Red Hat Enterprise Linux 7 system and provides guidance about which ones can be safely disabled.
Red Hat Enterprise Linux 7 provides a convenient minimal install option that essentially installs the bare necessities for a functional system. When building Red Hat Enterprise Linux 7 systems, it is highly recommended to select the minimal packages and then build up the system from there.

contains 6 rules

### Cron and At Daemons [ref]group

The cron and at services are used to allow commands to be executed at a later time. The cron service is required by almost all systems to perform necessary maintenance tasks, while at may or may not be required on a given system. Both daemons should be configured defensively.

contains 1 rule

#### Disable At Service (atd) [ref]rule

The `at` and `batch` commands can be used to schedule tasks that are meant to be executed only once. This allows delayed execution in a manner similar to cron, except that it is not recurring. The daemon `atd` keeps track of tasks scheduled via `at` and `batch`, and executes them at the specified time. The `atd` service can be disabled with the following command:

    $ sudo systemctl disable atd.service

Rationale:

The `atd` service could be used by an unsophisticated insider to carry out activities outside of a normal login session, which could complicate accountability. Furthermore, the need to schedule tasks with `at` or `batch` is not common.

Severity: unknown

References: CCI-000381, CM-7

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | disable

    # Function to enable/disable and start/stop services on RHEL and Fedora systems.
    #
    # Example Call(s):
    #
    #     service_command enable bluetooth
    #     service_command disable bluetooth.service
    #
    #     Using xinetd:
    #     service_command disable rsh.socket xinetd=rsh
    #
    function service_command {

    # Load function arguments into local variables
    local service_state=$1
    local service=$2
    local xinetd=$(echo $3 | cut -d'=' -f2)

    # Check sanity of the input
    if [ $# -lt "2" ]
    then
      echo "Usage: service_command 'enable/disable' 'service_name.service'"
      echo
      echo "To enable or disable xinetd services add \'xinetd=service_name\'"
      echo "as the last argument"
      echo "Aborting."
      exit 1
    fi

    # If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
    if [ -f "/usr/bin/systemctl" ] ; then
      service_util="/usr/bin/systemctl"
    else
      service_util="/sbin/service"
      chkconfig_util="/sbin/chkconfig"
    fi

    # If disable is not specified in arg1, set variables to enable services.
    # Otherwise, variables are to be set to disable services.
    if [ "$service_state" != 'disable' ] ; then
      service_state="enable"
      service_operation="start"
      chkconfig_state="on"
    else
      service_state="disable"
      service_operation="stop"
      chkconfig_state="off"
    fi

    # If chkconfig_util is not empty, use chkconfig/service commands.
    if [ "x$chkconfig_util" != x ] ; then
      $service_util $service $service_operation
      $chkconfig_util --level 0123456 $service $chkconfig_state
    else
      $service_util $service_operation $service
      $service_util $service_state $service
      # The service may not be running because it has been started and failed,
      # so let's reset the state so OVAL checks pass.
      # Service should be 'inactive', not 'failed' after reboot though.
      $service_util reset-failed $service
    fi

    # Test if local variable xinetd is empty using non-bashism.
    # If empty, then xinetd is not being used.
    if [ "x$xinetd" != x ] ; then
      grep -qi disable /etc/xinetd.d/$xinetd && \

      if [ "$service_operation" = 'disable' ] ; then
        sed -i "s/disable.*/disable         = no/gI" /etc/xinetd.d/$xinetd
      else
        sed -i "s/disable.*/disable         = yes/gI" /etc/xinetd.d/$xinetd
      fi
    fi

    }

    service_command disable atd

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | disable

    - name: Disable service atd
      service:
        name="{{item}}"
        enabled="no"
        state="stopped"
      register: service_result
      failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
      with_items:
        - atd
      tags:
        - service_atd_disabled
        - unknown_severity
        - disable_strategy
        - low_complexity
        - low_disruption
        - CCE-80345-2
        - NIST-800-53-CM-7

### Base Services [ref]group

This section addresses the base services that are installed on a Red Hat Enterprise Linux 7 default installation which are not covered in other sections. Some of these services listen on the network and should be treated with particular discretion. Other services are local system utilities that may or may not be extraneous. In general, system services should be disabled if not required.

contains 5 rules

#### Disable Network Router Discovery Daemon (rdisc) [ref]rule

The `rdisc` service implements the client side of the ICMP Internet Router Discovery Protocol (IRDP), which allows discovery of routers on the local subnet. If a router is discovered then the local routing table is updated with a corresponding default route. By default this daemon is disabled. The `rdisc` service can be disabled with the following command:

    $ sudo systemctl disable rdisc.service

Rationale:

General-purpose systems typically have their network and routing information configured statically by a system administrator. Workstations or some special-purpose systems often use DHCP (instead of IRDP) to retrieve dynamic network configuration information.

Severity: unknown

References: CCI-000382, AC-17(8), AC-4, CM-7

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | disable

    # Function to enable/disable and start/stop services on RHEL and Fedora systems.
    #
    # Example Call(s):
    #
    #     service_command enable bluetooth
    #     service_command disable bluetooth.service
    #
    #     Using xinetd:
    #     service_command disable rsh.socket xinetd=rsh
    #
    function service_command {

    # Load function arguments into local variables
    local service_state=$1
    local service=$2
    local xinetd=$(echo $3 | cut -d'=' -f2)

    # Check sanity of the input
    if [ $# -lt "2" ]
    then
      echo "Usage: service_command 'enable/disable' 'service_name.service'"
      echo
      echo "To enable or disable xinetd services add \'xinetd=service_name\'"
      echo "as the last argument"
      echo "Aborting."
      exit 1
    fi

    # If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
    if [ -f "/usr/bin/systemctl" ] ; then
      service_util="/usr/bin/systemctl"
    else
      service_util="/sbin/service"
      chkconfig_util="/sbin/chkconfig"
    fi

    # If disable is not specified in arg1, set variables to enable services.
    # Otherwise, variables are to be set to disable services.
    if [ "$service_state" != 'disable' ] ; then
      service_state="enable"
      service_operation="start"
      chkconfig_state="on"
    else
      service_state="disable"
      service_operation="stop"
      chkconfig_state="off"
    fi

    # If chkconfig_util is not empty, use chkconfig/service commands.
    if [ "x$chkconfig_util" != x ] ; then
      $service_util $service $service_operation
      $chkconfig_util --level 0123456 $service $chkconfig_state
    else
      $service_util $service_operation $service
      $service_util $service_state $service
      # The service may not be running because it has been started and failed,
      # so let's reset the state so OVAL checks pass.
      # Service should be 'inactive', not 'failed' after reboot though.
      $service_util reset-failed $service
    fi

    # Test if local variable xinetd is empty using non-bashism.
    # If empty, then xinetd is not being used.
    if [ "x$xinetd" != x ] ; then
      grep -qi disable /etc/xinetd.d/$xinetd && \

      if [ "$service_operation" = 'disable' ] ; then
        sed -i "s/disable.*/disable         = no/gI" /etc/xinetd.d/$xinetd
      else
        sed -i "s/disable.*/disable         = yes/gI" /etc/xinetd.d/$xinetd
      fi
    fi

    }

    service_command disable rdisc

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | disable

    - name: Disable service rdisc
      service:
        name="{{item}}"
        enabled="no"
        state="stopped"
      register: service_result
      failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
      with_items:
        - rdisc
      tags:
        - service_rdisc_disabled
        - unknown_severity
        - disable_strategy
        - low_complexity
        - low_disruption
        - CCE-80268-6
        - NIST-800-53-AC-17(8)
        - NIST-800-53-AC-4
        - NIST-800-53-CM-7

#### Disable Odd Job Daemon (oddjobd) [ref]rule

The `oddjobd` service exists to provide an interface and access control mechanism through which specified privileged tasks can run tasks for unprivileged client applications. Communication with `oddjobd` through the system message bus. The `oddjobd` service can be disabled with the following command:

    $ sudo systemctl disable oddjobd.service

Rationale:

The `oddjobd` service may provide necessary functionality in some environments, and can be disabled if it is not needed. Execution of tasks by privileged programs, on behalf of unprivileged ones, has traditionally been a source of privilege escalation security issues.

Severity: unknown

References: CCI-000381, CM-7

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | disable

    # Function to enable/disable and start/stop services on RHEL and Fedora systems.
    #
    # Example Call(s):
    #
    #     service_command enable bluetooth
    #     service_command disable bluetooth.service
    #
    #     Using xinetd:
    #     service_command disable rsh.socket xinetd=rsh
    #
    function service_command {

    # Load function arguments into local variables
    local service_state=$1
    local service=$2
    local xinetd=$(echo $3 | cut -d'=' -f2)

    # Check sanity of the input
    if [ $# -lt "2" ]
    then
      echo "Usage: service_command 'enable/disable' 'service_name.service'"
      echo
      echo "To enable or disable xinetd services add \'xinetd=service_name\'"
      echo "as the last argument"
      echo "Aborting."
      exit 1
    fi

    # If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
    if [ -f "/usr/bin/systemctl" ] ; then
      service_util="/usr/bin/systemctl"
    else
      service_util="/sbin/service"
      chkconfig_util="/sbin/chkconfig"
    fi

    # If disable is not specified in arg1, set variables to enable services.
    # Otherwise, variables are to be set to disable services.
    if [ "$service_state" != 'disable' ] ; then
      service_state="enable"
      service_operation="start"
      chkconfig_state="on"
    else
      service_state="disable"
      service_operation="stop"
      chkconfig_state="off"
    fi

    # If chkconfig_util is not empty, use chkconfig/service commands.
    if [ "x$chkconfig_util" != x ] ; then
      $service_util $service $service_operation
      $chkconfig_util --level 0123456 $service $chkconfig_state
    else
      $service_util $service_operation $service
      $service_util $service_state $service
      # The service may not be running because it has been started and failed,
      # so let's reset the state so OVAL checks pass.
      # Service should be 'inactive', not 'failed' after reboot though.
      $service_util reset-failed $service
    fi

    # Test if local variable xinetd is empty using non-bashism.
    # If empty, then xinetd is not being used.
    if [ "x$xinetd" != x ] ; then
      grep -qi disable /etc/xinetd.d/$xinetd && \

      if [ "$service_operation" = 'disable' ] ; then
        sed -i "s/disable.*/disable         = no/gI" /etc/xinetd.d/$xinetd
      else
        sed -i "s/disable.*/disable         = yes/gI" /etc/xinetd.d/$xinetd
      fi
    fi

    }

    service_command disable oddjobd

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | disable

    - name: Disable service oddjobd
      service:
        name="{{item}}"
        enabled="no"
        state="stopped"
      register: service_result
      failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
      with_items:
        - oddjobd
      tags:
        - service_oddjobd_disabled
        - unknown_severity
        - disable_strategy
        - low_complexity
        - low_disruption
        - CCE-80263-7
        - NIST-800-53-CM-7

#### Disable Apache Qpid (qpidd) [ref]rule

The `qpidd` service provides high speed, secure, guaranteed delivery services. It is an implementation of the Advanced Message Queuing Protocol. By default the qpidd service will bind to port 5672 and listen for connection attempts. The `qpidd` service can be disabled with the following command:

    $ sudo systemctl disable qpidd.service

Rationale:

The qpidd service is automatically installed when the "base" package selection is selected during installation. The qpidd service listens for network connections, which increases the attack surface of the system. If the system is not intended to receive AMQP traffic, then the `qpidd` service is not needed and should be disabled or removed.

Severity: unknown

References: CCI-000382, AC-17(8), CM-7

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | disable

    # Function to enable/disable and start/stop services on RHEL and Fedora systems.
    #
    # Example Call(s):
    #
    #     service_command enable bluetooth
    #     service_command disable bluetooth.service
    #
    #     Using xinetd:
    #     service_command disable rsh.socket xinetd=rsh
    #
    function service_command {

    # Load function arguments into local variables
    local service_state=$1
    local service=$2
    local xinetd=$(echo $3 | cut -d'=' -f2)

    # Check sanity of the input
    if [ $# -lt "2" ]
    then
      echo "Usage: service_command 'enable/disable' 'service_name.service'"
      echo
      echo "To enable or disable xinetd services add \'xinetd=service_name\'"
      echo "as the last argument"
      echo "Aborting."
      exit 1
    fi

    # If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
    if [ -f "/usr/bin/systemctl" ] ; then
      service_util="/usr/bin/systemctl"
    else
      service_util="/sbin/service"
      chkconfig_util="/sbin/chkconfig"
    fi

    # If disable is not specified in arg1, set variables to enable services.
    # Otherwise, variables are to be set to disable services.
    if [ "$service_state" != 'disable' ] ; then
      service_state="enable"
      service_operation="start"
      chkconfig_state="on"
    else
      service_state="disable"
      service_operation="stop"
      chkconfig_state="off"
    fi

    # If chkconfig_util is not empty, use chkconfig/service commands.
    if [ "x$chkconfig_util" != x ] ; then
      $service_util $service $service_operation
      $chkconfig_util --level 0123456 $service $chkconfig_state
    else
      $service_util $service_operation $service
      $service_util $service_state $service
      # The service may not be running because it has been started and failed,
      # so let's reset the state so OVAL checks pass.
      # Service should be 'inactive', not 'failed' after reboot though.
      $service_util reset-failed $service
    fi

    # Test if local variable xinetd is empty using non-bashism.
    # If empty, then xinetd is not being used.
    if [ "x$xinetd" != x ] ; then
      grep -qi disable /etc/xinetd.d/$xinetd && \

      if [ "$service_operation" = 'disable' ] ; then
        sed -i "s/disable.*/disable         = no/gI" /etc/xinetd.d/$xinetd
      else
        sed -i "s/disable.*/disable         = yes/gI" /etc/xinetd.d/$xinetd
      fi
    fi

    }

    service_command disable qpidd

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | disable

    - name: Disable service qpidd
      service:
        name="{{item}}"
        enabled="no"
        state="stopped"
      register: service_result
      failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
      with_items:
        - qpidd
      tags:
        - service_qpidd_disabled
        - unknown_severity
        - disable_strategy
        - low_complexity
        - low_disruption
        - CCE-80266-0
        - NIST-800-53-AC-17(8)
        - NIST-800-53-CM-7

#### Disable Automatic Bug Reporting Tool (abrtd) [ref]rule

The Automatic Bug Reporting Tool (`abrtd`) daemon collects and reports crash data when an application crash is detected. Using a variety of plugins, abrtd can email crash reports to system administrators, log crash reports to files, or forward crash reports to a centralized issue tracking system such as RHTSupport. The `abrtd` service can be disabled with the following command:

    $ sudo systemctl disable abrtd.service

Rationale:

Mishandling crash data could expose sensitive information about vulnerabilities in software executing on the system, as well as sensitive information from within a process's address space or registers.

Severity: unknown

References: AC-17(8), CM-7

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | disable

    # Function to enable/disable and start/stop services on RHEL and Fedora systems.
    #
    # Example Call(s):
    #
    #     service_command enable bluetooth
    #     service_command disable bluetooth.service
    #
    #     Using xinetd:
    #     service_command disable rsh.socket xinetd=rsh
    #
    function service_command {

    # Load function arguments into local variables
    local service_state=$1
    local service=$2
    local xinetd=$(echo $3 | cut -d'=' -f2)

    # Check sanity of the input
    if [ $# -lt "2" ]
    then
      echo "Usage: service_command 'enable/disable' 'service_name.service'"
      echo
      echo "To enable or disable xinetd services add \'xinetd=service_name\'"
      echo "as the last argument"
      echo "Aborting."
      exit 1
    fi

    # If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
    if [ -f "/usr/bin/systemctl" ] ; then
      service_util="/usr/bin/systemctl"
    else
      service_util="/sbin/service"
      chkconfig_util="/sbin/chkconfig"
    fi

    # If disable is not specified in arg1, set variables to enable services.
    # Otherwise, variables are to be set to disable services.
    if [ "$service_state" != 'disable' ] ; then
      service_state="enable"
      service_operation="start"
      chkconfig_state="on"
    else
      service_state="disable"
      service_operation="stop"
      chkconfig_state="off"
    fi

    # If chkconfig_util is not empty, use chkconfig/service commands.
    if [ "x$chkconfig_util" != x ] ; then
      $service_util $service $service_operation
      $chkconfig_util --level 0123456 $service $chkconfig_state
    else
      $service_util $service_operation $service
      $service_util $service_state $service
      # The service may not be running because it has been started and failed,
      # so let's reset the state so OVAL checks pass.
      # Service should be 'inactive', not 'failed' after reboot though.
      $service_util reset-failed $service
    fi

    # Test if local variable xinetd is empty using non-bashism.
    # If empty, then xinetd is not being used.
    if [ "x$xinetd" != x ] ; then
      grep -qi disable /etc/xinetd.d/$xinetd && \

      if [ "$service_operation" = 'disable' ] ; then
        sed -i "s/disable.*/disable         = no/gI" /etc/xinetd.d/$xinetd
      else
        sed -i "s/disable.*/disable         = yes/gI" /etc/xinetd.d/$xinetd
      fi
    fi

    }

    service_command disable abrtd

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | disable

    - name: Disable service abrtd
      service:
        name="{{item}}"
        enabled="no"
        state="stopped"
      register: service_result
      failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
      with_items:
        - abrtd
      tags:
        - service_abrtd_disabled
        - unknown_severity
        - disable_strategy
        - low_complexity
        - low_disruption
        - CCE-26872-2
        - NIST-800-53-AC-17(8)
        - NIST-800-53-CM-7

#### Disable ntpdate Service (ntpdate) [ref]rule

The `ntpdate` service sets the local hardware clock by polling NTP servers when the system boots. It synchronizes to the NTP servers listed in `/etc/ntp/step-tickers` or `/etc/ntp.conf` and then sets the local hardware clock to the newly synchronized system time. The `ntpdate` service can be disabled with the following command:

    $ sudo systemctl disable ntpdate.service

Rationale:

The `ntpdate` service may only be suitable for systems which are rebooted frequently enough that clock drift does not cause problems between reboots. In any event, the functionality of the ntpdate service is now available in the ntpd program and should be considered deprecated.

Severity: unknown

References: CCI-000382, AC-17(8), CM-7

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | disable

    # Function to enable/disable and start/stop services on RHEL and Fedora systems.
    #
    # Example Call(s):
    #
    #     service_command enable bluetooth
    #     service_command disable bluetooth.service
    #
    #     Using xinetd:
    #     service_command disable rsh.socket xinetd=rsh
    #
    function service_command {

    # Load function arguments into local variables
    local service_state=$1
    local service=$2
    local xinetd=$(echo $3 | cut -d'=' -f2)

    # Check sanity of the input
    if [ $# -lt "2" ]
    then
      echo "Usage: service_command 'enable/disable' 'service_name.service'"
      echo
      echo "To enable or disable xinetd services add \'xinetd=service_name\'"
      echo "as the last argument"
      echo "Aborting."
      exit 1
    fi

    # If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
    if [ -f "/usr/bin/systemctl" ] ; then
      service_util="/usr/bin/systemctl"
    else
      service_util="/sbin/service"
      chkconfig_util="/sbin/chkconfig"
    fi

    # If disable is not specified in arg1, set variables to enable services.
    # Otherwise, variables are to be set to disable services.
    if [ "$service_state" != 'disable' ] ; then
      service_state="enable"
      service_operation="start"
      chkconfig_state="on"
    else
      service_state="disable"
      service_operation="stop"
      chkconfig_state="off"
    fi

    # If chkconfig_util is not empty, use chkconfig/service commands.
    if [ "x$chkconfig_util" != x ] ; then
      $service_util $service $service_operation
      $chkconfig_util --level 0123456 $service $chkconfig_state
    else
      $service_util $service_operation $service
      $service_util $service_state $service
      # The service may not be running because it has been started and failed,
      # so let's reset the state so OVAL checks pass.
      # Service should be 'inactive', not 'failed' after reboot though.
      $service_util reset-failed $service
    fi

    # Test if local variable xinetd is empty using non-bashism.
    # If empty, then xinetd is not being used.
    if [ "x$xinetd" != x ] ; then
      grep -qi disable /etc/xinetd.d/$xinetd && \

      if [ "$service_operation" = 'disable' ] ; then
        sed -i "s/disable.*/disable         = no/gI" /etc/xinetd.d/$xinetd
      else
        sed -i "s/disable.*/disable         = yes/gI" /etc/xinetd.d/$xinetd
      fi
    fi

    }

    service_command disable ntpdate

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | disable

    - name: Disable service ntpdate
      service:
        name="{{item}}"
        enabled="no"
        state="stopped"
      register: service_result
      failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
      with_items:
        - ntpdate
      tags:
        - service_ntpdate_disabled
        - unknown_severity
        - disable_strategy
        - low_complexity
        - low_disruption
        - CCE-80262-9
        - NIST-800-53-AC-17(8)
        - NIST-800-53-CM-7

### System Settings [ref]group

Contains rules that check correct system settings.

contains 45 rules

### Configure Syslog [ref]group

The syslog service has been the default Unix logging mechanism for many years. It has a number of downsides, including inconsistent log format, lack of authentication for received messages, and lack of authentication, encryption, or reliable transport for messages sent over a network. However, due to its long history, syslog is a de facto standard which is supported by almost all Unix applications.
In Red Hat Enterprise Linux 7, rsyslog has replaced ksyslogd as the syslog daemon of choice, and it includes some additional security features such as reliable, connection-oriented (i.e. TCP) transmission of logs, the option to log to database formats, and the encryption of log data en route to a central logging server. This section discusses how to configure rsyslog for best effect, and how to use tools provided with the system to maintain and monitor logs.

contains 2 rules

#### Enable rsyslog Service [ref]rule

The `rsyslog` service provides syslog-style logging by default on Red Hat Enterprise Linux 7. The `rsyslog` service can be enabled with the following command:

    $ sudo systemctl enable rsyslog.service

Rationale:

The `rsyslog` service must be running in order to provide logging services, which are essential to system administration.

Severity: medium

References: NT28(R5), NT28(R46), 4.2.1.1, CCI-001311, CCI-001312, CCI-001557, CCI-001851, 164.312(a)(2)(ii), A.12.3.1, AU-4(1), AU-12

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    # Function to enable/disable and start/stop services on RHEL and Fedora systems.
    #
    # Example Call(s):
    #
    #     service_command enable bluetooth
    #     service_command disable bluetooth.service
    #
    #     Using xinetd:
    #     service_command disable rsh.socket xinetd=rsh
    #
    function service_command {

    # Load function arguments into local variables
    local service_state=$1
    local service=$2
    local xinetd=$(echo $3 | cut -d'=' -f2)

    # Check sanity of the input
    if [ $# -lt "2" ]
    then
      echo "Usage: service_command 'enable/disable' 'service_name.service'"
      echo
      echo "To enable or disable xinetd services add \'xinetd=service_name\'"
      echo "as the last argument"
      echo "Aborting."
      exit 1
    fi

    # If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
    if [ -f "/usr/bin/systemctl" ] ; then
      service_util="/usr/bin/systemctl"
    else
      service_util="/sbin/service"
      chkconfig_util="/sbin/chkconfig"
    fi

    # If disable is not specified in arg1, set variables to enable services.
    # Otherwise, variables are to be set to disable services.
    if [ "$service_state" != 'disable' ] ; then
      service_state="enable"
      service_operation="start"
      chkconfig_state="on"
    else
      service_state="disable"
      service_operation="stop"
      chkconfig_state="off"
    fi

    # If chkconfig_util is not empty, use chkconfig/service commands.
    if [ "x$chkconfig_util" != x ] ; then
      $service_util $service $service_operation
      $chkconfig_util --level 0123456 $service $chkconfig_state
    else
      $service_util $service_operation $service
      $service_util $service_state $service
      # The service may not be running because it has been started and failed,
      # so let's reset the state so OVAL checks pass.
      # Service should be 'inactive', not 'failed' after reboot though.
      $service_util reset-failed $service
    fi

    # Test if local variable xinetd is empty using non-bashism.
    # If empty, then xinetd is not being used.
    if [ "x$xinetd" != x ] ; then
      grep -qi disable /etc/xinetd.d/$xinetd && \

      if [ "$service_operation" = 'disable' ] ; then
        sed -i "s/disable.*/disable         = no/gI" /etc/xinetd.d/$xinetd
      else
        sed -i "s/disable.*/disable         = yes/gI" /etc/xinetd.d/$xinetd
      fi
    fi

    }

    service_command enable rsyslog

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    - name: Enable service rsyslog
      service:
        name="{{item}}"
        enabled="yes"
        state="started"
      with_items:
        - rsyslog
      tags:
        - service_rsyslog_enabled
        - medium_severity
        - enable_strategy
        - low_complexity
        - low_disruption
        - CCE-80188-6
        - NIST-800-53-AU-4(1)
        - NIST-800-53-AU-12

#### Ensure rsyslog is Installed [ref]rule

Rsyslog is installed by default. The `rsyslog` package can be installed with the following command:

    $ sudo yum install rsyslog

Rationale:

The rsyslog package provides the rsyslog daemon, which provides system logging services.

Severity: medium

References: NT28(R5), NT28(R46), 4.2.3, CCI-001311, CCI-001312, 164.312(a)(2)(ii), A.12.3.1, AU-9(2)

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    # Function to install packages on RHEL, Fedora, Debian, and possibly other systems.
    #
    # Example Call(s):
    #
    #     package_install aide
    #
    function package_install {

    # Load function arguments into local variables
    local package="$1"

    # Check sanity of the input
    if [ $# -ne "1" ]
    then
      echo "Usage: package_install 'package_name'"
      echo "Aborting."
      exit 1
    fi

    if which dnf ; then
      if ! rpm -q --quiet "$package"; then
        dnf install -y "$package"
      fi
    elif which yum ; then
      if ! rpm -q --quiet "$package"; then
        yum install -y "$package"
      fi
    elif which apt-get ; then
      apt-get install -y "$package"
    else
      echo "Failed to detect available packaging system, tried dnf, yum and apt-get!"
      echo "Aborting."
      exit 1
    fi

    }

    package_install rsyslog

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    - name: Ensure rsyslog is installed
      package:
        name="{{item}}"
        state=present
      with_items:
        - rsyslog
      tags:
        - package_rsyslog_installed
        - medium_severity
        - enable_strategy
        - low_complexity
        - low_disruption
        - CCE-80187-8
        - NIST-800-53-AU-9(2)

Remediation Puppet snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    include install_rsyslog

    class install_rsyslog {
      package { 'rsyslog':
        ensure => 'installed',
      }
    }

Remediation Anaconda snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | enable

    package --add=rsyslog

### Account and Access Control [ref]group

In traditional Unix security, if an attacker gains shell access to a certain login account, they can perform any action or access any file to which that account has access. Therefore, making it more difficult for unauthorized people to gain shell access to accounts, particularly to privileged accounts, is a necessary part of securing a system. This section introduces mechanisms for restricting access to accounts under Red Hat Enterprise Linux 7.

contains 2 rules

### Protect Accounts by Restricting Password-Based Login [ref]group

Conventionally, Unix shell accounts are accessed by providing a username and password to a login program, which tests these values for correctness using the `/etc/passwd` and `/etc/shadow` files. Password-based login is vulnerable to guessing of weak passwords, and to sniffing and man-in-the-middle attacks against passwords entered over a network or at an insecure console. Therefore, mechanisms for accessing accounts by entering usernames and passwords should be restricted to those which are operationally necessary.

contains 1 rule

### Verify Proper Storage and Existence of Password Hashes [ref]group

By default, password hashes for local accounts are stored in the second field (colon-separated) in `/etc/shadow`. This file should be readable only by processes running with root credentials, preventing users from casually accessing others' password hashes and attempting to crack them. However, it remains possible to misconfigure the system and store password hashes in world-readable files such as `/etc/passwd`, or to even store passwords themselves in plaintext on the system. Using system-provided tools for password change/creation should allow administrators to avoid such misconfiguration.

contains 1 rule

#### Prevent Log In to Accounts With Empty Password [ref]rule

If an account is configured for password authentication but does not have an assigned password, it may be possible to log into the account without authentication. Remove any instances of the `nullok` option in `/etc/pam.d/system-auth` to prevent logins with empty passwords.

Rationale:

If an account has an empty password, anyone could log in and run commands with the privileges of that account. Accounts with empty passwords should never be used in operational environments.

Severity: high

References: FIA_AFL.1, SV-86561r2_rule, 5.5.2, 3.1.1, 3.1.5, CCI-000366, 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii), AC-6, IA-5(b), IA-5(c), IA-5(1)(a), Req-8.2.3, SRG-OS-000480-GPOS-00227

Remediation Shell script: (show)

    sed --follow-symlinks -i 's/\<nullok\>//g' /etc/pam.d/system-auth
    sed --follow-symlinks -i 's/\<nullok\>//g' /etc/pam.d/password-auth

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | medium
Strategy: | configure

    - name: "Prevent Log In to Accounts With Empty Password - system-auth"
      replace:
        dest: /etc/pam.d/system-auth
        follow: yes
        regexp: 'nullok'
      tags:
        - no_empty_passwords
        - high_severity
        - configure_strategy
        - low_complexity
        - medium_disruption
        - CCE-27286-4
        - NIST-800-53-AC-6
        - NIST-800-53-IA-5(b)
        - NIST-800-53-IA-5(c)
        - NIST-800-53-IA-5(1)(a)
        - NIST-800-171-3.1.1
        - NIST-800-171-3.1.5
        - PCI-DSS-Req-8.2.3
        - CJIS-5.5.2
        - DISA-STIG-RHEL-07-010290

    - name: "Prevent Log In to Accounts With Empty Password - password-auth"
      replace:
        dest: /etc/pam.d/password-auth
        follow: yes
        regexp: 'nullok'
      tags:
        - no_empty_passwords
        - high_severity
        - configure_strategy
        - low_complexity
        - medium_disruption
        - CCE-27286-4
        - NIST-800-53-AC-6
        - NIST-800-53-IA-5(b)
        - NIST-800-53-IA-5(c)
        - NIST-800-53-IA-5(1)(a)
        - NIST-800-171-3.1.1
        - NIST-800-171-3.1.5
        - PCI-DSS-Req-8.2.3
        - CJIS-5.5.2
        - DISA-STIG-RHEL-07-010290

### Secure Session Configuration Files for Login Accounts [ref]group

When a user logs into a Unix account, the system configures the user's session by reading a number of files. Many of these files are located in the user's home directory, and may have weak permissions as a result of user error or misconfiguration. If an attacker can modify or even read certain types of account configuration information, they can often gain full access to the affected user's account. Therefore, it is important to test and correct configuration file permissions for interactive accounts, particularly those of privileged users such as root or system administrators.

contains 1 rule

### Ensure that No Dangerous Directories Exist in Root's Path [ref]group

The active path of the root account can be obtained by starting a new root shell and running:

    # echo $PATH

This will produce a colon-separated list of directories in the path.
Certain path elements could be considered dangerous, as they could lead to root executing unknown or untrusted programs, which could contain malicious code. Since root may sometimes work inside untrusted directories, the `.` character, which represents the current directory, should never be in the root path, nor should any directory which can be written to by an unprivileged or semi-privileged (system) user.
It is a good practice for administrators to always execute privileged commands by typing the full path to the command.

contains 1 rule

#### Ensure that Root's Path Does Not Include World or Group-Writable Directories [ref]rule

For each element in root's path, run:

    # ls -ld DIR

and ensure that write permissions are disabled for group and other.Rationale:

Such entries increase the risk that root could execute code provided by unprivileged users, and potentially malicious code.

Severity: unknown

References: CCI-000366, CM-6(b)

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | medium
Strategy: | restrict

    - name: "Fail if user is not root"
      fail:
        msg: 'Root account required to read root $PATH'
      when: ansible_user != "root"
      tags:
        - accounts_root_path_dirs_no_write
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - medium_disruption
        - CCE-80200-9
        - NIST-800-53-CM-6(b)

    - name: "Get root paths which are not symbolic links"
      shell: 'tr ":" "\n" <<< "$PATH" | xargs -I% find % -maxdepth 0 -type d'
      changed_when: False
      failed_when: False
      register: root_paths
      when: ansible_user == "root"
      check_mode: no
      tags:
        - accounts_root_path_dirs_no_write
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - medium_disruption
        - CCE-80200-9
        - NIST-800-53-CM-6(b)

    - name: "Disable writability to root directories"
      file:
        path: "{{item}}"
        mode: "g-w,o-w"
      with_items: "{{ root_paths.stdout_lines }}"
      when: root_paths.stdout_lines is defined
      tags:
        - accounts_root_path_dirs_no_write
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - medium_disruption
        - CCE-80200-9
        - NIST-800-53-CM-6(b)

### File Permissions and Masks [ref]group

Traditional Unix security relies heavily on file and directory permissions to prevent unauthorized users from reading or modifying files to which they should not have access.
Several of the commands in this section search filesystems for files or directories with certain characteristics, and are intended to be run on every local partition on a given system. When the variable PART appears in one of the commands below, it means that the command is intended to be run repeatedly, with the name of each local partition substituted for PART in turn.
The following command prints a list of all xfs partitions on the local system, which is the default filesystem for Red Hat Enterprise Linux 7 installations:

    $ mount -t xfs | awk '{print $3}'

For any systems that use a different local filesystem type, modify this command as appropriate.

contains 7 rules

### Verify Permissions on Important Files and Directories [ref]group

Permissions for many files on a system must be set restrictively to ensure sensitive information is properly protected. This section discusses important permission restrictions which can be verified to ensure that no harmful discrepancies have arisen.

contains 4 rules

#### Ensure All SGID Executables Are Authorized [ref]rule

The SGID (set group id) bit should be set only on files that were installed via authorized means. A straightforward means of identifying unauthorized SGID files is determine if any were not installed as part of an RPM package, which is cryptographically verified. Investigate the origin of any unpackaged SGID files.

Rationale:

Executable files with the SGID permission run with the privileges of the owner of the file. SGID files of uncertain provenance could allow for unprivileged users to elevate privileges. The presence of these files should be strictly controlled on the system.

Severity: unknown

References: 6.1.14, AC-6(1)

#### Ensure No World-Writable Files Exist [ref]rule

It is generally a good idea to remove global (other) write access to a file when it is discovered. However, check with documentation for specific applications before making changes. Also, monitor for recurring world-writable files, as these may be symptoms of a misconfigured application or user account. Finally, this applies to real files and not virtual files that are a part of pseudo file systems such as `sysfs` or `procfs`.

Rationale:

Data in world-writable files can be modified by any user on the system. In almost all circumstances, files can be configured using a combination of user and group permissions to support whatever legitimate access is needed without the risk caused by world-writable files.

Severity: medium

References: 6.1.10, AC-6

#### Verify that All World-Writable Directories Have Sticky Bits Set [ref]rule

When the so-called 'sticky bit' is set on a directory, only the owner of a given file may remove that file from the directory. Without the sticky bit, any user with write access to a directory may remove any file in the directory. Setting the sticky bit prevents users from removing each other's files. In cases where there is no reason for a directory to be world-writable, a better solution is to remove that permission rather than to set the sticky bit. However, if a directory is used by a particular application, consult that application's documentation instead of blindly changing modes.
To set the sticky bit on a world-writable directory DIR, run the following command:

    $ sudo chmod +t DIR

Rationale:

Failing to set the sticky bit on public directories allows unauthorized users to delete files in the directory structure.
The only authorized public directories are those temporary directories supplied with the system, or those designed to be temporary file repositories. The setting is normally reserved for directories used by the system, by users for temporary file storage (such as `/tmp`), and for directories requiring global read/write access.

Severity: unknown

References: 1.1.21, AC-6

#### Ensure All SUID Executables Are Authorized [ref]rule

The SUID (set user id) bit should be set only on files that were installed via authorized means. A straightforward means of identifying unauthorized SGID files is determine if any were not installed as part of an RPM package, which is cryptographically verified. Investigate the origin of any unpackaged SUID files.

Rationale:

Executable files with the SUID permission run with the privileges of the owner of the file. SUID files of uncertain provenance could allow for unprivileged users to elevate privileges. The presence of these files should be strictly controlled on the system.

Severity: unknown

References: 6.1.13, AC-6(1)

### Restrict Dynamic Mounting and Unmounting of Filesystems [ref]group

Linux includes a number of facilities for the automated addition and removal of filesystems on a running system. These facilities may be necessary in many environments, but this capability also carries some risk -- whether direct risk from allowing users to introduce arbitrary filesystems, or risk that software flaws in the automated mount facility itself could allow an attacker to compromise the system.
This command can be used to list the types of filesystems that are available to the currently executing kernel:

    $ find /lib/modules/`uname -r`/kernel/fs -type f -name '*.ko'

If these filesystems are not required then they can be explicitly disabled in a configuratio file in `/etc/modprobe.d`.

contains 1 rule

#### Disable the Automounter [ref]rule

The `autofs` daemon mounts and unmounts filesystems, such as user home directories shared via NFS, on demand. In addition, autofs can be used to handle removable media, and the default configuration provides the cdrom device as `/misc/cd`. However, this method of providing access to removable media is not common, so autofs can almost always be disabled if NFS is not in use. Even if NFS is required, it may be possible to configure filesystem mounts statically by editing `/etc/fstab` rather than relying on the automounter.
The `autofs` service can be disabled with the following command:

    $ sudo systemctl disable autofs.service

Rationale:

Disabling the automounter permits the administrator to statically control filesystem mounting through `/etc/fstab`.
Additionally, automatically mounting filesystems permits easy introduction of unknown devices, thereby facilitating malicious activity.

Severity: medium

References: SV-86609r1_rule, 1.1.22, 3.4.6, CCI-000366, CCI-000778, CCI-001958, 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.310(d)(1), 164.310(d)(2), 164.312(a)(1), 164.312(a)(2)(iv), 164.312(b), AC-19(a), AC-19(d), AC-19(e), IA-3, SRG-OS-000114-GPOS-00059, SRG-OS-000378-GPOS-00163, SRG-OS-000480-GPOS-00227

Remediation Shell script: (show)

Complexity: | low
Disruption: | low
Strategy: | disable

    # Function to enable/disable and start/stop services on RHEL and Fedora systems.
    #
    # Example Call(s):
    #
    #     service_command enable bluetooth
    #     service_command disable bluetooth.service
    #
    #     Using xinetd:
    #     service_command disable rsh.socket xinetd=rsh
    #
    function service_command {

    # Load function arguments into local variables
    local service_state=$1
    local service=$2
    local xinetd=$(echo $3 | cut -d'=' -f2)

    # Check sanity of the input
    if [ $# -lt "2" ]
    then
      echo "Usage: service_command 'enable/disable' 'service_name.service'"
      echo
      echo "To enable or disable xinetd services add \'xinetd=service_name\'"
      echo "as the last argument"
      echo "Aborting."
      exit 1
    fi

    # If systemctl is installed, use systemctl command; otherwise, use the service/chkconfig commands
    if [ -f "/usr/bin/systemctl" ] ; then
      service_util="/usr/bin/systemctl"
    else
      service_util="/sbin/service"
      chkconfig_util="/sbin/chkconfig"
    fi

    # If disable is not specified in arg1, set variables to enable services.
    # Otherwise, variables are to be set to disable services.
    if [ "$service_state" != 'disable' ] ; then
      service_state="enable"
      service_operation="start"
      chkconfig_state="on"
    else
      service_state="disable"
      service_operation="stop"
      chkconfig_state="off"
    fi

    # If chkconfig_util is not empty, use chkconfig/service commands.
    if [ "x$chkconfig_util" != x ] ; then
      $service_util $service $service_operation
      $chkconfig_util --level 0123456 $service $chkconfig_state
    else
      $service_util $service_operation $service
      $service_util $service_state $service
      # The service may not be running because it has been started and failed,
      # so let's reset the state so OVAL checks pass.
      # Service should be 'inactive', not 'failed' after reboot though.
      $service_util reset-failed $service
    fi

    # Test if local variable xinetd is empty using non-bashism.
    # If empty, then xinetd is not being used.
    if [ "x$xinetd" != x ] ; then
      grep -qi disable /etc/xinetd.d/$xinetd && \

      if [ "$service_operation" = 'disable' ] ; then
        sed -i "s/disable.*/disable         = no/gI" /etc/xinetd.d/$xinetd
      else
        sed -i "s/disable.*/disable         = yes/gI" /etc/xinetd.d/$xinetd
      fi
    fi

    }

    service_command disable autofs

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | disable

    - name: Disable service autofs
      service:
        name="{{item}}"
        enabled="no"
        state="stopped"
      register: service_result
      failed_when: "service_result|failed and ('Could not find the requested service' not in service_result.msg)"
      with_items:
        - autofs
      tags:
        - service_autofs_disabled
        - medium_severity
        - disable_strategy
        - low_complexity
        - low_disruption
        - CCE-27498-5
        - NIST-800-53-AC-19(a)
        - NIST-800-53-AC-19(d)
        - NIST-800-53-AC-19(e)
        - NIST-800-53-IA-3
        - NIST-800-171-3.4.6
        - DISA-STIG-RHEL-07-020110

### Restrict Partition Mount Options [ref]group

System partitions can be mounted with certain options that limit what files on those partitions can do. These options are set in the `/etc/fstab` configuration file, and can be used to make certain types of malicious behavior more difficult.

contains 2 rules

#### Add nosuid Option to /dev/shm [ref]rule

The `nosuid` mount option can be used to prevent execution of setuid programs in `/dev/shm`. The SUID and SGID permissions should not be required in these world-writable directories. Add the `nosuid` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/dev/shm`.

Rationale:

The presence of SUID and SGID executables should be tightly controlled. Users should not be able to execute SUID or SGID binaries from temporary storage partitions.

Severity: unknown

References: 1.1.16, CM-7, MP-2

Remediation Shell script: (show)

    function include_mount_options_functions {
    	:
    }

    # $1: mount point
    # $2: new mount point option
    function ensure_mount_option_in_fstab {
    	local _mount_point="$1" _new_opt="$2" _mount_point_match_regexp="" _previous_mount_opts=""
    	_mount_point_match_regexp="$(get_mount_point_regexp "$_mount_point")"

    	if [ $(grep "$_mount_point_match_regexp" /etc/fstab | grep -c "$_new_opt" ) -eq 0 ]; then
    		_previous_mount_opts=$(grep "$_mount_point_match_regexp" /etc/fstab | awk '{print $4}')
    		sed -i "s|\(${_mount_point_match_regexp}.*${_previous_mount_opts}\)|\1,${_new_opt}|" /etc/fstab
    	fi
    }

    # $1: mount point
    function get_mount_point_regexp {
    		printf "[[:space:]]%s[[:space:]]" "$1"
    }

    # $1: mount point
    function assert_mount_point_in_fstab {
    	local _mount_point_match_regexp
    	_mount_point_match_regexp="$(get_mount_point_regexp "$1")"
    	grep "$_mount_point_match_regexp" -q /etc/fstab \
    		|| { echo "The mount point '$1' is not even in /etc/fstab, so we can't set up mount options" >&2; return 1; }
    }

    # $1: mount point
    function remove_defaults_from_fstab_if_overriden {
    	local _mount_point_match_regexp
    	_mount_point_match_regexp="$(get_mount_point_regexp "$1")"
    	if [ $(grep "$_mount_point_match_regexp" /etc/fstab | grep -q "defaults,") -gt 0 ]
    	then
    		sed -i "s|\(${_mount_point_match_regexp}.*\)defaults,|\1|" /etc/fstab
    	fi
    }

    # $1: mount point
    function ensure_partition_is_mounted {
    	local _mount_point="$1"
    	mkdir -p "$_mount_point" || return 1
    	if mountpoint -q "$_mount_point"; then
    		mount -o remount --target "$_mount_point"
    	else
    		mount --target "$_mount_point"
    	fi
    }

    include_mount_options_functions

    # test "$mount_has_to_exist" = 'yes'
    test "yes" = 'yes' && assert_mount_point_in_fstab /dev/shm \
    	|| { echo "Not remediating, because there is no record of /dev/shm in /etc/fstab" >&2; exit 1; }

    ensure_mount_option_in_fstab "/dev/shm" "nosuid"

    ensure_partition_is_mounted "/dev/shm"

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | high
Strategy: | configure

    - name: get back device associated to mountpoint
      shell: mount | grep ' /dev/shm ' |cut -d ' ' -f 1
      register: device_name
      check_mode: no
      tags:
        - mount_option_dev_shm_nosuid
        - unknown_severity
        - configure_strategy
        - low_complexity
        - high_disruption
        - CCE-80154-8
        - NIST-800-53-CM-7
        - NIST-800-53-MP-2

    - name: get back device previous mount option
      shell: mount | grep ' /dev/shm ' | sed -re 's:.*\((.*)\):\1:'
      register: device_cur_mountoption
      check_mode: no
      tags:
        - mount_option_dev_shm_nosuid
        - unknown_severity
        - configure_strategy
        - low_complexity
        - high_disruption
        - CCE-80154-8
        - NIST-800-53-CM-7
        - NIST-800-53-MP-2

    - name: get back device fstype
      shell: mount | grep ' /dev/shm ' | cut -d ' ' -f 5
      register: device_fstype
      check_mode: no
      tags:
        - mount_option_dev_shm_nosuid
        - unknown_severity
        - configure_strategy
        - low_complexity
        - high_disruption
        - CCE-80154-8
        - NIST-800-53-CM-7
        - NIST-800-53-MP-2

    - name: Ensure permission nosuid are set on /dev/shm
      mount:
        path: "/dev/shm"
        src: "{{device_name.stdout}}"
        opts: "{{device_cur_mountoption.stdout}},nosuid"
        state: "mounted"
        fstype: "{{device_fstype.stdout}}"
      tags:
        - mount_option_dev_shm_nosuid
        - unknown_severity
        - configure_strategy
        - low_complexity
        - high_disruption
        - CCE-80154-8
        - NIST-800-53-CM-7
        - NIST-800-53-MP-2

Remediation Anaconda snippet: (show)

Complexity: | low
Disruption: | high
Strategy: | enable

    part /dev/shm --mountoptions="nosuid"

#### Add nodev Option to /dev/shm [ref]rule

The `nodev` mount option can be used to prevent creation of device files in `/dev/shm`. Legitimate character and block devices should not exist within temporary directories like `/dev/shm`. Add the `nodev` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/dev/shm`.

Rationale:

The only legitimate location for device files is the `/dev` directory located on the root partition. The only exception to this is chroot jails.

Severity: unknown

References: 1.1.15, CM-7, MP-2

Remediation Shell script: (show)

    function include_mount_options_functions {
    	:
    }

    # $1: mount point
    # $2: new mount point option
    function ensure_mount_option_in_fstab {
    	local _mount_point="$1" _new_opt="$2" _mount_point_match_regexp="" _previous_mount_opts=""
    	_mount_point_match_regexp="$(get_mount_point_regexp "$_mount_point")"

    	if [ $(grep "$_mount_point_match_regexp" /etc/fstab | grep -c "$_new_opt" ) -eq 0 ]; then
    		_previous_mount_opts=$(grep "$_mount_point_match_regexp" /etc/fstab | awk '{print $4}')
    		sed -i "s|\(${_mount_point_match_regexp}.*${_previous_mount_opts}\)|\1,${_new_opt}|" /etc/fstab
    	fi
    }

    # $1: mount point
    function get_mount_point_regexp {
    		printf "[[:space:]]%s[[:space:]]" "$1"
    }

    # $1: mount point
    function assert_mount_point_in_fstab {
    	local _mount_point_match_regexp
    	_mount_point_match_regexp="$(get_mount_point_regexp "$1")"
    	grep "$_mount_point_match_regexp" -q /etc/fstab \
    		|| { echo "The mount point '$1' is not even in /etc/fstab, so we can't set up mount options" >&2; return 1; }
    }

    # $1: mount point
    function remove_defaults_from_fstab_if_overriden {
    	local _mount_point_match_regexp
    	_mount_point_match_regexp="$(get_mount_point_regexp "$1")"
    	if [ $(grep "$_mount_point_match_regexp" /etc/fstab | grep -q "defaults,") -gt 0 ]
    	then
    		sed -i "s|\(${_mount_point_match_regexp}.*\)defaults,|\1|" /etc/fstab
    	fi
    }

    # $1: mount point
    function ensure_partition_is_mounted {
    	local _mount_point="$1"
    	mkdir -p "$_mount_point" || return 1
    	if mountpoint -q "$_mount_point"; then
    		mount -o remount --target "$_mount_point"
    	else
    		mount --target "$_mount_point"
    	fi
    }

    include_mount_options_functions

    # test "$mount_has_to_exist" = 'yes'
    test "yes" = 'yes' && assert_mount_point_in_fstab /dev/shm \
    	|| { echo "Not remediating, because there is no record of /dev/shm in /etc/fstab" >&2; exit 1; }

    ensure_mount_option_in_fstab "/dev/shm" "nodev"

    ensure_partition_is_mounted "/dev/shm"

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | high
Strategy: | configure

    - name: get back device associated to mountpoint
      shell: mount | grep ' /dev/shm ' |cut -d ' ' -f 1
      register: device_name
      check_mode: no
      tags:
        - mount_option_dev_shm_nodev
        - unknown_severity
        - configure_strategy
        - low_complexity
        - high_disruption
        - CCE-80152-2
        - NIST-800-53-CM-7
        - NIST-800-53-MP-2

    - name: get back device previous mount option
      shell: mount | grep ' /dev/shm ' | sed -re 's:.*\((.*)\):\1:'
      register: device_cur_mountoption
      check_mode: no
      tags:
        - mount_option_dev_shm_nodev
        - unknown_severity
        - configure_strategy
        - low_complexity
        - high_disruption
        - CCE-80152-2
        - NIST-800-53-CM-7
        - NIST-800-53-MP-2

    - name: get back device fstype
      shell: mount | grep ' /dev/shm ' | cut -d ' ' -f 5
      register: device_fstype
      check_mode: no
      tags:
        - mount_option_dev_shm_nodev
        - unknown_severity
        - configure_strategy
        - low_complexity
        - high_disruption
        - CCE-80152-2
        - NIST-800-53-CM-7
        - NIST-800-53-MP-2

    - name: Ensure permission nodev are set on /dev/shm
      mount:
        path: "/dev/shm"
        src: "{{device_name.stdout}}"
        opts: "{{device_cur_mountoption.stdout}},nodev"
        state: "mounted"
        fstype: "{{device_fstype.stdout}}"
      tags:
        - mount_option_dev_shm_nodev
        - unknown_severity
        - configure_strategy
        - low_complexity
        - high_disruption
        - CCE-80152-2
        - NIST-800-53-CM-7
        - NIST-800-53-MP-2

Remediation Anaconda snippet: (show)

Complexity: | low
Disruption: | high
Strategy: | enable

    part /dev/shm --mountoptions="nodev"

### System Accounting with <tt>auditd</tt> [ref]group

The audit service provides substantial capabilities for recording system activities. By default, the service audits about SELinux AVC denials and certain types of security-relevant events such as system logins, account modifications, and authentication events performed by programs such as sudo. Under its default configuration, `auditd` has modest disk space requirements, and should not noticeably impact system performance.
NOTE: The Linux Audit daemon `auditd` can be configured to use the `augenrules` program to read audit rules files (`*.rules`) located in `/etc/audit/rules.d` location and compile them to create the resulting form of the `/etc/audit/audit.rules` configuration file during the daemon startup (default configuration). Alternatively, the `auditd` daemon can use the `auditctl` utility to read audit rules from the `/etc/audit/audit.rules` configuration file during daemon startup, and load them into the kernel. The expected behavior is configured via the appropriate `ExecStartPost` directive setting in the `/usr/lib/systemd/system/auditd.service` configuration file. To instruct the `auditd` daemon to use the `augenrules` program to read audit rules (default configuration), use the following setting:

    ExecStartPost=-/sbin/augenrules --load

in the `/usr/lib/systemd/system/auditd.service` configuration file. In order to instruct the `auditd` daemon to use the `auditctl` utility to read audit rules, use the following setting:

    ExecStartPost=-/sbin/auditctl -R /etc/audit/audit.rules

in the `/usr/lib/systemd/system/auditd.service` configuration file. Refer to `[Service]` section of the `/usr/lib/systemd/system/auditd.service` configuration file for further details.
Government networks often have substantial auditing requirements and `auditd` can be configured to meet these requirements. Examining some example audit records demonstrates how the Linux audit system satisfies common requirements. The following example from Fedora Documentation available at `https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/SELinux_Users_and_Administrators_Guide/sect-Security-Enhanced_Linux-Troubleshooting-Fixing_Problems.html#sect-Security-Enhanced_Linux-Fixing_Problems-Raw_Audit_Messages` shows the substantial amount of information captured in a two typical "raw" audit messages, followed by a breakdown of the most important fields. In this example the message is SELinux-related and reports an AVC denial (and the associated system call) that occurred when the Apache HTTP Server attempted to access the `/var/www/html/file1` file (labeled with the `samba_share_t` type):

    type=AVC msg=audit(1226874073.147:96): avc:  denied  { getattr } for pid=2465 comm="httpd"
    path="/var/www/html/file1" dev=dm-0 ino=284133 scontext=unconfined_u:system_r:httpd_t:s0
    tcontext=unconfined_u:object_r:samba_share_t:s0 tclass=file

    type=SYSCALL msg=audit(1226874073.147:96): arch=40000003 syscall=196 success=no exit=-13
    a0=b98df198 a1=bfec85dc a2=54dff4 a3=2008171 items=0 ppid=2463 pid=2465 auid=502 uid=48
    gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=6 comm="httpd"
    exe="/usr/sbin/httpd" subj=unconfined_u:system_r:httpd_t:s0 key=(null)

- `msg=audit(1226874073.147:96)`
  - The number in parentheses is the unformatted time stamp (Epoch time) for the event, which can be converted to standard time by using the `date` command.
- `{ getattr }`
  - The item in braces indicates the permission that was denied. `getattr` indicates the source process was trying to read the target file's status information. This occurs before reading files. This action is denied due to the file being accessed having the wrong label. Commonly seen permissions include `getattr`, `read`, and `write`.
- `comm="httpd"`
  - The executable that launched the process. The full path of the executable is found in the `exe=` section of the system call (`SYSCALL`) message, which in this case, is `exe="/usr/sbin/httpd"`.
- `path="/var/www/html/file1"`
  - The path to the object (target) the process attempted to access.
- `scontext="unconfined_u:system_r:httpd_t:s0"`
  - The SELinux context of the process that attempted the denied action. In this case, it is the SELinux context of the Apache HTTP Server, which is running in the `httpd_t` domain.
- `tcontext="unconfined_u:object_r:samba_share_t:s0"`
  - The SELinux context of the object (target) the process attempted to access. In this case, it is the SELinux context of `file1`. Note: the `samba_share_t` type is not accessible to processes running in the `httpd_t` domain.
- From the system call (`SYSCALL`) message, two items are of interest:
  - `success=no`: indicates whether the denial (AVC) was enforced or not. `success=no` indicates the system call was not successful (SELinux denied access). `success=yes` indicates the system call was successful - this can be seen for permissive domains or unconfined domains, such as `initrc_t` and `kernel_t`.
  - `exe="/usr/sbin/httpd"`: the full path to the executable that launched the process, which in this case, is `exe="/usr/sbin/httpd"`.

contains 27 rules

### Configure <tt>auditd</tt> Rules for Comprehensive Auditing [ref]group

The `auditd` program can perform comprehensive monitoring of system activity. This section describes recommended configuration settings for comprehensive auditing, but a full description of the auditing system's capabilities is beyond the scope of this guide. The mailing list linux-audit@redhat.com exists to facilitate community discussion of the auditing system.
The audit subsystem supports extensive collection of events, including:

- Tracing of arbitrary system calls (identified by name or number) on entry or exit.
- Filtering by PID, UID, call success, system call argument (with some limitations), etc.
- Monitoring of specific files for modifications to the file's contents or metadata.

Auditing rules at startup are controlled by the file `/etc/audit/audit.rules`. Add rules to it to meet the auditing requirements for your organization. Each line in `/etc/audit/audit.rules` represents a series of arguments that can be passed to `auditctl` and can be individually tested during runtime. See documentation in `/usr/share/doc/audit-VERSION` and in the related man pages for more details.
If copying any example audit rulesets from `/usr/share/doc/audit-VERSION`, be sure to comment out the lines containing `arch=` which are not appropriate for your system's architecture. Then review and understand the following rules, ensuring rules are activated as needed for the appropriate architecture.
After reviewing all the rules, reading the following sections, and editing as needed, the new rules can be activated as follows:

    $ sudo service auditd restart

contains 27 rules

### Record Information on Kernel Modules Loading and Unloading [ref]group

To capture kernel module loading and unloading events, use following lines, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:

    -w /usr/sbin/insmod -p x -k modules
    -w /usr/sbin/rmmod -p x -k modules
    -w /usr/sbin/modprobe -p x -k modules
    -a always,exit -F arch=ARCH -S init_module,delete_module -F key=modules

Place to add the lines depends on a way `auditd` daemon is configured. If it is configured to use the `augenrules` program (the default), add the lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`. If the `auditd` daemon is configured to use the `auditctl` utility, add the lines to file `/etc/audit/audit.rules`.

contains 1 rule

#### Ensure auditd Collects Information on Kernel Module Loading and Unloading [ref]rule

To capture kernel module loading and unloading events, use following lines, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:

    -w /usr/sbin/insmod -p x -k modules
    -w /usr/sbin/rmmod -p x -k modules
    -w /usr/sbin/modprobe -p x -k modules
    -a always,exit -F arch=ARCH -S init_module,delete_module -F key=modules

Place to add the lines depends on a way `auditd` daemon is configured. If it is configured to use the `augenrules` program (the default), add the lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`. If the `auditd` daemon is configured to use the `auditctl` utility, add the lines to file `/etc/audit/audit.rules`.Rationale:

The addition/removal of kernel modules can be used to alter the behavior of the kernel and potentially introduce malicious code into kernel space. It is important to have an audit trail of modules that have been introduced into the kernel.

Severity: medium

References: 5.2.17, 5.4.1.1, 3.1.7, CCI-000172, AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.7

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    # Note: 32-bit and 64-bit kernel syscall numbers not always line up =>
    #       it's required on a 64-bit system to check also for the presence
    #       of 32-bit's equivalent of the corresponding rule.
    #       (See `man 7 audit.rules` for details )
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
            PATTERN="-a always,exit -F arch=$ARCH -S init_module -S delete_module \(-F key=\|-k \).*"
            GROUP="modules"
            FULL_RULE="-a always,exit -F arch=$ARCH -S init_module -S delete_module -k modules"
            # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

            fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
            fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

    # Then perform the remediations for the watch rules
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# For each of the matched entries
    	for match in "${matches[@]}"
    	do
    		# Extract filepath from the match
    		rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    		# Append that path into list of files for inspection
    		files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
    	done
    	# Case when particular audit rule isn't defined yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

    	# Check if audit watch file system object rule for given path already present
    	if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local sp="[[:space:]]"
    		# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
    		current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
    		# Split required access bits string into characters array
    		# (to check bit's presence for one bit at a time)
    		for access_bit in $(echo "$required_access_bits" | grep -o .)
    		do
    			# For each from the required access bits (e.g. 'w', 'a') check
    			# if they are already present in current access bits for rule.
    			# If not, append that bit at the end
    			if ! grep -q "$access_bit" <<< "$current_access_bits"
    			then
    				# Concatenate the existing mask with the missing bit
    				current_access_bits="$current_access_bits$access_bit"
    			fi
    		done
    		# Propagate the updated rule's access bits (original + the required
    		# ones) back into the /etc/audit/audit.rules file for that rule
    		sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
    	else
    		# Rule isn't present yet. Append it at the end of $audit_rules_file file
    		# with proper key

    		echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/usr/sbin/insmod" "x" "modules"
    fix_audit_watch_rule "augenrules" "/usr/sbin/insmod" "x" "modules"
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# For each of the matched entries
    	for match in "${matches[@]}"
    	do
    		# Extract filepath from the match
    		rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    		# Append that path into list of files for inspection
    		files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
    	done
    	# Case when particular audit rule isn't defined yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

    	# Check if audit watch file system object rule for given path already present
    	if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local sp="[[:space:]]"
    		# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
    		current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
    		# Split required access bits string into characters array
    		# (to check bit's presence for one bit at a time)
    		for access_bit in $(echo "$required_access_bits" | grep -o .)
    		do
    			# For each from the required access bits (e.g. 'w', 'a') check
    			# if they are already present in current access bits for rule.
    			# If not, append that bit at the end
    			if ! grep -q "$access_bit" <<< "$current_access_bits"
    			then
    				# Concatenate the existing mask with the missing bit
    				current_access_bits="$current_access_bits$access_bit"
    			fi
    		done
    		# Propagate the updated rule's access bits (original + the required
    		# ones) back into the /etc/audit/audit.rules file for that rule
    		sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
    	else
    		# Rule isn't present yet. Append it at the end of $audit_rules_file file
    		# with proper key

    		echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/usr/sbin/rmmod" "x" "modules"
    fix_audit_watch_rule "augenrules" "/usr/sbin/rmmod" "x" "modules"
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# For each of the matched entries
    	for match in "${matches[@]}"
    	do
    		# Extract filepath from the match
    		rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    		# Append that path into list of files for inspection
    		files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
    	done
    	# Case when particular audit rule isn't defined yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

    	# Check if audit watch file system object rule for given path already present
    	if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local sp="[[:space:]]"
    		# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
    		current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
    		# Split required access bits string into characters array
    		# (to check bit's presence for one bit at a time)
    		for access_bit in $(echo "$required_access_bits" | grep -o .)
    		do
    			# For each from the required access bits (e.g. 'w', 'a') check
    			# if they are already present in current access bits for rule.
    			# If not, append that bit at the end
    			if ! grep -q "$access_bit" <<< "$current_access_bits"
    			then
    				# Concatenate the existing mask with the missing bit
    				current_access_bits="$current_access_bits$access_bit"
    			fi
    		done
    		# Propagate the updated rule's access bits (original + the required
    		# ones) back into the /etc/audit/audit.rules file for that rule
    		sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
    	else
    		# Rule isn't present yet. Append it at the end of $audit_rules_file file
    		# with proper key

    		echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/usr/sbin/modprobe" "x" "modules"
    fix_audit_watch_rule "augenrules" "/usr/sbin/modprobe" "x" "modules"

### Records Events that Modify Date and Time Information [ref]group

Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time. All changes to the system time should be audited.

contains 5 rules

#### Record Attempts to Alter Time Through stime [ref]rule

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d` for both 32 bit and 64 bit systems:

    -a always,exit -F arch=b32 -S stime -F key=audit_time_rules

Since the 64 bit version of the "stime" system call is not defined in the audit lookup table, the corresponding "-F arch=b64" form of this rule is not expected to be defined on 64 bit systems (the aforementioned "-F arch=b32" stime rule form itself is sufficient for both 32 bit and 64 bit systems). If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file for both 32 bit and 64 bit systems:

    -a always,exit -F arch=b32 -S stime -F key=audit_time_rules

Since the 64 bit version of the "stime" system call is not defined in the audit lookup table, the corresponding "-F arch=b64" form of this rule is not expected to be defined on 64 bit systems (the aforementioned "-F arch=b32" stime rule form itself is sufficient for both 32 bit and 64 bit systems). The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined system calls:

    -a always,exit -F arch=b64 -S adjtimex,settimeofday -F key=audit_time_rules

Rationale:

Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.

Severity: unknown

References: 5.4.1.1, 3.1.7, CCI-001487, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.4.2.b

Remediation Shell script: (show)

    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    # Perform the remediation for the 'adjtimex', 'settimeofday', and 'stime' audit
    # system calls on Red Hat Enterprise Linux 7 or Fedora OSes
    function rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation {

    # Retrieve hardware architecture of the underlying system
    [ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do

    	PATTERN="-a always,exit -F arch=${ARCH} -S .* -k *"
    	# Create expected audit group and audit rule form for particular system call & architecture
    	if [ ${ARCH} = "b32" ]
    	then
    		# stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output)
    		# so append it to the list of time group system calls to be audited
    		GROUP="\(adjtimex\|settimeofday\|stime\)"
    		FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -S stime -k audit_time_rules"
    	elif [ ${ARCH} = "b64" ]
    	then
    		# stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output)
    		# therefore don't add it to the list of time group system calls to be audited
    		GROUP="\(adjtimex\|settimeofday\)"
    		FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -k audit_time_rules"
    	fi
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

    }

    rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation

#### Record attempts to alter time through settimeofday [ref]rule

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S settimeofday -F key=audit_time_rules

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S settimeofday -F key=audit_time_rules

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S settimeofday -F key=audit_time_rules

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S settimeofday -F key=audit_time_rules

The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined syscalls:

    -a always,exit -F arch=b64 -S adjtimex,settimeofday -F key=audit_time_rules

Rationale:

Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.

Severity: unknown

References: 5.2.4, 5.4.1.1, 3.1.7, CCI-001487, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.4.2.b

Remediation Shell script: (show)

    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    # Perform the remediation for the 'adjtimex', 'settimeofday', and 'stime' audit
    # system calls on Red Hat Enterprise Linux 7 or Fedora OSes
    function rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation {

    # Retrieve hardware architecture of the underlying system
    [ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do

    	PATTERN="-a always,exit -F arch=${ARCH} -S .* -k *"
    	# Create expected audit group and audit rule form for particular system call & architecture
    	if [ ${ARCH} = "b32" ]
    	then
    		# stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output)
    		# so append it to the list of time group system calls to be audited
    		GROUP="\(adjtimex\|settimeofday\|stime\)"
    		FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -S stime -k audit_time_rules"
    	elif [ ${ARCH} = "b64" ]
    	then
    		# stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output)
    		# therefore don't add it to the list of time group system calls to be audited
    		GROUP="\(adjtimex\|settimeofday\)"
    		FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -k audit_time_rules"
    	fi
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

    }

    rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation

#### Record Attempts to Alter the localtime File [ref]rule

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -w /etc/localtime -p wa -k audit_time_rules

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -w /etc/localtime -p wa -k audit_time_rules

The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport and should always be used.Rationale:

Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.

Severity: unknown

References: 5.2.4, 5.4.1.1, 3.1.7, CCI-001487, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(b), IR-5, Req-10.4.2.b

Remediation Shell script: (show)

    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# For each of the matched entries
    	for match in "${matches[@]}"
    	do
    		# Extract filepath from the match
    		rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    		# Append that path into list of files for inspection
    		files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
    	done
    	# Case when particular audit rule isn't defined yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

    	# Check if audit watch file system object rule for given path already present
    	if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local sp="[[:space:]]"
    		# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
    		current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
    		# Split required access bits string into characters array
    		# (to check bit's presence for one bit at a time)
    		for access_bit in $(echo "$required_access_bits" | grep -o .)
    		do
    			# For each from the required access bits (e.g. 'w', 'a') check
    			# if they are already present in current access bits for rule.
    			# If not, append that bit at the end
    			if ! grep -q "$access_bit" <<< "$current_access_bits"
    			then
    				# Concatenate the existing mask with the missing bit
    				current_access_bits="$current_access_bits$access_bit"
    			fi
    		done
    		# Propagate the updated rule's access bits (original + the required
    		# ones) back into the /etc/audit/audit.rules file for that rule
    		sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
    	else
    		# Rule isn't present yet. Append it at the end of $audit_rules_file file
    		# with proper key

    		echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    fix_audit_watch_rule "augenrules" "/etc/localtime" "wa" "audit_time_rules"

#### Record Attempts to Alter Time Through clock_settime [ref]rule

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -F key=time-change

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -F key=time-change

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -F key=time-change

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -F key=time-change

The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined syscalls:

    -a always,exit -F arch=b64 -S adjtimex,settimeofday -F key=audit_time_rules

Rationale:

Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.

Severity: unknown

References: 5.2.4, 5.4.1.1, 3.1.7, CCI-001487, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.4.2.b

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S clock_settime -F a0=.* \(-F key=\|-k \).*"
    	GROUP="clock_settime"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S clock_settime -F a0=0x0 -k time-change"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

#### Record attempts to alter time through adjtimex [ref]rule

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S adjtimex -F key=audit_time_rules

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S adjtimex -F key=audit_time_rules

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S adjtimex -F key=audit_time_rules

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S adjtimex -F key=audit_time_rules

The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined syscalls:

    -a always,exit -F arch=b64 -S adjtimex,settimeofday -F key=audit_time_rules

Rationale:

Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.

Severity: unknown

References: 5.2.4, 5.4.1.1, 3.1.7, CCI-001487, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.4.2.b

Remediation Shell script: (show)

    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    # Perform the remediation for the 'adjtimex', 'settimeofday', and 'stime' audit
    # system calls on Red Hat Enterprise Linux 7 or Fedora OSes
    function rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation {

    # Retrieve hardware architecture of the underlying system
    [ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do

    	PATTERN="-a always,exit -F arch=${ARCH} -S .* -k *"
    	# Create expected audit group and audit rule form for particular system call & architecture
    	if [ ${ARCH} = "b32" ]
    	then
    		# stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output)
    		# so append it to the list of time group system calls to be audited
    		GROUP="\(adjtimex\|settimeofday\|stime\)"
    		FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -S stime -k audit_time_rules"
    	elif [ ${ARCH} = "b64" ]
    	then
    		# stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output)
    		# therefore don't add it to the list of time group system calls to be audited
    		GROUP="\(adjtimex\|settimeofday\)"
    		FULL_RULE="-a always,exit -F arch=${ARCH} -S adjtimex -S settimeofday -k audit_time_rules"
    	fi
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

    }

    rhel7_fedora_perform_audit_adjtimex_settimeofday_stime_remediation

### Record Events that Modify the System's Discretionary Access Controls [ref]group

At a minimum, the audit system should collect file permission changes for all users and root. Note that the "-F arch=b32" lines should be present even on a 64 bit system. These commands identify system calls for auditing. Even if the system is 64 bit it can still execute 32 bit system calls. Additionally, these rules can be configured in a number of ways while still achieving the desired effect. An example of this is that the "-S" calls could be split up and placed on separate lines, however, this is less efficient. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
        -a always,exit -F arch=b32 -S chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
        -a always,exit -F arch=b32 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If your system is 64 bit then these lines should be duplicated and the arch=b32 replaced with arch=b64 as follows:

    -a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
        -a always,exit -F arch=b64 -S chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod
        -a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

contains 13 rules

#### Record Events that Modify the System's Discretionary Access Controls - fchown [ref]rule

At a minimum, the audit system should collect file permission changes for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: FAU_GEN.1.1.c, SV-86723r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S fchown.*"
    	GROUP="perm_mod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit fchown tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_fchown

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_fchown.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_fchown.files | map(attribute='path') | list | first }}"
      when: find_fchown.matched > 0

    - name: Inserts/replaces the fchown rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_fchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27356-5
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030380

    - name: Inserts/replaces the fchown rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27356-5
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030380
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the fchown rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_fchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27356-5
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030380

    - name: Inserts/replaces the fchown rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S fchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27356-5
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030380

#### Record Events that Modify the System's Discretionary Access Controls - setxattr [ref]rule

At a minimum, the audit system should collect file permission changes for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: FAU_GEN.1.1.c, SV-86735r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S setxattr.*"
    	GROUP="perm_mod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit setxattr tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_setxattr

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_setxattr.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_setxattr.files | map(attribute='path') | list | first }}"
      when: find_setxattr.matched > 0

    - name: Inserts/replaces the setxattr rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_setxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27213-8
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030440

    - name: Inserts/replaces the setxattr rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_setxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27213-8
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030440
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the setxattr rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_setxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27213-8
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030440

    - name: Inserts/replaces the setxattr rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S setxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_setxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27213-8
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030440

#### Record Events that Modify the System's Discretionary Access Controls - chown [ref]rule

At a minimum, the audit system should collect file permission changes for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: FAU_GEN.1.1.c, SV-86721r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S chown.*"
    	GROUP="perm_mod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit chown tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_chown

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_chown.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_chown.files | map(attribute='path') | list | first }}"
      when: find_chown.matched > 0

    - name: Inserts/replaces the chown rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_chown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27364-9
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030370

    - name: Inserts/replaces the chown rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_chown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27364-9
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030370
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the chown rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_chown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27364-9
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030370

    - name: Inserts/replaces the chown rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_chown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27364-9
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030370

#### Record Events that Modify the System's Discretionary Access Controls - lsetxattr [ref]rule

At a minimum, the audit system should collect file permission changes for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: FAU_GEN.1.1.c, SV-86739r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S lsetxattr.*"
    	GROUP="perm_mod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit lsetxattr tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_lsetxattr

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_lsetxattr.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_lsetxattr.files | map(attribute='path') | list | first }}"
      when: find_lsetxattr.matched > 0

    - name: Inserts/replaces the lsetxattr rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_lsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27280-7
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030460

    - name: Inserts/replaces the lsetxattr rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_lsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27280-7
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030460
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the lsetxattr rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_lsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27280-7
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030460

    - name: Inserts/replaces the lsetxattr rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S lsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_lsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27280-7
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030460

#### Record Events that Modify the System's Discretionary Access Controls - lchown [ref]rule

At a minimum, the audit system should collect file permission changes for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: FAU_GEN.1.1.c, SV-86725r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S lchown.*"
    	GROUP="perm_mod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit lchown tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_lchown

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_lchown.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_lchown.files | map(attribute='path') | list | first }}"
      when: find_lchown.matched > 0

    - name: Inserts/replaces the lchown rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_lchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27083-5
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030390

    - name: Inserts/replaces the lchown rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_lchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27083-5
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030390
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the lchown rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_lchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27083-5
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030390

    - name: Inserts/replaces the lchown rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S lchown -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_lchown
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27083-5
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030390

#### Record Events that Modify the System's Discretionary Access Controls - chmod [ref]rule

At a minimum, the audit system should collect file permission changes for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: FAU_GEN.1.1.c, SV-86729r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S chmod.*"
    	GROUP="perm_mod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit chmod tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_chmod

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_chmod.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_chmod.files | map(attribute='path') | list | first }}"
      when: find_chmod.matched > 0

    - name: Inserts/replaces the chmod rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_chmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27339-1
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030410

    - name: Inserts/replaces the chmod rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_chmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27339-1
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030410
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the chmod rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_chmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27339-1
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030410

    - name: Inserts/replaces the chmod rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_chmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27339-1
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030410

#### Record Events that Modify the System's Discretionary Access Controls - removexattr [ref]rule

At a minimum, the audit system should collect file permission changes for all users and root.
If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: medium

References: FAU_GEN.1.1.c, SV-86741r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S removexattr.*"
    	GROUP="perm_mod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit removexattr tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_removexattr

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_removexattr.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_removexattr.files | map(attribute='path') | list | first }}"
      when: find_removexattr.matched > 0

    - name: Inserts/replaces the removexattr rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_removexattr
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27367-2
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030470

    - name: Inserts/replaces the removexattr rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_removexattr
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27367-2
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030470
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the removexattr rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_removexattr
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27367-2
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030470

    - name: Inserts/replaces the removexattr rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S removexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_removexattr
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27367-2
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030470

#### Record Events that Modify the System's Discretionary Access Controls - fchmod [ref]rule

At a minimum, the audit system should collect file permission changes for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: FAU_GEN.1.1.c, SV-86731r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S fchmod.*"
    	GROUP="perm_mod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit fchmod tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_fchmod

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_fchmod.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_fchmod.files | map(attribute='path') | list | first }}"
      when: find_fchmod.matched > 0

    - name: Inserts/replaces the fchmod rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_fchmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27393-8
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030420

    - name: Inserts/replaces the fchmod rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27393-8
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030420
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the fchmod rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_fchmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27393-8
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030420

    - name: Inserts/replaces the fchmod rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S fchmod -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchmod
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27393-8
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030420

#### Record Events that Modify the System's Discretionary Access Controls - fchownat [ref]rule

At a minimum, the audit system should collect file permission changes for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: FAU_GEN.1.1.c, SV-86727r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S fchownat.*"
    	GROUP="perm_mod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit fchownat tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_fchownat

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_fchownat.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_fchownat.files | map(attribute='path') | list | first }}"
      when: find_fchownat.matched > 0

    - name: Inserts/replaces the fchownat rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_fchownat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27387-0
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030400

    - name: Inserts/replaces the fchownat rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchownat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27387-0
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030400
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the fchownat rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_fchownat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27387-0
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030400

    - name: Inserts/replaces the fchownat rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S fchownat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchownat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27387-0
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030400

#### Record Events that Modify the System's Discretionary Access Controls - fremovexattr [ref]rule

At a minimum, the audit system should collect file permission changes for all users and root.
If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: medium

References: FAU_GEN.1.1.c, SV-86743r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S fremovexattr.*"
    	GROUP="perm_mod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit fremovexattr tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_fremovexattr

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_fremovexattr.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_fremovexattr.files | map(attribute='path') | list | first }}"
      when: find_fremovexattr.matched > 0

    - name: Inserts/replaces the fremovexattr rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_fremovexattr
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27353-2
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030480

    - name: Inserts/replaces the fremovexattr rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fremovexattr
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27353-2
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030480
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the fremovexattr rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_fremovexattr
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27353-2
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030480

    - name: Inserts/replaces the fremovexattr rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S fremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fremovexattr
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27353-2
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030480

#### Record Events that Modify the System's Discretionary Access Controls - lremovexattr [ref]rule

At a minimum, the audit system should collect file permission changes for all users and root.
If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: medium

References: FAU_GEN.1.1.c, SV-86745r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S lremovexattr.*"
    	GROUP="perm_mod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit lremovexattr tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_lremovexattr

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_lremovexattr.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_lremovexattr.files | map(attribute='path') | list | first }}"
      when: find_lremovexattr.matched > 0

    - name: Inserts/replaces the lremovexattr rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_lremovexattr
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27410-0
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030490

    - name: Inserts/replaces the lremovexattr rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_lremovexattr
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27410-0
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030490
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the lremovexattr rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_lremovexattr
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27410-0
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030490

    - name: Inserts/replaces the lremovexattr rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S lremovexattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_lremovexattr
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27410-0
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030490

#### Record Events that Modify the System's Discretionary Access Controls - fsetxattr [ref]rule

At a minimum, the audit system should collect file permission changes for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: FAU_GEN.1.1.c, SV-86737r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S fsetxattr.*"
    	GROUP="perm_mod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit fsetxattr tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_fsetxattr

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_fsetxattr.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_fsetxattr.files | map(attribute='path') | list | first }}"
      when: find_fsetxattr.matched > 0

    - name: Inserts/replaces the fsetxattr rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_fsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27389-6
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030450

    - name: Inserts/replaces the fsetxattr rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27389-6
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030450
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the fsetxattr rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_fsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27389-6
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030450

    - name: Inserts/replaces the fsetxattr rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S fsetxattr -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fsetxattr
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27389-6
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030450

#### Record Events that Modify the System's Discretionary Access Controls - fchmodat [ref]rule

At a minimum, the audit system should collect file permission changes for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

If the system is 64 bit then also add the following line:

    -a always,exit -F arch=b64 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod

Rationale:

The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.

Severity: unknown

References: FAU_GEN.1.1.c, SV-86733r3_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-000126, CCI-000172, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5, SRG-OS-000064-GPOS-00033, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S fchmodat.*"
    	GROUP="perm_mod"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict

    #
    # What architecture are we on?
    #
    - name: Set architecture for audit fchmodat tasks
      set_fact:
        audit_arch: "b{{ ansible_architecture | regex_replace('.*(\\d\\d$)','\\1') }}"

    #
    # Inserts/replaces the rule in /etc/audit/rules.d
    #
    - name: Search /etc/audit/rules.d for other DAC audit rules
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "-F key=perm_mod$"
        patterns: "*.rules"
      register: find_fchmodat

    - name: If existing DAC ruleset not found, use /etc/audit/rules.d/privileged.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/privileged.rules
      when: find_fchmodat.matched == 0

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - "{{ find_fchmodat.files | map(attribute='path') | list | first }}"
      when: find_fchmodat.matched > 0

    - name: Inserts/replaces the fchmodat rule in rules.d when on x86
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b32 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      tags:
        - audit_rules_dac_modification_fchmodat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27388-8
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030430

    - name: Inserts/replaces the fchmodat rule in rules.d when on x86_64
      lineinfile:
        path: "{{ all_files[0] }}"
        line: "-a always,exit -F arch=b64 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
        create: yes
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchmodat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27388-8
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030430
    #
    # Inserts/replaces the rule in /etc/audit/audit.rules
    #
    - name: Inserts/replaces the fchmodat rule in /etc/audit/audit.rules when on x86
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
      with_items:
        - "-a always,exit -F arch=b32 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      tags:
        - audit_rules_dac_modification_fchmodat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27388-8
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030430

    - name: Inserts/replaces the fchmodat rule in audit.rules when on x86_64
      lineinfile:
        line: "{{ item }}"
        state: present
        dest: /etc/audit/audit.rules
        create: yes
      with_items:
        - "-a always,exit -F arch=b64 -S fchmodat -F auid>=1000 -F auid!=4294967295 -F key=perm_mod"
      when: audit_arch == 'b64'
      tags:
        - audit_rules_dac_modification_fchmodat
        - unknown_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27388-8
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.5.5
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030430

### Record File Deletion Events by User [ref]group

At a minimum, the audit system should collect file deletion events for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`, setting ARCH to either b32 or b64 as appropriate for your system:

    -a always,exit -F arch=ARCH -S rmdir,unlink,unlinkat,rename,renameat -F auid>=1000 -F auid!=4294967295 -F key=delete

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file, setting ARCH to either b32 or b64 as appropriate for your system:

    -a always,exit -F arch=ARCH -S rmdir,unlink,unlinkat,rename,renameat -F auid>=1000 -F auid!=4294967295 -F key=delete

contains 1 rule

#### Ensure auditd Collects File Deletion Events by User [ref]rule

At a minimum the audit system should collect file deletion events for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`, setting ARCH to either b32 or b64 as appropriate for your system:

    -a always,exit -F arch=ARCH -S rmdiri,unlink,unlinkat,rename,renameat -F auid>=1000 -F auid!=4294967295 -F key=delete

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file, setting ARCH to either b32 or b64 as appropriate for your system:

    -a always,exit -F arch=ARCH -S rmdir,unlink,unlinkat,rename -S renameat -F auid>=1000 -F auid!=4294967295 -F key=delete

Rationale:

Auditing file deletions will create an audit trail for files that are removed from the system. The audit trail could aid in system troubleshooting, as well as, detecting malicious processes that attempt to delete log files to conceal their presence.

Severity: medium

References: FAU_GEN.1.1.c, 5.2.14, 5.4.1.1, 3.1.7, CCI-000366, CCI-000172, CCI-002884, AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.7

Remediation Shell script: (show)

    # Perform the remediation for the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F auid>=1000 -F auid!=4294967295 -k *"
    	# Use escaped BRE regex to specify rule group
    	GROUP="\(rmdir\|unlink\|rename\)"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S rmdir -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -F auid!=4294967295 -k delete"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

### Record Information on the Use of Privileged Commands [ref]group

At a minimum, the audit system should collect the execution of privileged commands for all users and root.

contains 1 rule

#### Ensure auditd Collects Information on the Use of Privileged Commands [ref]rule

At a minimum, the audit system should collect the execution of privileged commands for all users and root. To find the relevant setuid / setgid programs, run the following command for each local partition PART:

    $ sudo find PART -xdev -type f -perm -4000 -o -type f -perm -2000 2>/dev/null

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add a line of the following form to a file with suffix `.rules` in the directory `/etc/audit/rules.d` for each setuid / setgid program on the system, replacing the SETUID_PROG_PATH part with the full path of that setuid / setgid program in the list:

    -a always,exit -F path=SETUID_PROG_PATH -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add a line of the following form to `/etc/audit/audit.rules` for each setuid / setgid program on the system, replacing the SETUID_PROG_PATH part with the full path of that setuid / setgid program in the list:

    -a always,exit -F path=SETUID_PROG_PATH -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged

Rationale:

Misuse of privileged functions, either intentionally or unintentionally by authorized users, or by unauthorized external entities that have compromised system accounts, is a serious and ongoing concern and can have significant adverse impacts on organizations. Auditing the use of privileged functions is one way to detect such misuse and identify the risk from insider and advanced persistent threast.
Privileged programs are subject to escalation-of-privilege attacks, which attempt to subvert their normal role of providing some necessary but limited capability. As such, motivation exists to monitor these programs for unusual activity.

Severity: medium

References: SV-86719r5_rule, 5.2.10, 5.4.1.1, 3.1.7, CCI-002234, AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-2(4), AU-6(9), AU-12(a), AU-12(c), IR-5, Req-10.2.2, SRG-OS-000327-GPOS-00127

Remediation Shell script: (show)

    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to perform remediation for 'audit_rules_privileged_commands' rule
    #
    # Expects two arguments:
    #
    # audit_tool		tool used to load audit rules
    # 			One of 'auditctl' or 'augenrules'
    #
    # min_auid		Minimum original ID the user logged in with
    # 			'500' for RHEL-6 and before, '1000' for RHEL-7 and after.
    #
    # Example Call(s):
    #
    #      perform_audit_rules_privileged_commands_remediation "auditctl" "500"
    #      perform_audit_rules_privileged_commands_remediation "augenrules"	"1000"
    #
    function perform_audit_rules_privileged_commands_remediation {
    #
    # Load function arguments into local variables
    local tool="$1"
    local min_auid="$2"

    # Check sanity of the input
    if [ $# -ne "2" ]
    then
    	echo "Usage: perform_audit_rules_privileged_commands_remediation 'auditctl | augenrules' '500 | 1000'"
    	echo "Aborting."
    	exit 1
    fi

    declare -a files_to_inspect=()

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then:
    # * add '/etc/audit/audit.rules'to the list of files to be inspected,
    # * specify '/etc/audit/audit.rules' as the output audit file, where
    #   missing rules should be inserted
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("/etc/audit/audit.rules")
    	output_audit_file="/etc/audit/audit.rules"
    #
    # If the audit tool is 'augenrules', then:
    # * add '/etc/audit/rules.d/*.rules' to the list of files to be inspected
    #   (split by newline),
    # * specify /etc/audit/rules.d/privileged.rules' as the output file, where
    #   missing rules should be inserted
    elif [ "$tool" == 'augenrules' ]
    then
    	IFS=$'\n' files_to_inspect=($(find /etc/audit/rules.d -maxdepth 1 -type f -name '*.rules' -print))
    	output_audit_file="/etc/audit/rules.d/privileged.rules"
    fi

    # Obtain the list of SUID/SGID binaries on the particular system (split by newline)
    # into privileged_binaries array
    IFS=$'\n' privileged_binaries=($(find / -xdev -type f -perm -4000 -o -type f -perm -2000 2>/dev/null))

    # Keep list of SUID/SGID binaries that have been already handled within some previous iteration
    declare -a sbinaries_to_skip=()

    # For each found sbinary in privileged_binaries list
    for sbinary in "${privileged_binaries[@]}"
    do

    	# Check if this sbinary wasn't already handled in some of the previous iterations
    	# Return match only if whole sbinary definition matched (not in the case just prefix matched!!!)
    	if [[ $(sed -ne "\|${sbinary}|p" <<< "${sbinaries_to_skip[*]}") ]]
    	then
    		# If so, don't process it second time & go to process next sbinary
    		continue
    	fi

    	# Reset the counter of inspected files when starting to check
    	# presence of existing audit rule for new sbinary
    	local count_of_inspected_files=0

    	# Define expected rule form for this binary
    	expected_rule="-a always,exit -F path=${sbinary} -F perm=x -F auid>=${min_auid} -F auid!=4294967295 -k privileged"

    	# If list of audit rules files to be inspected is empty, just add new rule and move on to next binary
    	if [[ ${#files_to_inspect[@]} -eq 0 ]]; then
    		echo "$expected_rule" >> "$output_audit_file"
    		continue
    	fi

    	# Replace possible slash '/' character in sbinary definition so we could use it in sed expressions below
    	sbinary_esc=${sbinary//$'/'/$'\/'}

    	# For each audit rules file from the list of files to be inspected
    	for afile in "${files_to_inspect[@]}"
    	do

    		# Search current audit rules file's content for match. Match criteria:
    		# * existing rule is for the same SUID/SGID binary we are currently processing (but
    		#   can contain multiple -F path= elements covering multiple SUID/SGID binaries)
    		# * existing rule contains all arguments from expected rule form (though can contain
    		#   them in arbitrary order)

    		base_search=$(sed -e '/-a always,exit/!d' -e '/-F path='"${sbinary_esc}"'/!d' \
    				-e '/-F path=[^[:space:]]\+/!d'   -e '/-F perm=.*/!d'                 \
    				-e '/-F auid>='"${min_auid}"'/!d' -e '/-F auid!=4294967295/!d'        \
    				-e '/-k privileged/!d' "$afile")

    		# Increase the count of inspected files for this sbinary
    		count_of_inspected_files=$((count_of_inspected_files + 1))

    		# Require execute access type to be set for existing audit rule
    		exec_access='x'

    		# Search current audit rules file's content for presence of rule pattern for this sbinary
    		if [[ $base_search ]]
    		then

    			# Current audit rules file already contains rule for this binary =>
    			# Store the exact form of found rule for this binary for further processing
    			concrete_rule=$base_search

    			# Select all other SUID/SGID binaries possibly also present in the found rule
    			IFS=$'\n' handled_sbinaries=($(grep -o -e "-F path=[^[:space:]]\+" <<< "$concrete_rule"))
    			IFS=$' ' handled_sbinaries=(${handled_sbinaries[@]//-F path=/})

    			# Merge the list of such SUID/SGID binaries found in this iteration with global list ignoring duplicates
    			sbinaries_to_skip=($(for i in "${sbinaries_to_skip[@]}" "${handled_sbinaries[@]}"; do echo "$i"; done | sort -du))

    			# Separate concrete_rule into three sections using hash '#'
    			# sign as a delimiter around rule's permission section borders
    			concrete_rule="$(echo "$concrete_rule" | sed -n "s/\(.*\)\+\(-F perm=[rwax]\+\)\+/\1#\2#/p")"

    			# Split concrete_rule into head, perm, and tail sections using hash '#' delimiter
    			IFS=$'#' read -r rule_head rule_perm rule_tail <<<  "$concrete_rule"

    			# Extract already present exact access type [r|w|x|a] from rule's permission section
    			access_type=${rule_perm//-F perm=/}

    			# Verify current permission access type(s) for rule contain 'x' (execute) permission
    			if ! grep -q "$exec_access" <<< "$access_type"
    			then

    				# If not, append the 'x' (execute) permission to the existing access type bits
    				access_type="$access_type$exec_access"
    				# Reconstruct the permissions section for the rule
    				new_rule_perm="-F perm=$access_type"
    				# Update existing rule in current audit rules file with the new permission section
    				sed -i "s#${rule_head}\(.*\)${rule_tail}#${rule_head}${new_rule_perm}${rule_tail}#" "$afile"

    			fi

    		# If the required audit rule for particular sbinary wasn't found yet, insert it under following conditions:
    		#
    		# * in the "auditctl" mode of operation insert particular rule each time
    		#   (because in this mode there's only one file -- /etc/audit/audit.rules to be inspected for presence of this rule),
    		#
    		# * in the "augenrules" mode of operation insert particular rule only once and only in case we have already
    		#   searched all of the files from /etc/audit/rules.d/*.rules location (since that audit rule can be defined
    		#   in any of those files and if not, we want it to be inserted only once into /etc/audit/rules.d/privileged.rules file)
    		#
    		elif [ "$tool" == "auditctl" ] || [[ "$tool" == "augenrules" && $count_of_inspected_files -eq "${#files_to_inspect[@]}" ]]
    		then

    			# Current audit rules file's content doesn't contain expected rule for this
    			# SUID/SGID binary yet => append it
    			echo "$expected_rule" >> "$output_audit_file"
    			continue
    		fi

    	done

    done
    }

    perform_audit_rules_privileged_commands_remediation "auditctl" "1000"
    perform_audit_rules_privileged_commands_remediation "augenrules" "1000"

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | low
Strategy: | restrict

    - name: Search for privileged commands
      shell: "find / -xdev -type f -perm -4000 -o -type f -perm -2000 2>/dev/null | cat"
      check_mode: no
      register: find_result
      tags:
        - audit_rules_privileged_commands
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27437-3
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-2(4)
        - NIST-800-53-AU-6(9)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.2.2
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030360

    # Inserts/replaces the rule in /etc/audit/rules.d

    - name: Search /etc/audit/rules.d for audit rule entries
      find:
        paths: "/etc/audit/rules.d"
        recurse: no
        contains: "^.*path={{ item }} .*$"
        patterns: "*.rules"
      with_items:
        - "{{ find_result.stdout_lines }}"
      register: files_result
      tags:
        - audit_rules_privileged_commands
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27437-3
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-2(4)
        - NIST-800-53-AU-6(9)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.2.2
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030360

    - name: Overwrites the rule in rules.d
      lineinfile:
        path: "{{ item.1.path }}"
        line: '-a always,exit -F path={{ item.0.item }} -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
        create: no
        regexp: "^.*path={{ item.0.item }} .*$"
      with_subelements:
        - "{{ files_result.results }}"
        - files
      tags:
        - audit_rules_privileged_commands
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27437-3
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-2(4)
        - NIST-800-53-AU-6(9)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.2.2
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030360

    - name: Adds the rule in rules.d
      lineinfile:
        path: /etc/audit/rules.d/privileged.rules
        line: '-a always,exit -F path={{ item.item }} -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
        create: yes
      with_items:
        - "{{ files_result.results }}"
      when: item.matched == 0
      tags:
        - audit_rules_privileged_commands
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27437-3
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-2(4)
        - NIST-800-53-AU-6(9)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.2.2
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030360

    # Adds/overwrites the rule in /etc/audit/audit.rules

    - name: Inserts/replaces the rule in audit.rules
      lineinfile:
        path: /etc/audit/audit.rules
        line: '-a always,exit -F path={{ item.item }} -F perm=x -F auid>=1000 -F auid!=4294967295 -F key=privileged'
        create: yes
        regexp: "^.*path={{ item.item }} .*$"
      with_items:
        - "{{ files_result.results }}"
      tags:
        - audit_rules_privileged_commands
        - medium_severity
        - restrict_strategy
        - low_complexity
        - low_disruption
        - CCE-27437-3
        - NIST-800-53-AC-17(7)
        - NIST-800-53-AU-1(b)
        - NIST-800-53-AU-2(a)
        - NIST-800-53-AU-2(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-2(4)
        - NIST-800-53-AU-6(9)
        - NIST-800-53-AU-12(a)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-IR-5
        - NIST-800-171-3.1.7
        - PCI-DSS-Req-10.2.2
        - CJIS-5.4.1.1
        - DISA-STIG-RHEL-07-030360

### Record Unauthorized Access Attempts Events to Files (unsuccessful) [ref]group

At a minimum, the audit system should collect unauthorized file accesses for all users and root. Note that the "-F arch=b32" lines should be present even on a 64 bit system. These commands identify system calls for auditing. Even if the system is 64 bit it can still execute 32 bit system calls. Additionally, these rules can be configured in a number of ways while still achieving the desired effect. An example of this is that the "-S" calls could be split up and placed on separate lines, however, this is less efficient. Add the following to `/etc/audit/audit.rules`:

    -a always,exit -F arch=b32 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
        -a always,exit -F arch=b32 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access

If your system is 64 bit then these lines should be duplicated and the arch=b32 replaced with arch=b64 as follows:

    -a always,exit -F arch=b64 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
        -a always,exit -F arch=b64 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access

contains 1 rule

#### Ensure auditd Collects Unauthorized Access Attempts to Files (unsuccessful) [ref]rule

At a minimum the audit system should collect unauthorized file accesses for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -a always,exit -F arch=b32 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
    -a always,exit -F arch=b32 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access

If the system is 64 bit then also add the following lines:

    -a always,exit -F arch=b64 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
    -a always,exit -F arch=b64 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file:

    -a always,exit -F arch=b32 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
    -a always,exit -F arch=b32 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access

If the system is 64 bit then also add the following lines:

    -a always,exit -F arch=b64 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -F key=access
    -a always,exit -F arch=b64 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -F key=access

Rationale:

Unsuccessful attempts to access files could be an indicator of malicious activity on a system. Auditing these events could serve as evidence of potential system compromise.

Severity: medium

References: 5.2.10, 5.4.1.1, 3.1.7, CCI-000172, CCI-002884, AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.4, Req-10.2.1

Remediation Shell script: (show)

    # Perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do

    	# First fix the -EACCES requirement
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -k *"
    	# Use escaped BRE regex to specify rule group
    	GROUP="\(creat\|open\|truncate\)"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -k access"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"

    	# Then fix the -EPERM requirement
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -k *"
    	# No need to change content of $GROUP variable - it's the same as for -EACCES case above
    	FULL_RULE="-a always,exit -F arch=$ARCH -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -k access"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"

    done

#### Ensure auditd Collects System Administrator Actions [ref]rule

At a minimum, the audit system should collect administrator actions for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -w /etc/sudoers -p wa -k actions
    -w /etc/sudoers.d/ -p wa -k actions

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -w /etc/sudoers -p wa -k actions
    -w /etc/sudoers.d/ -p wa -k actions

Rationale:

The actions taken by system administrators should be audited to keep a record of what was executed on the system, as well as, for accountability purposes.

Severity: unknown

References: FAU_GEN.1.1.c, SV-86787r4_rule, 5.4.1.1, 3.1.7, CCI-000126, CCI-000130, CCI-000135, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-2(7)(b), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), iAU-3(1), AU-12(a), AU-12(c), IR-5, Req-10.2.2, Req-10.2.5.b, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215

Remediation Shell script: (show)

    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# For each of the matched entries
    	for match in "${matches[@]}"
    	do
    		# Extract filepath from the match
    		rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    		# Append that path into list of files for inspection
    		files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
    	done
    	# Case when particular audit rule isn't defined yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

    	# Check if audit watch file system object rule for given path already present
    	if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local sp="[[:space:]]"
    		# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
    		current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
    		# Split required access bits string into characters array
    		# (to check bit's presence for one bit at a time)
    		for access_bit in $(echo "$required_access_bits" | grep -o .)
    		do
    			# For each from the required access bits (e.g. 'w', 'a') check
    			# if they are already present in current access bits for rule.
    			# If not, append that bit at the end
    			if ! grep -q "$access_bit" <<< "$current_access_bits"
    			then
    				# Concatenate the existing mask with the missing bit
    				current_access_bits="$current_access_bits$access_bit"
    			fi
    		done
    		# Propagate the updated rule's access bits (original + the required
    		# ones) back into the /etc/audit/audit.rules file for that rule
    		sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
    	else
    		# Rule isn't present yet. Append it at the end of $audit_rules_file file
    		# with proper key

    		echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/sudoers" "wa" "actions"
    fix_audit_watch_rule "augenrules" "/etc/sudoers" "wa" "actions"

#### Record Events that Modify the System's Network Environment [ref]rule

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`, setting ARCH to either b32 or b64 as appropriate for your system:

    -a always,exit -F arch=ARCH -S sethostname,setdomainname -F key=audit_rules_networkconfig_modification
    -w /etc/issue -p wa -k audit_rules_networkconfig_modification
    -w /etc/issue.net -p wa -k audit_rules_networkconfig_modification
    -w /etc/hosts -p wa -k audit_rules_networkconfig_modification
    -w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file, setting ARCH to either b32 or b64 as appropriate for your system:

    -a always,exit -F arch=ARCH -S sethostname,setdomainname -F key=audit_rules_networkconfig_modification
    -w /etc/issue -p wa -k audit_rules_networkconfig_modification
    -w /etc/issue.net -p wa -k audit_rules_networkconfig_modification
    -w /etc/hosts -p wa -k audit_rules_networkconfig_modification
    -w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification

Rationale:

The network environment should not be modified by anything other than administrator action. Any change to network parameters should be audited.

Severity: unknown

References: 5.2.6, 5.4.1.1, 3.1.7, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5

Remediation Shell script: (show)

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -k *"
    	# Use escaped BRE regex to specify rule group
    	GROUP="set\(host\|domain\)name"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S sethostname -S setdomainname -k audit_rules_networkconfig_modification"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

    # Then perform the remediations for the watch rules
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# For each of the matched entries
    	for match in "${matches[@]}"
    	do
    		# Extract filepath from the match
    		rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    		# Append that path into list of files for inspection
    		files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
    	done
    	# Case when particular audit rule isn't defined yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

    	# Check if audit watch file system object rule for given path already present
    	if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local sp="[[:space:]]"
    		# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
    		current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
    		# Split required access bits string into characters array
    		# (to check bit's presence for one bit at a time)
    		for access_bit in $(echo "$required_access_bits" | grep -o .)
    		do
    			# For each from the required access bits (e.g. 'w', 'a') check
    			# if they are already present in current access bits for rule.
    			# If not, append that bit at the end
    			if ! grep -q "$access_bit" <<< "$current_access_bits"
    			then
    				# Concatenate the existing mask with the missing bit
    				current_access_bits="$current_access_bits$access_bit"
    			fi
    		done
    		# Propagate the updated rule's access bits (original + the required
    		# ones) back into the /etc/audit/audit.rules file for that rule
    		sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
    	else
    		# Rule isn't present yet. Append it at the end of $audit_rules_file file
    		# with proper key

    		echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/issue" "wa" "audit_rules_networkconfig_modification"
    fix_audit_watch_rule "augenrules" "/etc/issue" "wa" "audit_rules_networkconfig_modification"
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# For each of the matched entries
    	for match in "${matches[@]}"
    	do
    		# Extract filepath from the match
    		rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    		# Append that path into list of files for inspection
    		files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
    	done
    	# Case when particular audit rule isn't defined yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

    	# Check if audit watch file system object rule for given path already present
    	if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local sp="[[:space:]]"
    		# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
    		current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
    		# Split required access bits string into characters array
    		# (to check bit's presence for one bit at a time)
    		for access_bit in $(echo "$required_access_bits" | grep -o .)
    		do
    			# For each from the required access bits (e.g. 'w', 'a') check
    			# if they are already present in current access bits for rule.
    			# If not, append that bit at the end
    			if ! grep -q "$access_bit" <<< "$current_access_bits"
    			then
    				# Concatenate the existing mask with the missing bit
    				current_access_bits="$current_access_bits$access_bit"
    			fi
    		done
    		# Propagate the updated rule's access bits (original + the required
    		# ones) back into the /etc/audit/audit.rules file for that rule
    		sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
    	else
    		# Rule isn't present yet. Append it at the end of $audit_rules_file file
    		# with proper key

    		echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/issue.net" "wa" "audit_rules_networkconfig_modification"
    fix_audit_watch_rule "augenrules" "/etc/issue.net" "wa" "audit_rules_networkconfig_modification"
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# For each of the matched entries
    	for match in "${matches[@]}"
    	do
    		# Extract filepath from the match
    		rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    		# Append that path into list of files for inspection
    		files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
    	done
    	# Case when particular audit rule isn't defined yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

    	# Check if audit watch file system object rule for given path already present
    	if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local sp="[[:space:]]"
    		# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
    		current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
    		# Split required access bits string into characters array
    		# (to check bit's presence for one bit at a time)
    		for access_bit in $(echo "$required_access_bits" | grep -o .)
    		do
    			# For each from the required access bits (e.g. 'w', 'a') check
    			# if they are already present in current access bits for rule.
    			# If not, append that bit at the end
    			if ! grep -q "$access_bit" <<< "$current_access_bits"
    			then
    				# Concatenate the existing mask with the missing bit
    				current_access_bits="$current_access_bits$access_bit"
    			fi
    		done
    		# Propagate the updated rule's access bits (original + the required
    		# ones) back into the /etc/audit/audit.rules file for that rule
    		sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
    	else
    		# Rule isn't present yet. Append it at the end of $audit_rules_file file
    		# with proper key

    		echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/hosts" "wa" "audit_rules_networkconfig_modification"
    fix_audit_watch_rule "augenrules" "/etc/hosts" "wa" "audit_rules_networkconfig_modification"
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# For each of the matched entries
    	for match in "${matches[@]}"
    	do
    		# Extract filepath from the match
    		rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    		# Append that path into list of files for inspection
    		files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
    	done
    	# Case when particular audit rule isn't defined yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

    	# Check if audit watch file system object rule for given path already present
    	if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local sp="[[:space:]]"
    		# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
    		current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
    		# Split required access bits string into characters array
    		# (to check bit's presence for one bit at a time)
    		for access_bit in $(echo "$required_access_bits" | grep -o .)
    		do
    			# For each from the required access bits (e.g. 'w', 'a') check
    			# if they are already present in current access bits for rule.
    			# If not, append that bit at the end
    			if ! grep -q "$access_bit" <<< "$current_access_bits"
    			then
    				# Concatenate the existing mask with the missing bit
    				current_access_bits="$current_access_bits$access_bit"
    			fi
    		done
    		# Propagate the updated rule's access bits (original + the required
    		# ones) back into the /etc/audit/audit.rules file for that rule
    		sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
    	else
    		# Rule isn't present yet. Append it at the end of $audit_rules_file file
    		# with proper key

    		echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/sysconfig/network" "wa" "audit_rules_networkconfig_modification"
    fix_audit_watch_rule "augenrules" "/etc/sysconfig/network" "wa" "audit_rules_networkconfig_modification"

#### Record Events that Modify User/Group Information [ref]rule

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`, in order to capture events that modify account changes:

    -w /etc/group -p wa -k audit_rules_usergroup_modification
    -w /etc/passwd -p wa -k audit_rules_usergroup_modification
    -w /etc/gshadow -p wa -k audit_rules_usergroup_modification
    -w /etc/shadow -p wa -k audit_rules_usergroup_modification
    -w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file, in order to capture events that modify account changes:

    -w /etc/group -p wa -k audit_rules_usergroup_modification
    -w /etc/passwd -p wa -k audit_rules_usergroup_modification
    -w /etc/gshadow -p wa -k audit_rules_usergroup_modification
    -w /etc/shadow -p wa -k audit_rules_usergroup_modification
    -w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification

Rationale:

In addition to auditing new user and group accounts, these watches will alert the system administrator(s) to any modifications. Any unexpected users, groups, or modifications should be investigated for legitimacy.

Severity: unknown

References: SV-86789r3_rule, 5.2.5, 5.4.1.1, 3.1.7, CCI-000018, CCI-000172, CCI-001403, CCI-002130, AC-2(4), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.2.5, SRG-OS-000004-GPOS-00004, SRG-OS-000239-GPOS-00089, SRG-OS-000241-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000476-GPOS-00221

Remediation Shell script: (show)

    # Perform the remediation
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# For each of the matched entries
    	for match in "${matches[@]}"
    	do
    		# Extract filepath from the match
    		rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    		# Append that path into list of files for inspection
    		files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
    	done
    	# Case when particular audit rule isn't defined yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

    	# Check if audit watch file system object rule for given path already present
    	if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local sp="[[:space:]]"
    		# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
    		current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
    		# Split required access bits string into characters array
    		# (to check bit's presence for one bit at a time)
    		for access_bit in $(echo "$required_access_bits" | grep -o .)
    		do
    			# For each from the required access bits (e.g. 'w', 'a') check
    			# if they are already present in current access bits for rule.
    			# If not, append that bit at the end
    			if ! grep -q "$access_bit" <<< "$current_access_bits"
    			then
    				# Concatenate the existing mask with the missing bit
    				current_access_bits="$current_access_bits$access_bit"
    			fi
    		done
    		# Propagate the updated rule's access bits (original + the required
    		# ones) back into the /etc/audit/audit.rules file for that rule
    		sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
    	else
    		# Rule isn't present yet. Append it at the end of $audit_rules_file file
    		# with proper key

    		echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/group" "wa" "audit_rules_usergroup_modification"
    fix_audit_watch_rule "augenrules" "/etc/group" "wa" "audit_rules_usergroup_modification"
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# For each of the matched entries
    	for match in "${matches[@]}"
    	do
    		# Extract filepath from the match
    		rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    		# Append that path into list of files for inspection
    		files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
    	done
    	# Case when particular audit rule isn't defined yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

    	# Check if audit watch file system object rule for given path already present
    	if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local sp="[[:space:]]"
    		# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
    		current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
    		# Split required access bits string into characters array
    		# (to check bit's presence for one bit at a time)
    		for access_bit in $(echo "$required_access_bits" | grep -o .)
    		do
    			# For each from the required access bits (e.g. 'w', 'a') check
    			# if they are already present in current access bits for rule.
    			# If not, append that bit at the end
    			if ! grep -q "$access_bit" <<< "$current_access_bits"
    			then
    				# Concatenate the existing mask with the missing bit
    				current_access_bits="$current_access_bits$access_bit"
    			fi
    		done
    		# Propagate the updated rule's access bits (original + the required
    		# ones) back into the /etc/audit/audit.rules file for that rule
    		sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
    	else
    		# Rule isn't present yet. Append it at the end of $audit_rules_file file
    		# with proper key

    		echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/passwd" "wa" "audit_rules_usergroup_modification"
    fix_audit_watch_rule "augenrules" "/etc/passwd" "wa" "audit_rules_usergroup_modification"
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# For each of the matched entries
    	for match in "${matches[@]}"
    	do
    		# Extract filepath from the match
    		rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    		# Append that path into list of files for inspection
    		files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
    	done
    	# Case when particular audit rule isn't defined yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

    	# Check if audit watch file system object rule for given path already present
    	if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local sp="[[:space:]]"
    		# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
    		current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
    		# Split required access bits string into characters array
    		# (to check bit's presence for one bit at a time)
    		for access_bit in $(echo "$required_access_bits" | grep -o .)
    		do
    			# For each from the required access bits (e.g. 'w', 'a') check
    			# if they are already present in current access bits for rule.
    			# If not, append that bit at the end
    			if ! grep -q "$access_bit" <<< "$current_access_bits"
    			then
    				# Concatenate the existing mask with the missing bit
    				current_access_bits="$current_access_bits$access_bit"
    			fi
    		done
    		# Propagate the updated rule's access bits (original + the required
    		# ones) back into the /etc/audit/audit.rules file for that rule
    		sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
    	else
    		# Rule isn't present yet. Append it at the end of $audit_rules_file file
    		# with proper key

    		echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/gshadow" "wa" "audit_rules_usergroup_modification"
    fix_audit_watch_rule "augenrules" "/etc/gshadow" "wa" "audit_rules_usergroup_modification"
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# For each of the matched entries
    	for match in "${matches[@]}"
    	do
    		# Extract filepath from the match
    		rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    		# Append that path into list of files for inspection
    		files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
    	done
    	# Case when particular audit rule isn't defined yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

    	# Check if audit watch file system object rule for given path already present
    	if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local sp="[[:space:]]"
    		# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
    		current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
    		# Split required access bits string into characters array
    		# (to check bit's presence for one bit at a time)
    		for access_bit in $(echo "$required_access_bits" | grep -o .)
    		do
    			# For each from the required access bits (e.g. 'w', 'a') check
    			# if they are already present in current access bits for rule.
    			# If not, append that bit at the end
    			if ! grep -q "$access_bit" <<< "$current_access_bits"
    			then
    				# Concatenate the existing mask with the missing bit
    				current_access_bits="$current_access_bits$access_bit"
    			fi
    		done
    		# Propagate the updated rule's access bits (original + the required
    		# ones) back into the /etc/audit/audit.rules file for that rule
    		sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
    	else
    		# Rule isn't present yet. Append it at the end of $audit_rules_file file
    		# with proper key

    		echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/shadow" "wa" "audit_rules_usergroup_modification"
    fix_audit_watch_rule "augenrules" "/etc/shadow" "wa" "audit_rules_usergroup_modification"
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# For each of the matched entries
    	for match in "${matches[@]}"
    	do
    		# Extract filepath from the match
    		rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    		# Append that path into list of files for inspection
    		files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
    	done
    	# Case when particular audit rule isn't defined yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

    	# Check if audit watch file system object rule for given path already present
    	if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local sp="[[:space:]]"
    		# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
    		current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
    		# Split required access bits string into characters array
    		# (to check bit's presence for one bit at a time)
    		for access_bit in $(echo "$required_access_bits" | grep -o .)
    		do
    			# For each from the required access bits (e.g. 'w', 'a') check
    			# if they are already present in current access bits for rule.
    			# If not, append that bit at the end
    			if ! grep -q "$access_bit" <<< "$current_access_bits"
    			then
    				# Concatenate the existing mask with the missing bit
    				current_access_bits="$current_access_bits$access_bit"
    			fi
    		done
    		# Propagate the updated rule's access bits (original + the required
    		# ones) back into the /etc/audit/audit.rules file for that rule
    		sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
    	else
    		# Rule isn't present yet. Append it at the end of $audit_rules_file file
    		# with proper key

    		echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/security/opasswd" "wa" "audit_rules_usergroup_modification"
    fix_audit_watch_rule "augenrules" "/etc/security/opasswd" "wa" "audit_rules_usergroup_modification"

#### Ensure auditd Collects Information on Exporting to Media (successful) [ref]rule

At a minimum, the audit system should collect media exportation events for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`, setting ARCH to either b32 or b64 as appropriate for your system:

    -a always,exit -F arch=ARCH -S mount -F auid>=1000 -F auid!=4294967295 -F key=export

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file, setting ARCH to either b32 or b64 as appropriate for your system:

    -a always,exit -F arch=ARCH -S mount -F auid>=1000 -F auid!=4294967295 -F key=export

Rationale:

The unauthorized exportation of data to external media could result in an information leak where classified information, Privacy Act information, and intellectual property could be lost. An audit trail should be created each time a filesystem is mounted to help identify and guard against information loss.

Severity: medium

References: SV-86795r5_rule, 5.2.13, 5.4.1.1, 3.1.7, CCI-000135, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-3(1), AU-12(a), AU-12(c), IR-5, Req-10.2.7, SRG-OS-000042-GPOS-00020, SRG-OS-000392-GPOS-00172

Remediation Shell script: (show)

    # Perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ $(getconf LONG_BIT) = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	PATTERN="-a always,exit -F arch=$ARCH -S .* -F auid>=1000 -F auid!=4294967295 -k *"
    	GROUP="mount"
    	FULL_RULE="-a always,exit -F arch=$ARCH -S mount -F auid>=1000 -F auid!=4294967295 -k export"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix syscall audit rule for given system call. It is
    # based on example audit syscall rule definitions as outlined in
    # /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
    # package. It will combine multiple system calls belonging to the same
    # syscall group into one audit rule (rather than to create audit rule per
    # different system call) to avoid audit infrastructure performance penalty
    # in the case of 'one-audit-rule-definition-per-one-system-call'. See:
    #
    #   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
    #
    # for further details.
    #
    # Expects five arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules
    # * audit rules' pattern		audit rule skeleton for same syscall
    # * syscall group			greatest common string this rule shares
    # 					with other rules from the same group
    # * architecture			architecture this rule is intended for
    # * full form of new rule to add	expected full form of audit rule as to be
    # 					added into audit.rules file
    #
    # Note: The 2-th up to 4-th arguments are used to determine how many existing
    # audit rules will be inspected for resemblance with the new audit rule
    # (5-th argument) the function is going to add. The rule's similarity check
    # is performed to optimize audit.rules definition (merge syscalls of the same
    # group into one rule) to avoid the "single-syscall-per-audit-rule" performance
    # penalty.
    #
    # Example call:
    #
    #	See e.g. 'audit_rules_file_deletion_events.sh' remediation script
    #
    function fix_audit_syscall_rule {

    # Load function arguments into local variables
    local tool="$1"
    local pattern="$2"
    local group="$3"
    local arch="$4"
    local full_rule="$5"

    # Check sanity of the input
    if [ $# -ne "5" ]
    then
    	echo "Usage: fix_audit_syscall_rule 'tool' 'pattern' 'group' 'arch' 'full rule'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    declare -a files_to_inspect

    retval=0

    # First check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	return 1
    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules' )
    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    elif [ "$tool" == 'augenrules' ]
    then
    	# Extract audit $key from audit rule so we can use it later
    	key=$(expr "$full_rule" : '.*-k[[:space:]]\([^[:space:]]\+\)' '|' "$full_rule" : '.*-F[[:space:]]key=\([^[:space:]]\+\)')
    	# Check if particular audit rule is already defined
    	IFS=$'\n' matches=($(sed -s -n -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d;F" /etc/audit/rules.d/*.rules))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS
    	for match in "${matches[@]}"
    	do
    		files_to_inspect=("${files_to_inspect[@]}" "${match}")
    	done
    	# Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    #
    # Indicator that we want to append $full_rule into $audit_file by default
    local append_expected_rule=0

    for audit_file in "${files_to_inspect[@]}"
    do

    	# Filter existing $audit_file rules' definitions to select those that:
    	# * follow the rule pattern, and
    	# * meet the hardware architecture requirement, and
    	# * are current syscall group specific
    	IFS=$'\n' existing_rules=($(sed -e "\;${pattern};!d" -e "/${arch}/!d" -e "/${group}/!d"  "$audit_file"))
    	if [ $? -ne 0 ]
    	then
    		retval=1
    	fi
    	# Reset IFS back to default
    	unset IFS

    	# Process rules found case-by-case
    	for rule in "${existing_rules[@]}"
    	do
    		# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
    		if [ "${rule}" != "${full_rule}" ]
    		then
    			# If so, isolate just '(-S \w)+' substring of that rule
    			rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
    			# Check if list of '-S syscall' arguments of that rule is subset
    			# of '-S syscall' list of expected $full_rule
    			if grep -q -- "$rule_syscalls" <<< "$full_rule"
    			then
    				# Rule is covered (i.e. the list of -S syscalls for this rule is
    				# subset of -S syscalls of $full_rule => existing rule can be deleted
    				# Thus delete the rule from audit.rules & our array
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				existing_rules=("${existing_rules[@]//$rule/}")
    			else
    				# Rule isn't covered by $full_rule - it besides -S syscall arguments
    				# for this group contains also -S syscall arguments for other syscall
    				# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
    				# since 'lchown' & 'fchownat' share 'chown' substring
    				# Therefore:
    				# * 1) delete the original rule from audit.rules
    				# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
    				# * 2) delete the -S syscall arguments for this syscall group, but
    				# keep those not belonging to this syscall group
    				# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
    				# * 3) append the modified (filtered) rule again into audit.rules
    				# if the same rule not already present
    				#
    				# 1) Delete the original rule
    				sed -i -e "\;${rule};d" "$audit_file"
    				if [ $? -ne 0 ]
    				then
    					retval=1
    				fi
    				# 2) Delete syscalls for this group, but keep those from other groups
    				# Convert current rule syscall's string into array splitting by '-S' delimiter
    				IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
    				# Reset IFS back to default
    				unset IFS
    				# Declare new empty string to hold '-S syscall' arguments from other groups
    				new_syscalls_for_rule=''
    				# Walk through existing '-S syscall' arguments
    				for syscall_arg in "${rule_syscalls_as_array[@]}"
    				do
    					# Skip empty $syscall_arg values
    					if [ "$syscall_arg" == '' ]
    					then
    						continue
    					fi
    					# If the '-S syscall' doesn't belong to current group add it to the new list
    					# (together with adding '-S' delimiter back for each of such item found)
    					if grep -q -v -- "$group" <<< "$syscall_arg"
    					then
    						new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
    					fi
    				done
    				# Replace original '-S syscall' list with the new one for this rule
    				updated_rule=${rule//$rule_syscalls/$new_syscalls_for_rule}
    				# Squeeze repeated whitespace characters in rule definition (if any) into one
    				updated_rule=$(echo "$updated_rule" | tr -s '[:space:]')
    				# 3) Append the modified / filtered rule again into audit.rules
    				#    (but only in case it's not present yet to prevent duplicate definitions)
    				if ! grep -q -- "$updated_rule" "$audit_file"
    				then
    					echo "$updated_rule" >> "$audit_file"
    				fi
    			fi
    		else
    			# $audit_file already contains the expected rule form for this
    			# architecture & key => don't insert it second time
    			append_expected_rule=1
    		fi
    	done

    	# We deleted all rules that were subset of the expected one for this arch & key.
    	# Also isolated rules containing system calls not from this system calls group.
    	# Now append the expected rule if it's not present in $audit_file yet
    	if [[ ${append_expected_rule} -eq "0" ]]
    	then
    		echo "$full_rule" >> "$audit_file"
    	fi
    done

    return $retval

    }

    	fix_audit_syscall_rule "auditctl" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    	fix_audit_syscall_rule "augenrules" "$PATTERN" "$GROUP" "$ARCH" "$FULL_RULE"
    done

#### Record Events that Modify the System's Mandatory Access Controls [ref]rule

If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:

    -w /etc/selinux/ -p wa -k MAC-policy

If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:

    -w /etc/selinux/ -p wa -k MAC-policy

Rationale:

The system's mandatory access policy (SELinux) should not be arbitrarily changed by anything other than administrator action. All changes to MAC policy should be audited.

Severity: unknown

References: FAU_GEN.1.1.c, 5.2.7, 5.4.1.1, 3.1.8, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), AC-17(7), AU-1(b), AU-2(a), AU-2(c), AU-2(d), AU-12(a), AU-12(c), IR-5, Req-10.5.5

Remediation Shell script: (show)

    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Function to fix audit file system object watch rule for given path:
    # * if rule exists, also verifies the -w bits match the requirements
    # * if rule doesn't exist yet, appends expected rule form to $files_to_inspect
    #   audit rules file, depending on the tool which was used to load audit rules
    #
    # Expects four arguments (each of them is required) in the form of:
    # * audit tool				tool used to load audit rules,
    # 					either 'auditctl', or 'augenrules'
    # * path                        	value of -w audit rule's argument
    # * required access bits        	value of -p audit rule's argument
    # * key                         	value of -k audit rule's argument
    #
    # Example call:
    #
    #       fix_audit_watch_rule "auditctl" "/etc/localtime" "wa" "audit_time_rules"
    #
    function fix_audit_watch_rule {

    # Load function arguments into local variables
    local tool="$1"
    local path="$2"
    local required_access_bits="$3"
    local key="$4"

    # Check sanity of the input
    if [ $# -ne "4" ]
    then
    	echo "Usage: fix_audit_watch_rule 'tool' 'path' 'bits' 'key'"
    	echo "Aborting."
    	exit 1
    fi

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    declare -a files_to_inspect

    # Check sanity of the specified audit tool
    if [ "$tool" != 'auditctl' ] && [ "$tool" != 'augenrules' ]
    then
    	echo "Unknown audit rules loading tool: $1. Aborting."
    	echo "Use either 'auditctl' or 'augenrules'!"
    	exit 1
    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    elif [ "$tool" == 'auditctl' ]
    then
    	files_to_inspect=("${files_to_inspect[@]}" '/etc/audit/audit.rules')
    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/$key.rules' to list of files for inspection.
    elif [ "$tool" == 'augenrules' ]
    then
    	# Case when particular audit rule is already defined in some of /etc/audit/rules.d/*.rules file
    	# Get pair -- filepath : matching_row into @matches array
    	IFS=$'\n' matches=($(grep -P "[\s]*-w[\s]+$path" /etc/audit/rules.d/*.rules))
    	# Reset IFS back to default
    	unset IFS
    	# For each of the matched entries
    	for match in "${matches[@]}"
    	do
    		# Extract filepath from the match
    		rulesd_audit_file=$(echo $match | cut -f1 -d ':')
    		# Append that path into list of files for inspection
    		files_to_inspect=("${files_to_inspect[@]}" "$rulesd_audit_file")
    	done
    	# Case when particular audit rule isn't defined yet
    	if [ ${#files_to_inspect[@]} -eq "0" ]
    	then
    		# Append '/etc/audit/rules.d/$key.rules' into list of files for inspection
    		files_to_inspect="/etc/audit/rules.d/$key.rules"
    		# If the $key.rules file doesn't exist yet, create it with correct permissions
    		if [ ! -e "$files_to_inspect" ]
    		then
    			touch "$files_to_inspect"
    			chmod 0640 "$files_to_inspect"
    		fi
    	fi
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do

    	# Check if audit watch file system object rule for given path already present
    	if grep -q -P -- "[\s]*-w[\s]+$path" "$audit_rules_file"
    	then
    		# Rule is found => verify yet if existing rule definition contains
    		# all of the required access type bits

    		# Escape slashes in path for use in sed pattern below
    		local esc_path=${path//$'/'/$'\/'}
    		# Define BRE whitespace class shortcut
    		local sp="[[:space:]]"
    		# Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
    		current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" "$audit_rules_file")
    		# Split required access bits string into characters array
    		# (to check bit's presence for one bit at a time)
    		for access_bit in $(echo "$required_access_bits" | grep -o .)
    		do
    			# For each from the required access bits (e.g. 'w', 'a') check
    			# if they are already present in current access bits for rule.
    			# If not, append that bit at the end
    			if ! grep -q "$access_bit" <<< "$current_access_bits"
    			then
    				# Concatenate the existing mask with the missing bit
    				current_access_bits="$current_access_bits$access_bit"
    			fi
    		done
    		# Propagate the updated rule's access bits (original + the required
    		# ones) back into the /etc/audit/audit.rules file for that rule
    		sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" "$audit_rules_file"
    	else
    		# Rule isn't present yet. Append it at the end of $audit_rules_file file
    		# with proper key

    		echo "-w $path -p $required_access_bits -k $key" >> "$audit_rules_file"
    	fi
    done
    }

    fix_audit_watch_rule "auditctl" "/etc/selinux/" "wa" "MAC-policy"
    fix_audit_watch_rule "augenrules" "/etc/selinux/" "wa" "MAC-policy"

### Installing and Maintaining Software [ref]group

The following sections contain information on security-relevant choices during the initial operating system installation process and the setup of software updates.

contains 7 rules

### System and Software Integrity [ref]group

System and software integrity can be gained by installing antivirus, increasing system encryption strength with FIPS, verifying installed software, enabling SELinux, installing an Intrusion Prevention System, etc. However, installing or enabling integrity checking tools cannot prevent intrusions, but they can detect that an intrusion may have occurred. Requirements for integrity checking may be highly dependent on the environment in which the system will be used. Snapshot-based approaches such as AIDE may induce considerable overhead in the presence of frequent software updates.

contains 2 rules

### Software Integrity Checking [ref]group

Both the AIDE (Advanced Intrusion Detection Environment) software and the RPM package management system provide mechanisms for verifying the integrity of installed software. AIDE uses snapshots of file metadata (such as hashes) and compares these to current system files in order to detect changes.
The RPM package management system can conduct integrity checks by comparing information in its metadata database with files installed on the system.

contains 2 rules

### Verify Integrity with RPM [ref]group

The RPM package management system includes the ability to verify the integrity of installed packages by comparing the installed files with information about the files taken from the package metadata stored in the RPM database. Although an attacker could corrupt the RPM database (analogous to attacking the AIDE database as described above), this check can still reveal modification of important files. To list which files on the system differ from what is expected by the RPM database:

    $ rpm -qVa

See the man page for `rpm` to see a complete explanation of each column.

contains 2 rules

#### Verify and Correct File Permissions with RPM [ref]rule

The RPM package management system can check file access permissions of installed software packages, including many that are important to system security. Verify that the file permissions of system files and commands match vendor values. Check the file permissions with the following command:

    $ sudo rpm -Va | grep '^.M'

Output indicates files that do not match vendor defaults. After locating a file with incorrect permissions, run the following command to determine which package owns it:

    $ rpm -qf FILENAME

Next, run the following command to reset its permissions to the correct values:

    $ sudo rpm --setperms PACKAGENAME

Rationale:

Permissions on system binaries and configuration files that are too generous could allow an unauthorized user to gain privileges that they should not have. The permissions set by the vendor should be maintained. Any deviations from this baseline should be investigated.

Severity: high

References: SV-86473r2_rule, 1.2.6, 6.1.3, 6.1.4, 6.1.5, 6.1.6, 6.1.7, 6.1.8, 6.1.9, 6.2.3, 5.10.4.1, 3.3.8, 3.4.1, CCI-001494, CCI-001496, 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i), AC-6, AU-9(1), AU-9(3), CM-6(d), CM-6(3), Req-11.5, SRG-OS-000257-GPOS-00098, SRG-OS-000278-GPOS-00108

Remediation Shell script: (show)

Complexity: | high
Disruption: | medium
Strategy: | restrict

    # Declare array to hold list of RPM packages we need to correct permissions for
    declare -a SETPERMS_RPM_LIST

    # Create a list of files on the system having permissions different from what
    # is expected by the RPM database
    FILES_WITH_INCORRECT_PERMS=($(rpm -Va --nofiledigest | grep '^.M' | cut -d ' ' -f4-))

    # For each file path from that list:
    # * Determine the RPM package the file path is shipped by,
    # * Include it into SETPERMS_RPM_LIST array

    for FILE_PATH in "${FILES_WITH_INCORRECT_PERMS[@]}"
    do
    	RPM_PACKAGE=$(rpm -qf "$FILE_PATH")
    	SETPERMS_RPM_LIST=("${SETPERMS_RPM_LIST[@]}" "$RPM_PACKAGE")
    done

    # Remove duplicate mention of same RPM in $SETPERMS_RPM_LIST (if any)
    SETPERMS_RPM_LIST=( $(echo "${SETPERMS_RPM_LIST[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ') )

    # For each of the RPM packages left in the list -- reset its permissions to the
    # correct values
    for RPM_PACKAGE in "${SETPERMS_RPM_LIST[@]}"
    do
    	rpm --setperms "${RPM_PACKAGE}"
    done

Remediation Ansible snippet: (show)

Complexity: | high
Disruption: | medium
Strategy: | restrict

    - name: "Read list of files with incorrect permissions"
      shell: "rpm -Va | grep '^.M' | cut -d ' ' -f5- | sed -r 's;^.*\\s+(.+);\\1;g'"
      register: files_with_incorrect_permissions
      failed_when: False
      changed_when: False
      check_mode: no
      tags:
        - rpm_verify_permissions
        - high_severity
        - restrict_strategy
        - high_complexity
        - medium_disruption
        - CCE-27209-6
        - NIST-800-53-AC-6
        - NIST-800-53-AU-9(1)
        - NIST-800-53-AU-9(3)
        - NIST-800-53-CM-6(d)
        - NIST-800-53-CM-6(3)
        - NIST-800-171-3.3.8
        - NIST-800-171-3.4.1
        - PCI-DSS-Req-11.5
        - CJIS-5.10.4.1
        - DISA-STIG-RHEL-07-010010

    - name: "Correct file permissions with RPM"
      shell: "rpm --setperms $(rpm -qf '{{item}}')"
      with_items: "{{ files_with_incorrect_permissions.stdout_lines }}"
      when: files_with_incorrect_permissions.stdout_lines | length > 0
      tags:
        - rpm_verify_permissions
        - high_severity
        - restrict_strategy
        - high_complexity
        - medium_disruption
        - CCE-27209-6
        - NIST-800-53-AC-6
        - NIST-800-53-AU-9(1)
        - NIST-800-53-AU-9(3)
        - NIST-800-53-CM-6(d)
        - NIST-800-53-CM-6(3)
        - NIST-800-171-3.3.8
        - NIST-800-171-3.4.1
        - PCI-DSS-Req-11.5
        - CJIS-5.10.4.1
        - DISA-STIG-RHEL-07-010010

#### Verify File Hashes with RPM [ref]rule

Without cryptographic integrity protections, system executables and files can be altered by unauthorized users without detection. The RPM package management system can check the hashes of installed software packages, including many that are important to system security. To verify that the cryptographic hash of system files and commands match vendor values, run the following command to list which files on the system have hashes that differ from what is expected by the RPM database:

    $ rpm -Va | grep '^..5'

A "c" in the second column indicates that a file is a configuration file, which may appropriately be expected to change. If the file was not expected to change, investigate the cause of the change using audit logs or other means. The package can then be reinstalled to restore the file. Run the following command to determine which package owns the file:

    $ rpm -qf FILENAME

The package can be reinstalled from a yum repository using the command:

    $ sudo yum reinstall PACKAGENAME

Alternatively, the package can be reinstalled from trusted media using the command:

    $ sudo rpm -Uvh PACKAGENAME

Rationale:

The hashes of important files like system executables should match the information given by the RPM database. Executables with erroneous hashes could be a sign of nefarious activity on the system.

Severity: high

References: SV-86479r2_rule, 1.2.6, 5.10.4.1, 3.3.8, 3.4.1, CCI-000663, 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i), CM-6(d), CM-6(3), SI-7(1), Req-11.5, SRG-OS-000480-GPOS-00227

Remediation Ansible snippet: (show)

Complexity: | high
Disruption: | medium

    - name: "Set fact: Package manager reinstall command (dnf)"
      set_fact:
        package_manager_reinstall_cmd: dnf reinstall -y
      when: ansible_distribution == "Fedora"
      tags:
        - rpm_verify_hashes
        - high_severity
        - unknown_strategy
        - high_complexity
        - medium_disruption
        - CCE-27157-7
        - NIST-800-53-CM-6(d)
        - NIST-800-53-CM-6(3)
        - NIST-800-53-SI-7(1)
        - NIST-800-171-3.3.8
        - NIST-800-171-3.4.1
        - PCI-DSS-Req-11.5
        - CJIS-5.10.4.1
        - DISA-STIG-RHEL-07-010020

    - name: "Set fact: Package manager reinstall command (yum)"
      set_fact:
        package_manager_reinstall_cmd: yum reinstall -y
      when: ansible_distribution == "RedHat" or ansible_distribution == "OracleLinux"
      tags:
        - rpm_verify_hashes
        - high_severity
        - unknown_strategy
        - high_complexity
        - medium_disruption
        - CCE-27157-7
        - NIST-800-53-CM-6(d)
        - NIST-800-53-CM-6(3)
        - NIST-800-53-SI-7(1)
        - NIST-800-171-3.3.8
        - NIST-800-171-3.4.1
        - PCI-DSS-Req-11.5
        - CJIS-5.10.4.1
        - DISA-STIG-RHEL-07-010020

    - name: "Read files with incorrect hash"
      shell: "rpm -Va | grep -E '^..5.* /(bin|sbin|lib|lib64|usr)/' | sed -r 's;^.*\\s+(.+);\\1;g'"
      register: files_with_incorrect_hash
      changed_when: False
      when: package_manager_reinstall_cmd is defined
      check_mode: no
      tags:
        - rpm_verify_hashes
        - high_severity
        - unknown_strategy
        - high_complexity
        - medium_disruption
        - CCE-27157-7
        - NIST-800-53-CM-6(d)
        - NIST-800-53-CM-6(3)
        - NIST-800-53-SI-7(1)
        - NIST-800-171-3.3.8
        - NIST-800-171-3.4.1
        - PCI-DSS-Req-11.5
        - CJIS-5.10.4.1
        - DISA-STIG-RHEL-07-010020

    - name: "Reinstall packages of files with incorrect hash"
      shell: "{{package_manager_reinstall_cmd}} $(rpm -qf '{{item}}')"
      with_items: "{{ files_with_incorrect_hash.stdout_lines }}"
      when: package_manager_reinstall_cmd is defined and (files_with_incorrect_hash.stdout_lines | length > 0)
      tags:
        - rpm_verify_hashes
        - high_severity
        - unknown_strategy
        - high_complexity
        - medium_disruption
        - CCE-27157-7
        - NIST-800-53-CM-6(d)
        - NIST-800-53-CM-6(3)
        - NIST-800-53-SI-7(1)
        - NIST-800-171-3.3.8
        - NIST-800-171-3.4.1
        - PCI-DSS-Req-11.5
        - CJIS-5.10.4.1
        - DISA-STIG-RHEL-07-010020

### Updating Software [ref]group

The `yum` command line tool is used to install and update software packages. The system also provides a graphical software update tool in the System menu, in the Administration submenu, called Software Update.
Red Hat Enterprise Linux systems contain an installed software catalog called the RPM database, which records metadata of installed packages. Consistently using `yum` or the graphical Software Update for all software installation allows for insight into the current inventory of installed software on the system.
Oracle Linux system contain an installed software catalog called the RPM database, which records metadata of installed packages. Consistently using `yum` or the graphical Software Update for all software installation allows for insight into the current inventory of installed software on the system.

contains 3 rules

#### Ensure Software Patches Installed [ref]rule

If the system is joined to the Red Hat Network, a Red Hat Satellite Server, or a yum server, run the following command to install updates:

    $ sudo yum update

If the system is not configured to use one of these sources, updates (in the form of RPM packages) can be manually downloaded from the Red Hat Network and installed using `rpm`.
NOTE: U.S. Defense systems are required to be patched within 30 days or sooner as local policy dictates.Rationale:

Installing software updates is a fundamental mitigation against the exploitation of publicly-known vulnerabilities. If the most recent security patches and updates are not installed, unauthorized users may take advantage of weaknesses in the unpatched software. The lack of prompt attention to patching could result in a system compromise.

Severity: high

References: FMT_MOF_EXT.1, SV-86623r3_rule, 1.8, 5.10.4.1, CCI-000366, SI-2, SI-2(c), MA-1(b), Req-6.2, SRG-OS-000480-GPOS-00227

Remediation Shell script: (show)

Complexity: | low
Disruption: | high
Reboot: | true
Strategy: | patch

    yum -y update

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | high
Reboot: | true
Strategy: | patch

    - name: "Security patches are up to date"
      package:
        name: "*"
        state: "latest"
      tags:
        - security_patches_up_to_date
        - high_severity
        - patch_strategy
        - low_complexity
        - high_disruption
        - CCE-26895-3
        - NIST-800-53-SI-2
        - NIST-800-53-SI-2(c)
        - NIST-800-53-MA-1(b)
        - PCI-DSS-Req-6.2
        - CJIS-5.10.4.1
        - DISA-STIG-RHEL-07-020260

#### Ensure Red Hat GPG Key Installed [ref]rule

To ensure the system can cryptographically verify base software packages come from Red Hat (and to connect to the Red Hat Network to receive them), the Red Hat GPG key must properly be installed. To install the Red Hat GPG key, run:

    $ sudo subscription-manager register

If the system is not connected to the Internet or an RHN Satellite, then install the Red Hat GPG key from trusted media such as the Red Hat installation CD-ROM or DVD. Assuming the disc is mounted in `/media/cdrom`, use the following command as the root user to import it into the keyring:

    $ sudo rpm --import /media/cdrom/RPM-GPG-KEY

Rationale:

Changes to software components can have significant effects on the overall security of the operating system. This requirement ensures the software has not been tampered with and that it has been provided by a trusted vendor. The Red Hat GPG key is necessary to cryptographically verify packages are from Red Hat.

Severity: high

References: FAU_GEN.1.1.c, 1.2.3, 5.10.4.1, 3.4.8, CCI-001749, 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i), CM-5(3), SI-7, MA-1(b), Req-6.2, 366

Remediation Shell script: (show)

    # The two fingerprints below are retrieved from https://access.redhat.com/security/team/key
    readonly REDHAT_RELEASE_2_FINGERPRINT="567E 347A D004 4ADE 55BA 8A5F 199E 2F91 FD43 1D51"
    readonly REDHAT_AUXILIARY_FINGERPRINT="43A6 E49C 4A38 F4BE 9ABF 2A53 4568 9C88 2FA6 58E0"
    # Location of the key we would like to import (once it's integrity verified)
    readonly REDHAT_RELEASE_KEY="/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release"

    RPM_GPG_DIR_PERMS=$(stat -c %a "$(dirname "$REDHAT_RELEASE_KEY")")

    # Verify /etc/pki/rpm-gpg directory permissions are safe
    if [ "${RPM_GPG_DIR_PERMS}" -le "755" ]
    then
      # If they are safe, try to obtain fingerprints from the key file
      # (to ensure there won't be e.g. CRC error).
      IFS=$'\n' GPG_OUT=($(gpg --with-fingerprint "${REDHAT_RELEASE_KEY}" | grep 'Key fingerprint ='))
      GPG_RESULT=$?
      # Reset IFS back to default
      unset IFS
      # No CRC error, safe to proceed
      if [ "${GPG_RESULT}" -eq "0" ]
      then
        tr -s ' ' <<< "${GPG_OUT}" | grep -vE "${REDHAT_RELEASE_2_FINGERPRINT}|${REDHAT_AUXILIARY_FINGERPRINT}" || {
          # If file doesn't contains any keys with unknown fingerprint, import it
          rpm --import "${REDHAT_RELEASE_KEY}"
        }
      fi
    fi

Remediation Ansible snippet: (show)

Complexity: | medium
Disruption: | medium
Strategy: | restrict

    - name: "Read permission of GPG key directory"
      stat:
        path: /etc/pki/rpm-gpg/
      register: gpg_key_directory_permission
      check_mode: no
      tags:
        - ensure_redhat_gpgkey_installed
        - high_severity
        - restrict_strategy
        - medium_complexity
        - medium_disruption
        - CCE-26957-1
        - NIST-800-53-CM-5(3)
        - NIST-800-53-SI-7
        - NIST-800-53-MA-1(b)
        - NIST-800-171-3.4.8
        - PCI-DSS-Req-6.2
        - CJIS-5.10.4.1

    # It should fail if it doesn't find any fingerprints in file - maybe file was not parsed well.

    - name: Read signatures in GPG key
      shell: gpg --with-fingerprint '/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release' | grep 'Key fingerprint =' | tr -s ' ' | sed 's;.*= ;;g'
      changed_when: False
      register: gpg_fingerprints
      check_mode: no
      tags:
        - ensure_redhat_gpgkey_installed
        - high_severity
        - restrict_strategy
        - medium_complexity
        - medium_disruption
        - CCE-26957-1
        - NIST-800-53-CM-5(3)
        - NIST-800-53-SI-7
        - NIST-800-53-MA-1(b)
        - NIST-800-171-3.4.8
        - PCI-DSS-Req-6.2
        - CJIS-5.10.4.1

    - name: Set Fact - Valid fingerprints
      set_fact:
         gpg_valid_fingerprints: ("567E 347A D004 4ADE 55BA 8A5F 199E 2F91 FD43 1D51" "43A6 E49C 4A38 F4BE 9ABF 2A53 4568 9C88 2FA6 58E0")
      tags:
        - ensure_redhat_gpgkey_installed
        - high_severity
        - restrict_strategy
        - medium_complexity
        - medium_disruption
        - CCE-26957-1
        - NIST-800-53-CM-5(3)
        - NIST-800-53-SI-7
        - NIST-800-53-MA-1(b)
        - NIST-800-171-3.4.8
        - PCI-DSS-Req-6.2
        - CJIS-5.10.4.1

    - name: Import RedHat GPG key
      rpm_key:
        state: present
        key: /etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
      when:
        (gpg_key_directory_permission.stat.mode <= '0755')
        and (( gpg_fingerprints.stdout_lines | difference(gpg_valid_fingerprints)) | length == 0)
        and (gpg_fingerprints.stdout_lines | length > 0)
        and (ansible_distribution == "RedHat")
      tags:
        - ensure_redhat_gpgkey_installed
        - high_severity
        - restrict_strategy
        - medium_complexity
        - medium_disruption
        - CCE-26957-1
        - NIST-800-53-CM-5(3)
        - NIST-800-53-SI-7
        - NIST-800-53-MA-1(b)
        - NIST-800-171-3.4.8
        - PCI-DSS-Req-6.2
        - CJIS-5.10.4.1

#### Ensure gpgcheck Enabled In Main Yum Configuration [ref]rule

The `gpgcheck` option controls whether RPM packages' signatures are always checked prior to installation. To configure yum to check package signatures before installing them, ensure the following line appears in `/etc/yum.conf` in the `[main]` section:

    gpgcheck=1

Rationale:

Changes to any software components can have significant effects on the overall security of the operating system. This requirement ensures the software has not been tampered with and that it has been provided by a trusted vendor.
Accordingly, patches, service packs, device drivers, or operating system components must be signed with a certificate recognized and approved by the organization.
Verifying the authenticity of the software prior to installation validates the integrity of the patch or upgrade received from a vendor. This ensures the software has not been tampered with and that it has been provided by a trusted vendor. Self-signed certificates are disallowed by this requirement. Certificates used to verify the software must be from an approved Certificate Authority (CA).

Severity: high

References: FAU_GEN.1.1.c, SV-86601r1_rule, 1.2.2, 5.10.4.1, 3.4.8, CCI-001749, 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i), CM-5(3), SI-7, MA-1(b), Req-6.2, SRG-OS-000366-GPOS-00153

Remediation Shell script: (show)

    # Function to replace configuration setting in config file or add the configuration setting if
    # it does not exist.
    #
    # Expects arguments:
    #
    # config_file:		Configuration file that will be modified
    # key:			Configuration option to change
    # value:		Value of the configuration option to change
    # cce:			The CCE identifier or '@CCENUM@' if no CCE identifier exists
    # format:		The printf-like format string that will be given stripped key and value as arguments,
    #			so e.g. '%s=%s' will result in key=value subsitution (i.e. without spaces around =)
    #
    # Optional arugments:
    #
    # format:		Optional argument to specify the format of how key/value should be
    # 			modified/appended in the configuration file. The default is key = value.
    #
    # Example Call(s):
    #
    #     With default format of 'key = value':
    #     replace_or_append '/etc/sysctl.conf' '^kernel.randomize_va_space' '2' '@CCENUM@'
    #
    #     With custom key/value format:
    #     replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' 'disabled' '@CCENUM@' '%s=%s'
    #
    #     With a variable:
    #     replace_or_append '/etc/sysconfig/selinux' '^SELINUX=' $var_selinux_state '@CCENUM@' '%s=%s'
    #
    function replace_or_append {
      local default_format='%s = %s' case_insensitive_mode=yes sed_case_insensitive_option='' grep_case_insensitive_option=''
      local config_file=$1
      local key=$2
      local value=$3
      local cce=$4
      local format=$5

      if [ "$case_insensitive_mode" = yes ]; then
        sed_case_insensitive_option="i"
        grep_case_insensitive_option="-i"
      fi
      [ -n "$format" ] || format="$default_format"
      # Check sanity of the input
      [ $# -ge "3" ] || { echo "Usage: replace_or_append <config_file_location> <key_to_search> <new_value> [<CCE number or literal '@CCENUM@' if unknown>] [printf-like format, default is '$default_format']" >&2; exit 1; }

      # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
      # Otherwise, regular sed command will do.
      sed_command=('sed' '-i')
      if test -L "$config_file"; then
        sed_command+=('--follow-symlinks')
      fi

      # Test that the cce arg is not empty or does not equal @CCENUM@.
      # If @CCENUM@ exists, it means that there is no CCE assigned.
      if [ -n "$cce" ] && [ "$cce" != '@CCENUM@' ]; then
        cce="CCE-${cce}"
      else
        cce="CCE"
      fi

      # Strip any search characters in the key arg so that the key can be replaced without
      # adding any search characters to the config file.
      stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "$key")

      # shellcheck disable=SC2059
      printf -v formatted_output "$format" "$stripped_key" "$value"

      # If the key exists, change it. Otherwise, add it to the config_file.
      # We search for the key string followed by a word boundary (matched by \>),
      # so if we search for 'setting', 'setting2' won't match.
      if grep -q $grep_case_insensitive_option "${key}\\>" "$config_file"; then
        "${sed_command[@]}" "s/${key}\\>.*/$formatted_output/g$sed_case_insensitive_option" "$config_file"
      else
        # \n is precaution for case where file ends without trailing newline
        printf '\n# Per %s: Set %s in %s\n' "$cce" "$formatted_output" "$config_file" >> "$config_file"
        printf '%s\n' "$formatted_output" >> "$config_file"
      fi
    }

    replace_or_append '/etc/yum.conf' '^gpgcheck' '1' 'CCE-26989-4'

Remediation Ansible snippet: (show)

Complexity: | low
Disruption: | medium

    - name: Check existence of yum on Fedora
      stat:
        path: /etc/yum.conf
      register: yum_config_file
      check_mode: no
      when: ansible_distribution == "Fedora"

    # Old versions of Fedora use yum

    - name: Ensure GPG check is globally activated (yum)
      ini_file:
        dest: "{{item}}"
        section: main
        option: gpgcheck
        value: 1
        create: False
      with_items: "/etc/yum.conf"
      when: ansible_distribution == "RedHat" or ansible_distribution == "CentOS" or yum_config_file.stat.exists
      tags:
        - ensure_gpgcheck_globally_activated
        - high_severity
        - unknown_strategy
        - low_complexity
        - medium_disruption
        - CCE-26989-4
        - NIST-800-53-CM-5(3)
        - NIST-800-53-SI-7
        - NIST-800-53-MA-1(b)
        - NIST-800-171-3.4.8
        - PCI-DSS-Req-6.2
        - CJIS-5.10.4.1
        - DISA-STIG-RHEL-07-020050

    - name: Ensure GPG check is globally activated (dnf)
      ini_file:
        dest: "{{item}}"
        section: main
        option: gpgcheck
        value: 1
        create: False
      with_items: "/etc/dnf/dnf.conf"
      when: ansible_distribution == "Fedora"
      tags:
        - ensure_gpgcheck_globally_activated
        - high_severity
        - unknown_strategy
        - low_complexity
        - medium_disruption
        - CCE-26989-4
        - NIST-800-53-CM-5(3)
        - NIST-800-53-SI-7
        - NIST-800-53-MA-1(b)
        - NIST-800-171-3.4.8
        - PCI-DSS-Req-6.2
        - CJIS-5.10.4.1
        - DISA-STIG-RHEL-07-020050

### Disk Partitioning [ref]group

To ensure separation and protection of data, there are top-level system directories which should be placed on their own physical partition or logical volume. The installer's default partitioning scheme creates separate logical volumes for `/`, `/boot`, and `swap`.

- If starting with any of the default layouts, check the box to "Review and modify partitioning." This allows for the easy creation of additional logical volumes inside the volume group already created, though it may require making `/`'s logical volume smaller to create space. In general, using logical volumes is preferable to using partitions because they can be more easily adjusted later.
- If creating a custom layout, create the partitions mentioned in the previous paragraph (which the installer will require anyway), as well as separate ones described in the following sections.

If a system has already been installed, and the default partitioning scheme was used, it is possible but nontrivial to modify it to create separate logical volumes for the directories listed above. The Logical Volume Manager (LVM) makes this possible. See the LVM HOWTO at http://tldp.org/HOWTO/LVM-HOWTO/ for more detailed information on LVM.

contains 2 rules

#### Ensure /var/log/audit Located On Separate Partition [ref]rule

Audit logs are stored in the `/var/log/audit` directory. Ensure that it has its own partition or logical volume at installation time, or migrate it later using LVM. Make absolutely certain that it is large enough to store all audit logs that will be created by the auditing daemon.

Rationale:

Placing `/var/log/audit` in its own partition enables better separation between audit files and other files, and helps ensure that auditing cannot be halted due to the partition running out of space.

Severity: low

References: SV-86687r5_rule, 1.1.12, CCI-000366, 164.312(a)(2)(ii), A.12.3.1, AU-4, AU-9, SC-32(1), SRG-OS-000480-GPOS-00227

Remediation Anaconda snippet: (show)

Complexity: | low
Disruption: | high
Strategy: | enable

    part /var/log/audit

#### Ensure /var/log Located On Separate Partition [ref]rule

System logs are stored in the `/var/log` directory. Ensure that it has its own partition or logical volume at installation time, or migrate it using LVM.

Rationale:

Placing `/var/log` in its own partition enables better separation between log files and other files in `/var/`.

Severity: unknown

References: 1.1.11, AU-9, SC-32

Remediation Anaconda snippet: (show)

Complexity: | low
Disruption: | high
Strategy: | enable

    part /var/log

Red Hat and Red Hat Enterprise Linux are either registered trademarks or trademarks of Red Hat, Inc. in the United States and other countries. All other names are registered trademarks or trademarks of their respective companies.