> Hello, > the code looks reasonable, some minor comments are below. I'll let Steve and > others comment on the high-level design (just to point out a question, is it > OK that auditctl will depend on sqlite?). > Mirek Changes were applied according to the comments.
> I didn't look in detail, this does not match my understanding of > "reset_vars()"; reset_vars() is supposed to reinitialize everything for a > next command, not free everything. (The free(rule_new) call you moved from > reset_vars() to free_vars() was at the beginning of reset_vars(), not at the > end.) I renamed "reset_vars()" to "init_vars()" so it could be clear enough. Signed-off-by: Juraj Hlista <[email protected]> --- audit.spec | 2 + lib/Makefile.am | 4 +- lib/errormsg.h | 7 +- lib/fieldtab.h | 2 +- lib/libaudit.c | 26 ++++ lib/libaudit.h | 4 + lib/msg_typetab.h | 1 + lib/reactarray.c | 77 +++++++++++ lib/reactarray.h | 41 ++++++ src/Makefile.am | 7 +- src/auditctl-reactsql.c | 332 +++++++++++++++++++++++++++++++++++++++++++++++ src/auditctl-reactsql.h | 55 ++++++++ src/auditctl.c | 240 +++++++++++++++++++++++++++++++--- src/mt/Makefile.am | 4 +- 14 files changed, 776 insertions(+), 26 deletions(-) create mode 100644 lib/reactarray.c create mode 100644 lib/reactarray.h create mode 100644 src/auditctl-reactsql.c create mode 100644 src/auditctl-reactsql.h diff --git a/audit.spec b/audit.spec index a7a94c4..af3ee43 100644 --- a/audit.spec +++ b/audit.spec @@ -82,6 +82,7 @@ mkdir -p $RPM_BUILD_ROOT/%{_mandir}/{man5,man8} mkdir -p $RPM_BUILD_ROOT/%{_lib} mkdir -p $RPM_BUILD_ROOT/%{_libdir}/audit mkdir -p $RPM_BUILD_ROOT/%{_var}/log/audit +mkdir -p $RPM_BUILD_ROOT/%{_var}/run/auditctl make DESTDIR=$RPM_BUILD_ROOT install mkdir -p $RPM_BUILD_ROOT/%{_libdir} @@ -187,6 +188,7 @@ fi %attr(755,root,root) %{_bindir}/ausyscall %attr(755,root,root) /etc/rc.d/init.d/auditd %attr(750,root,root) %{_var}/log/audit +%attr(750,root,root) %{_var}/run/auditctl %attr(750,root,root) %dir /etc/audit %attr(750,root,root) %dir /etc/audisp %attr(750,root,root) %dir /etc/audisp/plugins.d diff --git a/lib/Makefile.am b/lib/Makefile.am index c5952f9..998215c 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -30,8 +30,8 @@ INCLUDES = -I. -I${top_srcdir} -I${top_srcdir}/auparse lib_LTLIBRARIES = libaudit.la include_HEADERS = libaudit.h libaudit_la_SOURCES = libaudit.c message.c netlink.c \ - lookup_table.c audit_logging.c deprecated.c \ - private.h errormsg.h + lookup_table.c audit_logging.c deprecated.c reactarray.c \ + reactarray.h private.h errormsg.h libaudit_la_LIBADD = libaudit_la_DEPENDENCIES = $(libaudit_la_SOURCES) ../config.h libaudit_la_LDFLAGS = -Wl,-z,relro -version-info $(VERSION_INFO) diff --git a/lib/errormsg.h b/lib/errormsg.h index 625611b..e6d78a9 100644 --- a/lib/errormsg.h +++ b/lib/errormsg.h @@ -54,5 +54,10 @@ static const struct msg_tab err_msgtab[] = { { -19, 0, "Key field needs a watch or syscall given prior to it" }, { -20, 2, "-F missing value after operation for" }, { -21, 2, "-F value should be number for" }, - { -22, 2, "-F missing field name before operator for" } + { -22, 2, "-F missing field name before operator for" }, + { -23, 0, "Too many reactions" }, + { -24, 0, "Out of memory adding reaction" }, + { -25, 0, "React field needs a watch or syscall given prior to it" }, + { -26, 0, "Bad operation used with react field" }, + { -27, 0, "Failed converting react string to number" } }; diff --git a/lib/fieldtab.h b/lib/fieldtab.h index ad95814..a973734 100644 --- a/lib/fieldtab.h +++ b/lib/fieldtab.h @@ -62,4 +62,4 @@ _S(AUDIT_ARG2, "a2" ) _S(AUDIT_ARG3, "a3" ) _S(AUDIT_FILTERKEY, "key" ) - +_S(AUDIT_REACTION, "react" ) diff --git a/lib/libaudit.c b/lib/libaudit.c index 337d1d2..ec18f5e 100644 --- a/lib/libaudit.c +++ b/lib/libaudit.c @@ -41,6 +41,7 @@ #include "libaudit.h" #include "private.h" #include "errormsg.h" +#include "reactarray.h" /* #defines for the audit failure query */ #define CONFIG_FILE "/etc/libaudit.conf" @@ -80,6 +81,7 @@ static const struct nv_list failure_actions[] = int audit_permadded hidden = 0; int audit_archadded hidden = 0; int audit_syscalladded hidden = 0; +struct react_array ra hidden; unsigned int audit_elf hidden = 0U; static struct libaudit_conf config; @@ -791,6 +793,7 @@ int audit_rule_fieldpair_data(struct audit_rule_data **rulep, const char *pair, int vlen; int offset; struct audit_rule_data *rule = *rulep; + uint32_t react_num; if (f == NULL) return -1; @@ -845,6 +848,21 @@ int audit_rule_fieldpair_data(struct audit_rule_data **rulep, const char *pair, /* Exclude filter can be used only with MSGTYPE field */ if (flags == AUDIT_FILTER_EXCLUDE && field != AUDIT_MSGTYPE) return -12; + /* reaction string identifiers are stored in an array at first */ + if (field == AUDIT_REACTION && !ra.add_to_rule) { + if (!audit_syscalladded && !audit_permadded) + return -25; + if (op != AUDIT_EQUAL) + return -26; + vlen = strlen(v); + if (vlen > AUDIT_MAX_KEY_LEN) + return -11; + if (ra.count >= AUDIT_MAX_REACTS) + return -23; + if (react_array_insert(&ra, v)) + return -24; + return 0; + } rule->fields[rule->field_count] = field; rule->fieldflags[rule->field_count] = op; @@ -965,6 +983,14 @@ int audit_rule_fieldpair_data(struct audit_rule_data **rulep, const char *pair, strncpy(&rule->buf[offset], v, vlen); break; + case AUDIT_REACTION: + /* string identifiers were converted to numbers */ + if (isdigit((unsigned char)*(v))) + react_num = (uint32_t)strtoul(v, NULL, 0); + else + return -27; + rule->values[rule->field_count] = react_num; + break; case AUDIT_ARCH: if (audit_syscalladded) return -3; diff --git a/lib/libaudit.h b/lib/libaudit.h index e0a1510..f3ff84c 100644 --- a/lib/libaudit.h +++ b/lib/libaudit.h @@ -203,6 +203,10 @@ extern "C" { /* This is related to the filterkey patch */ #define AUDIT_KEY_SEPARATOR 0x01 +#define AUDIT_MAX_REACTS 8 +#define AUDIT_REACTION 220 +#define AUDIT_REACT_RULE 1323 + /* These are used in filter control */ #define AUDIT_FILTER_EXCLUDE AUDIT_FILTER_TYPE #define AUDIT_FILTER_MASK 0x07 /* Mask to get actual filter */ diff --git a/lib/msg_typetab.h b/lib/msg_typetab.h index 017bb27..dcbc8da 100644 --- a/lib/msg_typetab.h +++ b/lib/msg_typetab.h @@ -102,6 +102,7 @@ _S(AUDIT_TTY, "TTY" ) _S(AUDIT_EOE, "EOE" ) _S(AUDIT_BPRM_FCAPS, "BPRM_FCAPS" ) _S(AUDIT_CAPSET, "CAPSET" ) +_S(AUDIT_REACT_RULE, "REACT_RULE" ) _S(AUDIT_AVC, "AVC" ) _S(AUDIT_SELINUX_ERR, "SELINUX_ERR" ) _S(AUDIT_AVC_PATH, "AVC_PATH" ) diff --git a/lib/reactarray.c b/lib/reactarray.c new file mode 100644 index 0000000..98fd6ba --- /dev/null +++ b/lib/reactarray.c @@ -0,0 +1,77 @@ +/* reactarray.c + * Copyright 2010 Juraj Hlista + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Authors: + * Juraj Hlista <[email protected]> + */ + +#include <stdlib.h> +#include <string.h> +#include "reactarray.h" + +/* + * Allocation and initialiazation of the array for + * reaction identifiers + */ +int react_array_init(struct react_array *a, const unsigned int size) +{ + int i; + + a->add_to_rule = 0; + a->processed = 0; + a->count = 0; + a->size = size; + a->str = calloc(size, sizeof(char *)); + if (!a->str) + return 1; + + return 0; +} + +/* + * Free identifiers + */ +void react_array_free(struct react_array *a) +{ + int i; + + if (!a->str) + return; + + for (i = 0; i < a->count; i++) + free(a->str[i]); + + free(a->str); +} + +/* + * Insert a string identifier into the array + */ +int react_array_insert(struct react_array *a, const char *s) +{ + /* error code reaturned in libaudit.c */ + if (a->count >= a->size) + return 0; + + a->str[a->count] = strdup(s); + if (!a->str[a->count]) + return 1; + a->count++; + + return 0; +} + diff --git a/lib/reactarray.h b/lib/reactarray.h new file mode 100644 index 0000000..904be95 --- /dev/null +++ b/lib/reactarray.h @@ -0,0 +1,41 @@ +/* reactarray.h + * Copyright 2010 Juraj Hlista + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Authors: + * Juraj Hlista <[email protected]> + */ + +#ifndef _REACTARRAY_H_ +#define _REACTARRAY_H_ + +struct react_array { + int add_to_rule; /* if 0 - identifiers are stored in the array */ + int processed; /* number of reactions per 1 rule stored in database */ + unsigned int count; /* number of reactions kept in this structure */ + unsigned int size; /* max number of reactions per 1 rule */ + char **str; /* identifiers */ +}; + + +int react_array_init(struct react_array *a, unsigned int size); + +void react_array_free(struct react_array *a); + +int react_array_insert(struct react_array *a, const char *s); + +#endif + diff --git a/src/Makefile.am b/src/Makefile.am index 124b77e..2bc1ff4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -28,7 +28,7 @@ sbin_PROGRAMS = auditd auditctl aureport ausearch autrace LIBS = -Lmt -lauditmt LDADD = -lpthread AM_CFLAGS = -D_REENTRANT -D_GNU_SOURCE -noinst_HEADERS = auditd-config.h auditd-event.h auditd-listen.h ausearch-llist.h ausearch-options.h auditctl-llist.h aureport-options.h ausearch-parse.h aureport-scan.h ausearch-lookup.h ausearch-int.h auditd-dispatch.h ausearch-string.h ausearch-nvpair.h ausearch-common.h ausearch-avc.h ausearch-time.h ausearch-lol.h +noinst_HEADERS = auditd-config.h auditd-event.h auditd-listen.h ausearch-llist.h ausearch-options.h auditctl-llist.h auditctl-reactsql.h aureport-options.h ausearch-parse.h aureport-scan.h ausearch-lookup.h ausearch-int.h auditd-dispatch.h ausearch-string.h ausearch-nvpair.h ausearch-common.h ausearch-avc.h ausearch-time.h ausearch-lol.h auditd_SOURCES = auditd.c auditd-event.c auditd-config.c auditd-reconfig.c auditd-sendmail.c auditd-dispatch.c auditd-listen.c auditd_CFLAGS = -fPIE -DPIE -g -D_REENTRANT -D_GNU_SOURCE -fno-strict-aliasing @@ -36,8 +36,9 @@ auditd_LDFLAGS = -pie -Wl,-z,relro auditd_DEPENDENCIES = mt/libauditmt.a libev/libev.a auditd_LDADD = @LIBWRAP_LIBS@ @libev_LIBS@ -Llibev -lev -lrt -lpthread -lm $(gss_libs) -auditctl_SOURCES = auditctl.c auditctl-llist.c delete_all.c -auditctl_DEPENDENCIES = mt/libauditmt.a +auditctl_SOURCES = auditctl.c auditctl-llist.c delete_all.c auditctl-reactsql.c +auditctl_DEPENDENCIES = mt/libauditmt.a +auditctl_LDADD = -lsqlite3 aureport_SOURCES = aureport.c auditd-config.c ausearch-llist.c aureport-options.c ausearch-string.c ausearch-parse.c aureport-scan.c aureport-output.c ausearch-lookup.c ausearch-int.c ausearch-time.c ausearch-nvpair.c ausearch-avc.c ausearch-lol.c aureport_DEPENDENCIES = mt/libauditmt.a diff --git a/src/auditctl-reactsql.c b/src/auditctl-reactsql.c new file mode 100644 index 0000000..f7e921e --- /dev/null +++ b/src/auditctl-reactsql.c @@ -0,0 +1,332 @@ +/* auditctl-reactsql.c + * Copyright 2010 Juraj Hlista + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Authors: + * Juraj Hlista <[email protected]> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "auditctl-reactsql.h" + +#define SQL_OFFSET 10000 + +const char *sql_errmsg[] = { + "", "", + "SQL query suppossed to return a value", + "Out of memory allocating reaction string", + "React number reached maximal value" +}; + +static int sql_table_check(sqlite3 *c); + +/* + * Print an error + */ +void sql_print_error(sqlite3 *c, int err) +{ + if (err == -SQL_ERROR) + fprintf(stderr, "SQLite error: %s\n", sqlite3_errmsg(c)); + else + fprintf(stderr, "SQLite error: %s\n", sql_errmsg[-err]); +} + +/* + * Open a database file and check if table exists - if not, create it + */ +int sql_open_database(sqlite3 **c, const char *db) +{ + if (sqlite3_open(db, c) || sql_table_check(*c)) + return -SQL_ERROR; + + return 0; +} + +/* + * Close database + */ +int sql_close_database(sqlite3 *c) +{ + if (sqlite3_close(c)) + return -SQL_ERROR; + + return 0; +} + +/* + * Get reaction string + */ +int sql_number_to_reaction(sqlite3 *c, const int num, char **str) +{ + const char *reaction = NULL; + sqlite3_stmt *find_str; + const char *query = "SELECT string FROM reaction WHERE number = ?"; + + if (sqlite3_prepare(c, query, -1, &find_str, NULL)) + return -SQL_ERROR; + + if (sqlite3_bind_int(find_str, 1, num)) + return -SQL_ERROR; + + if (sqlite3_step(find_str) != SQLITE_ROW) { + sqlite3_finalize(find_str); + return -SQL_NO_VALUE; + } + + reaction = (const char *)sqlite3_column_text(find_str, 0); + *str = strdup(reaction); + if (*str == NULL) { + sqlite3_finalize(find_str); + return -SQL_NO_MEMORY; + } + + if (sqlite3_finalize(find_str)) + return -SQL_ERROR; + + return 0; +} + +/* + * Get reaction number + */ +int sql_reaction_to_number(sqlite3 *c, const char *str, int *num) +{ + sqlite3_stmt *find_num; + const char *query = "SELECT number FROM reaction WHERE string = ?"; + + if (sqlite3_prepare(c, query, -1, &find_num, NULL)) + return -SQL_ERROR; + + if (sqlite3_bind_text(find_num, 1, str, -1, NULL)) + return -SQL_ERROR; + + if (sqlite3_step(find_num) != SQLITE_ROW) { + sqlite3_finalize(find_num); + return -SQL_NO_VALUE; + } + + *num = sqlite3_column_int(find_num, 0); + + if (sqlite3_finalize(find_num)) + return -SQL_ERROR; + + return 0; +} + +/* + * Add a reaction to the database - if num->action is UPDATE, + * a reaction identifier (string) is already in the database and only + * 'used' is incremented. If there is not such a reaction string, a new + * one is inserted into the database and 'used' is set to 1. + */ +int sql_add_reaction(sqlite3 *c, const struct react_number *num, const char *str) +{ + sqlite3_stmt *change; + + /* update table */ + if (num->action == UPDATE) { + const char *query = "UPDATE reaction SET " + "string = ?, used = used + 1 " + "WHERE number = ?"; + + if (sqlite3_prepare(c, query, -1, &change, NULL)) + return -SQL_ERROR; + + if (sqlite3_bind_text(change, 1, str, -1, NULL)) + return -SQL_ERROR; + + if (sqlite3_bind_int(change, 2, num->number)) + return -SQL_ERROR; + } + /* insert into table */ + else if (num->action == INSERT) { + const char *query = "INSERT INTO reaction VALUES(?, ?, 1)"; + + if (sqlite3_prepare(c, query, -1, &change, NULL)) + return -SQL_ERROR; + + if (sqlite3_bind_int(change, 1, num->number)) + return -SQL_ERROR; + + if (sqlite3_bind_text(change, 2, str, -1, NULL)) + return -SQL_ERROR; + } + + sqlite3_step(change); + + if (sqlite3_finalize(change)) + return -SQL_ERROR; + + return 0; +} + +/* + * Delete reaction by decreasing of used + */ +int sql_del_reaction(sqlite3 *c, const char *str) +{ + sqlite3_stmt *change; + const char *query = "UPDATE reaction SET used = used - 1 " + "WHERE string = ?"; + + if (sqlite3_prepare(c, query, -1, &change, NULL)) + return -SQL_ERROR; + + if (sqlite3_bind_text(change, 1, str, -1, NULL)) + return -SQL_ERROR; + + sqlite3_step(change); + + if (sqlite3_finalize(change)) + return -SQL_ERROR; + + return 0; +} + +/* + * Drop table + */ +int sql_del_reaction_all(sqlite3 *c) +{ + sqlite3_stmt *drop; + const char *query = "DROP TABLE reaction"; + + if (sqlite3_prepare(c, query, -1, &drop, NULL)) + return -SQL_ERROR; + + sqlite3_step(drop); + + if (sqlite3_finalize(drop)) + return -SQL_ERROR; + + return 0; +} + +/* + * This function must be called before adding reactions to the database. + */ +struct react_number sql_get_next_number(sqlite3 *c, const char *str) +{ + int x; + struct react_number result = {ERROR, 0}; + sqlite3_stmt *get_num; + /* if table is empty, return 1 + * if a reaction 'string' is in the table, return the string's 'number' + * if 'used' is 0, return 'number' in this row + * return max 'number' + 1 otherwise + * number 10000 must be the same as SQL_OFFSET + */ + const char *query = "SELECT COALESCE " + "(CASE WHEN A.cnt = 0 THEN 1 END, " + "B.num + 10000, C.num + 10000, D.num) " + "FROM " + "(SELECT COUNT(*) AS cnt FROM reaction) AS A, " + "(SELECT MAX(number) AS num " + "FROM reaction WHERE string = ?) AS B, " + "(SELECT MIN(number) AS num " + "FROM reaction WHERE used = 0) AS C, " + "(SELECT (MAX(number) + 1) AS num " + "FROM reaction) AS D"; + + if (sqlite3_prepare(c, query, -1, &get_num, NULL)) { + result.number -SQL_ERROR; + return result; + } + + if (sqlite3_bind_text(get_num, 1, str, -1, NULL)) { + result.number = -SQL_ERROR; + return result; + } + + if (sqlite3_step(get_num) != SQLITE_ROW) { + sqlite3_finalize(get_num); + result.number = -SQL_NO_VALUE; + return result; + } + + result.number = sqlite3_column_int(get_num, 0); + + if (sqlite3_finalize(get_num)) { + result.number = -SQL_ERROR; + return result; + } + + x = result.number - SQL_OFFSET; + /* there are SQL_OFFSET - 1 numbers to be used with reaction strings */ + if (!x || x >= SQL_OFFSET) { + result.number = -SQL_MAX_NUMBER; + return result; + } else { + if (x < 0) { + result.action = INSERT; + } else { + result.action = UPDATE; + result.number = x; + } + } + + return result; +} + +/* + * Check if table exists, if not, a new one is created. + */ +static int sql_table_check(sqlite3 *c) +{ + int rc; + sqlite3_stmt *check, *create; + + const char *query1 = "SELECT name " + "FROM sqlite_master " + "WHERE type='table' AND name='reaction'"; + + /* check if table exists */ + if (sqlite3_prepare(c, query1, -1, &check, NULL)) + return -SQL_ERROR; + + rc = sqlite3_step(check); + + if (sqlite3_finalize(check)) + return -SQL_ERROR; + + /* table doesn't exist, create table */ + if (rc != SQLITE_ROW) { + /* number - the value that is in the kernel + * string - reaction identifier + * used - how many times is the reaction used with rules + */ + const char *query2 = "CREATE TABLE reaction " + "(number INTEGER NOT NULL " + "CHECK (number > 0), " + "string VARCHAR(255) NOT NULL, " + "used INTEGER CHECK (used >= 0), " + "UNIQUE(number), " + "UNIQUE(string))"; + + if (sqlite3_prepare(c, query2, -1, &create, NULL)) + return -SQL_ERROR; + + sqlite3_step(create); + + if (sqlite3_finalize(create)) + return -SQL_ERROR; + } + + return 0; +} + diff --git a/src/auditctl-reactsql.h b/src/auditctl-reactsql.h new file mode 100644 index 0000000..8c70a31 --- /dev/null +++ b/src/auditctl-reactsql.h @@ -0,0 +1,55 @@ +/* auditctl-reactsql.h + * Copyright 2010 Juraj Hlista + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Authors: + * Juraj Hlista <[email protected]> + */ + +#ifndef CTLREACTSQL_HEADER +#define CTLREACTSQL_HEADER + +#include <sqlite3.h> + +struct react_number { + enum { + ERROR, + INSERT, + UPDATE + } action; + int number; +}; + +enum { + SQL_ERROR = 1, + SQL_NO_VALUE, + SQL_NO_MEMORY, + SQL_MAX_NUMBER +}; + + +int sql_open_database(sqlite3 **c, const char *db); + +int sql_close_database(sqlite3 *c); + +int sql_add_reaction(sqlite3 *c, const struct react_number *num, const char *str); + +int sql_del_reaction(sqlite3 *c, const char *str); + +struct react_number sql_get_next_number(sqlite3 *c, const char *str); + +#endif + diff --git a/src/auditctl.c b/src/auditctl.c index 03cac39..def078d 100644 --- a/src/auditctl.c +++ b/src/auditctl.c @@ -37,6 +37,8 @@ #include <limits.h> /* PATH_MAX */ #include "libaudit.h" #include "private.h" +#include "auditctl-reactsql.h" +#include "reactarray.h" /* This define controls how many rule options we will allow when * reading a rule from a file. 64 fields are allowed by the kernel, so I @@ -50,6 +52,8 @@ */ #define LINE_SIZE 1600 +/* Database file where mapping of reaction strings to numbers is stored */ +#define REACT_DB "/var/run/auditctl/react.db" /* Global functions */ static int handle_request(int status); @@ -73,14 +77,15 @@ static const char key_sep[2] = { AUDIT_KEY_SEPARATOR, 0 }; /* External vars */ extern int audit_archadded; extern int audit_syscalladded; +extern struct react_array ra; extern unsigned int audit_elf; extern int audit_permadded; /* - * This function will reset everything used for each loop when loading - * a ruleset from a file. + * This function will init everything used for each loop when loading + * rules */ -static int reset_vars(void) +static int init_vars(void) { list_requested = 0; audit_syscalladded = 0; @@ -92,19 +97,32 @@ static int reset_vars(void) action = -1; exclude = 0; multiple = 0; - - free(rule_new); rule_new = malloc(sizeof(struct audit_rule_data)); + if (!rule_new) { + fprintf(stderr, "Out of memory allocating new rule\n"); + return 1; + } memset(rule_new, 0, sizeof(struct audit_rule_data)); + if (react_array_init(&ra, AUDIT_MAX_REACTS)) { + fprintf(stderr, "Out of memory allocating reaction array\n"); + return 1; + } if (fd < 0) { if ((fd = audit_open()) < 0) { fprintf(stderr, "Cannot open netlink audit socket\n"); return 1; } } + return 0; } +static void free_vars(void) +{ + react_array_free(&ra); + free(rule_new); +} + static void usage(void) { printf( @@ -463,6 +481,28 @@ void check_rule_mismatch(int lineno, const char *option) } } +/* + * Remove reactions from database + */ +static int db_del_reacts(struct react_array *arr) +{ + int rc, i; + sqlite3 *conn; + + if (sql_open_database(&conn, REACT_DB) < 0) + return 1; + for (i = 0; i < arr->count; i++) { + if (sql_del_reaction(conn, arr->str[i]) < 0) { + sql_close_database(conn); + return 1; + } + } + sql_close_database(conn); + + return 0; +} + + // FIXME: Change these to enums /* * returns: -3 deprecated, -2 success - no reply, -1 error - noreply, @@ -731,8 +771,8 @@ static int setopt(int count, int lineno, char *vars[]) audit_number_to_errmsg(rc, optarg); retval = -1; } else { - if (rule_new->fields[rule_new->field_count-1] == - AUDIT_PERM) + if (rule_new->field_count > 0 && + rule_new->fields[rule_new->field_count - 1] == AUDIT_PERM) audit_permadded = 1; } @@ -772,6 +812,21 @@ static int setopt(int count, int lineno, char *vars[]) } retval = delete_all_rules(fd); if (retval == 0) { + sqlite3 *conn; + rc = sql_open_database(&conn, REACT_DB); + if (rc < 0) { + sql_print_error(conn, rc); + retval = -1; + break; + } + rc = sql_del_reaction_all(conn); + if (rc < 0) { + sql_print_error(conn, rc); + sql_close_database(conn); + retval = -1; + break; + } + sql_close_database(conn); audit_request_rule_list(fd); key[0] = 0; retval = -2; @@ -917,6 +972,93 @@ static int setopt(int count, int lineno, char *vars[]) retval = -1; } } + + /* If there are any react fields, reaction string(s) is/are stored in the + * array and need to be converted to numbers. Mapping string <-> number is + * kept in a SQLite database file. Every insert/update is dependent on the + * previous insert/update. + */ + if (ra.count && retval >= 0) { + int i; + char *cmd = NULL; + int flags = 0; + sqlite3 *conn; + + if (add != AUDIT_FILTER_UNSET) + flags = add & AUDIT_FILTER_MASK; + else if (del != AUDIT_FILTER_UNSET) + flags = del & AUDIT_FILTER_MASK; + + rc = sql_open_database(&conn, REACT_DB); + if (rc < 0) { + sql_print_error(conn, rc); + return -4; + } + + ra.add_to_rule = 1; + for (i = 0; i < ra.count; i++) { + /* add rule */ + if (add != AUDIT_FILTER_UNSET) { + struct react_number num; + /* get a number for the reaction string */ + num = sql_get_next_number(conn, ra.str[i]); + if (num.action == ERROR) { + sql_print_error(conn, num.number); + sql_close_database(conn); + return -4; + } + asprintf(&cmd, "react=%u", num.number); + if (!cmd) { + fprintf(stderr, + "Out of memory adding reaction\n"); + sql_close_database(conn); + return -4; + } + rc = audit_rule_fieldpair_data(&rule_new, cmd, flags); + free(cmd); + if (rc < 0) { + audit_number_to_errmsg(rc, NULL); + sql_close_database(conn); + return -4; + } + rc = sql_add_reaction(conn, &num, ra.str[i]); + if (rc < 0) { + sql_print_error(conn, rc); + sql_close_database(conn); + return -4; + } + /* In case an error occurs, keep the number of + * successfully inserted/updated reactions, + * so that these changes can be rolled back. + */ + ra.processed++; + /* delete rule */ + } else if (del != AUDIT_FILTER_UNSET) { + int del_num; + rc = sql_reaction_to_number(conn, ra.str[i], &del_num); + if (rc < 0) { + sql_print_error(conn, rc); + sql_close_database(conn); + return -4; + } + asprintf(&cmd, "react=%u", del_num); + if (!cmd) { + fprintf(stderr, + "Out of memory adding reaction\n"); + sql_close_database(conn); + return -4; + } + rc = audit_rule_fieldpair_data(&rule_new, cmd, flags); + if (rc < 0) { + audit_number_to_errmsg(rc, NULL); + sql_close_database(conn); + return -4; + } + } + } + sql_close_database(conn); + } + if (retval == -1 && errno == ECONNREFUSED) fprintf(stderr, "The audit system is disabled\n"); return retval; @@ -1021,7 +1163,8 @@ static int fileopt(const char *file) options[i] = NULL; /* Parse it */ - if (reset_vars()) { + if (init_vars()) { + free_vars(); fclose(f); return -1; } @@ -1045,6 +1188,8 @@ static int fileopt(const char *file) return -1; } } + } else { + free_vars(); } lineno++; } @@ -1085,13 +1230,13 @@ int main(int argc, char *argv[]) else return 0; } else { - if (reset_vars()) { - free(rule_new); + if (init_vars()) { + free_vars(); return 1; } retval = setopt(argc, 0, argv); if (retval == -3) { - free(rule_new); + free_vars(); return 0; } } @@ -1102,11 +1247,11 @@ int main(int argc, char *argv[]) fprintf(stderr, "The audit system is in immutable " "mode, no rules loaded\n"); - free(rule_new); + free_vars(); return 0; } else if (errno == ECONNREFUSED) { fprintf(stderr, "The audit system is disabled\n"); - free(rule_new); + free_vars(); return 0; } } @@ -1132,7 +1277,7 @@ static int handle_request(int status) } else if (status == -2) status = 0; // report success else if (status > 0) { - int rc; + int rc, i; if (add != AUDIT_FILTER_UNSET) { // if !task add syscall any if not specified if ((add & AUDIT_FILTER_MASK) != AUDIT_FILTER_TASK && @@ -1155,6 +1300,14 @@ static int handle_request(int status) "Error sending add rule data request (%s)\n", errno == EEXIST ? "Rule exists" : strerror(-rc)); + /* undo changes in database */ + if (ra.count) + /* Error - database must + * contain the same values + * as it had before adding + * the rule + */ + db_del_reacts(&ra); } } } @@ -1175,25 +1328,54 @@ static int handle_request(int status) rule_new->fields[0] = AUDIT_WATCH; rc = audit_delete_rule_data(fd,rule_new, del, action); + if (rc >= 0 && ra.count) + /* success - delete reactions */ + db_del_reacts(&ra); } else { fprintf(stderr, "Error sending delete rule data request (%s)\n", errno == EEXIST ? "Rule exists" : strerror(-rc)); } + } else if (ra.count){ + db_del_reacts(&ra); } } else { usage(); - audit_close(fd); + audit_close(fd); + free_vars(); exit(1); } if (rc <= 0) status = -1; else status = 0; - } else + /* There was an error working with database */ + } else if (status == -4) { + if (ra.processed) { + int rc, i; + sqlite3 *conn; + + rc = sql_open_database(&conn, REACT_DB); + if (rc < 0) + status = -1; + + if (status != -1) { + /* some reactions were inserted/updated successfully */ + for (i = 0; i < ra.processed; i++) { + rc = sql_del_reaction(conn, ra.str[i]); + if (rc < 0) + break; + } + sql_close_database(conn); + } + } status = -1; + } else + status = -1; + + free_vars(); audit_close(fd); fd = -1; return status; @@ -1278,6 +1460,7 @@ int key_match(struct audit_reply *rep) */ static int audit_print_reply(struct audit_reply *rep) { + int rc; unsigned int i; int first; int sparse; @@ -1382,6 +1565,28 @@ static int audit_print_reply(struct audit_reply *rep) key_sep); } free(rkey); + } else if (field == AUDIT_REACTION) { + sqlite3 *conn; + char *str_react = NULL; + rc = sql_open_database(&conn, + REACT_DB); + if (rc < 0) { + sql_print_error(conn, rc); + return -1; + } + rc = sql_number_to_reaction(conn, + rep->ruledata->values[i], + &str_react); + if (rc < 0) { + /* print only number */ + printf(" react=%u\n", + rep->ruledata->values[i]); + sql_print_error(conn, rc); + return -1; + } + printf(" react=%s", str_react); + free(str_react); + sql_close_database(conn); } else if (field == AUDIT_PERM) { char perms[5]; int val=rep->ruledata->values[i]; @@ -1419,7 +1624,8 @@ static int audit_print_reply(struct audit_reply *rep) field > AUDIT_SUBJ_CLR) && field != AUDIT_WATCH && field != AUDIT_FILTERKEY && - field != AUDIT_PERM) + field != AUDIT_PERM && + field != AUDIT_REACTION) printf(" (0x%x)", rep->ruledata->values[i]); } if (show_syscall && diff --git a/src/mt/Makefile.am b/src/mt/Makefile.am index 5f1ebc0..8827b2c 100644 --- a/src/mt/Makefile.am +++ b/src/mt/Makefile.am @@ -32,9 +32,9 @@ noinst_LIBRARIES = libauditmt.a libauditmt_a_SOURCES = ${top_srcdir}/lib/libaudit.c \ ${top_srcdir}/lib/message.c ${top_srcdir}/lib/netlink.c \ ${top_srcdir}/lib/lookup_table.c ${top_srcdir}/lib/audit_logging.c \ - ${top_srcdir}/lib/deprecated.c + ${top_srcdir}/lib/deprecated.c ${top_srcdir}/lib/reactarray.c libauditmt_a_HEADERS: ${top_builddir}/config.h ${top_srcdir}/lib/libaudit.h \ - ${top_srcdir}/lib/private.h + ${top_srcdir}/lib/private.h ${top_srcdir}/lib/reactarray.h libauditmt_a_DEPENDENCIES = $(libaudit_a_SOURCES) ${top_builddir}/config.h \ ${top_srcdir}/lib/gen_tables.h ${top_builddir}/lib/i386_tables.h \ ${top_builddir}/lib/ia64_tables.h ${top_builddir}/lib/ppc_tables.h \ -- 1.6.4.4 -- Linux-audit mailing list [email protected] https://www.redhat.com/mailman/listinfo/linux-audit
