From c07f7ae44bbcfd3fd5b980c2161fb7dd74730908 Mon Sep 17 00:00:00 2001 From: yixiangzhike Date: Fri, 15 Aug 2025 12:02:14 +0800 Subject: [PATCH] fix CVE-2025-54389 CVE-2025-54409 --- aide.spec | 10 +- backport-CVE-2025-54389.patch | 419 ++++++++++++++++++++++++++++++++++ backport-CVE-2025-54409.patch | 58 +++++ 3 files changed, 486 insertions(+), 1 deletion(-) create mode 100644 backport-CVE-2025-54389.patch create mode 100644 backport-CVE-2025-54409.patch diff --git a/aide.spec b/aide.spec index ccb0498..019f78a 100644 --- a/aide.spec +++ b/aide.spec @@ -2,7 +2,7 @@ Name: aide Version: 0.18.6 -Release: 6 +Release: 7 Summary: Advanced Intrusion Detection Environment License: GPLv2+ URL: https://sourceforge.net/projects/aide @@ -25,6 +25,8 @@ Patch1: backport-Fix-condition-for-error-message-of-failing-to-open-g.patch Patch2: backport-Fix-parsing-of-lowercase-group-names.patch Patch3: backport-Fix-concurrent-reading-of-extended-attributes-xattrs.patch Patch4: backport-Handle-SIGUSR1-only-after-config-parsing.patch +Patch5: backport-CVE-2025-54389.patch +Patch6: backport-CVE-2025-54409.patch %description AIDE (Advanced Intrusion Detection Environment, [eyd]) is a file and directory integrity checker. @@ -77,6 +79,12 @@ make check %{_mandir}/*/* %changelog +* Fri Aug 15 2025 yixiangzhike - 0.18.6-7 +- Type: CVE +- ID: CVE-2025-54389 CVE-2025-54409 +- SUG: NA +- DESC: fix CVE-2025-54389 CVE-2025-54409 + * Tue Jul 8 2025 yixiangzhike - 0.18.6-6 - Type: bugfix - ID: NA diff --git a/backport-CVE-2025-54389.patch b/backport-CVE-2025-54389.patch new file mode 100644 index 0000000..ced7718 --- /dev/null +++ b/backport-CVE-2025-54389.patch @@ -0,0 +1,419 @@ +From: Hannes von Haugwitz +Description: Fix for CVE-2025-54389 + +Reference: https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/aide/0.18.6-2ubuntu0.1/aide_0.18.6-2ubuntu0.1.debian.tar.xz + +diff --git before/doc/aide.1 after/doc/aide.1 +index c68335f..4737f73 100644 +--- before/doc/aide.1 ++++ after/doc/aide.1 +@@ -130,12 +130,25 @@ SIGUSR1 toggles the log_level between current and debug level. + .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 | 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 --git before/include/util.h after/include/util.h +index 897ac64..c22c9d6 100644 +--- before/include/util.h ++++ after/include/util.h +@@ -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 --git before/src/aide.c after/src/aide.c +index f9821f4..aa8a569 100644 +--- before/src/aide.c ++++ after/src/aide.c +@@ -282,7 +282,8 @@ static void read_param(int argc,char**argv) + 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); +diff --git before/src/gen_list.c after/src/gen_list.c +index 9561443..729dff3 100644 +--- before/src/gen_list.c ++++ after/src/gen_list.c +@@ -339,35 +339,39 @@ void print_match(char* filename, rx_rule *rule, match_result match, RESTRICTION_ + char * str; + char* attr_str; + char file_type = get_restriction_char(restriction); ++ char *filename_safe = stresc(filename); + 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); ++ str = stresc(conf->limit); ++ fprintf(stdout, "[ ] %c '%s': outside of limit '%s'\n", file_type, filename_safe, str); ++ free(str); + break; + } ++ free(filename_safe); + } + + /* +diff --git before/src/log.c after/src/log.c +index 3f741a6..ce5417c 100644 +--- before/src/log.c ++++ after/src/log.c +@@ -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,const char* format, va_list ap) { + 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); ++ size_t 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(stderr, "%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 --git before/src/report_json.c after/src/report_json.c +index 2fc77bc..47e7fe7 100644 +--- before/src/report_json.c ++++ after/src/report_json.c +@@ -29,6 +29,8 @@ + #include "report.h" + #include "seltree_struct.h" + #include "stdbool.h" ++#include "string.h" ++#include "stdio.h" + #include "url.h" + + #define JSON_FMT_ARRAY_BEGIN "%*c\"%s\": [\n" +@@ -57,12 +59,53 @@ static int _escape_json_string(const char *src, char *escaped_string) { + 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%04d", 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++; +@@ -302,9 +345,11 @@ static void print_report_diff_attrs_entries_json(report_t *report) { + report_printf(report, JSON_FMT_OBJECT_BEGIN, 2, ' ', "different_attributes"); + for(int i = 0; i < report->num_diff_attrs_entries; ++i) { + char *str = NULL; ++ char *escaped_filename = _get_escaped_json_string(report->diff_attrs_entries[i].entry); + report_printf(report, i+1num_diff_attrs_entries?JSON_FMT_STRING_COMMA:JSON_FMT_STRING_LAST , 4, ' ', +- report->diff_attrs_entries[i].entry, ++ escaped_filename, + str= diff_attributes(report->diff_attrs_entries[i].old_attrs, report->diff_attrs_entries[i].new_attrs)); ++ free(escaped_filename); + free(str); + } + report->num_diff_attrs_entries = 0; +diff --git before/src/report_plain.c after/src/report_plain.c +index c87fc72..7d8a746 100644 +--- before/src/report_plain.c ++++ after/src/report_plain.c +@@ -55,7 +55,9 @@ static char* _get_not_grouped_list_string(report_t *report) { + 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_t *report, config_option option, const c + } + + 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= 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,19 +150,21 @@ static void print_report_summary_plain(report_t *report) { + } + + static void print_line_plain(report_t* report, seltree* node) { ++ char *filename_safe = stresc(((node->checked&NODE_REMOVED)?node->old_data:node->new_data)->filename); + if(report->summarize_changes) { + 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; + } else { + if (node->checked&NODE_ADDED) { +- report_printf(report, _("\nadded: %s"),(node->new_data)->filename); ++ report_printf(report, _("\nadded: %s"),filename_safe); + } else if (node->checked&NODE_REMOVED) { +- report_printf(report, _("\nremoved: %s"),(node->old_data)->filename); ++ report_printf(report, _("\nremoved: %s"),filename_safe); + } else if (node->checked&NODE_CHANGED) { +- report_printf(report, _("\nchanged: %s"),(node->new_data)->filename); ++ report_printf(report, _("\nchanged: %s"),filename_safe); + } + } ++ free(filename_safe); + } + + static void print_report_dbline_attributes_plain(report_t *report, db_line* oline, db_line* nline, DB_ATTR_TYPE report_attrs) { +@@ -158,7 +174,9 @@ static void print_report_dbline_attributes_plain(report_t *report, db_line* olin + if (file_type) { + report_printf(report, "%s: ", file_type); + } +- report_printf(report, "%s\n", (nline==NULL?oline:nline)->filename); ++ char *filename_safe = stresc((nline==NULL?oline:nline)->filename); ++ report_printf(report, "%s\n", filename_safe); ++ free(filename_safe); + + print_dbline_attrs(report, oline, nline, report_attrs, _print_attribute); + } +@@ -195,9 +213,11 @@ static void print_report_details_plain(report_t *report, seltree* node) { + 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 --git before/src/util.c after/src/util.c +index 87f6801..f5c5e60 100644 +--- before/src/util.c ++++ after/src/util.c +@@ -105,6 +105,40 @@ 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) + { diff --git a/backport-CVE-2025-54409.patch b/backport-CVE-2025-54409.patch new file mode 100644 index 0000000..3e37446 --- /dev/null +++ b/backport-CVE-2025-54409.patch @@ -0,0 +1,58 @@ +From: Hannes von Haugwitz +Description: Fix for CVE-2025-54409 + +Reference: https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/aide/0.18.6-2ubuntu0.1/aide_0.18.6-2ubuntu0.1.debian.tar.xz + +diff --git before/src/db.c after/src/db.c +index dfcc973..4a16236 100644 +--- before/src/db.c ++++ after/src/db.c +@@ -351,17 +351,27 @@ db_line* db_char2line(char** ss, database* db){ + num = 0; + while (num < line->xattrs->num) + { +- byte *val = NULL; +- size_t vsz = 0; +- + tval = strtok(NULL, ","); + line->xattrs->ents[num].key = db_readchar(checked_strdup(tval)); + tval = strtok(NULL, ","); +- val = base64tobyte(tval, strlen(tval), &vsz); +- line->xattrs->ents[num].val = val; +- line->xattrs->ents[num].vsz = vsz; +- +- ++num; ++ if (strcmp(tval,"0") != 0) { ++ line->xattrs->ents[num].val = decode_base64(tval, strlen(tval), &line->xattrs->ents[num].vsz); ++ } else { ++ line->xattrs->ents[num].val = checked_strdup(""); ++ line->xattrs->ents[num].vsz = 0; ++ } ++ if (line->xattrs->ents[num].val == NULL) { ++ LOG_DB_FORMAT_LINE(LOG_LEVEL_WARNING, "error while reading xattrs for '%s' from database (discarding extended attributes)", line->filename) ++ for (int j = num; j >= 0 ; --j) { ++ free(line->xattrs->ents[j].key); ++ line->xattrs->ents[j].key = NULL; ++ free(line->xattrs->ents[j].val); ++ line->xattrs->ents[j].val = NULL; ++ } ++ line->xattrs->num = 0; ++ } else { ++ ++num; ++ } + } + } + #endif +diff --git before/src/util.c after/src/util.c +index f5c5e60..49ac5da 100644 +--- before/src/util.c ++++ after/src/util.c +@@ -45,7 +45,7 @@ + #include "util.h" + #include "errorcodes.h" + +-#define URL_UNSAFE " <>\"#%{}|\\^~[]`@:\033'" ++#define URL_UNSAFE " <>\"#%{}|\\^~[]`@:\033'," + #define ISPRINT(c) (isascii(c) && isprint(c)) + + const char* btoa(bool b) { -- Gitee