Description
aide --update --limit <regex> crashes with SIGSEGV when the regex matches an entry whose file name contains a character that AIDE percent-encodes when it writes the database. Measured on space, #, %, : and @ — all stored as %20, %23, %25, %3A, %40 respectively, all crashing. +, the one candidate character that is stored unencoded, does not crash, and neither does a name with no special character at all.
The regex spelling is irrelevant: the crash is identical whether the character is written literally, as \@, as [@], as \x40, or merely matched by . — so this is not shell quoting or regex escaping on the caller's side.
Expected: --update --limit reports and updates the matched entry, exiting with the usual "changes found" status.
Actual: SIGSEGV (exit code 139, core dumped). A database_out file is created but holds only the header and the @@db_spec line — not a single entry (185 bytes here) — so the limited update does not happen.
An AddressSanitizer build of 0.19.3 locates it as a NULL-pointer read in the scan worker thread:
==27787==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000118
==27787==The signal is caused by a READ memory access.
==27787==Hint: address points to the zero page.
#0 get_different_attributes src/gen_list.c:322
#1 add_file_to_tree src/gen_list.c:476
#2 process_path src/db_disk.c:341
#3 process_disk_entries src/db_disk.c:359
#4 worker src/db_disk.c:379
SUMMARY: AddressSanitizer: SEGV src/gen_list.c:322 in get_different_attributes
Thread T1 created by T0 here:
#1 db_scan_disk src/db_disk.c:409
#2 populate_tree src/gen_list.c:891
#3 main src/aide.c:882
I am deliberately not offering an explanation for the crash — an earlier theory of mine (that @ is special in AIDE's rule/macro syntax and the escaping never reached the --limit path) did not survive reading the source: read_param() copies the option value straight to conf->limit and hands it to pcre2_compile(), with no config parser in between, and @ is an ordinary literal to PCRE2. So the trigger is established, the cause is not.
Symlinks are not involved. The paths that first exposed this were systemd .wants/ symlinks to template unit instances, so they contained @ and were symlinks. Varying both axes independently shows symlinks are irrelevant: a @ in a regular file crashes, a symlink without one does not.
Which characters, and how they are stored. One file per candidate character, one --limit run per file, same tree and database throughout:
| character in file name |
stored in the database as |
--limit onto that file |
none (plain.txt) |
plain.txt |
ok (rc=4) |
| space |
we%20ird.txt |
SIGSEGV |
# |
we%23ird.txt |
SIGSEGV |
% |
we%25ird.txt |
SIGSEGV |
: |
we%3Aird.txt |
SIGSEGV |
@ |
we%40ird.txt |
SIGSEGV |
+ |
we+ird.txt (not encoded) |
ok (rc=4) |
The encoding is done by encode_string() (src/util.c, called from src/db_file.c). I mention it only because it is what the correlation points at; I am not claiming it is the cause.
Which spelling the regex matches makes the difference. Addressing the very same entry by its percent-encoded name does not crash. Instead the entry is reported as removed and is missing from the resulting database_out, while the file is still on disk and unchanged. So each full-name spelling appears to reach only one side of the comparison, and a plain prefix regex — the only form that matches the entry under either spelling — is also the only full-coverage form that behaves normally. I am reporting this as an observation and deliberately not proposing a mechanism for it. Measured, same tree and database throughout, and identically for @ and for a space:
--limit argument |
result |
.../we@ird\.txt$ (raw name; likewise we\@ird, we[@]ird, we\x40ird, we.ird) |
SIGSEGV |
.../we%40ird\.txt$ (same entry, percent-encoded name) |
no crash, rc=2, reported as removed, entry dropped from database_out |
.../(normal|we@ird)\.txt$ (matches both entries) |
SIGSEGV |
.../ and .../.* (prefix, covers both entries) |
ok (rc=4) |
.../normal\.txt$ (the entry without a special character) |
ok (rc=4) |
| tree with no special character at all, one leaf matched at a time |
ok (rc=4) |
A tree containing no such character at all never crashes, for any of the regexes tried — including ones that skip earlier siblings and match a later entry, so "skipped sibling, then match" is not it either. And to rule out the obvious guess: names are reconciled on the read path. An unlimited aide --check on an untouched tree reports no differences at all, and modifying the file whose name contains a space reports it as changed under its raw name.
On escaping. aide.conf(5) requires @, space and backslash to be escaped in configuration rules, so escaping was the first thing I suspected. It does not apply here: --limit is a command-line regex, aide(1) documents no character restrictions for it, and writing the character as \@ — the very form the config syntax prescribes — crashes just the same. Nothing in the manpages, the ChangeLog or the existing issues describes this, as far as I can tell.
Affected versions and builds. All three crash identically, with the same matrix of results:
- 0.19.1 — distribution package
0.19.1-2+deb13u2 (Debian 13). This is the version the aide --version output below comes from.
- 0.19.3 — manual build from the official release tarball, configured to match that Debian package's compile-time options exactly (verified: both
--version outputs list identical options).
- 0.19.3 — manual build from the same tarball on Arch Linux (rolling), configured with libgcrypt instead of Nettle and without ACL/SELinux/xattr/caps/e2fsattrs support, against pcre2 10.47.
The install-method dropdown below only takes one value, so it says "package manager"; the two tarball builds above are the other half.
The third build is what rules most explanations out. Between it and the Debian package, the distribution, the libc, the compiler, the crypto backend, the pcre2 version and the compiled-in attribute set all differ — and the behaviour is identical down to the exit codes: the @ entry and a name containing a space both SIGSEGV, the entries without such a character exit 4, the prefix regex exits 4, and the database again stores the name percent-encoded (we%40ird.txt). So this is neither distro-specific nor a property of one crypto library, one pcre2 release or one attribute set.
Unrelated build note for 0.19.3 on current Nettle: the Arch build had to use libgcrypt because the Nettle path does not compile against Nettle 4.0 — src/md.c:169 passes three arguments to the digest function, which now takes two (error: too many arguments to function 'nettle_functions[...].digest'; expected 2, have 3). configure accepts it regardless, because it only checks nettle >= 3.7. Same failure with 0.19.1. Mentioned only so the mismatch between the configure check and the code is on record; happy to open it separately if it is not already known.
Side note: git master could not be bootstrapped, which is why there is no test against it. ./autogen.sh fails with configure.ac:19: error: possibly undefined macro: AC_MSG_ERROR, preceded by configure.ac:3: warning: file 'version.m4' included several times (pulled in both by the generated aclocal.m4 and by m4_include([version.m4]) in configure.ac). Reproduced with autoconf 2.72 (Debian trixie) and 2.73 (Arch Linux) on a clean checkout of 417e72465f1087b86938bbcaabbf53a50e3e6c45 after git clean -xdf. Happy to open this separately if it is not already known — I did not want to bury it inside a crash report.
Also, 0.19.1 does not build against Nettle ≥ 3.10 (nettle_hash_digest_func lost an argument; src/md.c:169 passes three), which is why the 0.19.1 side of this is the distribution package rather than a local build.
This report — the reducer, the two-axis test that ruled out symlinks as the trigger, and the AddressSanitizer run — was put together with the help of Claude (Anthropic).
How to reproduce the issue?
Self-contained: no systemd, no site configuration, and it only ever writes under its own WORK directory. It builds four entries — @/no-@ crossed with regular file/symlink — takes a baseline, changes all four, and then runs one --update --limit per entry against a pristine copy of that baseline:
set -uo pipefail
AIDE_BIN="${AIDE_BIN:-aide}"; WORK="${WORK:-/tmp/aide-repro}"
rm -rf "$WORK"; mkdir -p "$WORK/tree/plain" "$WORK/tree/link" "$WORK/db"
echo alpha > "$WORK/tree/plain/normal.txt" # no @, regular
echo bravo > "$WORK/tree/plain/[email protected]" # @, regular
ln -s ../plain/normal.txt "$WORK/tree/link/normal.link" # no @, symlink
ln -s ../plain/[email protected] "$WORK/tree/link/[email protected]" # @, symlink
cat > "$WORK/aide.conf" <<EOF
database_in=file:$WORK/db/aide.db
database_out=file:$WORK/db/aide.db.new
report_url=stdout
Rule = p+i+n+u+g+s+m+c+l+md5
$WORK/tree Rule
EOF
"$AIDE_BIN" -c "$WORK/aide.conf" --init >/dev/null 2>&1
mv -f "$WORK/db/aide.db.new" "$WORK/db/aide.db"
cp "$WORK/db/aide.db" "$WORK/db/aide.db.pristine"
echo charlie >> "$WORK/tree/plain/normal.txt"
echo delta >> "$WORK/tree/plain/[email protected]"
ln -sfn ../plain/[email protected] "$WORK/tree/link/normal.link"
ln -sfn ../plain/normal.txt "$WORK/tree/link/[email protected]"
for t in "plain/normal.txt" "plain/[email protected]" "link/normal.link" "link/[email protected]"; do
cp -f "$WORK/db/aide.db.pristine" "$WORK/db/aide.db"; rm -f "$WORK/db/aide.db.new"
"$AIDE_BIN" -c "$WORK/aide.conf" --update --limit "$WORK/tree/$t\$" >/dev/null 2>&1
printf '%-24s rc=%s\n' "$t" "$?" # 139 = SIGSEGV, 1..7 = normal "changes found"
done
Observed output on all three builds listed in the description — rc=4 for the two entries without @, rc=139 for the two with:
plain/normal.txt rc=4
plain/[email protected] rc=139
link/normal.link rc=4
link/[email protected] rc=139
To see the other characters from the table in the description, replace [email protected] with a name containing a space, #, %, : or +; the + case is the negative control and does not crash.
Which version of AIDE are you using?
AIDE 0.19.1
Compile-time options:
use pcre2: mandatory
use pthread: mandatory
use zlib compression: yes
use POSIX ACLs: yes
use SELinux: yes
use xattr: yes
use POSIX 1003.1e capabilities: yes
use e2fsattrs: yes
use cURL: no
use Nettle crypto library: yes
use GNU crypto library: no
use Linux Auditing Framework: yes
use locale: no
syslog ident: aide
syslog logopt: LOG_CONS
syslog priority: LOG_NOTICE
default syslog facility: LOG_LOCAL0
Default config values:
config file:
database_in:
database_out:
Available compiled-in attributes:
acl: yes
xattrs: yes
selinux: yes
e2fsattrs: yes
caps: yes
Available hashsum attributes:
md5: yes
sha1: yes
sha256: yes
sha512: yes
rmd160: yes
tiger: no
crc32: no
crc32b: no
haval: no
whirlpool: no
gost: yes
stribog256: yes
stribog512: yes
sha512_256: yes
sha3_256: yes
sha3_512: yes
Available file system type names:
9p autofs bcachefs binfmt
bpf btrfs cgroup cgroup2
configfs debugfs devpts efivarfs
exfat ext f2fs fuse
fusectl hugetlbfs mqueue nfs
nilfs overlayfs proc pstore
ramfs securityfs selinuxfs squashfs
sysfs tmpfs tracefs udf
vfat xfs
Default compound groups:
R: l+p+u+g+s+c+m+i+n+acl+selinux+xattrs+ftype+e2fsattrs+caps+sha3_256
L: l+p+u+g+i+n+acl+selinux+xattrs+ftype+e2fsattrs+caps
: l+p+u+g+s+i+n+acl+selinux+xattrs+ftype+e2fsattrs+caps+growing
H: sha256+sha512+stribog256+stribog512+sha512_256+sha3_256+sha3_512
X: acl+selinux+xattrs+e2fsattrs+caps
(Distribution packages on that host: aide 0.19.1-2+deb13u2, aide-common 0.19.1-2+deb13u2)
The second crashing build, for comparison — 0.19.3 from the release tarball on Arch Linux,
configured with libgcrypt because the Nettle path does not compile against Nettle 4.0:
AIDE 0.19.3
Compile-time options:
use pcre2: mandatory
use pthread: mandatory
use zlib compression: yes
use POSIX ACLs: no
use SELinux: no
use xattr: no
use POSIX 1003.1e capabilities: no
use e2fsattrs: no
use cURL: no
use Nettle crypto library: no
use GNU crypto library: yes
How did you install AIDE?
manual build from release tarball
What's your operating system?
debian, arch
Description
aide --update --limit <regex>crashes with SIGSEGV when the regex matches an entry whose file name contains a character that AIDE percent-encodes when it writes the database. Measured on space,#,%,:and@— all stored as%20,%23,%25,%3A,%40respectively, all crashing.+, the one candidate character that is stored unencoded, does not crash, and neither does a name with no special character at all.The regex spelling is irrelevant: the crash is identical whether the character is written literally, as
\@, as[@], as\x40, or merely matched by.— so this is not shell quoting or regex escaping on the caller's side.Expected:
--update --limitreports and updates the matched entry, exiting with the usual "changes found" status.Actual: SIGSEGV (exit code 139, core dumped). A
database_outfile is created but holds only the header and the@@db_specline — not a single entry (185 bytes here) — so the limited update does not happen.An AddressSanitizer build of 0.19.3 locates it as a NULL-pointer read in the scan worker thread:
I am deliberately not offering an explanation for the crash — an earlier theory of mine (that
@is special in AIDE's rule/macro syntax and the escaping never reached the--limitpath) did not survive reading the source:read_param()copies the option value straight toconf->limitand hands it topcre2_compile(), with no config parser in between, and@is an ordinary literal to PCRE2. So the trigger is established, the cause is not.Symlinks are not involved. The paths that first exposed this were systemd
.wants/symlinks to template unit instances, so they contained@and were symlinks. Varying both axes independently shows symlinks are irrelevant: a@in a regular file crashes, a symlink without one does not.Which characters, and how they are stored. One file per candidate character, one
--limitrun per file, same tree and database throughout:--limitonto that fileplain.txt)plain.txtwe%20ird.txt#we%23ird.txt%we%25ird.txt:we%3Aird.txt@we%40ird.txt+we+ird.txt(not encoded)The encoding is done by
encode_string()(src/util.c, called fromsrc/db_file.c). I mention it only because it is what the correlation points at; I am not claiming it is the cause.Which spelling the regex matches makes the difference. Addressing the very same entry by its percent-encoded name does not crash. Instead the entry is reported as removed and is missing from the resulting
database_out, while the file is still on disk and unchanged. So each full-name spelling appears to reach only one side of the comparison, and a plain prefix regex — the only form that matches the entry under either spelling — is also the only full-coverage form that behaves normally. I am reporting this as an observation and deliberately not proposing a mechanism for it. Measured, same tree and database throughout, and identically for@and for a space:--limitargument.../we@ird\.txt$(raw name; likewisewe\@ird,we[@]ird,we\x40ird,we.ird).../we%40ird\.txt$(same entry, percent-encoded name)database_out.../(normal|we@ird)\.txt$(matches both entries).../and.../.*(prefix, covers both entries).../normal\.txt$(the entry without a special character)A tree containing no such character at all never crashes, for any of the regexes tried — including ones that skip earlier siblings and match a later entry, so "skipped sibling, then match" is not it either. And to rule out the obvious guess: names are reconciled on the read path. An unlimited
aide --checkon an untouched tree reports no differences at all, and modifying the file whose name contains a space reports it as changed under its raw name.On escaping.
aide.conf(5)requires@, space and backslash to be escaped in configuration rules, so escaping was the first thing I suspected. It does not apply here:--limitis a command-line regex,aide(1)documents no character restrictions for it, and writing the character as\@— the very form the config syntax prescribes — crashes just the same. Nothing in the manpages, the ChangeLog or the existing issues describes this, as far as I can tell.Affected versions and builds. All three crash identically, with the same matrix of results:
0.19.1-2+deb13u2(Debian 13). This is the version theaide --versionoutput below comes from.--versionoutputs list identical options).The install-method dropdown below only takes one value, so it says "package manager"; the two tarball builds above are the other half.
The third build is what rules most explanations out. Between it and the Debian package, the distribution, the libc, the compiler, the crypto backend, the pcre2 version and the compiled-in attribute set all differ — and the behaviour is identical down to the exit codes: the
@entry and a name containing a space bothSIGSEGV, the entries without such a character exit 4, the prefix regex exits 4, and the database again stores the name percent-encoded (we%40ird.txt). So this is neither distro-specific nor a property of one crypto library, one pcre2 release or one attribute set.Unrelated build note for 0.19.3 on current Nettle: the Arch build had to use libgcrypt because the Nettle path does not compile against Nettle 4.0 —
src/md.c:169passes three arguments to the digest function, which now takes two (error: too many arguments to function 'nettle_functions[...].digest'; expected 2, have 3).configureaccepts it regardless, because it only checksnettle >= 3.7. Same failure with 0.19.1. Mentioned only so the mismatch between the configure check and the code is on record; happy to open it separately if it is not already known.Side note: git master could not be bootstrapped, which is why there is no test against it.
./autogen.shfails withconfigure.ac:19: error: possibly undefined macro: AC_MSG_ERROR, preceded byconfigure.ac:3: warning: file 'version.m4' included several times(pulled in both by the generatedaclocal.m4and bym4_include([version.m4])inconfigure.ac). Reproduced with autoconf 2.72 (Debian trixie) and 2.73 (Arch Linux) on a clean checkout of417e72465f1087b86938bbcaabbf53a50e3e6c45aftergit clean -xdf. Happy to open this separately if it is not already known — I did not want to bury it inside a crash report.Also, 0.19.1 does not build against Nettle ≥ 3.10 (
nettle_hash_digest_funclost an argument;src/md.c:169passes three), which is why the 0.19.1 side of this is the distribution package rather than a local build.This report — the reducer, the two-axis test that ruled out symlinks as the trigger, and the AddressSanitizer run — was put together with the help of Claude (Anthropic).
How to reproduce the issue?
Self-contained: no systemd, no site configuration, and it only ever writes under its own
WORKdirectory. It builds four entries —@/no-@crossed with regular file/symlink — takes a baseline, changes all four, and then runs one--update --limitper entry against a pristine copy of that baseline:Observed output on all three builds listed in the description —
rc=4for the two entries without@,rc=139for the two with:To see the other characters from the table in the description, replace
[email protected]with a name containing a space,#,%,:or+; the+case is the negative control and does not crash.Which version of AIDE are you using?
AIDE 0.19.1
Compile-time options:
use pcre2: mandatory
use pthread: mandatory
use zlib compression: yes
use POSIX ACLs: yes
use SELinux: yes
use xattr: yes
use POSIX 1003.1e capabilities: yes
use e2fsattrs: yes
use cURL: no
use Nettle crypto library: yes
use GNU crypto library: no
use Linux Auditing Framework: yes
use locale: no
syslog ident: aide
syslog logopt: LOG_CONS
syslog priority: LOG_NOTICE
default syslog facility: LOG_LOCAL0
Default config values:
config file:
database_in:
database_out:
Available compiled-in attributes:
acl: yes
xattrs: yes
selinux: yes
e2fsattrs: yes
caps: yes
Available hashsum attributes:
md5: yes
sha1: yes
sha256: yes
sha512: yes
rmd160: yes
tiger: no
crc32: no
crc32b: no
haval: no
whirlpool: no
gost: yes
stribog256: yes
stribog512: yes
sha512_256: yes
sha3_256: yes
sha3_512: yes
Available file system type names:
9p autofs bcachefs binfmt
bpf btrfs cgroup cgroup2
configfs debugfs devpts efivarfs
exfat ext f2fs fuse
fusectl hugetlbfs mqueue nfs
nilfs overlayfs proc pstore
ramfs securityfs selinuxfs squashfs
sysfs tmpfs tracefs udf
vfat xfs
Default compound groups:
R: l+p+u+g+s+c+m+i+n+acl+selinux+xattrs+ftype+e2fsattrs+caps+sha3_256
L: l+p+u+g+i+n+acl+selinux+xattrs+ftype+e2fsattrs+caps
(Distribution packages on that host: aide 0.19.1-2+deb13u2, aide-common 0.19.1-2+deb13u2)
The second crashing build, for comparison — 0.19.3 from the release tarball on Arch Linux,
configured with libgcrypt because the Nettle path does not compile against Nettle 4.0:
AIDE 0.19.3
Compile-time options:
use pcre2: mandatory
use pthread: mandatory
use zlib compression: yes
use POSIX ACLs: no
use SELinux: no
use xattr: no
use POSIX 1003.1e capabilities: no
use e2fsattrs: no
use cURL: no
use Nettle crypto library: no
use GNU crypto library: yes
How did you install AIDE?
manual build from release tarball
What's your operating system?
debian, arch