Description
auditd rules in /etc/audit/rules.d/60-cirsec-perm.rules, for actions of users who logged in:
- mode changes (
chmod,fchmod,fchmodat, andfchmodat2where the kernel and auditd know it); - owner changes (
chown,fchown,lchown,fchownat); - extended attribute changes (
setxattr,removexattrand their variants), which also hold ACLs; - runs of
chcon,setfaclandchacl(keyperm_chng).
Every system call rule exists for 64-bit and 32-bit programs (key perm_mod).
chcon belongs to coreutils; setfacl and chacl come with the acl package, often absent. A rule on a program that is not installed is accepted and applies once it is installed.
Rationale
Permission changes are how a user makes a private file readable, a script writable by others or a binary setuid. Recording them makes such changes visible and attributable.
Impact
Users who change many permissions (builds, package managers run by users, rsync) produce many records. Actions of system services (no login session) are not recorded.
Setting
| Location | /etc/audit/rules.d/60-cirsec-perm.rules |
| Default | No audit rules |
| Recommended | Rules loaded (auditctl -l) and present in /etc/audit/rules.d/60-cirsec-perm.rules |
Audit
- Run
auditctl -las root. It must list the rules shown below (keysperm_chng,perm_mod). - Run
grep -h . /etc/audit/rules.d/*.rules. The same rules must be present, so they load at boot.
Expected rules, as auditctl -l prints them:
-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b32 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b64 -S chown,fchown,lchown,fchownat -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b32 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -S all -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=-1 -F key=perm_chng
-a always,exit -S all -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=-1 -F key=perm_chng
-a always,exit -S all -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=-1 -F key=perm_chng
Automated: every check must pass. Scanners report each check by its id.
rules
Rules loaded in the kernel and present in /etc/audit/rules.d.
script: |
command -v auditctl >/dev/null 2>&1 || { echo "auditctl not found: auditd is not installed"; exit 1; }
want() {
cat <<'RULES'
-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b32 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b64 -S chown,fchown,lchown,fchownat -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b32 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -S all -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=-1 -F key=perm_chng
-a always,exit -S all -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=-1 -F key=perm_chng
-a always,exit -S all -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=-1 -F key=perm_chng
RULES
# fchmodat2 exists from Linux 6.6 (Debian 13); auditd 3.0 (Debian 12) does not know it
if ausyscall x86_64 fchmodat2 >/dev/null 2>&1; then
echo '-a always,exit -F arch=b64 -S fchmodat2 -F auid>=1000 -F auid!=-1 -F key=perm_mod'
echo '-a always,exit -F arch=b32 -S fchmodat2 -F auid>=1000 -F auid!=-1 -F key=perm_mod'
fi
}
loaded=$(auditctl -l 2>&1) || { echo "$loaded"; exit 3; }
disk=$(cat /etc/audit/rules.d/*.rules 2>/dev/null)
n=0 rc=0
while IFS= read -r r; do
[ -n "$r" ] || continue
n=$((n + 1))
printf '%s\n' "$loaded" | grep -Fxq -- "$r" || { echo "not loaded: $r"; rc=1; }
printf '%s\n' "$disk" | grep -Fxq -- "$r" || { echo "not in /etc/audit/rules.d: $r"; rc=1; }
done <<EOF
$(want)
EOF
[ "$rc" = 1 ] || echo "$n rules loaded and present in /etc/audit/rules.d"
exit "$rc"
Remediation
- Write the expected rules to
/etc/audit/rules.d/60-cirsec-perm.rules(mode 0640). - Run
augenrules --load. Ifauditctl -sshowsenabled 2, the configuration is locked: reboot to load the new rules.
| Restart | none |
| Disruption | none |
Rollback
Delete /etc/audit/rules.d/60-cirsec-perm.rules and run augenrules --load (or reboot if the configuration is locked).
rules-file
command -v augenrules >/dev/null 2>&1 || { echo "auditd is not installed" >&2; exit 1; }
want() {
cat <<'RULES'
-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b32 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b64 -S chown,fchown,lchown,fchownat -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b32 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -S all -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=-1 -F key=perm_chng
-a always,exit -S all -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=-1 -F key=perm_chng
-a always,exit -S all -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=-1 -F key=perm_chng
RULES
# fchmodat2 exists from Linux 6.6 (Debian 13); auditd 3.0 (Debian 12) does not know it
if ausyscall x86_64 fchmodat2 >/dev/null 2>&1; then
echo '-a always,exit -F arch=b64 -S fchmodat2 -F auid>=1000 -F auid!=-1 -F key=perm_mod'
echo '-a always,exit -F arch=b32 -S fchmodat2 -F auid>=1000 -F auid!=-1 -F key=perm_mod'
fi
}
f=/etc/audit/rules.d/60-cirsec-perm.rules
t=$(mktemp)
{ echo '# Written by CIRSEC Baselines CB-DEB-AUDIT-020'; want; } >"$t"
if ! cmp -s "$t" "$f"; then
cb_backup "$f"
cat "$t" >"$f"
fi
rm -f "$t"
chmod 640 "$f"
if auditctl -s | grep -q '^enabled 2'; then
echo "audit rules are locked (-e 2): reboot to load $f"
else
augenrules --load >/dev/null
fi
Handwritten code reads parameters from CB_<NAME> environment variables. Each release turns these fixes into remediate.sh and an Ansible role per profile: dry run by default, a fix runs only when its check fails.
References
- audit.rules(7), Debian manual pages Debian, vendor-doc, accessed 2026-10-18
- auditctl(8), Debian manual pages Debian, vendor-doc, accessed 2026-10-18
- ANSSI-BP-028 v2.0 - Recommandations de configuration d'un système GNU/Linux (R73) ANSSI, guide, accessed 2026-10-18
Mappings
| Framework | ID | Relation |
|---|---|---|
| ANSSI-BP-028 - Recommandations de configuration d'un système GNU/Linux2.0 | R73 | partial |
| NIST SP 800-53 Rev. 55.1.1 | AU-12 | related |
How this control relates to the other framework's: equivalent, stricter, looser, partial or related.
Threats mitigated
| Taxonomy | ID |
|---|---|
| MITRE ATT&CK | T1222.002 |
Related controls
- requires
CB-DEB-AUDIT-011Install and run the audit daemon - related
CB-DEB-AUDIT-029Lock the audit configuration and load every valid rule
People and lifecycle
| Authors |
|
| Verified | 2026-10-18 by Sn0wAlice on 12, Proxmox VM, Debian 12 genericcloud image, kernel 6.1.0-54-cloud-amd64, auditd 1:3.0.9-1, BIOS boot. Rules fail on fresh install; fix writes /etc/audit/rules.d/60-cirsec-perm.rules and loads it; rules match auditctl -l, kept after reboot; second run idempotent. |
| Verified | 2026-10-18 by Sn0wAlice on 13, Proxmox VM, Debian 13 genericcloud image, kernel 6.12.113+deb13-cloud-amd64, auditd 1:4.0.2-2+deb13u1, BIOS boot. Rules fail on fresh install; fix writes /etc/audit/rules.d/60-cirsec-perm.rules and loads it; rules match auditctl -l, kept after reboot; second run idempotent. |
| Introduced in | v0.1.0 |
| Created, updated | 2026-10-18, 2026-10-18 |
| Translations | fr |