RHEL 10.0.Z ERRATUM

CVE-2025-54389 aide: improper output neutralization enables bypassing
Resolves: RHEL-108928
This commit is contained in:
Cropi 2025-08-19 16:23:34 +02:00
parent 901cf02fb7
commit 3b9c95c51f
2 changed files with 410 additions and 1 deletions

View File

@ -1,7 +1,7 @@
Summary: Intrusion detection environment
Name: aide
Version: 0.18.6
Release: 8%{?dist}
Release: 8%{?dist}.1
URL: http://sourceforge.net/projects/aide
License: GPL-2.0-or-later
Source0: %{url}/files/aide/%{version}/%{name}-%{version}.tar.gz
@ -26,6 +26,7 @@ BuildRequires: automake libtool
Patch1: aide-verbose.patch
Patch2: gnutls.patch
Patch3: escape-control-chars-CVE-2025-54389.patch
%description
@ -40,6 +41,7 @@ cp -a %{S:2} .
%patch -P 1 -p1 -b .verbose
%patch -P 2 -p1 -b .gnutls
%patch -P 3 -p1 -b .CVE-2025-54389
%build
autoreconf -ivf
@ -77,6 +79,11 @@ mkdir -p -m0700 %{buildroot}%{_localstatedir}/lib/aide
%dir %attr(0700,root,root) %{_localstatedir}/log/aide
%changelog
* Tue Aug 19 2025 Attila Lakatos <alakatos@redhat.com> - 0.18.6-8.1
RHEL 10.1 ERRATUM
- CVE-2025-54389 aide: improper output neutralization enables bypassing
Resolves: RHEL-108928
* Wed Jan 29 2025 Radovan Sroka <rsroka@redhat.com> - 0.18.6-8
RHEL 10.0 ERRATUM
- /boot/grub2/grubenv's timestamp is getting modified continuously due to "boot_success" implementation

View File

@ -0,0 +1,402 @@
diff -U0 aide-0.18.6/ChangeLog.orig aide-0.18.6/ChangeLog
diff -up aide-0.18.6/doc/aide.1.orig aide-0.18.6/doc/aide.1
--- aide-0.18.6/doc/aide.1.orig 2025-08-19 11:35:36.082823977 +0200
+++ aide-0.18.6/doc/aide.1 2025-08-19 11:35:36.082823977 +0200
@@ -130,12 +130,25 @@ SIGUSR1 toggles the log_level between cu
.PP
.SH NOTES
+.IP "Checksum encoding"
+
The checksums in the database and in the output are by default base64
encoded (see also report_base16 option).
To decode them you can use the following shell command:
echo <encoded_checksum> | base64 \-d | hexdump \-v \-e '32/1 "%02x" "\\n"'
+.IP "Control characters"
+
+Control characters (00-31 and 127) are always escaped in log and plain report
+output. They are escaped by a literal backslash (\\) followed by exactly 3
+digits representing the character in octal notation (e.g. a newline is output
+as "\fB\\012\fR"). A literal backslash is not escaped unless it is followed by
+3 digits (0-9), in this case the literal backslash is escaped as
+"\fB\\134\fR". Reports in JSON format are escaped according to the JSON specs
+(e.g. a newline is output as "\fB\\b\fR" or an escape (\fBESC\fR) is output as
+"\fB\\u001b\fR")
+
.PP
.SH FILES
diff -up aide-0.18.6/include/util.h.orig aide-0.18.6/include/util.h
--- aide-0.18.6/include/util.h.orig 2025-08-19 11:35:36.081823966 +0200
+++ aide-0.18.6/include/util.h 2025-08-19 11:35:36.080823957 +0200
@@ -57,6 +57,9 @@ int cmpurl(url_t*, url_t*);
int contains_unsafe(const char*);
+char *strnesc(const char *, size_t);
+char *stresc(const char *);
+
void decode_string(char*);
char* encode_string(const char*);
diff -up aide-0.18.6/src/aide.c.orig aide-0.18.6/src/aide.c
--- aide-0.18.6/src/aide.c.orig 2025-08-19 11:35:36.083823987 +0200
+++ aide-0.18.6/src/aide.c 2025-08-19 11:35:36.083823987 +0200
@@ -285,8 +285,8 @@ static void read_param(int argc,char**ar
if((conf->limit_crx=pcre2_compile((PCRE2_SPTR) conf->limit, PCRE2_ZERO_TERMINATED, PCRE2_UTF|PCRE2_ANCHORED, &pcre2_errorcode, &pcre2_erroffset, NULL)) == NULL) {
PCRE2_UCHAR pcre2_error[128];
pcre2_get_error_message(pcre2_errorcode, pcre2_error, 128);
- INVALID_ARGUMENT("--limit", error in regular expression '%s' at %zu: %s, conf->limit, pcre2_erroffset, pcre2_error)
-
+ char * limit_safe = stresc(conf->limit);
+ INVALID_ARGUMENT("--limit", error in regular expression '%s' at %zu: %s, limit_safe, pcre2_erroffset, pcre2_error)
}
conf->limit_md = pcre2_match_data_create_from_pattern(conf->limit_crx, NULL);
if (conf->limit_md == NULL) {
diff -up aide-0.18.6/src/gen_list.c.orig aide-0.18.6/src/gen_list.c
--- aide-0.18.6/src/gen_list.c.orig 2025-08-19 11:35:36.083823987 +0200
+++ aide-0.18.6/src/gen_list.c 2025-08-19 11:35:36.083823987 +0200
@@ -339,35 +339,40 @@ void print_match(char* filename, rx_rule
char * str;
char* attr_str;
char file_type = get_restriction_char(restriction);
+ char *filename_safe = stresc(filename);
+ char *limit_safe = conf->limit?stresc(conf->limit):NULL;
+
switch (match) {
case RESULT_SELECTIVE_MATCH:
str = get_restriction_string(rule->restriction);
attr_str = diff_attributes(0, rule->attr);
- fprintf(stdout, "[X] %c '%s': selective rule: '%s %s %s' (%s:%d: '%s%s%s')\n", file_type, filename, rule->rx, str, attr_str, rule->config_filename, rule->config_linenumber, rule->config_line, rule->prefix?"', prefix: '":"", rule->prefix?rule->prefix:"");
+ fprintf(stdout, "[X] %c '%s': selective rule: '%s %s %s' (%s:%d: '%s%s%s')\n", file_type, filename_safe, rule->rx, str, attr_str, rule->config_filename, rule->config_linenumber, rule->config_line, rule->prefix?"', prefix: '":"", rule->prefix?rule->prefix:"");
free(attr_str);
free(str);
break;
case RESULT_EQUAL_MATCH:
str = get_restriction_string(rule->restriction);
attr_str = diff_attributes(0, rule->attr);
- fprintf(stdout, "[X] %c '%s': equal rule: '=%s %s %s' (%s:%d: '%s%s%s')\n", file_type, filename, rule->rx, str, attr_str, rule->config_filename, rule->config_linenumber, rule->config_line, rule->prefix?"', prefix: '":"", rule->prefix?rule->prefix:"");
+ fprintf(stdout, "[X] %c '%s': equal rule: '=%s %s %s' (%s:%d: '%s%s%s')\n", file_type, filename_safe, rule->rx, str, attr_str, rule->config_filename, rule->config_linenumber, rule->config_line, rule->prefix?"', prefix: '":"", rule->prefix?rule->prefix:"");
free(attr_str);
free(str);
break;
case RESULT_PARTIAL_MATCH:
case RESULT_NO_MATCH:
if (rule) {
- fprintf(stdout, "[ ] %c '%s': negative rule: '!%s %s' (%s:%d: '%s%s%s')\n", file_type, filename, rule->rx, str = get_restriction_string(rule->restriction), rule->config_filename, rule->config_linenumber, rule->config_line, rule->prefix?"', prefix: '":"", rule->prefix?rule->prefix:"");
+ fprintf(stdout, "[ ] %c '%s': negative rule: '!%s %s' (%s:%d: '%s%s%s')\n", file_type, filename_safe, rule->rx, str = get_restriction_string(rule->restriction), rule->config_filename, rule->config_linenumber, rule->config_line, rule->prefix?"', prefix: '":"", rule->prefix?rule->prefix:"");
free(str);
} else {
- fprintf(stdout, "[ ] %c '%s': no matching rule\n", file_type, filename);
+ fprintf(stdout, "[ ] %c '%s': no matching rule\n", file_type, filename_safe);
}
break;
case RESULT_PARTIAL_LIMIT_MATCH:
case RESULT_NO_LIMIT_MATCH:
- fprintf(stdout, "[ ] %c '%s': outside of limit '%s'\n", file_type, filename, conf->limit);
+ fprintf(stdout, "[ ] %c '%s': outside of limit '%s'\n", file_type, filename_safe, limit_safe);
break;
}
+ free(filename_safe);
+ free(limit_safe);
}
/*
diff -up aide-0.18.6/src/log.c.orig aide-0.18.6/src/log.c
--- aide-0.18.6/src/log.c.orig 2025-08-19 11:35:36.083823987 +0200
+++ aide-0.18.6/src/log.c 2025-08-19 11:42:16.887261761 +0200
@@ -30,6 +30,7 @@
#include "log.h"
#include "errorcodes.h"
+#include "util.h"
LOG_LEVEL prev_log_level = LOG_LEVEL_UNSET;
LOG_LEVEL log_level = LOG_LEVEL_UNSET;
@@ -118,7 +119,9 @@ static void log_cached_lines(void) {
for(int i = 0; i < ncachedlines; ++i) {
LOG_LEVEL level = cached_lines[i].level;
if (level == LOG_LEVEL_ERROR || level <= log_level) {
- fprintf(url, "%s: %s\n", log_level_array[level-1].log_string, cached_lines[i].message);
+ char *msg_safe = stresc(cached_lines[i].message);
+ fprintf(url, "%s: %s\n", log_level_array[level-1].log_string, msg_safe);
+ free(msg_safe);
}
free(cached_lines[i].message);
}
@@ -135,9 +138,24 @@ static void vlog_msg(LOG_LEVEL level,con
FILE* url = stderr;
if (level == LOG_LEVEL_ERROR || level <= log_level) {
- fprintf(url, "%s: ", log_level_array[level-1].log_string );
- vfprintf(url, format, ap);
- fprintf(url, "\n");
+ va_list aq;
+ va_copy(aq, ap);
+ int n = vsnprintf(NULL, 0, format, aq) + 1;
+ va_end(aq);
+
+ int size = n * sizeof(char);
+ char *msg_unsafe = malloc(size);
+ if (msg_unsafe == NULL) {
+ fprintf(url, "%s: malloc failed to allocate %d bytes of memory\n", log_level_array[LOG_LEVEL_ERROR-1].log_string, size);
+ exit(MEMORY_ALLOCATION_FAILURE);
+ }
+
+ vsnprintf(msg_unsafe, n, format, ap);
+ char *msg_safe = stresc(msg_unsafe);
+ free(msg_unsafe);
+ fprintf(url, "%s: %s\n", log_level_array[level-1].log_string, msg_safe);
+ free(msg_safe);
+
} else if (log_level == LOG_LEVEL_UNSET) {
cache_line(level, format, ap);
}
diff -up aide-0.18.6/src/report_json.c.orig aide-0.18.6/src/report_json.c
--- aide-0.18.6/src/report_json.c.orig 2025-08-19 11:35:36.084823997 +0200
+++ aide-0.18.6/src/report_json.c 2025-08-19 11:35:36.083823987 +0200
@@ -57,12 +57,53 @@ static int _escape_json_string(const cha
int n = 0;
for (i = 0; i < strlen(src); ++i) {
- if (src[i] == '\\') {
- if (escaped_string) { escaped_string[n] = '\\'; }
- n++;
+ switch(src[i]) {
+ case '\n':
+ if (escaped_string) { escaped_string[n] = '\\'; }
+ n++;
+ if (escaped_string) { escaped_string[n] = 'n'; }
+ n++;
+ break;
+ case '\t':
+ if (escaped_string) { escaped_string[n] = '\\'; }
+ n++;
+ if (escaped_string) { escaped_string[n] = 't'; }
+ n++;
+ break;
+ case '\b':
+ if (escaped_string) { escaped_string[n] = '\\'; }
+ n++;
+ if (escaped_string) { escaped_string[n] = 'b'; }
+ n++;
+ break;
+ case '\f':
+ if (escaped_string) { escaped_string[n] = '\\'; }
+ n++;
+ if (escaped_string) { escaped_string[n] = 'f'; }
+ n++;
+ break;
+ case '\r':
+ if (escaped_string) { escaped_string[n] = '\\'; }
+ n++;
+ if (escaped_string) { escaped_string[n] = 'r'; }
+ n++;
+ break;
+ case '"':
+ case '\\':
+ if (escaped_string) { escaped_string[n] = '\\'; }
+ n++;
+ if (escaped_string) { escaped_string[n] = src[i]; }
+ n++;
+ break;
+ default:
+ if (src[i] >= 0 && (src[i] < 0x1f || src[i] == 0x7f)) {
+ if (escaped_string) { snprintf(&escaped_string[n], 7, "\\u%04x", src[i]); }
+ n += 6;
+ } else {
+ if (escaped_string) { escaped_string[n] = src[i]; }
+ n++;
+ }
}
- if (escaped_string) { escaped_string[n] = src[i]; }
- n++;
}
if (escaped_string) { escaped_string[n] = '\0'; }
n++;
diff -up aide-0.18.6/src/report_plain.c.orig aide-0.18.6/src/report_plain.c
--- aide-0.18.6/src/report_plain.c.orig 2025-08-19 11:35:36.084823997 +0200
+++ aide-0.18.6/src/report_plain.c 2025-08-19 11:35:36.083823987 +0200
@@ -55,7 +55,9 @@ static char* _get_not_grouped_list_strin
static void _print_config_option(report_t *report, config_option option, const char* value) {
if (first) { first=false; }
else { report_printf(report," | "); }
- report_printf(report, "%s: %s", config_options[option].report_string, value);
+ char *value_safe = stresc(value);
+ report_printf(report, "%s: %s", config_options[option].report_string, value_safe);
+ free(value_safe);
}
static void _print_report_option(report_t *report, config_option option, const char* value) {
@@ -63,37 +65,49 @@ static void _print_report_option(report_
}
static void _print_attribute(report_t *report, db_line* oline, db_line* nline, ATTRIBUTE attribute) {
- char **ovalue = NULL;
- char **nvalue = NULL;
+ char **ovalues = NULL;
+ char **nvalues = NULL;
int onumber, nnumber, i, c;
int p = (width_details-(4 + MAX_WIDTH_DETAILS_STRING))/2;
DB_ATTR_TYPE attr = ATTR(attribute);
const char* name = attributes[attribute].details_string;
- onumber=get_attribute_values(attr, oline, &ovalue, report);
- nnumber=get_attribute_values(attr, nline, &nvalue, report);
+ onumber=get_attribute_values(attr, oline, &ovalues, report);
+ nnumber=get_attribute_values(attr, nline, &nvalues, report);
i = 0;
while (i<onumber || i<nnumber) {
- int olen = i<onumber?strlen(ovalue[i]):0;
- int nlen = i<nnumber?strlen(nvalue[i]):0;
+ char *ovalue = NULL;
+ char *nvalue = NULL;
+ int olen = 0;
+ int nlen = 0;
+ if (i<onumber){
+ ovalue = stresc(ovalues[i]);
+ olen = strlen(ovalue);
+ }
+ if (i<nnumber) {
+ nvalue = stresc(nvalues[i]);
+ nlen = strlen(nvalue);
+ }
int k = 0;
while (olen-p*k >= 0 || nlen-p*k >= 0) {
c = k*(p-1);
if (!onumber) {
- report_printf(report," %-*s%c %-*c %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p, ' ', p-1, nlen-c>0?&nvalue[i][c]:"");
+ report_printf(report," %-*s%c %-*c %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p, ' ', p-1, nlen-c>0?&nvalue[c]:"");
} else if (!nnumber) {
- report_printf(report," %-*s%c %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p-1, olen-c>0?&ovalue[i][c]:"");
+ report_printf(report," %-*s%c %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p-1, olen-c>0?&ovalue[c]:"");
} else {
- report_printf(report," %-*s%c %-*.*s| %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p, p-1, olen-c>0?&ovalue[i][c]:"", p-1, nlen-c>0?&nvalue[i][c]:"");
+ report_printf(report," %-*s%c %-*.*s| %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p, p-1, olen-c>0?&ovalue[c]:"", p-1, nlen-c>0?&nvalue[c]:"");
}
k++;
}
++i;
+ free(ovalue);
+ free(nvalue);
}
- for(i=0; i < onumber; ++i) { free(ovalue[i]); ovalue[i]=NULL; } free(ovalue); ovalue=NULL;
- for(i=0; i < nnumber; ++i) { free(nvalue[i]); nvalue[i]=NULL; } free(nvalue); nvalue=NULL;
+ for(i=0; i < onumber; ++i) { free(ovalues[i]); ovalues[i]=NULL; } free(ovalues); ovalues=NULL;
+ for(i=0; i < nnumber; ++i) { free(nvalues[i]); nvalues[i]=NULL; } free(nvalues); nvalues=NULL;
}
static void _print_database_attributes(report_t *report, db_line* db) {
@@ -136,17 +150,29 @@ static void print_report_summary_plain(r
}
static void print_line_plain(report_t* report, seltree* node) {
- if(report->summarize_changes) {
+ if(report->summarize_changes) {
+ char *filename = ((node->checked&NODE_REMOVED)?node->old_data:node->new_data)->filename;
+ char *filename_safe = stresc(filename);
char* summary = get_summarize_changes_string(report, node);
- report_printf(report, "\n%s: %s", summary, ((node->checked&NODE_REMOVED)?node->old_data:node->new_data)->filename);
+ report_printf(report, "\n%s: %s", summary, filename_safe);
free(summary); summary=NULL;
+ free(filename_safe);
} else {
if (node->checked&NODE_ADDED) {
- report_printf(report, _("\nadded: %s"),(node->new_data)->filename);
+ char *filename = (node->new_data)->filename;
+ char *filename_safe = stresc(filename);
+ report_printf(report, _("\nadded: %s"), filename_safe);
+ free(filename_safe);
} else if (node->checked&NODE_REMOVED) {
- report_printf(report, _("\nremoved: %s"),(node->old_data)->filename);
+ char *filename = (node->old_data)->filename;
+ char *filename_safe = stresc(filename);
+ report_printf(report, _("\nremoved: %s"), filename_safe);
+ free(filename_safe);
} else if (node->checked&NODE_CHANGED) {
- report_printf(report, _("\nchanged: %s"),(node->new_data)->filename);
+ char *filename = (node->new_data)->filename;
+ char *filename_safe = stresc(filename);
+ report_printf(report, _("\nchanged: %s"), filename_safe);
+ free(filename_safe);
}
}
}
@@ -154,11 +180,14 @@ static void print_line_plain(report_t* r
static void print_report_dbline_attributes_plain(report_t *report, db_line* oline, db_line* nline, DB_ATTR_TYPE report_attrs) {
if (report_attrs) {
char *file_type = get_file_type_string((nline==NULL?oline:nline)->perm);
+ db_line* line = nline==NULL?oline:nline;
report_printf(report, "\n");
if (file_type) {
report_printf(report, "%s: ", file_type);
}
- report_printf(report, "%s\n", (nline==NULL?oline:nline)->filename);
+ char *filename_safe = stresc(line->filename);
+ report_printf(report, "%s\n", filename_safe);
+ free(filename_safe);
print_dbline_attrs(report, oline, nline, report_attrs, _print_attribute);
}
@@ -195,9 +224,11 @@ static void print_report_details_plain(r
static void print_report_diff_attrs_entries_plain(report_t *report) {
for(int i = 0; i < report->num_diff_attrs_entries; ++i) {
char *str = NULL;
+ char *entry_safe = stresc(report->diff_attrs_entries[i].entry);
report_printf(report, "Entry %s in databases has different attributes: %s\n",
- report->diff_attrs_entries[i].entry,
+ entry_safe,
str= diff_attributes(report->diff_attrs_entries[i].old_attrs, report->diff_attrs_entries[i].new_attrs));
+ free(entry_safe);
free(str);
}
report->num_diff_attrs_entries = 0;
diff -up aide-0.18.6/src/util.c.orig aide-0.18.6/src/util.c
--- aide-0.18.6/src/util.c.orig 2025-08-19 11:35:36.084823997 +0200
+++ aide-0.18.6/src/util.c 2025-08-19 11:35:36.082823977 +0200
@@ -105,6 +105,41 @@ int cmpurl(url_t* u1,url_t* u2)
return RETOK;
}
+static size_t escape_str(const char *unescaped_str, char *str, size_t s) {
+ size_t n = 0;
+ size_t i = 0;
+ char c;
+ while (i < s && (c = unescaped_str[i])) {
+ if ((c >= 0 && (c < 0x1f || c == 0x7f)) ||
+ (c == '\\' && isdigit(unescaped_str[i+1])
+ && isdigit(unescaped_str[i+2])
+ && isdigit(unescaped_str[i+3])
+ ) ) {
+ if (str) { snprintf(&str[n], 5, "\\%03o", c); }
+ n += 4;
+ } else {
+ if (str) { str[n] = c; }
+ n++;
+ }
+ i++;
+ }
+ if (str) { str[n] = '\0'; }
+ n++;
+ return n;
+}
+
+char *strnesc(const char *unescaped_str, size_t s) {
+ int n = escape_str(unescaped_str, NULL, s);
+ char *str = checked_malloc(n);
+ escape_str(unescaped_str, str, s);
+ return str;
+}
+
+char *stresc(const char *unescaped_str) {
+ return strnesc(unescaped_str, strlen(unescaped_str));
+}
+
+
/* Returns 1 if the string contains unsafe characters, 0 otherwise. */
int contains_unsafe (const char *s)
{