Summary ======= Rajesh Pangare discovered that AIDE, a host-based file and directory integrity checker, improperly handles file names with invalid UTF-8 byte sequences. An attacker can craft a malicious file or directory name by including invalid UTF-8 bytes so that the file/directory is silently ignored by AIDE. A local user can exploit this to bypass the AIDE detection of malicious files.
Project
=======
AIDE (Advanced Intrusion Detection Environment)
Affected versions
=================
AIDE >= 0.16, <= 0.19.4
CVE ID
======
A CVE ID has been requested, but is not yet available; please cross-reference
this vulnerability as GHSA-wq25-fm54-5q3q for now. The advisory will be
updated, as soon as a CVE ID is available.
Proof of concept
================
$ mkdir test
$ touch test/"$(printf 'invalid_\x80_byte.txt')"
$ touch test/regular_file.txt
$ aide --config <(printf -- "database_in=file:/dev/null\nroot_prefix =
./test\n/ R") --check
Start timestamp: 2026-10-17 19:30:35 +0200 (AIDE 0.19.4)
AIDE found differences between database and filesystem!!
Root prefix: ./test
Summary:
Total number of entries: 2
Added entries: 2
Removed entries: 0
Changed entries: 0
---------------------------------------------------
Added entries:
---------------------------------------------------
d++++++++++++++++++: /
f++++++++++++++++++: /regular_file.txt
---------------------------------------------------
The attributes of the (uncompressed) database(s):
---------------------------------------------------
/dev/null
SHA256 : 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NM
pJWZG3hSuFU=
SHA512 : z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXc
g/SpIdNs6c5H0NE8XYXysP+DGNKHfuwv
Y7kxvUdBeoGlODJ6+SfaPg==
STRIBOG256: P1OaIT6XyALMIp1HTGqjKoJaNgsqkzqU
n9klII2c4bs=
STRIBOG512: jpRdogmqhp8EVZKFKbyuRnnphzq3B7VT
FfVs65i+8Kc2L3FVKDVu6DzaXyqsTGrS
ujpxXBvNgcuOn5C/TBwaig==
SHA512/256: xnK40e9W7Sirh8NiLFEUBpvdOte4+XN0
mNDAHs7wlno=
SHA3-256 : p//G+L8e12ZRwUdWoGHWYvWA/03kO0n6
gtgKS4D4Q0o=
SHA3-512 : pp9zzKI6msXItWfcGFp1bpfJghZP4lhZ
4NHcwUdcgKYVshI68fX5TBHj6UAsOsVY
9QAZnZW20+MBdYWGKB3NJg==
End timestamp: 2026-10-17 19:30:35 +0200 (run time: 0m 0s)
The report correctly shows the addition of `regular_file.txt` but misses the
addition of the malicious file. The malicious file is also not added to the
AIDE database.
Analysis
========
The vulnerability is caused by improper handling of UTF-8 validation errors
returned from `pcre2_match` function, resulting in malicious file names never
matching any regular expression rule.
This allows a user to craft a file name including invalid UTF-8 byte sequences
to effectively hide the file from AIDE detection.
Mitigation
==========
Upgrade to AIDE v0.19.5
Alternatively apply the provided patch:
aide-0.19_GHSA-wq25-fm54-5q3q_invalid-utf-8.patch
Credit
======
The issue was reported by Rajesh Pangare.
References
==========
[AIDE](https://aide.github.io)
[v0.19.5](https://github.com/aide/aide/releases/tag/v0.19.5)
[GHSA-wq25-fm54-5q3q](https://github.com/aide/aide/security/advisories/GHSA-wq25-fm54-5q3q)
diff --git before/doc/aide.conf.5 after/doc/aide.conf.5
index 0f7da37..2213db2 100644
--- before/doc/aide.conf.5
+++ after/doc/aide.conf.5
@@ -480,6 +480,12 @@ against the complete path.
Special characters can be escaped using
two-digit URL encoding (for example, %20 to represent a space).
+Rules and file names are always treated as single byte sequences (i.e. not as
+UTF strings). That means that multi byte characters aren't matched by single
+dot ('.') or inside a character class. To match UTF multi byte characters,
+explicitly state them in the rule either verbatim (e.g. `®` for registered
+sign) or by hexadecimal escaping (e.g. '\\xC2\\xAE').
+
AIDE uses a deepest-match algorithm to find the tree node to search,
but a first-match algorithm inside the node.
(see also \fBrule\fP log level).
diff --git before/src/aide.c after/src/aide.c
index 622e108..aeef53f 100644
--- before/src/aide.c
+++ after/src/aide.c
@@ -299,7 +299,7 @@ static void read_param(int argc,char**argv)
int len = (strlen(optarg)+1) * sizeof(char);
conf->limit=checked_malloc(len);
strncpy(conf->limit, optarg, len);
- if((conf->limit_crx=pcre2_compile((PCRE2_SPTR) conf->limit, PCRE2_ZERO_TERMINATED, PCRE2_UTF|PCRE2_ANCHORED, &pcre2_errorcode, &pcre2_erroffset, NULL)) == NULL) {
+ if((conf->limit_crx=pcre2_compile((PCRE2_SPTR) conf->limit, PCRE2_ZERO_TERMINATED, PCRE2_NEVER_UTF|PCRE2_ANCHORED, &pcre2_errorcode, &pcre2_erroffset, NULL)) == NULL) {
PCRE2_UCHAR pcre2_error[128];
pcre2_get_error_message(pcre2_errorcode, pcre2_error, 128);
char * limit_safe = stresc(conf->limit);
diff --git before/src/conf_eval.c after/src/conf_eval.c
index 5774ce6..e884569 100644
--- before/src/conf_eval.c
+++ after/src/conf_eval.c
@@ -597,7 +597,7 @@ static void include_directory(const char* dir, const char* rx, bool execute, cha
PCRE2_SIZE pcre2_erroffset;
pcre2_code* crx;
- if((crx = pcre2_compile((PCRE2_SPTR) rx, PCRE2_ZERO_TERMINATED, PCRE2_UTF, &pcre2_errorcode, &pcre2_erroffset, NULL)) == NULL) {
+ if((crx = pcre2_compile((PCRE2_SPTR) rx, PCRE2_ZERO_TERMINATED, PCRE2_NEVER_UTF, &pcre2_errorcode, &pcre2_erroffset, NULL)) == NULL) {
PCRE2_UCHAR pcre2_error[128];
pcre2_get_error_message(pcre2_errorcode, pcre2_error, 128);
LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'%s': error in regular expression '%s' at %zu: %s", execute?"@@x_include":"@@include", rx, pcre2_erroffset, pcre2_error)
diff --git before/src/seltree.c after/src/seltree.c
index 912a2bc..2c623a1 100644
--- before/src/seltree.c
+++ after/src/seltree.c
@@ -231,7 +231,7 @@ rx_rule * add_rx_to_tree(char * rx, rx_restriction_t restriction, AIDE_RULE_TYPE
int pcre2_errorcode;
PCRE2_SIZE pcre2_erroffset;
- if((r->crx=pcre2_compile((PCRE2_SPTR) r->rx, PCRE2_ZERO_TERMINATED, PCRE2_UTF|PCRE2_ANCHORED, &pcre2_errorcode, &pcre2_erroffset, NULL)) == NULL) {
+ if((r->crx=pcre2_compile((PCRE2_SPTR) r->rx, PCRE2_ZERO_TERMINATED, PCRE2_NEVER_UTF|PCRE2_ANCHORED, &pcre2_errorcode, &pcre2_erroffset, NULL)) == NULL) {
PCRE2_UCHAR pcre2_error[128];
pcre2_get_error_message(pcre2_errorcode, pcre2_error, 128);
log_msg(LOG_LEVEL_ERROR, "%s:%d:%zu: error in rule '%s': %s (line: '%s')", filename, linenumber, pcre2_erroffset, rx, pcre2_error, linebuf);
signature.asc
Description: PGP signature
