Compare commits

..

No commits in common. "c8" and "c9-beta" have entirely different histories.
c8 ... c9-beta

22 changed files with 1779 additions and 3145 deletions

View File

@ -1 +1,2 @@
b97f65bb12701a42baa2cce45b41ed6367a70734 SOURCES/aide-0.16.tar.gz
8f5a43df4256133b66902fd412b2d03a9e770435 SOURCES/aide-0.19.2.tar.gz
ff960034941c33f56690697d06bba03c2b9f702c SOURCES/gpgkey-aide.gpg

3
.gitignore vendored
View File

@ -1 +1,2 @@
SOURCES/aide-0.16.tar.gz
SOURCES/aide-0.19.2.tar.gz
SOURCES/gpgkey-aide.gpg

View File

@ -20,21 +20,28 @@
compared with the AIDE database. Prior to running a check manually,
ensure that the AIDE binary and database have not been modified
without your knowledge.
Caution!
With the default setup, an AIDE check is not run periodically as a
cron job. It cannot be guaranteed that the AIDE binaries, config
file and database are intact. It is not recommended that you run
automated AIDE checks without verifying AIDE yourself frequently.
In addition to that, AIDE does not implement any password or
encryption protection for its own files.
It is up to you how to put a file integrity checker to good effect
and how to set up automated checks if you think it adds a level of
safety (e.g. detecting failed/incomplete compromises or unauthorized
modification of special files). On a compromised system, the
intruder could disable the automated check. Or he could replace the
AIDE binary, config file and database easily when they are not
located on read-only media.
6) To schedule daily integrity checks, enable the systemd timer:
systemctl enable --now aide-check.timer
View results with: journalctl -u aide-check
Check timer status with: systemctl status aide-check.timer
The timer runs daily with low CPU/IO priority to minimize impact
on production workloads. It is disabled by default — only enable
it after initializing the database (steps 2-4).
Caution!
It cannot be guaranteed that the AIDE binaries, config file and
database are intact. It is not recommended that you run automated
AIDE checks without verifying AIDE yourself frequently. In addition
to that, AIDE does not implement any password or encryption
protection for its own files.
It is up to you how to put a file integrity checker to good effect.
On a compromised system, the intruder could disable the automated
check. Or he could replace the AIDE binary, config file and database
easily when they are not located on read-only media.

View File

@ -1,496 +0,0 @@
diff -up ./doc/aide.conf.5.in.syslog_format ./doc/aide.conf.5.in
--- ./doc/aide.conf.5.in.syslog_format 2016-07-25 22:58:12.000000000 +0200
+++ ./doc/aide.conf.5.in 2018-09-27 19:09:09.697371212 +0200
@@ -57,6 +57,25 @@ inclusive. This parameter can only be gi
occurrence is used. If \-\-verbose or \-V is used then the value from that
is used. The default is 5. If verbosity is 20 then additional report
output is written when doing \-\-check, \-\-update or \-\-compare.
+.IP "syslog_format"
+Valid values are yes,true,no and false. This option enables new syslog format
+which is suitable for logging. Every change is logged as one simple line. This option
+changes verbose level to 0 and prints everything that was changed. It is suggested
+to use this option with "report_url=syslog:...". Default value is "false/no".
+Maximum size of message is 1KB which is limitation of syslog call. If message is
+greater than limit, message will be truncated.
+Option summarize_changes has no impact for this format.
+.nf
+.eo
+
+Output always starts with:
+"AIDE found differences between database and filesystem!!"
+And it is followed by summary:
+summary;total_number_of_files=1000;added_files=0;removed_files=0;changed_files=1
+And finally there are logs about changes:
+dir=/usr/sbin;Mtime_old=0000-00-00 00:00:00;Mtime_new=0000-00-00 00:00:00;...
+.ec
+.fi
.IP "report_url"
The url that the output is written to. There can be multiple instances
of this parameter. Output is written to all of them. The default is
diff -up ./include/db_config.h.syslog_format ./include/db_config.h
--- ./include/db_config.h.syslog_format 2016-07-25 22:56:55.000000000 +0200
+++ ./include/db_config.h 2018-09-27 19:09:09.697371212 +0200
@@ -311,6 +311,7 @@ typedef struct db_config {
FILE* db_out;
int config_check;
+ int syslog_format;
struct md_container *mdc_in;
struct md_container *mdc_out;
diff -up ./src/aide.c.syslog_format ./src/aide.c
--- ./src/aide.c.syslog_format 2018-09-27 19:09:09.695371197 +0200
+++ ./src/aide.c 2018-09-27 19:09:09.698371220 +0200
@@ -283,6 +283,7 @@ static void setdefaults_before_config()
}
/* Setting some defaults */
+ conf->syslog_format=0;
conf->report_db=0;
conf->tree=NULL;
conf->config_check=0;
@@ -495,6 +496,10 @@ static void setdefaults_after_config()
if(conf->verbose_level==-1){
conf->verbose_level=5;
}
+ if(conf->syslog_format==1){
+ conf->verbose_level=0;
+ }
+
}
diff -up ./src/compare_db.c.syslog_format ./src/compare_db.c
--- ./src/compare_db.c.syslog_format 2016-07-25 22:56:55.000000000 +0200
+++ ./src/compare_db.c 2018-09-27 19:09:09.698371220 +0200
@@ -110,7 +110,7 @@ const DB_ATTR_TYPE details_attributes[]
#endif
};
-const char* details_string[] = { _("File type") , _("Lname"), _("Size"), _("Size (>)"), _("Bcount"), _("Perm"), _("Uid"), _("Gid"), _("Atime"), _("Mtime"), _("Ctime"), _("Inode"), _("Linkcount"), _("MD5"), _("SHA1"), _("RMD160"), _("TIGER"), _("SHA256"), _("SHA512")
+const char* details_string[] = { _("File type") , _("Lname"), _("Size"), _("Size"), _("Bcount"), _("Perm"), _("Uid"), _("Gid"), _("Atime"), _("Mtime"), _("Ctime"), _("Inode"), _("Linkcount"), _("MD5"), _("SHA1"), _("RMD160"), _("TIGER"), _("SHA256"), _("SHA512")
#ifdef WITH_MHASH
, _("CRC32"), _("HAVAL"), _("GOST"), _("CRC32B"), _("WHIRLPOOL")
#endif
@@ -269,12 +269,19 @@ static int xattrs2array(xattrs_type* xat
if ((len == xattrs->ents[num - 1].vsz) || ((len == (xattrs->ents[num - 1].vsz - 1)) && !val[len])) {
length = 8 + width + strlen(xattrs->ents[num - 1].key) + strlen(val);
(*values)[num]=malloc(length *sizeof(char));
- snprintf((*values)[num], length , "[%.*zd] %s = %s", width, num, xattrs->ents[num - 1].key, val);
+
+ char * fmt = "[%.*zd] %s = %s";
+ if (conf->syslog_format) fmt = "[%.*zd]%s=%s"; // its smaller so it has to be enough space allocated.
+ snprintf((*values)[num], length , fmt, width, num, xattrs->ents[num - 1].key, val);
+
} else {
val = encode_base64(xattrs->ents[num - 1].val, xattrs->ents[num - 1].vsz);
length = 10 + width + strlen(xattrs->ents[num - 1].key) + strlen(val);
(*values)[num]=malloc( length *sizeof(char));
- snprintf((*values)[num], length , "[%.*zd] %s <=> %s", width, num, xattrs->ents[num - 1].key, val);
+
+ char * fmt = "[%.*zd] %s <=> %s";
+ if (conf->syslog_format) fmt = "[%.*zd]%s<=>%s"; // its smaller so it has to be enough space allocated.
+ snprintf((*values)[num], length , fmt, width, num, xattrs->ents[num - 1].key, val);
free(val);
}
}
@@ -302,6 +309,26 @@ static int acl2array(acl_type* acl, char
}
if (acl->acl_a || acl->acl_d) {
int j, k, i;
+ if (conf->syslog_format) {
+ *values = malloc(2 * sizeof(char*));
+
+ char *A, *D = "<NONE>";
+
+ if (acl->acl_a) { A = acl->acl_a; }
+ if (acl->acl_d) { D = acl->acl_d; }
+
+ (*values)[0] = (char*) malloc(strlen(A) + 3); // "A:" and \0
+ snprintf((*values)[0], strlen(A) + 3, "A:%s", A);
+
+ (*values)[1] = (char*) malloc(strlen(D) + 3); // "D:" and \0
+ snprintf((*values)[1], strlen(D) + 3, "D:%s", D);
+
+ i = 0; while ( (*values)[0][i] ) { if ( (*values)[0][i]=='\n') { (*values)[0][i] = ' '; } i++; }
+ i = 0; while ( (*values)[1][i] ) { if ( (*values)[1][i]=='\n') { (*values)[1][i] = ' '; } i++; }
+
+ return 2;
+ }
+
if (acl->acl_a) { i = 0; while (acl->acl_a[i]) { if (acl->acl_a[i++]=='\n') { n++; } } }
if (acl->acl_d) { i = 0; while (acl->acl_d[i]) { if (acl->acl_d[i++]=='\n') { n++; } } }
*values = malloc(n * sizeof(char*));
@@ -338,25 +365,25 @@ static char* e2fsattrs2string(unsigned l
static char* get_file_type_string(mode_t mode) {
switch (mode & S_IFMT) {
- case S_IFREG: return _("File");
- case S_IFDIR: return _("Directory");
+ case S_IFREG: return conf->syslog_format ? "file" : _("File");
+ case S_IFDIR: return conf->syslog_format ? "dir" : _("Directory");
#ifdef S_IFIFO
- case S_IFIFO: return _("FIFO");
+ case S_IFIFO: return conf->syslog_format ? "fifo" : _("FIFO");
#endif
- case S_IFLNK: return _("Link");
- case S_IFBLK: return _("Block device");
- case S_IFCHR: return _("Character device");
+ case S_IFLNK: return conf->syslog_format ? "link" : _("Link");
+ case S_IFBLK: return conf->syslog_format ? "blockd" : _("Block device");
+ case S_IFCHR: return conf->syslog_format ? "chard" : _("Character device");
#ifdef S_IFSOCK
- case S_IFSOCK: return _("Socket");
+ case S_IFSOCK: return conf->syslog_format ? "socket" : _("Socket");
#endif
#ifdef S_IFDOOR
- case S_IFDOOR: return _("Door");
+ case S_IFDOOR: return conf->syslog_format ? "door" : _("Door");
#endif
#ifdef S_IFPORT
- case S_IFPORT: return _("Port");
+ case S_IFPORT: return conf->syslog_format ? "port" : _("Port");
#endif
case 0: return NULL;
- default: return _("Unknown file type");
+ default: return conf->syslog_format ? "unknown" : _("Unknown file type");
}
}
@@ -554,6 +581,51 @@ static void print_dbline_attributes(db_l
}
}
+
+static void print_dbline_attributes_syslog(db_line* oline, db_line* nline, DB_ATTR_TYPE
+ changed_attrs, DB_ATTR_TYPE force_attrs) {
+ char **ovalue, **nvalue;
+ int onumber, nnumber, i, j;
+ int length = sizeof(details_attributes)/sizeof(DB_ATTR_TYPE);
+ DB_ATTR_TYPE attrs;
+ char *file_type = get_file_type_string((nline==NULL?oline:nline)->perm);
+ if (file_type) {
+ error(0,"%s=", file_type);
+ }
+ error(0,"%s", (nline==NULL?oline:nline)->filename);
+ attrs=force_attrs|(~(ignored_changed_attrs)&changed_attrs);
+ for (j=0; j < length; ++j) {
+ if (details_attributes[j]&attrs) {
+ onumber=get_attribute_values(details_attributes[j], oline, &ovalue);
+ nnumber=get_attribute_values(details_attributes[j], nline, &nvalue);
+
+ if (details_attributes[j] == DB_ACL || details_attributes[j] == DB_XATTRS) {
+
+ error(0, ";%s_old=|", details_string[j]);
+
+ for (i = 0 ; i < onumber ; i++) {
+ error(0, "%s|", ovalue[i]);
+ }
+
+ error(0, ";%s_new=|", details_string[j]);
+
+ for (i = 0 ; i < nnumber ; i++) {
+ error(0, "%s|", nvalue[i]);
+ }
+
+ } else {
+
+ error(0, ";%s_old=%s;%s_new=%s", details_string[j], *ovalue, details_string[j], *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;
+ }
+ }
+ error(0, "\n");
+}
+
static void print_attributes_added_node(db_line* line) {
print_dbline_attributes(NULL, line, 0, line->attr);
}
@@ -562,6 +634,26 @@ static void print_attributes_removed_nod
print_dbline_attributes(line, NULL, 0, line->attr);
}
+static void print_attributes_added_node_syslog(db_line* line) {
+
+ char *file_type = get_file_type_string(line->perm);
+ if (file_type) {
+ error(0,"%s=", file_type);
+ }
+ error(0,"%s; added\n", line->filename);
+
+}
+
+static void print_attributes_removed_node_syslog(db_line* line) {
+
+ char *file_type = get_file_type_string(line->perm);
+ if (file_type) {
+ error(0,"%s=", file_type);
+ }
+ error(0,"%s; removed\n", line->filename);
+
+}
+
static void terse_report(seltree* node) {
list* r=NULL;
if ((node->checked&(DB_OLD|DB_NEW)) != 0) {
@@ -626,6 +718,26 @@ static void print_report_details(seltree
}
}
+static void print_syslog_format(seltree* node) {
+ list* r=NULL;
+
+ if (node->checked&NODE_CHANGED) {
+ print_dbline_attributes_syslog(node->old_data, node->new_data, node->changed_attrs, forced_attrs);
+ }
+
+ if (node->checked&NODE_ADDED) {
+ print_attributes_added_node_syslog(node->new_data);
+ }
+
+ if (node->checked&NODE_REMOVED) {
+ print_attributes_removed_node_syslog(node->old_data);
+ }
+
+ for(r=node->childs;r;r=r->next){
+ print_syslog_format((seltree*)r->data);
+ }
+}
+
static void print_report_header() {
char *time;
int first = 1;
@@ -747,39 +859,53 @@ int gen_report(seltree* node) {
send_audit_report();
#endif
if ((nadd|nrem|nchg) > 0 || conf->report_quiet == 0) {
- print_report_header();
- if(conf->action&(DO_COMPARE|DO_DIFF) || (conf->action&DO_INIT && conf->report_detailed_init) ) {
- if (conf->grouped) {
- if (nadd) {
- error(2,(char*)report_top_format,_("Added entries"));
- print_report_list(node, NODE_ADDED);
- }
- if (nrem) {
- error(2,(char*)report_top_format,_("Removed entries"));
- print_report_list(node, NODE_REMOVED);
- }
- if (nchg) {
- error(2,(char*)report_top_format,_("Changed entries"));
- print_report_list(node, NODE_CHANGED);
- }
- } else if (nadd || nrem || nchg) {
- if (nadd && nrem && nchg) { error(2,(char*)report_top_format,_("Added, removed and changed entries")); }
- else if (nadd && nrem) { error(2,(char*)report_top_format,_("Added and removed entries")); }
- else if (nadd && nchg) { error(2,(char*)report_top_format,_("Added and changed entries")); }
- else if (nrem && nchg) { error(2,(char*)report_top_format,_("Removed and changed entries")); }
- else if (nadd) { error(2,(char*)report_top_format,_("Added entries")); }
- else if (nrem) { error(2,(char*)report_top_format,_("Removed entries")); }
- else if (nchg) { error(2,(char*)report_top_format,_("Changed entries")); }
- print_report_list(node, NODE_ADDED|NODE_REMOVED|NODE_CHANGED);
- }
- if (nadd || nrem || nchg) {
- error(nchg?5:7,(char*)report_top_format,_("Detailed information about changes"));
- print_report_details(node);
- }
- }
- print_report_databases();
- conf->end_time=time(&(conf->end_time));
- print_report_footer();
+
+ if (!conf->syslog_format) {
+ print_report_header();
+ }
+
+ if(conf->action&(DO_COMPARE|DO_DIFF) || (conf->action&DO_INIT && conf->report_detailed_init) ) {
+ if (!conf->syslog_format && conf->grouped) {
+ if (nadd) {
+ error(2,(char*)report_top_format,_("Added entries"));
+ print_report_list(node, NODE_ADDED);
+ }
+ if (nrem) {
+ error(2,(char*)report_top_format,_("Removed entries"));
+ print_report_list(node, NODE_REMOVED);
+ }
+ if (nchg) {
+ error(2,(char*)report_top_format,_("Changed entries"));
+ print_report_list(node, NODE_CHANGED);
+ }
+ } else if (!conf->syslog_format && ( nadd || nrem || nchg ) ) {
+ if (nadd && nrem && nchg) { error(2,(char*)report_top_format,_("Added, removed and changed entries")); }
+ else if (nadd && nrem) { error(2,(char*)report_top_format,_("Added and removed entries")); }
+ else if (nadd && nchg) { error(2,(char*)report_top_format,_("Added and changed entries")); }
+ else if (nrem && nchg) { error(2,(char*)report_top_format,_("Removed and changed entries")); }
+ else if (nadd) { error(2,(char*)report_top_format,_("Added entries")); }
+ else if (nrem) { error(2,(char*)report_top_format,_("Removed entries")); }
+ else if (nchg) { error(2,(char*)report_top_format,_("Changed entries")); }
+ print_report_list(node, NODE_ADDED|NODE_REMOVED|NODE_CHANGED);
+ }
+ if (nadd || nrem || nchg) {
+ if (!conf->syslog_format) {
+ error(nchg?5:7,(char*)report_top_format,_("Detailed information about changes"));
+ print_report_details(node);
+ } else {
+ /* Syslog Format */
+ error(0, "AIDE found differences between database and filesystem!!\n");
+ error(0, "summary;total_number_of_files=%ld;added_files=%ld;"
+ "removed_files=%ld;changed_files=%ld\n",ntotal,nadd,nrem,nchg);
+ print_syslog_format(node);
+ }
+ }
+ }
+ if (!conf->syslog_format) {
+ print_report_databases();
+ conf->end_time=time(&(conf->end_time));
+ print_report_footer();
+ }
}
return conf->action&(DO_COMPARE|DO_DIFF) ? (nadd!=0)*1+(nrem!=0)*2+(nchg!=0)*4 : 0;
diff -up ./src/conf_lex.l.syslog_format ./src/conf_lex.l
--- ./src/conf_lex.l.syslog_format 2016-07-25 22:56:55.000000000 +0200
+++ ./src/conf_lex.l 2018-09-27 19:09:09.698371220 +0200
@@ -401,6 +401,12 @@ int var_in_conflval=0;
return (TROOT_PREFIX);
}
+^[\t\ ]*"syslog_format"{E} {
+ error(230,"%li:syslog_format =\n",conf_lineno);
+ BEGIN CONFVALHUNT;
+ return (SYSLOG_FORMAT);
+}
+
^[\t\ ]*"recstop"{E} {
error(230,"%li:recstop =\n",conf_lineno);
BEGIN CONFVALHUNT;
diff -up ./src/conf_yacc.y.syslog_format ./src/conf_yacc.y
--- ./src/conf_yacc.y.syslog_format 2016-07-25 22:56:55.000000000 +0200
+++ ./src/conf_yacc.y 2018-09-27 19:09:09.699371228 +0200
@@ -89,6 +89,7 @@ extern long conf_lineno;
%token TREPORT_URL
%token TGZIPDBOUT
%token TROOT_PREFIX
+%token SYSLOG_FORMAT
%token TUMASK
%token TTRUE
%token TFALSE
@@ -160,7 +161,7 @@ line : rule | equrule | negrule | define
| ifdefstmt | ifndefstmt | ifhoststmt | ifnhoststmt
| groupdef | db_in | db_out | db_new | db_attrs | verbose | report_detailed_init | config_version
| database_add_metadata | report | gzipdbout | root_prefix | report_base16 | report_quiet
- | report_ignore_e2fsattrs | recursion_stopper | warn_dead_symlinks | grouped
+ | report_ignore_e2fsattrs | syslogformat | recursion_stopper | warn_dead_symlinks | grouped
| summarize_changes | acl_no_symlink_follow | beginconfigstmt | endconfigstmt
| TEOF {
newlinelastinconfig=1;
@@ -408,6 +409,15 @@ conf->gzip_dbout=0;
#endif
} ;
+syslogformat : SYSLOG_FORMAT TTRUE {
+conf->syslog_format=1;
+} |
+ SYSLOG_FORMAT TFALSE {
+conf->syslog_format=0;
+} ;
+
+
+
recursion_stopper : TRECSTOP TSTRING {
/* FIXME implement me */
diff -up ./src/error.c.syslog_format ./src/error.c
--- ./src/error.c.syslog_format 2016-07-25 22:56:55.000000000 +0200
+++ ./src/error.c 2018-09-27 19:13:40.312416750 +0200
@@ -38,6 +38,9 @@
/*for locale support*/
#include "util.h"
+#define MAX_BUFFER_SIZE 1024
+static char syslog_buffer[MAX_BUFFER_SIZE+1];
+
int cmp_url(url_t* url1,url_t* url2){
return ((url1->type==url2->type)&&(strcmp(url1->value,url2->value)==0));
@@ -48,7 +51,9 @@ int error_init(url_t* url,int initial)
{
list* r=NULL;
FILE* fh=NULL;
- int sfac;
+ int sfac;
+
+ memset(syslog_buffer, 0, MAX_BUFFER_SIZE+1);
if (url->type==url_database) {
conf->report_db++;
@@ -163,13 +168,24 @@ void error(int errorlevel,char* error_ms
}
#ifdef HAVE_SYSLOG
if(conf->initial_report_url->type==url_syslog){
-#ifdef HAVE_VSYSLOG
- vsyslog(SYSLOG_PRIORITY,error_msg,ap);
-#else
- char buf[1024];
- vsnprintf(buf,1024,error_msg,ap);
- syslog(SYSLOG_PRIORITY,"%s",buf);
-#endif
+
+ char buff[MAX_BUFFER_SIZE+1];
+ vsnprintf(buff,MAX_BUFFER_SIZE,error_msg,ap);
+ size_t buff_len = strlen(buff);
+
+ char result_buff[MAX_BUFFER_SIZE+1];
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-truncation"
+ snprintf(result_buff, MAX_BUFFER_SIZE, "%s%s", syslog_buffer, buff);
+#pragma GCC diagnostic pop
+
+ if(buff[buff_len-1] == '\n'){
+ syslog(SYSLOG_PRIORITY,"%s",result_buff);
+ memset(syslog_buffer, 0, MAX_BUFFER_SIZE+1);
+ } else {
+ memcpy(syslog_buffer, result_buff, MAX_BUFFER_SIZE);
+ }
+
va_end(ap);
return;
}
@@ -181,17 +197,25 @@ void error(int errorlevel,char* error_ms
#ifdef HAVE_SYSLOG
if (conf->report_syslog!=0) {
-#ifdef HAVE_VSYSLOG
- va_start(ap,error_msg);
- vsyslog(SYSLOG_PRIORITY,error_msg,ap);
- va_end(ap);
-#else
- char buf[1024];
- va_start(ap,error_msg);
- vsnprintf(buf,1024,error_msg,ap);
+ va_start(ap, error_msg);
+
+ char buff[MAX_BUFFER_SIZE+1];
+ vsnprintf(buff,MAX_BUFFER_SIZE,error_msg,ap);
+ size_t buff_len = strlen(buff);
+
+ char result_buff[MAX_BUFFER_SIZE+1];
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-truncation"
+ snprintf(result_buff, MAX_BUFFER_SIZE, "%s%s", syslog_buffer, buff);
+#pragma GCC diagnostic pop
+
+ if(buff[buff_len-1] == '\n'){
+ syslog(SYSLOG_PRIORITY,"%s",result_buff);
+ memset(syslog_buffer, 0, MAX_BUFFER_SIZE+1);
+ } else {
+ memcpy(syslog_buffer, result_buff, MAX_BUFFER_SIZE);
+ }
va_end(ap);
- syslog(SYSLOG_PRIORITY,"%s",buf);
-#endif
}
#endif

View File

@ -1,123 +0,0 @@
diff --git a/include/base64.h b/include/base64.h
index 0ff7116..381ef5d 100644
--- a/include/base64.h
+++ b/include/base64.h
@@ -36,7 +36,6 @@
#include <assert.h>
#include "types.h"
-#define B64_BUF 16384
#define FAIL -1
#define SKIP -2
diff --git a/src/base64.c b/src/base64.c
index fd01bac..1b0f301 100644
--- a/src/base64.c
+++ b/src/base64.c
@@ -85,11 +85,9 @@ FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL
};
/* Returns NULL on error */
-/* FIXME Possible buffer overflow on outputs larger than B64_BUF */
char* encode_base64(byte* src,size_t ssize)
{
char* outbuf;
- char* retbuf;
int pos;
int i, l, left;
unsigned long triple;
@@ -101,7 +99,10 @@ char* encode_base64(byte* src,size_t ssize)
error(240,"\n");
return NULL;
}
- outbuf = (char *)malloc(sizeof(char)*B64_BUF);
+
+ /* length of encoded base64 string (padded) */
+ size_t length = sizeof(char)* ((ssize + 2) / 3) * 4;
+ outbuf = (char *)malloc(length + 1);
/* Initialize working pointers */
inb = src;
@@ -162,20 +163,14 @@ char* encode_base64(byte* src,size_t ssize)
inb++;
}
- /* outbuf is not completely used so we use retbuf */
- retbuf=(char*)malloc(sizeof(char)*(pos+1));
- memcpy(retbuf,outbuf,pos);
- retbuf[pos]='\0';
- free(outbuf);
+ outbuf[pos]='\0';
- return retbuf;
+ return outbuf;
}
-/* FIXME Possible buffer overflow on outputs larger than B64_BUF */
byte* decode_base64(char* src,size_t ssize, size_t *ret_len)
{
byte* outbuf;
- byte* retbuf;
char* inb;
int i;
int l;
@@ -188,10 +183,18 @@ byte* decode_base64(char* src,size_t ssize, size_t *ret_len)
if (!ssize||src==NULL)
return NULL;
+ /* exit on unpadded input */
+ if (ssize % 4) {
+ error(3, "decode_base64: '%s' has invalid length (missing padding characters?)", src);
+ return NULL;
+ }
+
+ /* calculate length of decoded string, substract padding chars if any (ssize is >= 4) */
+ size_t length = sizeof(byte) * ((ssize / 4) * 3)- (src[ssize-1] == '=') - (src[ssize-2] == '=');
/* Initialize working pointers */
inb = src;
- outbuf = (byte *)malloc(sizeof(byte)*B64_BUF);
+ outbuf = (byte *)malloc(length + 1);
l = 0;
triple = 0;
@@ -243,15 +246,11 @@ byte* decode_base64(char* src,size_t ssize, size_t *ret_len)
inb++;
}
- retbuf=(byte*)malloc(sizeof(byte)*(pos+1));
- memcpy(retbuf,outbuf,pos);
- retbuf[pos]='\0';
-
- free(outbuf);
+ outbuf[pos]='\0';
if (ret_len) *ret_len = pos;
- return retbuf;
+ return outbuf;
}
size_t length_base64(char* src,size_t ssize)
diff --git a/src/db.c b/src/db.c
index 858240d..62c4faa 100644
--- a/src/db.c
+++ b/src/db.c
@@ -664,13 +664,15 @@ db_line* db_char2line(char** ss,int db){
time_t base64totime_t(char* s){
+ if(strcmp(s,"0")==0){
+ return 0;
+ }
byte* b=decode_base64(s,strlen(s),NULL);
char* endp;
- if (b==NULL||strcmp(s,"0")==0) {
+ if (b==NULL) {
/* Should we print error here? */
- free(b);
return 0;
} else {

View File

@ -1,220 +0,0 @@
diff -up aide-0.16/src/db_disk.c.orig aide-0.16/src/db_disk.c
--- aide-0.16/src/db_disk.c.orig 2025-08-21 09:58:21.271581589 +0200
+++ aide-0.16/src/db_disk.c 2025-08-21 10:01:26.310573141 +0200
@@ -139,7 +139,11 @@ void add_child (db_line * fil)
int i;
struct seltree *new_r;
- error (255, "Adding child %s\n", fil->filename);
+ {
+ char *fname_safe = stresc(fil->filename);
+ error (255, "Adding child %s\n", fname_safe);
+ free(fname_safe);
+ }
new_r = get_seltree_node (r, fil->filename);
if (new_r != NULL) {
@@ -182,9 +186,13 @@ static int get_file_status(char *filenam
if(sres == -1){
char* er = strerror(errno);
if (er == NULL) {
- error(0,"get_file_status: lstat() failed for %s. strerror() failed for %i\n", filename, errno);
+ char *filename_safe = stresc(filename);
+ error(0,"get_file_status: lstat() failed for %s. strerror() failed for %i\n", filename_safe, errno);
+ free(filename_safe);
} else {
- error(0,"get_file_status: lstat() failed for %s: %s\n", filename, er);
+ char *filename_safe = stresc(filename);
+ error(0,"get_file_status: lstat() failed for %s: %s\n", filename_safe, er);
+ free(filename_safe);
}
}
return sres;
@@ -220,7 +228,11 @@ db_line *db_readline_disk ()
error (240, "%s attr=%llu\n", &fullname[conf->root_prefix_length], attr);
if (fil != NULL) {
- error (240, "%s attr=%llu\n", fil->filename, fil->attr);
+ {
+ char *fname_safe = stresc(fil->filename);
+ error (240, "%s attr=%llu\n", fname_safe, fil->attr);
+ free(fname_safe);
+ }
return fil;
}
}
@@ -269,7 +281,11 @@ recursion:
error (240, "%s attr=%llu\n", &fullname[conf->root_prefix_length], attr);
if (fil != NULL) {
- error (240, "%s attr=%llu\n", fil->filename, fil->attr);
+ {
+ char *fname_safe = stresc(fil->filename);
+ error (240, "%s attr=%llu\n", fname_safe, fil->attr);
+ free(fname_safe);
+ }
} else {
/*
Something went wrong during read process ->
diff -up aide-0.16/src/gen_list.c.orig aide-0.16/src/gen_list.c
--- aide-0.16/src/gen_list.c.orig 2025-08-21 09:58:21.273581610 +0200
+++ aide-0.16/src/gen_list.c 2025-08-21 10:04:29.190502666 +0200
@@ -37,6 +37,7 @@
#include "list.h"
#include "gen_list.h"
#include "seltree.h"
+#include "util.h"
#include "db.h"
#include "db_config.h"
#include "commandconf.h"
@@ -993,16 +994,28 @@ int check_rxtree(char* filename,seltree*
if(conf->limit!=NULL) {
retval=pcre_exec(conf->limit_crx, NULL, filename, strlen(filename), 0, PCRE_PARTIAL_SOFT, NULL, 0);
if (retval >= 0) {
- error(220, "check_rxtree: %s does match limit: %s\n", filename, conf->limit);
+ char *fname_safe = stresc(filename);
+ char *limit_safe = conf->limit?stresc(conf->limit):NULL;
+ error(220, "check_rxtree: %s does match limit: %s\n", fname_safe, limit_safe?limit_safe:"");
+ free(fname_safe);
+ free(limit_safe);
} else if (retval == PCRE_ERROR_PARTIAL) {
- error(220, "check_rxtree: %s does PARTIAL match limit: %s\n", filename, conf->limit);
+ char *fname_safe = stresc(filename);
+ char *limit_safe = conf->limit?stresc(conf->limit):NULL;
+ error(220, "check_rxtree: %s does PARTIAL match limit: %s\n", fname_safe, limit_safe?limit_safe:"");
if(S_ISDIR(perm) && get_seltree_node(tree,filename)==NULL){
- error(220, "check_rxtree: creating new seltree node for '%s'\n", filename);
+ error(220, "check_rxtree: creating new seltree node for '%s'\n", fname_safe);
new_seltree_node(tree,filename,0,NULL);
}
+ free(fname_safe);
+ free(limit_safe);
return -1;
} else {
- error(220, "check_rxtree: %s does NOT match limit: %s\n", filename, conf->limit);
+ char *fname_safe = stresc(filename);
+ char *limit_safe = conf->limit?stresc(conf->limit):NULL;
+ error(220, "check_rxtree: %s does NOT match limit: %s\n", fname_safe, limit_safe?limit_safe:"");
+ free(fname_safe);
+ free(limit_safe);
return -2;
}
}
@@ -1039,13 +1052,25 @@ db_line* get_file_attrs(char* filename,D
} else {
if(fs->st_atime>cur_time){
- error(CLOCK_SKEW,_("%s atime in future\n"),filename);
+ {
+ char *fname_safe = stresc(filename);
+ error(CLOCK_SKEW,_("%s atime in future\n"),fname_safe);
+ free(fname_safe);
+ }
}
if(fs->st_mtime>cur_time){
- error(CLOCK_SKEW,_("%s mtime in future\n"),filename);
+ {
+ char *fname_safe = stresc(filename);
+ error(CLOCK_SKEW,_("%s mtime in future\n"),fname_safe);
+ free(fname_safe);
+ }
}
if(fs->st_ctime>cur_time){
- error(CLOCK_SKEW,_("%s ctime in future\n"),filename);
+ {
+ char *fname_safe = stresc(filename);
+ error(CLOCK_SKEW,_("%s ctime in future\n"),fname_safe);
+ free(fname_safe);
+ }
}
}
@@ -1220,7 +1245,11 @@ void hsymlnk(db_line* line) {
int sres;
sres=AIDE_STAT_FUNC(line->fullpath,&fs);
if (sres!=0 && sres!=EACCES) {
- error(4,"Dead symlink detected at %s\n",line->fullpath);
+ {
+ char *fp_safe = stresc(line->fullpath);
+ error(4,"Dead symlink detected at %s\n",fp_safe);
+ free(fp_safe);
+ }
}
if(!(line->attr&DB_RDEV))
fs.st_rdev=0;
diff -up aide-0.16/src/util.c.orig aide-0.16/src/util.c
--- aide-0.16/src/util.c.orig 2025-08-21 09:58:21.272581600 +0200
+++ aide-0.16/src/util.c 2025-08-21 10:07:50.157894133 +0200
@@ -104,9 +104,11 @@ url_t* parse_url(char* val)
r+=2;
for(i=0;r[0]!='/'&&r[0]!='\0';r++,i++);
if(r[0]=='\0'){
- error(0,"Invalid file-URL,no path after hostname: file:%s\n",t);
+ char *t_safe = stresc(t);
+ error(0,"Invalid file-URL,no path after hostname: file:%s\n",t_safe);
+ free(t_safe);
free(hostname);
- return NULL;
+ return NULL;
}
u->value=strdup(r);
r[0]='\0';
@@ -118,9 +120,11 @@ url_t* parse_url(char* val)
free(hostname);
break;
} else {
- error(0,"Invalid file-URL, cannot use hostname other than localhost or %s: file:%s\n",hostname,u->value);
- free(hostname);
- return NULL;
+ char *value_safe = stresc(u->value);
+ error(0,"Invalid file-URL, cannot use hostname other than localhost or %s: file:%s\n",hostname,value_safe);
+ free(value_safe);
+ free(hostname);
+ return NULL;
}
break;
@@ -150,6 +154,43 @@ url_t* parse_url(char* val)
return u;
}
+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 = malloc(n);
+ if (str == NULL) {
+ error(0, "malloc: failed to allocate %d bytes of memory\n", n);
+ exit(1);
+ }
+ 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)
{

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +0,0 @@
--- ./src/do_md.c 2018-03-19 05:10:19.994957024 -0400
+++ ./src/do_md.c 2018-03-19 05:19:05.829957024 -0400
@@ -135,8 +135,13 @@
continue;
while (!bingo && (data = elf_getdata (scn, data)) != NULL) {
- int maxndx = data->d_size / shdr.sh_entsize;
+ int maxndx;
int ndx;
+
+ if (shdr.sh_entsize != 0)
+ maxndx = data->d_size / shdr.sh_entsize;
+ else
+ continue;
for (ndx = 0; ndx < maxndx; ++ndx) {
(void) gelf_getdyn (data, ndx, &dyn);

View File

@ -1,153 +0,0 @@
diff -up ./include/md.h.crypto ./include/md.h
--- ./include/md.h.crypto 2016-07-25 22:56:55.000000000 +0200
+++ ./include/md.h 2018-08-29 15:00:30.827491299 +0200
@@ -149,6 +149,7 @@ int init_md(struct md_container*);
int update_md(struct md_container*,void*,ssize_t);
int close_md(struct md_container*);
void md2line(struct md_container*,struct db_line*);
+DB_ATTR_TYPE get_available_crypto();
#endif /*_MD_H_INCLUDED*/
diff -up ./src/aide.c.crypto ./src/aide.c
--- ./src/aide.c.crypto 2018-08-29 15:00:30.825491309 +0200
+++ ./src/aide.c 2018-08-29 15:00:30.827491299 +0200
@@ -349,7 +349,7 @@ static void setdefaults_before_config()
conf->db_attrs = 0;
#if defined(WITH_MHASH) || defined(WITH_GCRYPT)
- conf->db_attrs |= DB_MD5|DB_TIGER|DB_HAVAL|DB_CRC32|DB_SHA1|DB_RMD160|DB_SHA256|DB_SHA512;
+ conf->db_attrs |= get_available_crypto();
#ifdef WITH_MHASH
conf->db_attrs |= DB_GOST;
#ifdef HAVE_MHASH_WHIRLPOOL
diff -up ./src/md.c.crypto ./src/md.c
--- ./src/md.c.crypto 2018-08-29 15:00:30.823491319 +0200
+++ ./src/md.c 2018-08-29 15:02:28.013903479 +0200
@@ -78,6 +78,49 @@ DB_ATTR_TYPE hash_gcrypt2attr(int i) {
return r;
}
+const char * hash_gcrypt2str(int i) {
+ char * r = "?";
+#ifdef WITH_GCRYPT
+ switch (i) {
+ case GCRY_MD_MD5: {
+ r = "MD5";
+ break;
+ }
+ case GCRY_MD_SHA1: {
+ r = "SHA1";
+ break;
+ }
+ case GCRY_MD_RMD160: {
+ r = "RMD160";
+ break;
+ }
+ case GCRY_MD_TIGER: {
+ r = "TIGER";
+ break;
+ }
+ case GCRY_MD_HAVAL: {
+ r = "HAVAL";
+ break;
+ }
+ case GCRY_MD_SHA256: {
+ r = "SHA256";
+ break;
+ }
+ case GCRY_MD_SHA512: {
+ r = "SHA512";
+ break;
+ }
+ case GCRY_MD_CRC32: {
+ r = "CRC32";
+ break;
+ }
+ default:
+ break;
+ }
+#endif
+ return r;
+}
+
DB_ATTR_TYPE hash_mhash2attr(int i) {
DB_ATTR_TYPE r=0;
#ifdef WITH_MHASH
@@ -163,6 +206,44 @@ DB_ATTR_TYPE hash_mhash2attr(int i) {
Initialise md_container according it's todo_attr field
*/
+DB_ATTR_TYPE get_available_crypto() {
+
+ DB_ATTR_TYPE ret = 0;
+
+/*
+ * This function is usually called before config processing
+ * and default verbose level is 5
+ */
+#define lvl 255
+
+ error(lvl, "get_available_crypto called\n");
+
+#ifdef WITH_GCRYPT
+
+ /*
+ * some initialization for FIPS
+ */
+ gcry_check_version(NULL);
+ error(lvl, "Found algos:");
+
+ for(int i=0;i<=HASH_GCRYPT_COUNT;i++) {
+
+ if ( (hash_gcrypt2attr(i) & HASH_USE_GCRYPT) == 0 )
+ continue;
+
+ if (gcry_md_algo_info(i, GCRYCTL_TEST_ALGO, NULL, NULL) == 0) {
+ ret |= hash_gcrypt2attr(i);
+ error(lvl, " %s", hash_gcrypt2str(i));
+ }
+ }
+ error(lvl, "\n");
+
+#endif
+
+ error(lvl, "get_available_crypto_returned with %lld\n", ret);
+ return ret;
+}
+
int init_md(struct md_container* md) {
int i;
@@ -201,18 +282,27 @@ int init_md(struct md_container* md) {
}
#endif
#ifdef WITH_GCRYPT
- if(gcry_md_open(&md->mdh,0,GCRY_MD_FLAG_SECURE)!=GPG_ERR_NO_ERROR){
+ if(gcry_md_open(&md->mdh,0,GCRY_MD_FLAG_SECURE)!=GPG_ERR_NO_ERROR){
error(0,"gcrypt_md_open failed\n");
exit(IO_ERROR);
}
for(i=0;i<=HASH_GCRYPT_COUNT;i++) {
+
+
if (((hash_gcrypt2attr(i)&HASH_USE_GCRYPT)&md->todo_attr)!=0) {
- DB_ATTR_TYPE h=hash_gcrypt2attr(i);
- error(255,"inserting %llu\n",h);
+
+ DB_ATTR_TYPE h=hash_gcrypt2attr(i);
+
+ if (gcry_md_algo_info(i, GCRYCTL_TEST_ALGO, NULL, NULL) != 0) {
+ error(0,"Algo %s is not available\n", hash_gcrypt2str(i));
+ exit(-1);
+ }
+
+ error(255,"inserting %llu\n",h);
if(gcry_md_enable(md->mdh,i)==GPG_ERR_NO_ERROR){
md->calc_attr|=h;
} else {
- error(0,"gcry_md_enable %i failed",i);
+ error(0,"gcry_md_enable %i failed\n",i);
md->todo_attr&=~h;
}
}

View File

@ -1,103 +0,0 @@
diff -up ./src/aide.c.orig ./aide-0.16b1/src/aide.c
--- ./src/aide.c.orig 2016-07-12 11:10:08.013158385 +0200
+++ ./src/aide.c 2016-07-12 11:30:54.867833064 +0200
@@ -511,9 +511,28 @@ int main(int argc,char**argv)
#endif
umask(0177);
init_sighandler();
-
setdefaults_before_config();
+#if WITH_GCRYPT
+ error(255,"Gcrypt library initialization\n");
+ /*
+ * Initialize libgcrypt as per
+ * http://www.gnupg.org/documentation/manuals/gcrypt/Initializing-the-library.html
+ *
+ *
+ */
+ gcry_control(GCRYCTL_SET_ENFORCED_FIPS_FLAG, 0);
+ gcry_control(GCRYCTL_INIT_SECMEM, 1);
+
+ if(!gcry_check_version(GCRYPT_VERSION)) {
+ error(0,"libgcrypt version mismatch\n");
+ exit(VERSION_MISMATCH_ERROR);
+ }
+
+ gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
+#endif /* WITH_GCRYPT */
+
+
if(read_param(argc,argv)==RETFAIL){
error(0, _("Invalid argument\n") );
exit(INVALID_ARGUMENT_ERROR);
@@ -646,6 +665,9 @@ int main(int argc,char**argv)
}
#endif
}
+#ifdef WITH_GCRYPT
+ gcry_control(GCRYCTL_TERM_SECMEM, 0);
+#endif /* WITH_GCRYPT */
return RETOK;
}
const char* aide_key_3=CONFHMACKEY_03;
diff -up ./src/md.c.orig ./aide-0.16b1/src/md.c
--- ./src/md.c.orig 2016-04-15 23:30:16.000000000 +0200
+++ ./src/md.c 2016-07-12 11:35:04.007675329 +0200
@@ -201,14 +201,7 @@ int init_md(struct md_container* md) {
}
#endif
#ifdef WITH_GCRYPT
- error(255,"Gcrypt library initialization\n");
- if(!gcry_check_version(GCRYPT_VERSION)) {
- error(0,"libgcrypt version mismatch\n");
- exit(VERSION_MISMATCH_ERROR);
- }
- gcry_control(GCRYCTL_DISABLE_SECMEM, 0);
- gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
- if(gcry_md_open(&md->mdh,0,0)!=GPG_ERR_NO_ERROR){
+ if(gcry_md_open(&md->mdh,0,GCRY_MD_FLAG_SECURE)!=GPG_ERR_NO_ERROR){
error(0,"gcrypt_md_open failed\n");
exit(IO_ERROR);
}
@@ -299,7 +292,7 @@ int close_md(struct md_container* md) {
/*. There might be more hashes in the library. Add those here.. */
- gcry_md_reset(md->mdh);
+ gcry_md_close(md->mdh);
#endif
#ifdef WITH_MHASH
diff -up ./src/util.c.orig ./aide-0.16b1/src/util.c
--- ./src/util.c.orig 2016-07-12 11:39:17.023437355 +0200
+++ ./src/util.c 2016-07-12 11:39:51.618721157 +0200
@@ -519,28 +519,5 @@ int syslog_facility_lookup(char *s)
return(AIDE_SYSLOG_FACILITY);
}
-/* We need these dummy stubs to fool the linker into believing that
- we do not need them at link time */
-
-void* dlopen(char*filename,int flag)
-{
- return NULL;
-}
-
-void* dlsym(void*handle,char*symbol)
-{
- return NULL;
-}
-
-void* dlclose(void*handle)
-{
- return NULL;
-}
-
-const char* dlerror(void)
-{
- return NULL;
-}
-
const char* aide_key_2=CONFHMACKEY_02;
const char* db_key_2=DBHMACKEY_02;

View File

@ -1,15 +0,0 @@
diff -up ./doc/aide.1.in.orig ./doc/aide.1.in
--- ./doc/aide.1.in.orig 2016-07-12 16:10:01.724595895 +0200
+++ ./doc/aide.1.in 2016-07-12 16:06:21.968639822 +0200
@@ -103,9 +103,9 @@ echo <encoded_checksum> | base64 \-d | h
.SH FILES
.IP \fB@sysconfdir@/aide.conf\fR
Default aide configuration file.
-.IP \fB@sysconfdir@/aide.db\fR
+.IP \fB@localstatedir@/lib/aide/aide.db\fR
Default aide database.
-.IP \fB@sysconfdir@/aide.db.new\fR
+.IP \fB@localstatedir@/lib/aide/aide.db.new\fR
Default aide output database.
.SH SEE ALSO
.BR aide.conf (5)

View File

@ -0,0 +1,657 @@
From f3e62eb87e0a0e9c6fd43c933670447c8ab0517a Mon Sep 17 00:00:00 2001
From: Cropi <alakatos@redhat.com>
Date: Thu, 28 May 2026 14:50:34 +0200
Subject: [PATCH] conf, report: add syslog_format config option
Re-implement the syslog_format option that existed as a Red Hat downstream
patch against aide 0.16 but was dropped during the rebase to 0.19.2.
Customers upgrading from RHEL 9.7 (aide 0.16) to RHEL 9.8 (aide 0.19.2)
received a fatal parse error on startup if their aide.conf contained
'syslog_format = true' (RHEL-178317).
The option is implemented as a new REPORT_FORMAT_SYSLOG value in the
existing report format module system, rather than as a standalone boolean,
which fits the 0.19.2 architecture cleanly.
syslog_format = yes/true is equivalent to report_format = syslog
Both spellings are accepted; last-write wins.
When active, the standard multi-line report is replaced with a compact
semicolon-delimited format where every file event is one line:
AIDE found differences between database and filesystem!!
summary;total_number_of_files=N;added_files=N;removed_files=N;changed_files=N
file=/usr/sbin/sshd;Mtime_old=...;Mtime_new=...;SHA256_old=...;SHA256_new=...
dir=/etc/cron.d; added
file=/usr/bin/old; removed
Each line is emitted with a single report_printf() call so that when used
with report_url=syslog:<FACILITY> exactly one syslog message is produced
per file event.
The module implements its own unconditional tree walker (not gated on
report_level) so added and removed entries are always included, matching
the original patch behaviour. ACL and xattr values are formatted directly
from db_line fields rather than through get_attribute_values() to avoid
embedded newlines breaking the single-line invariant. The original patch's
uninitialized 'char *A' in the ACL path is fixed.
Signed-off-by: Cropi <alakatos@redhat.com>
---
Makefile.am | 1 +
doc/aide.conf.5 | 28 ++++
include/conf_ast.h | 1 +
include/db_config.h | 1 +
include/report.h | 3 +
include/report_syslog.h | 28 ++++
src/aide.c | 1 +
src/conf_ast.c | 1 +
src/conf_eval.c | 8 +
src/conf_lex.l | 7 +
src/report.c | 16 +-
src/report_syslog.c | 338 ++++++++++++++++++++++++++++++++++++++++
12 files changed, 432 insertions(+), 1 deletion(-)
create mode 100644 include/report_syslog.h
create mode 100644 src/report_syslog.c
diff --git a/Makefile.am b/Makefile.am
index f78a96c..356b983 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -31,6 +31,7 @@ aide_SOURCES = src/aide.c include/aide.h \
include/report.h src/report.c \
include/report_plain.h src/report_plain.c \
include/report_json.h src/report_json.c \
+ include/report_syslog.h src/report_syslog.c \
include/conf_ast.h src/conf_ast.c \
include/conf_eval.h src/conf_eval.c \
include/conf_lex.h src/conf_lex.l \
diff --git a/doc/aide.conf.5 b/doc/aide.conf.5
index 4ad9f3e..5366be4 100644
--- a/doc/aide.conf.5
+++ b/doc/aide.conf.5
@@ -266,6 +266,34 @@ The report format to use. The available report formats are as follows:
\fBjson\fP: Print report in json machine-readable format.
.RE
+.IP "syslog_format (type: bool, default: \fBno\fR)"
+Valid values are \fByes\fR, \fBtrue\fR, \fBno\fR and \fBfalse\fR.
+
+When enabled, the standard multi-line report is replaced with a compact
+semicolon-delimited format where every file event is emitted as a single line.
+This ensures that when used with \fBreport_url=syslog:\fILOG_FACILITY\fR, exactly
+one syslog message is produced per changed, added, or removed file.
+
+Output starts with a header line when differences are found:
+.nf
+AIDE found differences between database and filesystem!!
+.fi
+Followed by a summary line:
+.nf
+summary;total_number_of_files=\fIN\fP;added_files=\fIN\fP;removed_files=\fIN\fP;changed_files=\fIN\fP
+.fi
+Then one line per changed, added, or removed entry:
+.nf
+file=/usr/sbin/sshd;Mtime_old=...;Mtime_new=...;SHA256_old=...;SHA256_new=...
+dir=/etc/cron.d; added
+file=/usr/bin/old; removed
+.fi
+
+The maximum size of a single syslog message depends on the syslog daemon
+(typically 1\(en8\ KB). Lines exceeding this limit will be silently truncated
+by the syslog daemon. This is not controlled by AIDE.
+
+The \fBreport_summarize_changes\fR option has no effect in this format.
.IP "report_base16 (type: bool, default: \fBfalse\fR, added in AIDE v0.17)"
Base16 encode the checksums in the report. The default is to
diff --git a/include/conf_ast.h b/include/conf_ast.h
index 8892a05..424f734 100644
--- a/include/conf_ast.h
+++ b/include/conf_ast.h
@@ -53,6 +53,7 @@ typedef enum config_option {
REPORT_FORMAT_OPTION,
LIMIT_CMDLINE_OPTION,
NUM_WORKERS,
+ SYSLOG_FORMAT_OPTION,
} config_option;
typedef struct {
diff --git a/include/db_config.h b/include/db_config.h
index 4173a4b..363631e 100644
--- a/include/db_config.h
+++ b/include/db_config.h
@@ -133,6 +133,7 @@ typedef struct db_config {
int report_detailed_init;
int report_base16;
int report_quiet;
+ int syslog_format;
bool report_append;
DB_ATTR_TYPE report_ignore_added_attrs;
diff --git a/include/report.h b/include/report.h
index 2ec3539..b2efa55 100644
--- a/include/report.h
+++ b/include/report.h
@@ -48,6 +48,7 @@ typedef enum {
REPORT_FORMAT_UNKNOWN = 0,
REPORT_FORMAT_PLAIN = 1,
REPORT_FORMAT_JSON = 2,
+ REPORT_FORMAT_SYSLOG = 3,
} REPORT_FORMAT;
extern const ATTRIBUTE report_attrs_order[];
@@ -138,6 +139,8 @@ typedef struct report_format_module {
void (*print_report_summary)(report_t*);
} report_format_module;
+DB_ATTR_TYPE get_report_attributes(seltree*, report_t*);
+
char* get_file_type_string(mode_t);
char* get_summarize_changes_string(report_t*, seltree*);
char* get_summary_string(report_t*);
diff --git a/include/report_syslog.h b/include/report_syslog.h
new file mode 100644
index 0000000..4da9ae4
--- /dev/null
+++ b/include/report_syslog.h
@@ -0,0 +1,28 @@
+/*
+ * AIDE (Advanced Intrusion Detection Environment)
+ *
+ * Copyright (C) 2025 Hannes von Haugwitz
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _REPORT_SYSLOG_H_INCLUDED
+#define _REPORT_SYSLOG_H_INCLUDED
+
+#include "report.h"
+
+extern report_format_module report_module_syslog;
+
+#endif
diff --git a/src/aide.c b/src/aide.c
index 6f728a9..f9a9cd4 100644
--- a/src/aide.c
+++ b/src/aide.c
@@ -439,6 +439,7 @@ static void setdefaults_before_config(void)
conf->report_detailed_init=0;
conf->report_base16=0;
conf->report_quiet=0;
+ conf->syslog_format=0;
conf->report_append=false;
conf->report_ignore_added_attrs = 0;
conf->report_ignore_removed_attrs = 0;
diff --git a/src/conf_ast.c b/src/conf_ast.c
index 366bc3f..7b66aed 100644
--- a/src/conf_ast.c
+++ b/src/conf_ast.c
@@ -58,6 +58,7 @@ config_option_t config_options[] = {
{ REPORT_FORMAT_OPTION, NULL, NULL },
{ LIMIT_CMDLINE_OPTION, "limit", "Limit" },
{ NUM_WORKERS, NULL, NULL },
+ { SYSLOG_FORMAT_OPTION, NULL, NULL },
};
static ast* new_ast_node(void) {
diff --git a/src/conf_eval.c b/src/conf_eval.c
index 5774ce6..bb39610 100644
--- a/src/conf_eval.c
+++ b/src/conf_eval.c
@@ -264,6 +264,9 @@ static void eval_config_statement(config_option_statement statement, int linenum
REPORT_FORMAT report_format = get_report_format(str);
if (report_format != REPORT_FORMAT_UNKNOWN) {
conf->report_format = report_format;
+ for (list *l = conf->report_urls; l; l = l->next) {
+ ((report_t *)l->data)->format = report_format;
+ }
LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_CONFIG, "set 'report_format' option to '%s' (raw: %d)", str, report_format)
} else {
LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "invalid report format: '%s'", str);
@@ -315,6 +318,11 @@ static void eval_config_statement(config_option_statement statement, int linenum
LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_NOTICE, "'num_workers' option already set (ignore new value '%s')", str)
}
break;
+ case SYSLOG_FORMAT_OPTION:
+ b = string_expression_to_bool(statement.e, linenumber, filename, linebuf);
+ conf->syslog_format = b;
+ LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_CONFIG, "set 'syslog_format' to '%s'", btoa(b))
+ break;
}
}
diff --git a/src/conf_lex.l b/src/conf_lex.l
index 877a125..1a9654f 100644
--- a/src/conf_lex.l
+++ b/src/conf_lex.l
@@ -362,6 +362,13 @@ LOG_LEVEL lex_log_level = LOG_LEVEL_DEBUG;
BEGIN (STRINGEQHUNT);
return (CONFIGOPTION);
}
+<CONFIG>"syslog_format" {
+ LOG_LEX_TOKEN(lex_log_level, CONFIGOPTION (SYSLOG_FORMAT_OPTION), conftext)
+ conflval.option = SYSLOG_FORMAT_OPTION;
+ BEGIN (STRINGEQHUNT);
+ return (CONFIGOPTION);
+}
+
<CONFIG>"report_level" {
LOG_LEX_TOKEN(lex_log_level, CONFIGOPTION (REPORT_LEVEL_OPTION), conftext)
diff --git a/src/report.c b/src/report.c
index 9e4d0d0..f87754c 100644
--- a/src/report.c
+++ b/src/report.c
@@ -59,6 +59,7 @@
#include "report.h"
#include "report_plain.h"
#include "report_json.h"
+#include "report_syslog.h"
/*for locale support*/
#include "locale-aide.h"
/*for locale support*/
@@ -146,6 +147,7 @@ struct report_format {
static struct report_format report_format_array[] = {
{ REPORT_FORMAT_PLAIN, "plain" },
{ REPORT_FORMAT_JSON, "json" },
+ { REPORT_FORMAT_SYSLOG, "syslog" },
{ REPORT_FORMAT_UNKNOWN, NULL }
};
@@ -509,6 +511,15 @@ bool init_report_urls(void) {
}
}
+ }
+ /* syslog_format is a downstream-only option that must win over report_format
+ * regardless of declaration order in the config file. Enforce it here,
+ * after the entire config AST has been evaluated, so no subsequent
+ * report_format setting can accidentally override the user's intent. */
+ if (conf->syslog_format) {
+ for (l=conf->report_urls; l; l=l->next) {
+ ((report_t *)l->data)->format = REPORT_FORMAT_SYSLOG;
+ }
}
return true;
}
@@ -677,7 +688,7 @@ char* get_summarize_changes_string(report_t* report, seltree* node) {
-static DB_ATTR_TYPE get_report_attributes(seltree* node, report_t *report) {
+DB_ATTR_TYPE get_report_attributes(seltree* node, report_t *report) {
db_line* oline = node->old_data;
db_line* nline = node->new_data;
DB_ATTR_TYPE attrs = node->changed_attrs;
@@ -966,6 +977,9 @@ int gen_report(seltree* node) {
case REPORT_FORMAT_JSON:
print_report(report, node, report_module_json);
break;
+ case REPORT_FORMAT_SYSLOG:
+ print_report(report, node, report_module_syslog);
+ break;
case REPORT_FORMAT_UNKNOWN:
/* skip unknown report format */
break;
diff --git a/src/report_syslog.c b/src/report_syslog.c
new file mode 100644
index 0000000..920e927
--- /dev/null
+++ b/src/report_syslog.c
@@ -0,0 +1,338 @@
+/*
+ * AIDE (Advanced Intrusion Detection Environment)
+ *
+ * Copyright (C) 2025 Hannes von Haugwitz
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "aide.h"
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include "attributes.h"
+#include "base64.h"
+#include "db.h"
+#include "db_line.h"
+#include "hashsum.h"
+#include "report.h"
+#include "report_syslog.h"
+#include "seltree.h"
+#include "seltree_struct.h"
+#include "tree.h"
+#include "util.h"
+
+/* Returns the compact syslog file-type prefix, or NULL when mode is unknown
+ * (mode & S_IFMT == 0), in which case callers omit the "type=" prefix. */
+static const char *get_syslog_type_prefix(mode_t mode) {
+ switch (mode & S_IFMT) {
+ case S_IFREG: return "file";
+ case S_IFDIR: return "dir";
+ case S_IFLNK: return "link";
+ case S_IFBLK: return "blockd";
+ case S_IFCHR: return "chard";
+#ifdef S_IFIFO
+ case S_IFIFO: return "fifo";
+#endif
+#ifdef S_IFSOCK
+ case S_IFSOCK: return "socket";
+#endif
+ case 0: return NULL;
+ default: return "unknown";
+ }
+}
+
+/* Returns the key name for attribute a. attr_sizeg is the only exception:
+ * its details_string is "Size (>)" which contains '>', so we substitute
+ * "Size" to match the original patch's explicit details_string[] change. */
+static const char *get_attr_key(ATTRIBUTE a) {
+ if (a == attr_sizeg) {
+ return "Size";
+ }
+ return attributes[a].details_string;
+}
+
+#ifdef WITH_XATTR
+
+#define SYSLOG_PRINTABLE_XATTR_VALS \
+ "0123456789" \
+ "abcdefghijklmnopqrstuvwxyz" \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ ".-_:;,[]{}<>()!@#$%^&*|\\/?~"
+
+/* Appends one side's xattr values to stream in compact syslog format.
+ * Format: ;XAttrs_<side>=|[1]key=val|[2]key=val| */
+static void build_xattrs_compact(FILE *stream, db_line *line, const char *side) {
+ xattrs_type *xattrs = line ? line->xattrs : NULL;
+
+ if (!xattrs || xattrs->num == 0) {
+ fprintf(stream, ";XAttrs_%s=|num=0|", side);
+ return;
+ }
+
+ fprintf(stream, ";XAttrs_%s=|num=%zu|", side, xattrs->num);
+
+ for (size_t i = 0; i < xattrs->num; i++) {
+ const char *key = xattrs->ents[i].key;
+ const char *val = (const char *)xattrs->ents[i].val;
+ size_t vsz = xattrs->ents[i].vsz;
+
+ /* Check printability (replicates xstrnspn logic from report.c). */
+ size_t plen = 0;
+ while (plen < vsz && strchr(SYSLOG_PRINTABLE_XATTR_VALS, val[plen]))
+ plen++;
+ bool printable = (plen == vsz) || (plen == vsz - 1 && val[plen] == '\0');
+
+ if (printable) {
+ fprintf(stream, "[%zu]%s=%s|", i + 1, key, val);
+ } else {
+ char *b64 = encode_base64((byte *)xattrs->ents[i].val, vsz);
+ fprintf(stream, "[%zu]%s<=>%s|", i + 1, key, b64 ? b64 : "");
+ free(b64);
+ }
+ }
+}
+#endif /* WITH_XATTR */
+
+#ifdef WITH_POSIX_ACL
+/* Appends one side's ACL to stream in compact syslog format.
+ * Format: ;ACL_<side>=A:<acl_a_newlines_as_spaces>|D:<acl_d_newlines_as_spaces>
+ *
+ * Both A and D are initialized to "<NONE>" before the conditionals, fixing
+ * the uninitialized-pointer bug in the original 0.16 patch. */
+static void build_acl_compact(FILE *stream, db_line *line, const char *side) {
+ acl_type *acl = line ? line->acl : NULL;
+
+ const char *A = "<NONE>";
+ const char *D = "<NONE>";
+ if (acl) {
+ if (acl->acl_a) { A = acl->acl_a; }
+ if (acl->acl_d) { D = acl->acl_d; }
+ }
+
+ /* Write A component, replacing newlines with spaces. */
+ fprintf(stream, ";ACL_%s=A:", side);
+ for (const char *p = A; *p; p++) {
+ fputc(*p == '\n' ? ' ' : *p, stream);
+ }
+
+ /* Write D component, replacing newlines with spaces. */
+ fprintf(stream, "|D:");
+ for (const char *p = D; *p; p++) {
+ fputc(*p == '\n' ? ' ' : *p, stream);
+ }
+}
+#endif /* WITH_POSIX_ACL */
+
+/* Assembles a complete syslog line for one file event into *out.
+ *
+ * Invariant: this function never calls report_printf(). The caller emits
+ * the result with exactly one report_printf() call to avoid fragmenting
+ * syslog messages (each report_printf to url_syslog calls vsyslog() once).
+ *
+ * Cases:
+ * oline != NULL && nline != NULL → changed entry
+ * oline == NULL → added entry
+ * nline == NULL → removed entry
+ *
+ * *out is heap-allocated by open_memstream; caller must free() it. */
+static void build_syslog_line(report_t *report, db_line *oline, db_line *nline,
+ DB_ATTR_TYPE attrs, char **out) {
+ db_line *ref = nline ? nline : oline;
+ const char *type = get_syslog_type_prefix(ref->perm);
+
+ char *buf = NULL;
+ size_t bufsz = 0;
+ FILE *stream = open_memstream(&buf, &bufsz);
+
+ /* Write "type=path" prefix. */
+ if (type) {
+ fprintf(stream, "%s=%s", type, ref->filename);
+ } else {
+ fprintf(stream, "%s", ref->filename);
+ }
+
+ if (oline && nline) {
+ /* Changed entry: emit only differing attributes. */
+ for (int j = 0; j < report_attrs_order_length; j++) {
+ ATTRIBUTE a = report_attrs_order[j];
+
+ switch (a) {
+ case attr_allhashsums:
+ /* Expand to each compiled-in hash, mirroring print_dbline_attrs(). */
+ for (int i = 0; i < num_hashes; i++) {
+ if (!(ATTR(hashsums[i].attribute) & attrs)) { continue; }
+ const char *key = get_attr_key(hashsums[i].attribute);
+ if (!key) { continue; }
+ char **oval = NULL, **nval = NULL;
+ get_attribute_values(ATTR(hashsums[i].attribute), oline, &oval, report);
+ get_attribute_values(ATTR(hashsums[i].attribute), nline, &nval, report);
+ fprintf(stream, ";%s_old=%s;%s_new=%s",
+ key, oval ? oval[0] : "", key, nval ? nval[0] : "");
+ if (oval) { free(oval[0]); free(oval); }
+ if (nval) { free(nval[0]); free(nval); }
+ }
+ break;
+
+ case attr_size:
+ /* attr_size and attr_sizeg share this slot in report_attrs_order. */
+ if (ATTR(attr_size) & attrs) {
+ const char *key = get_attr_key(attr_size);
+ if (key) {
+ char **oval = NULL, **nval = NULL;
+ get_attribute_values(ATTR(attr_size), oline, &oval, report);
+ get_attribute_values(ATTR(attr_size), nline, &nval, report);
+ fprintf(stream, ";%s_old=%s;%s_new=%s",
+ key, oval ? oval[0] : "", key, nval ? nval[0] : "");
+ if (oval) { free(oval[0]); free(oval); }
+ if (nval) { free(nval[0]); free(nval); }
+ }
+ }
+ if (ATTR(attr_sizeg) & attrs) {
+ const char *key = get_attr_key(attr_sizeg); /* returns "Size" */
+ if (key) {
+ char **oval = NULL, **nval = NULL;
+ get_attribute_values(ATTR(attr_sizeg), oline, &oval, report);
+ get_attribute_values(ATTR(attr_sizeg), nline, &nval, report);
+ fprintf(stream, ";%s_old=%s;%s_new=%s",
+ key, oval ? oval[0] : "", key, nval ? nval[0] : "");
+ if (oval) { free(oval[0]); free(oval); }
+ if (nval) { free(nval[0]); free(nval); }
+ }
+ }
+ break;
+
+ default:
+ if (!(ATTR(a) & attrs)) { break; }
+#ifdef WITH_XATTR
+ if (a == attr_xattrs) {
+ build_xattrs_compact(stream, oline, "old");
+ build_xattrs_compact(stream, nline, "new");
+ break;
+ }
+#endif
+#ifdef WITH_POSIX_ACL
+ if (a == attr_acl) {
+ build_acl_compact(stream, oline, "old");
+ build_acl_compact(stream, nline, "new");
+ break;
+ }
+#endif
+ {
+ const char *key = get_attr_key(a);
+ if (!key) { break; }
+ char **oval = NULL, **nval = NULL;
+ get_attribute_values(ATTR(a), oline, &oval, report);
+ get_attribute_values(ATTR(a), nline, &nval, report);
+ fprintf(stream, ";%s_old=%s;%s_new=%s",
+ key, oval ? oval[0] : "", key, nval ? nval[0] : "");
+ if (oval) { free(oval[0]); free(oval); }
+ if (nval) { free(nval[0]); free(nval); }
+ }
+ break;
+ }
+ }
+ } else if (!oline) {
+ fprintf(stream, "; added");
+ } else {
+ fprintf(stream, "; removed");
+ }
+
+ fclose(stream);
+ *out = buf;
+}
+
+/* Emits exactly one syslog line for a file event. */
+static void emit_syslog_entry(report_t *report, db_line *oline, db_line *nline,
+ DB_ATTR_TYPE attrs) {
+ char *line = NULL;
+ build_syslog_line(report, oline, nline, attrs, &line);
+ report_printf(report, "%s\n", line);
+ free(line);
+}
+
+/* Unconditional tree walker — does not gate added/removed on report->level,
+ * matching the original patch's print_syslog_format() behavior. */
+static void syslog_walk_tree(report_t *report, seltree *node) {
+ pthread_rwlock_rdlock(&node->rwlock);
+
+ if (node->checked & NODE_CHANGED) {
+ emit_syslog_entry(report, node->old_data, node->new_data,
+ get_report_attributes(node, report));
+ }
+ if (node->checked & NODE_ADDED) {
+ emit_syslog_entry(report, NULL, node->new_data,
+ node->new_data->attr & ~report->ignore_added_attrs);
+ }
+ if (node->checked & NODE_REMOVED) {
+ emit_syslog_entry(report, node->old_data, NULL,
+ node->old_data->attr & ~report->ignore_removed_attrs);
+ }
+
+ for (tree_node *x = tree_walk_first(node->children); x != NULL; x = tree_walk_next(x)) {
+ syslog_walk_tree(report, tree_get_data(x));
+ }
+
+ pthread_rwlock_unlock(&node->rwlock);
+}
+
+/* ── Module callbacks ─────────────────────────────────────────────────── */
+
+static void noop_header(report_t *report) { (void)report; }
+static void noop_footer(report_t *report) { (void)report; }
+static void noop_databases(report_t *report) { (void)report; }
+static void noop_config_options(report_t *report) { (void)report; }
+static void noop_report_options(report_t *report) { (void)report; }
+static void noop_starttime_version(report_t *r, const char *t, const char *v) { (void)r; (void)t; (void)v; }
+static void noop_endtime_runtime(report_t *r, const char *t, long rt) { (void)r; (void)t; (void)rt; }
+static void noop_new_database_written(report_t *report) { (void)report; }
+static void noop_entries(report_t *r, seltree *n, const int f) { (void)r; (void)n; (void)f; }
+static void noop_diff_attrs(report_t *report) { (void)report; }
+static void noop_summary(report_t *report) { (void)report; }
+
+/* Emits header + summary when there are differences. Two separate
+ * report_printf() calls — each becomes one syslog message. */
+static void syslog_outline(report_t *report) {
+ if (report->nadd || report->nrem || report->nchg) {
+ report_printf(report, "AIDE found differences between database and filesystem!!\n");
+ report_printf(report,
+ "summary;total_number_of_files=%ld;added_files=%ld;"
+ "removed_files=%ld;changed_files=%ld\n",
+ report->ntotal, report->nadd, report->nrem, report->nchg);
+ }
+}
+
+static void syslog_details(report_t *report, seltree *node) {
+ syslog_walk_tree(report, node);
+}
+
+report_format_module report_module_syslog = {
+ .print_report_config_options = noop_config_options,
+ .print_report_databases = noop_databases,
+ .print_report_details = syslog_details,
+ .print_report_diff_attrs_entries = noop_diff_attrs,
+ .print_report_endtime_runtime = noop_endtime_runtime,
+ .print_report_entries = noop_entries,
+ .print_report_footer = noop_footer,
+ .print_report_header = noop_header,
+ .print_report_new_database_written = noop_new_database_written,
+ .print_report_outline = syslog_outline,
+ .print_report_report_options = noop_report_options,
+ .print_report_starttime_version = noop_starttime_version,
+ .print_report_summary = noop_summary,
+};
--
2.54.0

View File

@ -0,0 +1,14 @@
-----BEGIN PGP SIGNATURE-----
iQGzBAABCgAdFiEEVJXNoXyawXqyOEGnGO6GOGAi71cFAmict6oACgkQGO6GOGAi
71e/Awv/cP4/UJSVJlaUjRLoeRIayqFxAxZeku1OK1cNuGo5aRp47WsxZFpGkkMc
eNhrtOpukD0CohFdsBFabd2KweaaY67pbCSTXxQBjhEMtYLb+Z5b441cJChJQgeL
q2P09mCGmOoJ4li7VxF2Kjy4uTj4C64SoXFnkYgMzfgBQBC0wD3HBisN8tRG9M4w
4kQa8ZvsO5hFEiFuVhBfLwDKLaUXf5MDcst1Q9MeAGxwvQN5xaSivOwSoCqhVgSv
cDBo1RmhYSf43vUuXTAgclC3WD1y6qm0Si/naVnwVvvN2ij4cGfnb89ixd6qGlKY
HV4klaVQWRQwSyDlhbTcHVxvwnopW+5YZ5SeF3Yl7XohaL89hLEEDxFcLQkrslzV
wR0B0lCtmbZfVhWsnZScNZvT9Cnco7p4FMkkYIHOEVad6kFH3RRXHH320kUD3aWl
hAKXLvuEnYmE//prriUlnh7Q4rsyh9N6ZLgdZxrdPcVbNSAzRiMd8A4kh/H5oMY9
rFqw9YnF
=JV2j
-----END PGP SIGNATURE-----

View File

@ -0,0 +1,10 @@
[Unit]
Description=AIDE file integrity check
Documentation=man:aide(1) man:aide.conf(5)
[Service]
Type=oneshot
ExecStart=/usr/sbin/aide --check
SuccessExitStatus=0 1 2 3 4 5 6 7
Nice=19
IOSchedulingClass=idle

11
SOURCES/aide-check.timer Normal file
View File

@ -0,0 +1,11 @@
[Unit]
Description=Daily AIDE file integrity check
Documentation=man:aide(1) man:aide.conf(5)
[Timer]
OnCalendar=daily
AccuracySec=1h
Persistent=true
[Install]
WantedBy=timers.target

View File

@ -0,0 +1,66 @@
diff --git a/src/conf_eval.c b/src/conf_eval.c
index 5774ce6..6503709 100644
--- a/src/conf_eval.c
+++ b/src/conf_eval.c
@@ -580,9 +580,9 @@ static void include_file(const char* file, bool execute, int include_depth, char
}
}
-void check_permissions(const char* path, struct stat *st, int linenumber, char *filename, char* linebuf) {
+static void check_permissions(const char* path, struct stat *st, const char *directive, int linenumber, char *filename, char* linebuf) {
if ((st->st_uid != geteuid() && st->st_uid != 0) || (st->st_mode & 002) != 0 || (st->st_mode & 020) != 0) {
- LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'@@x_include': bad ownership or modes for '%s' (please ensure it is neither group- nor world-writable and owned by the current user or root)", path)
+ LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'%s': bad ownership or modes for '%s' (please ensure it is neither group- nor world-writable and owned by the current user or root)", directive, path)
exit(INVALID_CONFIGURELINE_ERROR);
}
}
@@ -611,13 +611,13 @@ static void include_directory(const char* dir, const char* rx, bool execute, cha
struct stat fs;
- if (execute) {
- if (stat(dir,&fs) == -1) {
- LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'@@x_include': stat for '%s' failed: %s", dir, strerror(errno))
- exit(INVALID_CONFIGURELINE_ERROR);
- }
- check_permissions(dir, &fs, linenumber, filename, linebuf);
+ /* stat() follows symlinks; we intentionally check the target's ownership
+ * and mode rather than the symlink node itself */
+ if (stat(dir,&fs) == -1) {
+ LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'%s': stat for '%s' failed: %s", execute?"@@x_include":"@@include", dir, strerror(errno))
+ exit(INVALID_CONFIGURELINE_ERROR);
}
+ check_permissions(dir, &fs, execute?"@@x_include":"@@include", linenumber, filename, linebuf);
n = scandir(dir, &namelist, dirfilter, alphasort);
if (n == -1) {
@@ -660,9 +660,8 @@ static void include_directory(const char* dir, const char* rx, bool execute, cha
log_msg(LOG_LEVEL_DEBUG,"%s: skip '%s' (reason: file name does not match regex '%s')", dir, namelist[i]->d_name, rx);
} else {
int exec = execute && S_IXUSR&fs.st_mode;
- if (exec) {
- check_permissions(filepath, &fs, linenumber, filename, linebuf);
- }
+ /* pass directive name (not exec flag) so the error names the directive the user wrote */
+ check_permissions(filepath, &fs, execute?"@@x_include":"@@include", linenumber, filename, linebuf);
log_msg(LOG_LEVEL_CONFIG,"%s: %s '%s'", dir, exec?"execute":"include", namelist[i]->d_name);
include_file(filepath, exec, include_depth, nested_rule_prefix);
}
@@ -701,14 +700,15 @@ static void eval_include_statement(include_statement statement, int include_dept
} else {
struct stat fs;
if (lstat(path,&fs) == -1) {
- LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'@@include': lstat for '%s' failed: %s", path, strerror(errno))
+ LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'%s': lstat for '%s' failed: %s", statement.execute?"@@x_include":"@@include", path, strerror(errno))
exit(INVALID_CONFIGURELINE_ERROR);
}
if (S_ISREG(fs.st_mode)) {
+ check_permissions(path, &fs, statement.execute?"@@x_include":"@@include", linenumber, filename, linebuf);
LOG_CONFIG_FORMAT_LINE_PREFIX(LOG_LEVEL_CONFIG, "include file '%s' (depth: %d)", path, include_depth)
include_file(path, statement.execute && S_IXUSR&fs.st_mode, include_depth, rule_prefix);
} else {
- LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'@@include': '%s' is not a regular file", path);
+ LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'%s': '%s' is not a regular file", statement.execute?"@@x_include":"@@include", path);
exit(INVALID_CONFIGURELINE_ERROR);
}
}

588
SOURCES/aide-migrate-config Executable file
View File

@ -0,0 +1,588 @@
#!/bin/bash
# aide-migrate-config -- migrate AIDE configuration files from pre-0.19 syntax
#
# Usage: aide-migrate-config [--dry-run] [--skip-init] <config-file>
#
# --dry-run Report what would change; do not modify any file.
# --skip-init Migrate config files only; do not reinitialise the database.
# <config-file> Path to the main AIDE config (e.g. /etc/aide.conf).
set -euo pipefail
# ---------------------------------------------------------------------------
# Globals
# ---------------------------------------------------------------------------
readonly SCRIPT="$(basename "$0")"
readonly TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
DRY_RUN=false
SKIP_INIT=false
REINIT_NEEDED=false
# Parallel arrays: BACKUP_ORIG[i] → BACKUP_COPY[i]
BACKUP_ORIG=()
BACKUP_COPY=()
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
usage() {
echo "Usage: $SCRIPT [--dry-run] [--skip-init] <config-file>" >&2
exit 1
}
die() {
echo "$SCRIPT: error: $*" >&2
exit 1
}
info() {
echo "$SCRIPT: $*" >&2
}
# log_action MESSAGE
# Log a migration action. Prefixes with "would " in dry-run mode.
log_action() {
if $DRY_RUN; then
info " would $*"
else
info " $*"
fi
}
# config_has FILE KEY
# Return 0 if FILE contains a KEY= directive.
config_has() {
local file="$1" key="$2"
grep -qE "^[[:space:]]*${key}[[:space:]]*=" "$file"
}
# config_del FILE KEY
# Delete all lines matching KEY= from FILE in-place.
config_del() {
local file="$1" key="$2"
sed -E -i "/^[[:space:]]*${key}[[:space:]]*=/d" "$file"
}
# config_rename FILE OLD_KEY NEW_KEY
# Rename OLD_KEY= to NEW_KEY= in FILE in-place.
config_rename() {
local file="$1" old_key="$2" new_key="$3"
sed -E -i "s/^([[:space:]]*)${old_key}([[:space:]]*=)/\1${new_key}\2/" "$file"
}
# append_setting FILE CONTENT
# Append "CONTENT\n" to FILE, guaranteeing it starts on a fresh line.
# No-op if the key extracted from CONTENT already exists in FILE.
append_setting() {
local file="$1" content="$2"
local key="${content%%=*}"
config_has "$file" "$key" && return 0
if [[ -s "$file" ]] && [[ "$(tail -c1 "$file" | wc -l)" -eq 0 ]]; then
printf '\n' >> "$file"
fi
printf '%s\n' "$content" >> "$file"
}
# backup_file FILE
# Create a timestamped backup of FILE. No-op in dry-run mode.
backup_file() {
local file="$1"
local backup="${file}.bak.${TIMESTAMP}"
if $DRY_RUN; then
return 0
fi
cp -p -- "$file" "$backup"
BACKUP_ORIG+=("$file")
BACKUP_COPY+=("$backup")
info "backup: $backup"
}
# restore_backups
# Called by ERR trap; restores every backed-up file.
restore_backups() {
local i
for i in "${!BACKUP_ORIG[@]}"; do
cp -p -- "${BACKUP_COPY[$i]}" "${BACKUP_ORIG[$i]}" && \
info "restored: ${BACKUP_ORIG[$i]}" || \
info "WARNING: could not restore ${BACKUP_ORIG[$i]} from ${BACKUP_COPY[$i]}"
done
}
# ---------------------------------------------------------------------------
# Include-file discovery
# ---------------------------------------------------------------------------
# extract_includes CONFIG_FILE
# Print one absolute path per line for each file pulled in by @@include directives.
extract_includes() {
local config="$1"
local dir
dir="$(dirname "$config")"
grep -E '^[[:space:]]*@@include[[:space:]]' "$config" 2>/dev/null | \
while IFS= read -r line; do
# Strip the @@include keyword
local args
args="${line#*@@include}"
args="${args#"${args%%[! ]*}"}" # ltrim
# Count tokens to distinguish FILE vs DIRECTORY REGEX forms
local tok1 tok2
tok1="${args%% *}"
tok2="${args#* }"
if [[ "$tok1" == "$args" ]]; then
# Single token: @@include FILE
if [[ "$tok1" = /* ]]; then
echo "$tok1"
else
echo "${dir}/${tok1}"
fi
else
# Two tokens: @@include DIRECTORY REGEX
local incdir regex
incdir="$tok1"
regex="$tok2"
if [[ "$incdir" != /* ]]; then
incdir="${dir}/${incdir}"
fi
if [[ -d "$incdir" ]]; then
find "$incdir" -maxdepth 1 -type f -regex "$regex" | sort
fi
fi
done
}
# ---------------------------------------------------------------------------
# Config file migration
# ---------------------------------------------------------------------------
# needs_migration FILE
# Return 0 (true) if FILE contains any pattern requiring migration.
needs_migration() {
local f="$1"
# Removed/renamed config options
local _key
for _key in database grouped summarize_changes ignore_list report_attributes verbose; do
config_has "$f" "$_key" && return 0
done
# sql: value starts with sql: (PostgreSQL backend removed)
grep -qE '^[[:space:]]*[a-z_]+[[:space:]]*=[[:space:]]*sql:' "$f" && return 0
# any h character in report_ignore_e2fsattrs value
grep -qE '^[[:space:]]*report_ignore_e2fsattrs[[:space:]]*=.*h' "$f" && return 0
# removed/deprecated hashsums; dash at end of character class to avoid range error in GNU sed
grep -v '^[[:space:]]*#' "$f" | \
grep -qE '[+[:space:]=-](crc32b|crc32|tiger|haval|whirlpool|md5|sha1|rmd160|gost)([+[:space:]-]|$)' && return 0
# standalone S attribute in an expression (skip comment lines)
grep -v '^[[:space:]]*#' "$f" | grep -qE '(^|[+=[:space:]-])S([+[:space:]-]|$)' && return 0
# deprecated preprocessor macros
grep -qE '^[[:space:]]*@@(ifdef|ifndef|ifhost|ifnhost)[[:space:]]' "$f" && return 0
# missing trailing newline
[[ -s "$f" ]] && [[ "$(tail -c1 "$f" | wc -l)" -eq 0 ]] && return 0
return 1
}
# remove_hashsum FILE HASHSUM
# Remove a single hashsum token from all group/rule expressions in FILE (in-place).
# Handles all positions: +hash, -hash, hash+ (first), and hash$ (last with minus prefix).
remove_hashsum() {
local file="$1" hash="$2"
# Pass 1: remove hash when it is preceded by an operator (+ or -)
# Keep whatever follows (next operator, space, or end-of-line).
sed -E -i \
-e "/^[[:space:]]*#/!s/[+-]${hash}([+[:space:]-])/\1/g" \
-e "/^[[:space:]]*#/!s/[+-]${hash}$//" \
"$file"
# Pass 2: remove hash at the start of an expression (after = or whitespace),
# not preceded by an operator (those were handled in pass 1).
sed -E -i \
-e "/^[[:space:]]*#/!s/(=[[:space:]]*)${hash}([+[:space:]-])/\1\2/g" \
-e "/^[[:space:]]*#/!s/(=[[:space:]]*)${hash}$/\1/" \
-e "/^[[:space:]]*#/!s/([[:space:]])${hash}([+[:space:]-])/\1\2/g" \
-e "/^[[:space:]]*#/!s/([[:space:]])${hash}$/\1/" \
"$file"
# Clean up artifacts: double operators, dangling operator after '=', trailing operator.
sed -E -i \
-e '/^[[:space:]]*#/!s/[+][+]/+/g' \
-e '/^[[:space:]]*#/!s/[+][-]/+/g' \
-e '/^[[:space:]]*#/!s/[-][+]/-/g' \
-e '/^[[:space:]]*#/!s/(=[[:space:]]*)[+-]/\1/g' \
-e '/^[[:space:]]*#/!s/[+-]$//' \
"$file"
}
# migrate_config_file FILE
# Apply all config transformations to FILE. Sets global REINIT_NEEDED when needed.
migrate_config_file() {
local file="$1"
if [[ ! -f "$file" ]]; then
info "WARNING: $file not found, skipping"
return 0
fi
if [[ ! -r "$file" ]]; then
info "WARNING: $file is not readable, skipping"
return 0
fi
if ! needs_migration "$file"; then
info "no migration needed: $file"
return 0
fi
info "migrating: $file"
backup_file "$file"
local tmpfile
tmpfile="$(mktemp "${file}.XXXXXX")"
# Ensure tmpfile is cleaned up if we exit abnormally before the final mv
trap "rm -f '$tmpfile'; restore_backups" ERR
cp -p -- "$file" "$tmpfile"
# -----------------------------------------------------------------------
# Rename removed config options
# The 'database' key anchors to KEY= so it cannot match 'database_in'/'database_out'.
# -----------------------------------------------------------------------
config_has "$tmpfile" database && log_action "rename: database= → database_in="
config_has "$tmpfile" grouped && log_action "rename: grouped= → report_grouped="
config_has "$tmpfile" summarize_changes && log_action "rename: summarize_changes= → report_summarize_changes="
config_has "$tmpfile" ignore_list && log_action "rename: ignore_list= → report_ignore_changed_attrs="
config_has "$tmpfile" report_attributes && log_action "rename: report_attributes= → report_force_attrs="
if ! $DRY_RUN; then
config_rename "$tmpfile" database database_in
config_rename "$tmpfile" grouped report_grouped
config_rename "$tmpfile" summarize_changes report_summarize_changes
config_rename "$tmpfile" ignore_list report_ignore_changed_attrs
config_rename "$tmpfile" report_attributes report_force_attrs
fi
# -----------------------------------------------------------------------
# Replace verbose= with the equivalent 0.19 options
# -----------------------------------------------------------------------
if config_has "$tmpfile" verbose; then
log_action "remove verbose="
config_has "$tmpfile" log_level || log_action "add log_level=warning"
config_has "$tmpfile" report_level || log_action "add report_level=changed_attributes"
if ! $DRY_RUN; then
config_del "$tmpfile" verbose
append_setting "$tmpfile" 'log_level=warning'
append_setting "$tmpfile" 'report_level=changed_attributes'
fi
fi
# -----------------------------------------------------------------------
# Remove sql: database URL lines (PostgreSQL backend removed)
# Match lines whose value (after =) begins with sql:.
# -----------------------------------------------------------------------
if grep -qE '^[[:space:]]*[a-z_]+[[:space:]]*=[[:space:]]*sql:' "$tmpfile"; then
log_action "remove sql: database URL lines"
if $DRY_RUN; then
# Check which keys survive after sql: removal (i.e. have at least one non-sql: value)
grep -E '^[[:space:]]*database_out[[:space:]]*=' "$tmpfile" | \
grep -qvE '^[[:space:]]*[a-z_]+[[:space:]]*=[[:space:]]*sql:' || \
info " would add default database_out=file:/var/lib/aide/aide.db.new.gz"
grep -E '^[[:space:]]*database_in[[:space:]]*=' "$tmpfile" | \
grep -qvE '^[[:space:]]*[a-z_]+[[:space:]]*=[[:space:]]*sql:' || \
info " would add default database_in=file:/var/lib/aide/aide.db.gz"
fi
if ! $DRY_RUN; then
sed -E -i '/^[[:space:]]*[a-z_]+[[:space:]]*=[[:space:]]*sql:/d' "$tmpfile"
config_has "$tmpfile" database_out || {
append_setting "$tmpfile" 'database_out=file:/var/lib/aide/aide.db.new.gz'
info " WARNING: sql: URL removed; default database_out added." \
"Verify storage path before running aide --init."
}
config_has "$tmpfile" database_in || {
append_setting "$tmpfile" 'database_in=file:/var/lib/aide/aide.db.gz'
info " WARNING: sql: database_in removed; default database_in added." \
"Verify path before running aide --init."
}
fi
REINIT_NEEDED=true
fi
# -----------------------------------------------------------------------
# Remove 'h' from report_ignore_e2fsattrs
# Use the sed address form to remove ALL 'h' characters from the value line.
# -----------------------------------------------------------------------
if grep -qE '^[[:space:]]*report_ignore_e2fsattrs[[:space:]]*=.*h' "$tmpfile"; then
log_action "remove 'h' from report_ignore_e2fsattrs"
if ! $DRY_RUN; then
sed -E -i '/^[[:space:]]*report_ignore_e2fsattrs[[:space:]]*=/s/h//g' "$tmpfile"
sed -E -i '/^[[:space:]]*report_ignore_e2fsattrs[[:space:]]*=[[:space:]]*$/d' "$tmpfile"
fi
fi
# -----------------------------------------------------------------------
# Remove deprecated and removed hashsums
# Process crc32b before crc32 to avoid prefix collision.
# Character classes use dash at end to prevent GNU sed range-error.
# -----------------------------------------------------------------------
local hash changed_hashes=false
for hash in crc32b crc32 tiger haval whirlpool md5 sha1 rmd160 gost; do
if grep -v '^[[:space:]]*#' "$tmpfile" | grep -qE "(^|[+[:space:]=-])${hash}([+[:space:]-]|\$)"; then
log_action "remove hashsum: $hash"
if ! $DRY_RUN; then
remove_hashsum "$tmpfile" "$hash"
fi
changed_hashes=true
REINIT_NEEDED=true
fi
done
# Post-removal: fill any group definition whose RHS became empty with sha256
if $changed_hashes; then
if ! $DRY_RUN; then
while IFS= read -r lineno; do
[[ -z "$lineno" ]] && continue
sed -i "${lineno}s/=.*/= sha256/" "$tmpfile"
info " group on line $lineno became empty after hashsum removal; added sha256"
done < <(grep -nE '^[A-Za-z0-9]+[[:space:]]*=[[:space:]]*[+-]?[[:space:]]*$' \
"$tmpfile" | cut -d: -f1)
else
info " note: any group containing only deprecated hashsums will have sha256 substituted"
fi
fi
# -----------------------------------------------------------------------
# Replace deprecated S attribute with growing+s
# Applies since AIDE 0.16 is being replaced; growing+s is unknown to 0.16.
# Character classes use dash at end to prevent GNU sed range-error.
# -----------------------------------------------------------------------
if grep -qE '(^|[+=[:space:]-])S([+[:space:]-]|$)' "$tmpfile"; then
log_action "replace S attribute with growing+s"
if ! $DRY_RUN; then
sed -E -i \
-e '/^[[:space:]]*#/!s/([+=[:space:]-])S([+[:space:]-]|$)/\1growing+s\2/g' \
-e '/^[[:space:]]*#/!s/^S([+[:space:]-]|$)/growing+s\1/g' \
"$tmpfile"
fi
fi
# -----------------------------------------------------------------------
# Replace deprecated @@ifdef/@@ifndef/@@ifhost/@@ifnhost macros
# -----------------------------------------------------------------------
if grep -qE '^[[:space:]]*@@(ifdef|ifndef|ifhost|ifnhost)[[:space:]]' "$tmpfile"; then
log_action "replace deprecated @@ifdef/@@ifndef/@@ifhost/@@ifnhost macros"
if ! $DRY_RUN; then
sed -E -i \
-e 's/^([[:space:]]*)@@ifdef([[:space:]])/\1@@if defined\2/g' \
-e 's/^([[:space:]]*)@@ifndef([[:space:]])/\1@@if not defined\2/g' \
-e 's/^([[:space:]]*)@@ifhost([[:space:]])/\1@@if hostname\2/g' \
-e 's/^([[:space:]]*)@@ifnhost([[:space:]])/\1@@if not hostname\2/g' \
"$tmpfile"
fi
fi
# -----------------------------------------------------------------------
# Ensure file ends with a newline
# -----------------------------------------------------------------------
local last_byte
last_byte="$(tail -c1 "$tmpfile" | od -An -tx1 | tr -d ' \n')"
if [[ -n "$last_byte" && "$last_byte" != '0a' ]]; then
log_action "add missing trailing newline"
$DRY_RUN || echo "" >> "$tmpfile"
fi
# H group's content changed in 0.19; warn if used without a custom definition.
# Only reached when the file had real 0.16-style options, so this never fires on a
# clean 0.19 config.
if grep -qE '(^|[+[:space:]-])H([+[:space:]-]|$)' "$tmpfile" 2>/dev/null; then
if ! grep -qE '^[[:space:]]*H[[:space:]]*=' "$tmpfile"; then
info "NOTE: built-in H group in use without custom definition;" \
"H content changed in 0.19 — run 'aide --init' to rebuild the database"
fi
fi
# -----------------------------------------------------------------------
# Commit changes
# -----------------------------------------------------------------------
if ! $DRY_RUN; then
mv -- "$tmpfile" "$file"
# Restore original permissions
chmod --reference="${BACKUP_COPY[-1]}" "$file" 2>/dev/null || true
else
rm -f "$tmpfile"
fi
# Disarm the local ERR trap and re-arm the global one
trap - ERR
trap restore_backups ERR
}
# ---------------------------------------------------------------------------
# Post-migration warnings
# ---------------------------------------------------------------------------
check_and_warn() {
local file="$1"
# Warn if a rule path starts with a macro variable.
# Only flag lines where @@{...} appears at the very start (after optional whitespace);
# mid-path macros like /path/@@{VAR}/sub are valid and must not be flagged.
local macro_rules
macro_rules="$(grep -nE '^[[:space:]]*@@\{[^}]+\}' "$file" 2>/dev/null || true)"
if [[ -n "$macro_rules" ]]; then
info "WARNING ($file): the following rule paths start with a macro variable." \
"Rewrite them so the path begins with a literal '/':"
echo "$macro_rules" >&2
fi
# Warn if a group name contains non-alphanumeric characters.
# Require an uppercase first letter to avoid false positives on config option names.
# Underscores are accepted by AIDE 0.19.2; only '-' and '.' cause parse errors.
local bad_groups
bad_groups="$(grep -nE '^[A-Z][A-Za-z0-9]*[-.][A-Za-z0-9][^=]*[[:space:]]*=' \
"$file" 2>/dev/null || true)"
if [[ -n "$bad_groups" ]]; then
info "WARNING ($file): the following group names contain non-alphanumeric characters." \
"Rename groups and all their references to [A-Za-z0-9] only:"
echo "$bad_groups" >&2
fi
}
# ---------------------------------------------------------------------------
# Database reinitialisation
# ---------------------------------------------------------------------------
# expand_aide_macros CONFIG VALUE
# Expand @@{NAME} references in VALUE using @@define lines from CONFIG.
expand_aide_macros() {
local config="$1" value="$2"
local key val
while IFS= read -r defline; do
key="$(echo "$defline" | awk '{print $2}')"
val="$(echo "$defline" | awk '{$1=$2=""; print substr($0,3)}')"
value="${value//@@\{${key}\}/$val}"
done < <(grep -E '^[[:space:]]*@@define[[:space:]]' "$config" || true)
echo "$value"
}
# reinit_database CONFIG_FILE
# Backup the existing database, run aide --init, move the new DB into place.
reinit_database() {
local config="$1"
# Parse database_in path (file: URLs only)
local raw_in
raw_in="$(grep -E '^[[:space:]]*database_in[[:space:]]*=' "$config" | \
head -1 | sed -E 's/^[^=]+=file://')" || true
[[ -z "$raw_in" ]] && { info "WARNING: database_in not found in config; skipping reinit"; return 0; }
local db_in
db_in="$(expand_aide_macros "$config" "$raw_in")"
# Parse database_out path (file: URLs only)
local raw_out
raw_out="$(grep -E '^[[:space:]]*database_out[[:space:]]*=' "$config" | \
head -1 | sed -E 's/^[^=]+=file://')" || true
[[ -z "$raw_out" ]] && { info "WARNING: database_out not found in config; skipping reinit"; return 0; }
local db_out
db_out="$(expand_aide_macros "$config" "$raw_out")"
if [[ ! -f "$db_in" ]]; then
info "no existing database at $db_in; run 'aide --init -c $config' when ready"
return 0
fi
info "reinitialising database (this may take several minutes)..."
backup_file "$db_in"
aide --init -c "$config"
if [[ ! -f "$db_out" ]]; then
die "aide --init completed but output database not found at $db_out"
fi
mv -- "$db_out" "$db_in"
chmod 0600 "$db_in"
chown root:root "$db_in"
info "database reinitialised: $db_in"
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
main() {
# CLI flags are out of scope for this script
info "Note: This script migrates aide config files only." \
"If you use '--verbose' or '--report' flags in wrapper scripts," \
"cron jobs, or systemd units, remove those flags manually."
# Argument parsing
local config_file=""
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=true; shift ;;
--skip-init) SKIP_INIT=true; shift ;;
-h|--help) usage ;;
--) shift; break ;;
-*) die "unknown option: $1" ;;
*) config_file="$1"; shift ;;
esac
done
[[ $# -gt 0 ]] && die "unexpected argument: $1"
[[ -z "$config_file" ]] && usage
[[ -f "$config_file" ]] || die "config file not found: $config_file"
[[ -r "$config_file" ]] || die "config file not readable: $config_file"
$DRY_RUN || [[ -w "$config_file" ]] || die "config file not writable: $config_file"
# Verify aide >= 0.19 is installed
local aide_ver
aide_ver="$(aide --version 2>&1 | grep -oE '[0-9]+\.[0-9]+' | head -1)" || true
if [[ -z "$aide_ver" ]]; then
info "aide not found; skipping migration"
exit 0
fi
local aide_major aide_minor
aide_major="${aide_ver%%.*}"
aide_minor="${aide_ver#*.}"
if [[ "$aide_major" -lt 1 && "$aide_minor" -lt 19 ]]; then
info "aide $aide_ver < 0.19; skipping migration"
exit 0
fi
$DRY_RUN && info "DRY-RUN mode: no files will be modified"
# Global ERR trap for cleanup
trap restore_backups ERR
# Collect all config files to process
local -a config_files=("$config_file")
while IFS= read -r inc; do
[[ -f "$inc" ]] && config_files+=("$inc")
done < <(extract_includes "$config_file")
# Migrate each config file
local f
for f in "${config_files[@]}"; do
migrate_config_file "$f"
done
# Post-migration warnings
for f in "${config_files[@]}"; do
check_and_warn "$f"
done
# Validate the resulting config
if ! $DRY_RUN; then
if ! aide --config-check -c "$config_file" >/dev/null 2>&1; then
info "ERROR: aide --config-check failed after migration; restoring all backups"
restore_backups
exit 1
fi
info "aide --config-check passed"
fi
# Database reinit
if $REINIT_NEEDED && ! $SKIP_INIT && ! $DRY_RUN; then
reinit_database "$config_file"
elif $REINIT_NEEDED && $SKIP_INIT && ! $DRY_RUN; then
info "database reinitialisation required but --skip-init set;" \
"run 'aide --init -c $config_file' manually"
elif $REINIT_NEEDED && $DRY_RUN; then
info "would require database reinitialisation after migration"
fi
info "migration complete"
}
main "$@"

View File

@ -0,0 +1,2 @@
d /var/log/aide 0700 root root -
d /var/lib/aide 0700 root root -

View File

@ -4,7 +4,7 @@
@@define LOGDIR /var/log/aide
# The location of the database to be read.
database=file:@@{DBDIR}/aide.db.gz
database_in=file:@@{DBDIR}/aide.db.gz
# The location of the database to be written.
#database_out=sql:host:port:database:login_name:passwd:table
@ -14,19 +14,49 @@ database_out=file:@@{DBDIR}/aide.db.new.gz
# Whether to gzip the output to database
gzip_dbout=yes
# Database attributes to include in report (H = all compiled hashsums, default)
database_attrs=H
# Add metadata to database (version info, timestamps)
database_add_metadata=yes
# Warn about unrestricted rules during config check (default: false)
config_check_warn_unrestricted_rules=false
# Number of workers for parallel processing (AIDE default: 1, overridden to 4 here)
num_workers=4
# Default.
verbose=5
log_level=warning
report_level=changed_attributes
# Report format (plain or json)
report_format=plain
# Group files in report by added/removed/changed
report_grouped=yes
# Summarize changes in report
report_summarize_changes=yes
# Don't report if no differences found
report_quiet=no
# Report encoding (base64 is default, base16 available)
report_base16=no
report_url=file:@@{LOGDIR}/aide.log
report_url=stdout
#report_url=stderr
#NOT IMPLEMENTED report_url=mailto:root@foo.com
#NOT IMPLEMENTED report_url=syslog:LOG_AUTH
#report_url=syslog:LOG_AUTH
# These are the default rules.
#
#ftype: file type
#fstype: file system type (Linux-only)
#p: permissions
#i: inode:
#i: inode
#l: link name (symbolic links only)
#n: number of links
#u: user
#g: group
@ -35,204 +65,217 @@ report_url=stdout
#m: mtime
#a: atime
#c: ctime
#S: check for growing size
#acl: Access Control Lists
#selinux SELinux security context
#xattrs: Extended file attributes
#md5: md5 checksum
#sha1: sha1 checksum
#e2fsattrs: file attributes on Linux file system
#caps: file capabilities (Linux-only)
# Hashsums attributes (regular files only)
#sha256: sha256 checksum
#sha512: sha512 checksum
#rmd160: rmd160 checksum
#tiger: tiger checksum
#sha512_256: SHA-512 checksum truncated to 256 output bits
#sha3_256: SHA3-256 checksum (modern)
#sha3_512: SHA3-512 checksum (modern)
#stribog256: GOST R 34.11-2012, 256 bit
#stribog512: GOST R 34.11-2012, 512 bit
#haval: haval checksum (MHASH only)
#gost: gost checksum (MHASH only)
#crc32: crc32 checksum (MHASH only)
#whirlpool: whirlpool checksum (MHASH only)
# Special attributes for advanced use cases:
#I: ignore changed filename - detects moved files by inode
#growing: ignore growing file size/timestamps for logs
#compressed: ignore compression - compares uncompressed content
#ANF: allow new files - new files ignored in report
#ARF: allow removed files - missing files ignored in report
#R: p+i+n+u+g+s+m+c+acl+selinux+xattrs+md5
#L: p+i+n+u+g+acl+selinux+xattrs
#E: Empty group
#>: Growing logfile p+u+g+i+n+S+acl+selinux+xattrs
# Default groups in AIDE v0.19:
# R = p+ftype+i+l+n+u+g+s+m+c+sha3_256+X
# L = p+ftype+i+l+n+u+g+X
# > = Growing file p+ftype+l+u+g+i+n+s+growing+X
# H = all compiled in (and not deprecated) hashsums
# X = acl+selinux+xattrs+e2fsattrs+caps (if compiled in)
# E = Empty group
# Use 'aide --version' to list the default compound groups.
# You can create custom rules like this.
# With MHASH...
# ALLXTRAHASHES = sha1+rmd160+sha256+sha512+whirlpool+tiger+haval+gost+crc32
ALLXTRAHASHES = sha1+rmd160+sha256+sha512+tiger
# Everything but access time (Ie. all changes)
# Note: Removed deprecated/removed hashsums (tiger, haval, crc32, crc32b, whirlpool, md5, sha1, rmd160, gost)
ALLXTRAHASHES = sha256+sha512+sha512_256+sha3_256+sha3_512+stribog256+stribog512
# Everything but access time (Ie. all changes) - updated with modern hashsums
EVERYTHING = R+ALLXTRAHASHES
# Sane
# NORMAL = R+sha512
NORMAL = p+i+n+u+g+s+m+c+acl+selinux+xattrs+sha512
# Base + sha512 (strong)
NORMAL = R+sha512-m-c
# For directories, don't bother doing hashes
DIR = p+i+n+u+g+acl+selinux+xattrs
# Content only - added file type and strong hash
CONTENT = ftype+sha512
# Access control only
PERMS = p+u+g+acl+selinux+xattrs
# For directories, don't bother doing hashes - added file type and link name
DIR = ftype+p+i+l+n+u+g+acl+selinux+xattrs
# Logfile are special, in that they often change
LOG = p+u+g+n+S+acl+selinux+xattrs
# Access control only - added file type and link name
PERMS = ftype+p+u+g+acl+selinux+xattrs
# Content + file type.
CONTENT = sha512+ftype
# Extended content + file type + access.
CONTENT_EX = sha512+ftype+p+u+g+n+acl+selinux+xattrs
# Logfiles are special, in that they often change due to log rotation
# Track only: permissions, file type, user, group, number of links, SELinux context, extended attributes
# Allow new files (ANF) and allow removed files (ARF) due to log rotation techniques
# Don't track: size, inodes, timestamps, checksums and some special attributes (these change frequently with log rotation)
LOG = p+ftype+u+g+n+ANF+ARF+selinux+xattrs
# Some files get updated automatically, so the inode/ctime/mtime change
# but we want to know when the data inside them changes
DATAONLY = p+n+u+g+s+acl+selinux+xattrs+sha512
# but we want to know when the data inside them changes - updated with modern hash
DATAONLY = ftype+p+l+n+u+g+s+acl+selinux+xattrs+sha256
# Read /etc/aide.d/*.conf files
@@include /etc/aide.d ^[a-zA-Z0-9_-]+\.conf$
# Next decide what directories/files you want in the database.
/boot CONTENT_EX
/opt CONTENT
# Admins dot files constantly change, just check perms
/root/\..* PERMS
# Otherwise get all of /root.
/root CONTENT_EX
/boot NORMAL
/bin NORMAL
/sbin NORMAL
/lib NORMAL
/lib64 NORMAL
# Monitor /opt selectively to avoid noise from auto-updating applications
/opt CONTENT
/usr NORMAL
# These are too volatile
!/usr/src
!/usr/tmp
# Otherwise get all of /usr.
/usr CONTENT_EX
# Admins dot files constantly change, just check perms
/root/\..* PERMS
!/root/.xauth*
/root NORMAL
# Check only permissions, inode, user and group for /etc, but
# cover some important files closely.
!/etc/mtab
# Ignore backup files
!/etc/.*~
# trusted databases
/etc/hosts$ CONTENT_EX
/etc/host.conf$ CONTENT_EX
/etc/hostname$ CONTENT_EX
/etc/issue$ CONTENT_EX
/etc/issue.net$ CONTENT_EX
/etc/protocols$ CONTENT_EX
/etc/services$ CONTENT_EX
/etc/localtime$ CONTENT_EX
/etc/alternatives CONTENT_EX
/etc/sysconfig CONTENT_EX
/etc/mime.types$ CONTENT_EX
/etc/terminfo CONTENT_EX
/etc/exports$ CONTENT_EX
/etc/fstab$ CONTENT_EX
/etc/passwd$ CONTENT_EX
/etc/group$ CONTENT_EX
/etc/gshadow$ CONTENT_EX
/etc/shadow$ CONTENT_EX
/etc/subgid$ CONTENT_EX
/etc/subuid$ CONTENT_EX
/etc/security/opasswd$ CONTENT_EX
/etc/skel CONTENT_EX
/etc/subuid$ CONTENT_EX
/etc/subgid$ CONTENT_EX
/etc/sssd CONTENT_EX
/etc/machine-id$ CONTENT_EX
/etc/swid CONTENT_EX
/etc/system-release-cpe$ CONTENT_EX
/etc/shells$ CONTENT_EX
/etc/tmux.conf$ CONTENT_EX
/etc/xattr.conf$ CONTENT_EX
/etc/hosts$ NORMAL
/etc/host.conf$ NORMAL
/etc/hostname$ NORMAL
/etc/issue$ NORMAL
/etc/issue.net$ NORMAL
/etc/protocols$ NORMAL
/etc/services$ NORMAL
/etc/localtime$ NORMAL
/etc/alternatives NORMAL
/etc/mime.types$ NORMAL
/etc/terminfo NORMAL
/etc/exports$ NORMAL
/etc/fstab$ NORMAL
/etc/passwd$ NORMAL
/etc/group$ NORMAL
/etc/gshadow$ NORMAL
/etc/shadow$ NORMAL
/etc/subgid$ NORMAL
/etc/subuid$ NORMAL
/etc/skel NORMAL
/etc/sssd NORMAL
/etc/swid NORMAL
/etc/system-release-cpe$ NORMAL
/etc/tmux.conf$ NORMAL
/etc/xattr.conf$ NORMAL
# networking
/etc/hosts.allow$ CONTENT_EX
/etc/hosts.deny$ CONTENT_EX
/etc/firewalld CONTENT_EX
/etc/firewalld NORMAL
!/etc/NetworkManager/system-connections
/etc/NetworkManager CONTENT_EX
/etc/networks$ CONTENT_EX
/etc/dhcp CONTENT_EX
/etc/wpa_supplicant CONTENT_EX
/etc/NetworkManager NORMAL
/etc/networks$ NORMAL
/etc/dhcp NORMAL
/etc/wpa_supplicant NORMAL
/etc/resolv.conf$ DATAONLY
/etc/nscd.conf$ CONTENT_EX
# logins and accounts
/etc/login.defs$ CONTENT_EX
/etc/libuser.conf$ CONTENT_EX
/etc/login.defs$ NORMAL
/etc/libuser.conf$ NORMAL
/var/log/faillog$ PERMS
/var/log/lastlog$ PERMS
/var/run/faillock PERMS
/etc/pam.d CONTENT_EX
/etc/security CONTENT_EX
/etc/securetty$ CONTENT_EX
/etc/polkit-1 CONTENT_EX
/etc/sudo.conf$ CONTENT_EX
/etc/sudoers$ CONTENT_EX
/etc/sudoers.d CONTENT_EX
/etc/pam.d NORMAL
/etc/security NORMAL
/etc/securetty$ NORMAL
/etc/polkit-1 NORMAL
/etc/sudo.conf$ NORMAL
/etc/sudoers$ NORMAL
/etc/sudoers.d NORMAL
# Shell/X startup files
/etc/profile$ CONTENT_EX
/etc/profile.d CONTENT_EX
/etc/bashrc$ CONTENT_EX
/etc/bash_completion.d CONTENT_EX
/etc/zprofile$ CONTENT_EX
/etc/zshrc$ CONTENT_EX
/etc/zlogin$ CONTENT_EX
/etc/zlogout$ CONTENT_EX
/etc/X11 CONTENT_EX
# Shell/X starting files
/etc/profile$ NORMAL
/etc/profile.d NORMAL
/etc/bashrc$ NORMAL
/etc/bash_completion.d NORMAL
/etc/zprofile$ NORMAL
/etc/zshrc$ NORMAL
/etc/zlogin$ NORMAL
/etc/zlogout$ NORMAL
/etc/X11 NORMAL
/etc/shells$ NORMAL
# Pkg manager
/etc/dnf CONTENT_EX
/etc/yum.conf$ CONTENT_EX
/etc/yum CONTENT_EX
/etc/yum.repos.d CONTENT_EX
# This gets new/removes-old filenames daily
!/var/log/sa
# As we are checking it, we've truncated yesterdays size to zero.
!/var/log/aide.log
/etc/dnf NORMAL
/etc/yum.repos.d NORMAL
# auditing
# AIDE produces an audit record, so this becomes perpetual motion.
/var/log/audit PERMS
/etc/audit CONTENT_EX
/etc/libaudit.conf$ CONTENT_EX
/etc/aide.conf$ CONTENT_EX
/etc/audit NORMAL
/etc/libaudit.conf$ NORMAL
/etc/aide.conf$ NORMAL
# System logs with proper logrotate handling
/etc/rsyslog.conf$ NORMAL
/etc/rsyslog.d NORMAL
/etc/logrotate.conf$ NORMAL
/etc/logrotate.d NORMAL
/etc/systemd/journald.conf$ NORMAL
# Log directory
/var/log LOG
# Journal files - exclude xattrs and link count due to systemd journal's user.crtime_usec extended attribute changes and new directory creation
/var/log/journal LOG-xattrs-n
# System logs
/etc/rsyslog.conf$ CONTENT_EX
/etc/rsyslog.d CONTENT_EX
/etc/logrotate.conf$ CONTENT_EX
/etc/logrotate.d CONTENT_EX
/etc/systemd/journald.conf$ CONTENT_EX
/var/log LOG+ANF+ARF
/var/run/utmp LOG
# secrets
/etc/pkcs11 CONTENT_EX
/etc/pki CONTENT_EX
/etc/crypto-policies CONTENT_EX
/etc/certmonger CONTENT_EX
/etc/pkcs11 NORMAL
/etc/pki NORMAL
/etc/ssl NORMAL
/etc/certmonger NORMAL
/var/lib/systemd/random-seed$ PERMS
# init system
/etc/systemd CONTENT_EX
/etc/rc.d CONTENT_EX
/etc/tmpfiles.d CONTENT_EX
/etc/systemd NORMAL
/etc/sysconfig NORMAL
/etc/rc.d NORMAL
/etc/tmpfiles.d NORMAL
/etc/machine-id$ NORMAL
# boot config
/etc/default CONTENT_EX
/etc/grub.d CONTENT_EX
/etc/dracut.conf$ CONTENT_EX
/etc/dracut.conf.d CONTENT_EX
/etc/default NORMAL
/etc/grub.d NORMAL
/etc/grub2.cfg$ NORMAL
/etc/dracut.conf$ NORMAL
/etc/dracut.conf.d NORMAL
# glibc linker
/etc/ld.so.cache$ CONTENT_EX
/etc/ld.so.conf$ CONTENT_EX
/etc/ld.so.conf.d CONTENT_EX
/etc/ld.so.preload$ CONTENT_EX
/etc/ld.so.cache$ NORMAL
/etc/ld.so.conf$ NORMAL
/etc/ld.so.conf.d NORMAL
/etc/ld.so.preload$ NORMAL
# kernel config
/etc/sysctl.conf$ CONTENT_EX
/etc/sysctl.d CONTENT_EX
/etc/modprobe.d CONTENT_EX
/etc/modules-load.d CONTENT_EX
/etc/depmod.d CONTENT_EX
/etc/udev CONTENT_EX
/etc/crypttab$ CONTENT_EX
/etc/sysctl.conf$ NORMAL
/etc/sysctl.d NORMAL
/etc/modprobe.d NORMAL
/etc/modules-load.d NORMAL
/etc/depmod.d NORMAL
/etc/udev NORMAL
/etc/crypttab$ NORMAL
#### Daemons ####
@ -240,68 +283,70 @@ DATAONLY = p+n+u+g+s+acl+selinux+xattrs+sha512
/var/spool/at CONTENT
/etc/at.allow$ CONTENT
/etc/at.deny$ CONTENT
/var/spool/anacron CONTENT
/etc/anacrontab$ CONTENT_EX
/etc/cron.allow$ CONTENT_EX
/etc/cron.deny$ CONTENT_EX
/etc/cron.d CONTENT_EX
/etc/cron.daily CONTENT_EX
/etc/cron.hourly CONTENT_EX
/etc/cron.monthly CONTENT_EX
/etc/cron.weekly CONTENT_EX
/etc/crontab$ CONTENT_EX
/etc/anacrontab$ NORMAL
/etc/cron.allow$ NORMAL
/etc/cron.deny$ NORMAL
/etc/cron.d NORMAL
/etc/cron.daily NORMAL
/etc/cron.hourly NORMAL
/etc/cron.monthly NORMAL
/etc/cron.weekly NORMAL
/etc/crontab$ NORMAL
/var/spool/cron/root CONTENT
# time keeping
/etc/chrony.conf$ CONTENT_EX
/etc/chrony.keys$ CONTENT_EX
/etc/chrony.conf$ NORMAL
/etc/chrony.keys$ NORMAL
# mail
/etc/aliases$ CONTENT_EX
/etc/aliases.db$ CONTENT_EX
/etc/postfix CONTENT_EX
/etc/aliases$ NORMAL
/etc/aliases.db$ NORMAL
/etc/postfix NORMAL
# ssh
/etc/ssh/sshd_config$ CONTENT_EX
/etc/ssh/ssh_config$ CONTENT_EX
/etc/ssh/sshd_config$ NORMAL
/etc/ssh/ssh_config$ NORMAL
# stunnel
/etc/stunnel CONTENT_EX
/etc/stunnel NORMAL
# ftp
/etc/vsftpd CONTENT
# printing
/etc/cups CONTENT_EX
/etc/cupshelpers CONTENT_EX
/etc/avahi CONTENT_EX
/etc/cups NORMAL
/etc/cupshelpers NORMAL
/etc/avahi NORMAL
# web server
/etc/httpd CONTENT_EX
/etc/httpd NORMAL
# dns
/etc/named CONTENT_EX
/etc/named.conf$ CONTENT_EX
/etc/named.iscdlv.key$ CONTENT_EX
/etc/named.rfc1912.zones$ CONTENT_EX
/etc/named.root.key$ CONTENT_EX
/etc/named NORMAL
/etc/named.conf$ NORMAL
/etc/named.iscdlv.key$ NORMAL
/etc/named.rfc1912.zones$ NORMAL
/etc/named.root.key$ NORMAL
# xinetd
/etc/xinetd.conf$ CONTENT_EX
/etc/xinetd.d CONTENT_EX
/etc/xinetd.conf$ NORMAL
/etc/xinetd.d NORMAL
# IPsec
/etc/ipsec.conf$ CONTENT_EX
/etc/ipsec.secrets$ CONTENT_EX
/etc/ipsec.d CONTENT_EX
/etc/ipsec.conf$ NORMAL
/etc/ipsec.secrets$ NORMAL
/etc/ipsec.d NORMAL
# USB guard
/etc/usbguard CONTENT_EX
# Ignore some files
!/etc/mtab$
!/etc/.*~
# USBGuard
/etc/usbguard NORMAL
# Now everything else
/etc PERMS
# This gets new/removes-old filenames daily
!/var/log/sa
# As we are checking it, we've truncated yesterdays size to zero.
!/var/log/aide.log
# With AIDE's default verbosity level of 5, these would give lots of
# warnings upon tree traversal. It might change with future version.
@ -310,8 +355,6 @@ DATAONLY = p+n+u+g+s+acl+selinux+xattrs+sha512
#=/home DIR
# Ditto /var/log/sa reason...
!/var/log/and-httpd
# Admins dot files constantly change, just check perms
/root/\..* PERMS
!/root/.xauth*
!/var/log/httpd
# /boot/grub2/grubenv's timestamp is getting modified continuously due to "boot_success" implementation
!/boot/grub2/grubenv

View File

@ -1,642 +0,0 @@
diff -up ./include/be.h.coverity ./include/be.h
--- ./include/be.h.coverity 2016-07-25 22:56:55.000000000 +0200
+++ ./include/be.h 2018-10-10 19:27:18.680632681 +0200
@@ -22,6 +22,6 @@
#define _BE_H_INCLUDED
#include "db_config.h"
-FILE* be_init(int inout,url_t* u,int iszipped);
+void* be_init(int inout,url_t* u,int iszipped);
#endif /* _BE_H_INCLUDED */
diff -up ./include/db_config.h.coverity ./include/db_config.h
--- ./include/db_config.h.coverity 2018-10-10 19:27:18.672632611 +0200
+++ ./include/db_config.h 2018-10-10 19:27:18.681632689 +0200
@@ -376,7 +376,7 @@ typedef struct db_config {
#endif
url_t* initial_report_url;
- FILE* initial_report_fd;
+ void* initial_report_fd;
/* report_url is a list of url_t*s */
list* report_url;
diff -up ./src/aide.c.coverity ./src/aide.c
--- ./src/aide.c.coverity 2018-10-10 19:27:18.678632663 +0200
+++ ./src/aide.c 2018-10-10 19:27:18.681632689 +0200
@@ -278,7 +278,7 @@ static void setdefaults_before_config()
error(0,_("Couldn't get hostname"));
free(s);
} else {
- s=(char*)realloc((void*)s,strlen(s)+1);
+ // s=(char*)realloc((void*)s,strlen(s)+1);
do_define("HOSTNAME",s);
}
@@ -506,8 +506,6 @@ static void setdefaults_after_config()
int main(int argc,char**argv)
{
int errorno=0;
- byte* dig=NULL;
- char* digstr=NULL;
#ifdef USE_LOCALE
setlocale(LC_ALL,"");
@@ -544,6 +542,10 @@ int main(int argc,char**argv)
}
errorno=commandconf('C',conf->config_file);
+ if (errorno==RETFAIL){
+ error(0,_("Configuration error\n"));
+ exit(INVALID_CONFIGURELINE_ERROR);
+ }
errorno=commandconf('D',"");
if (errorno==RETFAIL){
@@ -594,6 +596,9 @@ int main(int argc,char**argv)
}
}
#ifdef WITH_MHASH
+ byte* dig=NULL;
+ char* digstr=NULL;
+
if(conf->config_check&&FORCECONFIGMD){
error(0,"Can't give config checksum when compiled with --enable-forced_configmd\n");
exit(INVALID_ARGUMENT_ERROR);
diff -up ./src/base64.c.coverity ./src/base64.c
--- ./src/base64.c.coverity 2016-07-25 22:56:55.000000000 +0200
+++ ./src/base64.c 2018-10-10 19:27:18.681632689 +0200
@@ -209,6 +209,7 @@ byte* decode_base64(char* src,size_t ssi
case FAIL:
error(3, "decode_base64: Illegal character: %c\n", *inb);
error(230, "decode_base64: Illegal line:\n%s\n", src);
+ free(outbuf);
return NULL;
break;
case SKIP:
@@ -260,7 +261,7 @@ size_t length_base64(char* src,size_t ss
int l;
int left;
size_t pos;
- unsigned long triple;
+ //unsigned long triple;
error(235, "decode base64\n");
/* Exit on empty input */
@@ -273,7 +274,7 @@ size_t length_base64(char* src,size_t ss
inb = src;
l = 0;
- triple = 0;
+ //triple = 0;
pos=0;
left = ssize;
/*
@@ -293,7 +294,7 @@ size_t length_base64(char* src,size_t ss
case SKIP:
break;
default:
- triple = triple<<6 | (0x3f & i);
+ //triple = triple<<6 | (0x3f & i);
l++;
break;
}
@@ -302,10 +303,10 @@ size_t length_base64(char* src,size_t ss
switch(l)
{
case 2:
- triple = triple>>4;
+ //triple = triple>>4;
break;
case 3:
- triple = triple>>2;
+ //triple = triple>>2;
break;
default:
break;
@@ -314,7 +315,7 @@ size_t length_base64(char* src,size_t ss
{
pos++;
}
- triple = 0;
+ //triple = 0;
l = 0;
}
inb++;
diff -up ./src/be.c.coverity ./src/be.c
--- ./src/be.c.coverity 2016-07-25 22:56:55.000000000 +0200
+++ ./src/be.c 2018-10-10 19:27:18.681632689 +0200
@@ -117,9 +117,9 @@ static char* get_first_value(char** in){
#endif
-FILE* be_init(int inout,url_t* u,int iszipped)
+void* be_init(int inout,url_t* u,int iszipped)
{
- FILE* fh=NULL;
+ void* fh=NULL;
long a=0;
char* err=NULL;
int fd;
diff -up ./src/commandconf.c.coverity ./src/commandconf.c
--- ./src/commandconf.c.coverity 2016-07-25 22:56:55.000000000 +0200
+++ ./src/commandconf.c 2018-10-10 19:27:18.682632698 +0200
@@ -106,7 +106,7 @@ int commandconf(const char mode,const ch
rv=0;
} else {
- rv=access(config,R_OK);
+ if (config != NULL) rv=access(config,R_OK);
if(rv==-1){
error(0,_("Cannot access config file: %s: %s\n"),config,strerror(errno));
}
@@ -166,14 +166,11 @@ int commandconf(const char mode,const ch
int conf_input_wrapper(char* buf, int max_size, FILE* in)
{
int retval=0;
- int c=0;
- char* tmp=NULL;
- void* key=NULL;
- int keylen=0;
/* FIXME Add support for gzipped config. :) */
#ifdef WITH_MHASH
/* Read a character at a time until we are doing md */
+ int c=0;
if(conf->do_configmd){
retval=fread(buf,1,max_size,in);
}else {
@@ -185,6 +182,9 @@ int conf_input_wrapper(char* buf, int ma
#endif
#ifdef WITH_MHASH
+ char* tmp=NULL;
+ void* key=NULL;
+ int keylen=0;
if(conf->do_configmd||conf->config_check){
if(((conf->do_configmd==1)&&conf->config_check)||!conf->confmd){
if(conf->do_configmd==1){
@@ -276,6 +276,9 @@ int db_input_wrapper(char* buf, int max_
#endif
break;
}
+ default: {
+ return 0;
+ }
}
#ifdef WITH_CURL
@@ -651,7 +654,6 @@ int handle_endif(int doit,int allow_else
case 0 : {
conferror("@@endif or @@else expected");
return -1;
- count=0;
}
default : {
@@ -816,6 +818,7 @@ void do_dbdef(int dbtype,char* val)
if(u==NULL||u->type==url_unknown||u->type==url_stdout
||u->type==url_stderr) {
error(0,_("Unsupported input URL-type:%s\n"),val);
+ free(u);
}
else {
*conf_db_url=u;
@@ -825,6 +828,7 @@ void do_dbdef(int dbtype,char* val)
case DB_WRITE: {
if(u==NULL||u->type==url_unknown||u->type==url_stdin){
error(0,_("Unsupported output URL-type:%s\n"),val);
+ free(u);
}
else{
conf->db_out_url=u;
@@ -848,6 +852,7 @@ void do_dbindef(char* val)
if(u==NULL||u->type==url_unknown||u->type==url_stdout
||u->type==url_stderr) {
error(0,_("Unsupported input URL-type:%s\n"),val);
+ free(u);
}
else {
conf->db_in_url=u;
@@ -869,6 +874,7 @@ void do_dboutdef(char* val)
* both input and output urls */
if(u==NULL||u->type==url_unknown||u->type==url_stdin){
error(0,_("Unsupported output URL-type:%s\n"),val);
+ free(u);
}
else{
conf->db_out_url=u;
@@ -894,7 +900,8 @@ void do_repurldef(char* val)
} else {
error_init(u,0);
}
-
+
+ free(u);
}
void do_verbdef(char* val)
@@ -984,7 +991,7 @@ void do_report_ignore_e2fsattrs(char* va
break;
}
}
- *val++;
+ val++;
}
}
#endif
diff -up ./src/compare_db.c.coverity ./src/compare_db.c
--- ./src/compare_db.c.coverity 2018-10-10 19:27:18.673632619 +0200
+++ ./src/compare_db.c 2018-10-10 19:27:18.682632698 +0200
@@ -312,7 +312,7 @@ static int acl2array(acl_type* acl, char
if (conf->syslog_format) {
*values = malloc(2 * sizeof(char*));
- char *A, *D = "<NONE>";
+ char *A= "<NONE>", *D = "<NONE>";
if (acl->acl_a) { A = acl->acl_a; }
if (acl->acl_d) { D = acl->acl_d; }
diff -up ./src/conf_lex.l.coverity ./src/conf_lex.l
--- ./src/conf_lex.l.coverity 2018-10-10 19:27:18.673632619 +0200
+++ ./src/conf_lex.l 2018-10-10 19:27:18.682632698 +0200
@@ -133,7 +133,7 @@ int var_in_conflval=0;
<EXPR>[\ \t]*\n {
conf_lineno++;
return (TNEWLINE);
- BEGIN 0;
+// BEGIN 0;
}
<EXPR>\+ {
diff -up ./src/db.c.coverity ./src/db.c
--- ./src/db.c.coverity 2016-07-25 22:56:55.000000000 +0200
+++ ./src/db.c 2018-10-10 19:27:18.683632707 +0200
@@ -27,6 +27,7 @@
#include "db_file.h"
#include "db_disk.h"
#include "md.h"
+#include "fopen.h"
#ifdef WITH_PSQL
#include "db_sql.h"
@@ -269,6 +270,9 @@ db_line* db_readline(int db){
db_order=&(conf->db_new_order);
break;
}
+ default: {
+ return NULL;
+ }
}
switch (db_url->type) {
@@ -368,7 +372,7 @@ db_line* db_char2line(char** ss,int db){
int i;
db_line* line=(db_line*)malloc(sizeof(db_line)*1);
- int* db_osize=0;
+ int* db_osize=NULL;
DB_FIELD** db_order=NULL;
switch (db) {
@@ -382,6 +386,10 @@ db_line* db_char2line(char** ss,int db){
db_order=&(conf->db_new_order);
break;
}
+ default: {
+ free(line);
+ return NULL;
+ }
}
@@ -601,7 +609,9 @@ db_line* db_char2line(char** ss,int db){
size_t vsz = 0;
tval = strtok(NULL, ",");
- line->xattrs->ents[num].key = db_readchar(strdup(tval));
+ char * tmp = strdup(tval);
+ line->xattrs->ents[num].key = db_readchar(tmp);
+ free(tmp);
tval = strtok(NULL, ",");
val = base64tobyte(tval, strlen(tval), &vsz);
line->xattrs->ents[num].val = val;
@@ -648,6 +658,8 @@ db_line* db_char2line(char** ss,int db){
default : {
error(0,_("Not implemented in db_char2line %i \n"),(*db_order)[i]);
+ free_db_line(line);
+ free(line);
return NULL;
}
@@ -826,7 +838,7 @@ void db_close() {
case url_ftp:
{
if (conf->db_out!=NULL) {
- url_fclose(conf->db_out);
+ url_fclose((URL_FILE*)conf->db_out);
}
break;
}
diff -up ./src/db_disk.c.coverity ./src/db_disk.c
--- ./src/db_disk.c.coverity 2016-07-25 22:56:55.000000000 +0200
+++ ./src/db_disk.c 2018-10-10 19:28:00.108995089 +0200
@@ -79,9 +79,15 @@ static DIR *open_dir(char* path) {
static void next_in_dir (void)
{
+
#ifdef HAVE_READDIR_R
- if (dirh != NULL)
+ if (dirh != NULL) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
rdres = AIDE_READDIR_R_FUNC (dirh, entp, resp);
+#pragma GCC diagnostic pop
+ }
+
#else
#ifdef HAVE_READDIR
if (dirh != NULL) {
diff -up ./src/db_file.c.coverity ./src/db_file.c
--- ./src/db_file.c.coverity 2016-07-25 22:56:55.000000000 +0200
+++ ./src/db_file.c 2018-10-10 19:27:18.683632707 +0200
@@ -171,7 +171,7 @@ int dofprintf( const char* s,...)
int db_file_read_spec(int db){
int i=0;
- int* db_osize=0;
+ int* db_osize=NULL;
DB_FIELD** db_order=NULL;
switch (db) {
@@ -187,6 +187,9 @@ int db_file_read_spec(int db){
db_lineno=&db_new_lineno;
break;
}
+ default: {
+ return RETFAIL;
+ }
}
*db_order=(DB_FIELD*) malloc(1*sizeof(DB_FIELD));
@@ -198,13 +201,10 @@ int db_file_read_spec(int db){
int l;
- /* Yes... we do not check if realloc returns nonnull */
-
- *db_order=(DB_FIELD*)
- realloc((void*)*db_order,
+ void * tmp = realloc((void*)*db_order,
((*db_osize)+1)*sizeof(DB_FIELD));
-
- if(*db_order==NULL){
+ if (tmp != NULL) *db_order=(DB_FIELD*) tmp;
+ else {
return RETFAIL;
}
@@ -291,8 +291,8 @@ char** db_readline_file(int db){
int* domd=NULL;
#ifdef WITH_MHASH
MHASH* md=NULL;
-#endif
char** oldmdstr=NULL;
+#endif
int* db_osize=0;
DB_FIELD** db_order=NULL;
FILE** db_filep=NULL;
@@ -302,9 +302,9 @@ char** db_readline_file(int db){
case DB_OLD: {
#ifdef WITH_MHASH
md=&(conf->dboldmd);
+ oldmdstr=&(conf->old_dboldmdstr);
#endif
domd=&(conf->do_dboldmd);
- oldmdstr=&(conf->old_dboldmdstr);
db_osize=&(conf->db_in_size);
db_order=&(conf->db_in_order);
@@ -316,9 +316,9 @@ char** db_readline_file(int db){
case DB_NEW: {
#ifdef WITH_MHASH
md=&(conf->dbnewmd);
+ oldmdstr=&(conf->old_dbnewmdstr);
#endif
domd=&(conf->do_dbnewmd);
- oldmdstr=&(conf->old_dbnewmdstr);
db_osize=&(conf->db_new_size);
db_order=&(conf->db_new_order);
@@ -328,7 +328,9 @@ char** db_readline_file(int db){
break;
}
}
-
+
+ if (db_osize == NULL) return NULL;
+
if (*db_osize==0) {
db_buff(db,*db_filep);
@@ -737,8 +739,6 @@ int db_writespec_file(db_config* dbconf)
int i=0;
int j=0;
int retval=1;
- void*key=NULL;
- int keylen=0;
struct tm* st;
time_t tim=time(&tim);
st=localtime(&tim);
@@ -750,6 +750,8 @@ int db_writespec_file(db_config* dbconf)
#ifdef WITH_MHASH
/* From hereon everything must MD'd before write to db */
+ void*key=NULL;
+ int keylen=0;
if((key=get_db_key())!=NULL){
keylen=get_db_key_len();
dbconf->do_dbnewmd=1;
diff -up ./src/do_md.c.coverity ./src/do_md.c
--- ./src/do_md.c.coverity 2016-07-25 22:56:55.000000000 +0200
+++ ./src/do_md.c 2018-10-10 19:27:18.683632707 +0200
@@ -202,7 +202,6 @@ void calc_md(struct AIDE_STAT_TYPE* old_
and we don't read from a pipe :)
*/
struct AIDE_STAT_TYPE fs;
- int sres=0;
int stat_diff,filedes;
#ifdef WITH_PRELINK
pid_t pid;
@@ -237,7 +236,7 @@ void calc_md(struct AIDE_STAT_TYPE* old_
return;
}
- sres=AIDE_FSTAT_FUNC(filedes,&fs);
+ AIDE_FSTAT_FUNC(filedes,&fs);
if(!(line->attr&DB_RDEV))
fs.st_rdev=0;
@@ -331,7 +330,7 @@ void calc_md(struct AIDE_STAT_TYPE* old_
}
#endif
#endif /* not HAVE_MMAP */
- buf=malloc(READ_BLOCK_SIZE);
+// buf=malloc(READ_BLOCK_SIZE);
#if READ_BLOCK_SIZE>SSIZE_MAX
#error "READ_BLOCK_SIZE" is too large. Max value is SSIZE_MAX, and current is READ_BLOCK_SIZE
#endif
diff -up ./src/gen_list.c.coverity ./src/gen_list.c
--- ./src/gen_list.c.coverity 2016-07-25 22:56:55.000000000 +0200
+++ ./src/gen_list.c 2018-10-10 19:27:18.684632716 +0200
@@ -843,15 +843,15 @@ static void add_file_to_tree(seltree* tr
DB_ATTR_TYPE localignorelist=0;
DB_ATTR_TYPE ignored_added_attrs, ignored_removed_attrs, ignored_changed_attrs;
+ if(file==NULL){
+ error(0, "add_file_to_tree was called with NULL db_line\n");
+ }
+
node=get_seltree_node(tree,file->filename);
if(!node){
node=new_seltree_node(tree,file->filename,0,NULL);
}
-
- if(file==NULL){
- error(0, "add_file_to_tree was called with NULL db_line\n");
- }
/* add note to this node which db has modified it */
node->checked|=db;
diff -up ./src/md.c.coverity ./src/md.c
--- ./src/md.c.coverity 2018-10-10 19:27:18.679632672 +0200
+++ ./src/md.c 2018-10-10 19:27:18.684632716 +0200
@@ -36,8 +36,8 @@
*/
DB_ATTR_TYPE hash_gcrypt2attr(int i) {
- DB_ATTR_TYPE r=0;
#ifdef WITH_GCRYPT
+ DB_ATTR_TYPE r=0;
switch (i) {
case GCRY_MD_MD5: {
r=DB_MD5;
@@ -74,13 +74,15 @@ DB_ATTR_TYPE hash_gcrypt2attr(int i) {
default:
break;
}
-#endif
return r;
+#else /* !WITH_GCRYPT */
+ return 0;
+#endif
}
const char * hash_gcrypt2str(int i) {
- char * r = "?";
#ifdef WITH_GCRYPT
+ char * r = "?";
switch (i) {
case GCRY_MD_MD5: {
r = "MD5";
@@ -117,13 +119,17 @@ const char * hash_gcrypt2str(int i) {
default:
break;
}
-#endif
return r;
+#else /* !WITH_GCRYPT */
+ return "?";
+#endif
}
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
DB_ATTR_TYPE hash_mhash2attr(int i) {
- DB_ATTR_TYPE r=0;
#ifdef WITH_MHASH
+ DB_ATTR_TYPE r=0;
switch (i) {
case MHASH_CRC32: {
r=DB_CRC32;
@@ -198,10 +204,15 @@ DB_ATTR_TYPE hash_mhash2attr(int i) {
default:
break;
}
-#endif
+
return r;
+#else /*!WITH_MHASH */
+ return 0;
+#endif
}
+#pragma GCC diagnostic pop
+
/*
Initialise md_container according it's todo_attr field
*/
@@ -317,7 +328,6 @@ int init_md(struct md_container* md) {
*/
int update_md(struct md_container* md,void* data,ssize_t size) {
- int i;
error(255,"update_md called\n");
@@ -328,6 +338,7 @@ int update_md(struct md_container* md,vo
#endif
#ifdef WITH_MHASH
+ int i;
for(i=0;i<=HASH_MHASH_COUNT;i++) {
if (md->mhash_mdh[i]!=MHASH_FAILED) {
@@ -348,7 +359,6 @@ int update_md(struct md_container* md,vo
*/
int close_md(struct md_container* md) {
- int i;
#ifdef _PARAMETER_CHECK_
if (md==NULL) {
return RETFAIL;
@@ -356,6 +366,7 @@ int close_md(struct md_container* md) {
#endif
error(255,"close_md called \n");
#ifdef WITH_MHASH
+ int i;
for(i=0;i<=HASH_MHASH_COUNT;i++) {
if (md->mhash_mdh[i]!=MHASH_FAILED) {
mhash (md->mhash_mdh[i], NULL, 0);
diff -up ./src/util.c.coverity ./src/util.c
--- ./src/util.c.coverity 2018-10-10 19:27:18.670632593 +0200
+++ ./src/util.c 2018-10-10 19:27:18.684632716 +0200
@@ -105,13 +105,15 @@ url_t* parse_url(char* val)
for(i=0;r[0]!='/'&&r[0]!='\0';r++,i++);
if(r[0]=='\0'){
error(0,"Invalid file-URL,no path after hostname: file:%s\n",t);
+ free(hostname);
return NULL;
}
u->value=strdup(r);
r[0]='\0';
if(gethostname(hostname,MAXHOSTNAMELEN)==-1){
- strncpy(hostname,"localhost", 10);
+ strncpy(hostname,"localhost", 10);
}
+
if( (strcmp(t,"localhost")==0)||(strcmp(t,hostname)==0)){
free(hostname);
break;
@@ -120,7 +122,7 @@ url_t* parse_url(char* val)
free(hostname);
return NULL;
}
- free(hostname);
+
break;
}
u->value=strdup(r);

View File

@ -1,31 +0,0 @@
diff --up ./src/compare_db.c ./src/compare_db.c
--- ./src/compare_db.c
+++ ./src/compare_db.c
@@ -438,7 +438,11 @@ snprintf(*values[0], l, "%s",s);
} else {
*values = malloc(1 * sizeof (char*));
if (DB_FTYPE&attr) {
- easy_string(get_file_type_string(line->perm))
+ char *file_type = get_file_type_string(line->perm);
+ if (!file_type) {
+ error(2,"%s: ", file_type);
+ }
+ easy_string(file_type)
} else if (DB_LINKNAME&attr) {
easy_string(line->linkname)
easy_number((DB_SIZE|DB_SIZEG),size,"%li")
diff -up ./src/db_file.c ./src/db_file.c
--- ./src/db_file.c
+++ ./src/db_file.c
@@ -194,6 +194,10 @@ int db_file_read_spec(int db){
*db_order=(DB_FIELD*) malloc(1*sizeof(DB_FIELD));
+ if (*db_order == NULL){
+ error(1,"malloc for *db_order failed in %s", __func__);
+ }
+
while ((i=db_scan())!=TNEWLINE){
switch (i) {

View File

@ -1,63 +1,59 @@
Summary: Intrusion detection environment
Name: aide
Version: 0.16
Release: 15%{?dist}.2
URL: http://sourceforge.net/projects/aide
Version: 0.19.2
Release: 7%{?dist}
URL: https://github.com/aide/aide
License: GPLv2+
Source0: %{url}/files/aide/%{version}/%{name}-%{version}.tar.gz
Source1: aide.conf
Source2: README.quickstart
Source3: aide.logrotate
Source0: %{url}/releases/download/v%{version}/%{name}-%{version}.tar.gz
Source1: %{url}/releases/download/v%{version}/%{name}-%{version}.tar.gz.asc
# gpg2 --recv-keys 2BBBD30FAAB29B3253BCFBA6F6947DAB68E7B931
# gpg2 --export --export-options export-minimal 2BBBD30FAAB29B3253BCFBA6F6947DAB68E7B931 >gpgkey-aide.gpg
Source2: gpgkey-aide.gpg
Source3: aide.conf
Source4: README.quickstart
Source5: aide.logrotate
Source6: aide-tmpfiles.conf
Source7: aide-migrate-config
Source8: aide-check.service
Source9: aide-check.timer
Patch0: aide-0.19.2-syslog-format.patch
Patch1: aide-include-permission-checks.patch
BuildRequires: gcc
BuildRequires: make
BuildRequires: bison flex
BuildRequires: pcre-devel
BuildRequires: libgpg-error-devel libgcrypt-devel
BuildRequires: libgpg-error-devel nettle-devel
BuildRequires: zlib-devel
BuildRequires: libcurl-devel
BuildRequires: libacl-devel
BuildRequires: pkgconfig(libselinux)
BuildRequires: libattr-devel
BuildRequires: e2fsprogs-devel
Buildrequires: audit-libs-devel
Requires: libgcrypt >= 1.8.5
# Customize the database file location in the man page.
Patch1: aide-0.16rc1-man.patch
# fix aide in FIPS mode
Patch2: aide-0.16b1-fipsfix.patch
Patch3: aide-0.15-syslog-format.patch
Patch4: aide-0.16-crypto-disable-haval-and-others.patch
Patch5: coverity.patch
Patch6: aide-0.16-crash-elf.patch
# 1676487 - Null pointer dereference fix spotted by coverity
Patch7: coverity2.patch
# 2041957 - CVE-2021-45417 aide: heap-based buffer overflow on outputs larger than B64_BUF
Patch8: aide-0.16-CVE-2021-45417.patch
# CVE-2025-54389 aide: improper output neutralization enables bypassing
Patch9: aide-0.16-CVE-2025-54389.patch
Patch10: aide-0.16-CVE-2025-54389-part2.patch
BuildRequires: audit-libs-devel
BuildRequires: autoconf automake libtool autoconf-archive
BuildRequires: systemd-rpm-macros
# For verifying signatures
BuildRequires: gnupg2
%description
AIDE (Advanced Intrusion Detection Environment) is a file integrity
checker and intrusion detection program.
%prep
%{gpgverify} --keyring='%{SOURCE2}' --signature='%{SOURCE1}' --data='%{SOURCE0}'
%autosetup -p1
cp -a %{S:2} .
cp -a %{SOURCE4} .
%build
autoreconf -ivf
%configure \
--disable-static \
--with-config_file=%{_sysconfdir}/aide.conf \
--with-gcrypt \
--without-gcrypt \
--with-nettle \
--with-zlib \
--with-curl \
--with-posix-acl \
@ -65,84 +61,175 @@ cp -a %{S:2} .
--with-xattr \
--with-e2fsattrs \
--with-audit
%make_build
%install
%make_install bindir=%{_sbindir}
install -Dpm0644 -t %{buildroot}%{_sysconfdir} %{S:1}
install -Dpm0644 %{S:3} %{buildroot}%{_sysconfdir}/logrotate.d/aide
install -Dpm0644 -t %{buildroot}%{_sysconfdir} %{SOURCE3}
install -Dpm0644 %{SOURCE5} %{buildroot}%{_sysconfdir}/logrotate.d/aide
mkdir -p %{buildroot}%{_localstatedir}/log/aide
mkdir -p -m0700 %{buildroot}%{_localstatedir}/lib/aide
# Install tmpfiles config
install -Dpm0644 %{SOURCE6} %{buildroot}%{_tmpfilesdir}/aide.conf
install -Dpm0755 %{SOURCE7} %{buildroot}%{_sbindir}/aide-migrate-config
# Create /etc/aide.d/
mkdir -p -m0700 %{buildroot}%{_sysconfdir}/aide.d
# Install systemd timer and service for scheduled integrity checks
install -Dpm0644 %{SOURCE8} %{buildroot}%{_unitdir}/aide-check.service
install -Dpm0644 %{SOURCE9} %{buildroot}%{_unitdir}/aide-check.timer
%files
%license COPYING
%doc AUTHORS ChangeLog NEWS README doc/manual.html contrib/
%doc AUTHORS ChangeLog NEWS README
%doc README.quickstart
%{_sbindir}/aide
%{_sbindir}/aide-migrate-config
%{_mandir}/man1/*.1*
%{_mandir}/man5/*.5*
%config(noreplace) %attr(0600,root,root) %{_sysconfdir}/aide.conf
%config(noreplace) %{_sysconfdir}/logrotate.d/aide
%dir %attr(0700,root,root) %{_sysconfdir}/aide.d
%dir %attr(0700,root,root) %{_localstatedir}/lib/aide
%dir %attr(0700,root,root) %{_localstatedir}/log/aide
%{_tmpfilesdir}/aide.conf
%{_unitdir}/aide-check.service
%{_unitdir}/aide-check.timer
%post
%systemd_post aide-check.timer
if [ $1 -ge 2 ]; then
/usr/sbin/aide-migrate-config /etc/aide.conf 2>&1 | \
tee -a /var/log/aide/aide-migrate.log || :
fi
%preun
%systemd_preun aide-check.timer
%postun
%systemd_postun_with_restart aide-check.timer
%changelog
* Thu Aug 21 2025 Attila Lakatos <alakatos@redhat.com> - 0.16.15.2
* Tue Jun 03 2026 Patrik Koncity <pkoncity@redhat.com> - 0.19.2-7
- Support for included files in /etc/aide.d/
Resolves: RHEL-178122
- Increase default values for num_workers
Resolves: RHEL-178123
- Add pre-configured systemd timer for aide check
Resolves: RHEL-178121
* Tue May 26 2026 Attila Lakatos <alakatos@redhat.com> - 0.19.2-6
- Add aide-migrate-config to automate config migration from pre-0.19 syntax
Resolves: RHEL-178317
- Re-add syslog_format config option dropped during rebase to 0.19.2
* Wed Oct 15 2025 Attila Lakatos <alakatos@redhat.com> - 0.19.2-5
- Adjust default config to avoid false positives in /etc
Resolves: RHEL-83776
* Thu Oct 09 2025 Attila Lakatos <alakatos@redhat.com> - 0.19.2-4
- /boot/grub2/grubenv is excluded from check due to boot_success implementation
Resolves: RHEL-83776
* Tue Sep 30 2025 Attila Lakatos <alakatos@redhat.com> - 0.19.2-3
- Do not monitor link count in /var/log/journal
Resolves: RHEL-83776
* Thu Sep 25 2025 Attila Lakatos <alakatos@redhat.com> - 0.19.2-2
RHEL 9.8.0 ERRATUM
- Modernize config file
Resolves: RHEL-83776
* Tue Sep 16 2025 Attila Lakatos <alakatos@redhat.com> - 0.19.2-1
RHEL 9.8.0 ERRATUM
- rebase to 0.19.2
Resolves: RHEL-110573
- Switch to libnettle for hashing
- prevent aide from crashing if database is a HTTPS URL
Resolves: RHEL-76014
- prevent aide from exiting if a file is truncated during check
Resolves: RHEL-1569
* Wed Aug 20 2025 Attila Lakatos <alakatos@redhat.com> - 0.16-105
RHEL 9.7 ERRATUM
- CVE-2025-54389 aide: improper output neutralization enables bypassing
resolves: RHEL-109907
Resolves: RHEL-109912
* Tue Jan 25 2022 Radovan Sroka <rsroka@redhat.com> - 0.16.15
- backported fix for CVE-2021-45417
resolves: rhbz#2041957
* Wed Jan 15 2025 Radovan Sroka <rsroka@redhat.com> - 0.16-103
RHEL 9.6.0 ERRATUM
- /boot/grub2/grubenv's timestamp is getting modified continuously due to "boot_success" implementation
Resolves: RHEL-4331
* Tue Jun 30 2020 Radovan Sroka <rsroka@redhat.com> = 0.16.14
- strict require for libgcrypt
resolves: rhbz#1852407
* Fri May 17 2024 Radovan Sroka <rsroka@redhat.com> - 0.16-102
RHEL 9.5.0 ERRATUM
- aide fails with "Not enough parameters in db:15384. Trying to continue." unexpectedly
Resolves: RHEL-27606
- AIDE fails when using root_prefix option
Resolves: RHEL-28882
* Tue May 19 2020 Attila Lakatos <alakatos@redhat.com> - 0.16-13
- RHEL 8.3
- minor edit of aide.conf to make it consistent
resolves: rhbz#1740754
* Mon Jan 24 2022 Radovan Sroka <rsroka@redhat.com> - 0.16-100
- backport fix for CVE-2021-45417
Resolves: rhbz#2041950
* Mon Apr 06 2020 Attila Lakatos <alakatos@redhat.com> - 0.16-12
- RHEL 8.3
- minor edit of aide.conf
resolves: rhbz#1740754
- do not generate false warnings when report_ignore_e2fsattrs is specified in aide.conf
resolves: rhbz#1806323
* Mon Aug 09 2021 Mohan Boddu <mboddu@redhat.com> - 0.16-21
- Rebuilt for IMA sigs, glibc 2.34, aarch64 flags
Related: rhbz#1991688
* Wed Jul 24 2019 Radovan Sroka <rsroka@redhat.com> - 0.16-11
- rebuild
- minor edit of aide.conf
* Thu May 27 2021 Zoltan Fridrich <zfridric@redhat.com> - 0.16-20
- fix configuration option with-dbhmactype
- do not use sha1 and md5 by default
Resolves: rhbz#1935457
- fix important static analysis issues
Resolves: rhbz#1938676
* Tue Jul 23 2019 Radovan Sroka <rsroka@redhat.com> - 0.16-10
- respin
- minor edit of aide.conf
* Mon May 10 2021 Zoltan Fridrich <zfridric@redhat.com> - 0.16-19
- use gating and config file from rhel-8.5
- remove check of periodically changing files
Resolves: rhbz#1957656
- config cleanup
Resolves: rhbz#1957654
* Tue Jul 23 2019 Radovan Sroka <rsroka@redhat.com> - 0.16-9
- Null pointer dereference fix spotted by coverity
resolves: rhbz#1676487
- aide.conf needs updates for RHEL 8
resolves: rhbz#1708015
* Thu Apr 15 2021 Mohan Boddu <mboddu@redhat.com> - 0.16-18
- Rebuilt for RHEL 9 BETA on Apr 15th 2021. Related: rhbz#1947937
* Tue Oct 09 2018 Radovan Sroka <rsroka@redhat.com> - 0.16-8
- fixed wrong line wrapping of messages in the syslog format
resolves: rhbz#1628153
- fixed coverity issues
resolves: rhbz#1602441
- fixed crash when processing .dynamic section
resolves: rhbz#1597250
* Mon Jan 25 2021 Fedora Release Engineering <releng@fedoraproject.org> - 0.16-17
- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild
* Wed Aug 29 2018 Radovan Sroka <rsroka@redhat.com> - 0.16-7
- fixed crypto problem with libgcrypt (fips)
- resolves: rhbz#1623045
* Fri Jul 31 2020 Fedora Release Engineering <releng@fedoraproject.org> - 0.16-16
- Second attempt - Rebuilt for
https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
* Wed Aug 22 2018 Radovan Sroka <rsroka@redhat.com> - 0.16-6
- ported syslog format from rhel7
resolves: rhbz#1584136
- fixed crypto problem with libgcrypt
resolves: rhbz#1584120
* Mon Jul 27 2020 Fedora Release Engineering <releng@fedoraproject.org> - 0.16-15
- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
* Wed Jun 24 2020 Radovan Sroka <rsroka@redhat.com> 0.16-14
- AIDE breaks when setting report_ignore_e2fsattrs
Resolves: rhbz#1850276
* Tue Jan 28 2020 Fedora Release Engineering <releng@fedoraproject.org> - 0.16-13
- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
* Wed Jul 31 2019 Radovan Sroka <rsroka@redhat.com> - 0.16-12
- backport some patches
Resolves: rhbz#1717140
* Wed Jul 24 2019 Fedora Release Engineering <releng@fedoraproject.org> - 0.16-11
- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild
* Wed Feb 20 2019 Daniel Kopecek <dkopecek@redhat.com> - 0.16-10
- Fix building with curl
Resolves: rhbz#1674637
* Thu Jan 31 2019 Fedora Release Engineering <releng@fedoraproject.org> - 0.16-9
- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild
* Tue Jul 31 2018 Florian Weimer <fweimer@redhat.com> - 0.16-8
- Rebuild with fixed binutils
* Thu Jul 12 2018 Fedora Release Engineering <releng@fedoraproject.org> - 0.16-7
- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
* Tue Feb 20 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 0.16-6
- Rebuild
* Wed Feb 07 2018 Fedora Release Engineering <releng@fedoraproject.org> - 0.16-5
- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild