[PATCH] mapping of reactions

Juraj Hlista
2010-03-30T22:24:02Z

From: Juraj Hlista <juro.hlista@gmail.com>

Reactions are identified by numbers in the kernel, but user space requires string identifiers of reactions. That's why there must be some mapping from numbers to strings and vice versa. This is implemented using SQLite. A table where the reaction strings and numbers are kept has the following columns: number, string, used. 'number' is the value used in the kernel, 'string' is used in the user space. 'used' means how many times is the reaction used in audit rules.

When a new reactive rule is added, the database determines what number will be sent to the kernel for each reaction:
1 if the table is empty
if string is already in the table, its number will be sent to the kernel and 'used' incremented
if there isn't such a string in the table, but there is some row with 'used' == 0, then string in this row will be rewritten by the new one and 'used' set to 1
otherwise max(number) + 1 will be used

When a reactive rule is removed, 'used' is just decremented.

More reactions per one rule are supported, they are defined by react field (-F react=reaction). This patch includes only mapping, audit plugin for triggering reactions is being developed.

Signed-off-by: Juraj Hlista <juro.hlista@gmail.com>
---
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 | 84 ++++++++++++
lib/reactarray.h | 41 ++++++
src/Makefile.am | 7 +-
src/auditctl-reactsql.c | 325 +++++++++++++++++++++++++++++++++++++++++++++++
src/auditctl-reactsql.h | 48 +++++++
src/auditctl.c | 231 +++++++++++++++++++++++++++++++--
src/mt/Makefile.am | 4 +-
14 files changed, 765 insertions(+), 21 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..9823e31 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((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..03b8a65
--- /dev/null
+++ b/lib/reactarray.c
@@ -0,0 +1,84 @@
+/* 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 <juro.hlista@gmail.com>
+ */
+
+#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, unsigned int size)
+{
+ int i;
+
+ a->add_to_rule = 0;
+ a->processed = 0;
+ a->count = 0;
+ a->size = size;
+ a->str = (char **)malloc(size * sizeof(char *));
+ if (!a->str)
+ return 1;
+
+ for (i = 0; i < size; i++)
+ a->str[i] = NULL;
+
+ 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++) {
+ if (a->str[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] = (char *)malloc((strlen(s) + 1) * sizeof(char));
+ if (!a->str[a->count])
+ return 1;
+
+ strcpy(a->str[a->count], s);
+ 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 <juro.hlista@gmail.com>
+ */
+
+#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..0a70658
--- /dev/null
+++ b/src/auditctl-reactsql.c
@@ -0,0 +1,325 @@
+/* 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 <juro.hlista@gmail.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "auditctl-reactsql.h"
+
+const char *sql_errmsg[] = {
+ "SQL query suppossed to return a value",
+ "Out of memory allocating reaction string",
+ "React number reached maximal value"
+};
+
+enum {
+ SQL_CHECK_DB = 0,
+ SQL_CREATE_TABLE,
+ SQL_GET_NEXT_NUMBER,
+ SQL_NUMBER_TO_STRING,
+ SQL_STRING_TO_NUMBER,
+ SQL_ADD_UPDATE,
+ SQL_ADD_INSERT,
+ SQL_DEL_UPDATE,
+ SQL_DEL_ALL
+};
+
+static const char *query[] = {
+ "SELECT name \
+ FROM sqlite_master \
+ WHERE type='table' AND name='reaction'",
+
+ /* number - the value that is in the kernel
+ * string - reaction identifier
+ * used - how many times is the reaction used with rules
+ */
+ "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 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
+ */
+ "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",
+
+ "SELECT string FROM reaction WHERE number = ?",
+
+ "SELECT number FROM reaction WHERE string = ?",
+
+ "UPDATE reaction SET string = ?, used = used + 1 WHERE number = ?",
+
+ "INSERT INTO reaction VALUES(?, ?, 1)",
+
+ "UPDATE reaction SET used = used - 1 WHERE string = ?",
+
+ "DROP TABLE reaction"
+};
+
+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 - 2]);
+}
+
+/*
+ * 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;
+
+ if (sqlite3_prepare(c, query[SQL_NUMBER_TO_STRING], -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 = malloc((strlen(reaction) + 1) * sizeof(char));
+ if (*str == NULL) {
+ sqlite3_finalize(find_str);
+ return -SQL_NO_MEMORY;
+ }
+ strcpy(*str, reaction);
+
+ 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;
+
+ if (sqlite3_prepare(c, query[SQL_STRING_TO_NUMBER], -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' is greater than SQL_OFFSET,
+ * 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 int num, const char *str)
+{
+ sqlite3_stmt *change;
+
+ /* update table */
+ if (num > SQL_OFFSET) {
+ if (sqlite3_prepare(c, query[SQL_ADD_UPDATE], -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 - SQL_OFFSET))
+ return -SQL_ERROR;
+ }
+ /* insert into table */
+ else {
+ if (sqlite3_prepare(c, query[SQL_ADD_INSERT], -1, &change, NULL))
+ return -SQL_ERROR;
+
+ if (sqlite3_bind_int(change, 1, num))
+ 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;
+
+ if (sqlite3_prepare(c, query[SQL_DEL_UPDATE], -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;
+
+ if (sqlite3_prepare(c, query[SQL_DEL_ALL], -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.
+ */
+int sql_get_next_number(sqlite3 *c, const char *str)
+{
+ int result;
+ int x;
+ sqlite3_stmt *get_num;
+
+ if (sqlite3_prepare(c, query[SQL_GET_NEXT_NUMBER], -1, &get_num, NULL))
+ return -SQL_ERROR;
+
+ if (sqlite3_bind_text(get_num, 1, str, -1, NULL))
+ return -SQL_ERROR;
+
+ if (sqlite3_step(get_num) != SQLITE_ROW) {
+ sqlite3_finalize(get_num);
+ return -SQL_NO_VALUE;
+ }
+
+ result = sqlite3_column_int(get_num, 0);
+
+ if (sqlite3_finalize(get_num))
+ return -SQL_ERROR;
+
+ x = result - SQL_OFFSET;
+ /* there are SQL_OFFSET - 1 numbers to be used with reaction strings */
+ if (!x || x >= SQL_OFFSET)
+ return -SQL_MAX_NUMBER;
+
+ 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;
+
+ /* check if table exists */
+ if (sqlite3_prepare(c, query[SQL_CHECK_DB], -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) {
+ if (sqlite3_prepare(c, query[SQL_CREATE_TABLE], -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..1e0ea97
--- /dev/null
+++ b/src/auditctl-reactsql.h
@@ -0,0 +1,48 @@
+/* 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 <juro.hlista@gmail.com>
+ */
+
+#ifndef CTLREACTSQL_HEADER
+#define CTLREACTSQL_HEADER
+
+#include <sqlite3.h>
+
+#define SQL_OFFSET 10000
+
+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 int num, const char *str);
+
+int sql_del_reaction(sqlite3 *c, const char *str);
+
+int sql_get_next_number(sqlite3 *c, const char *str);
+
+#endif
+
diff --git a/src/auditctl.c b/src/auditctl.c
index 03cac39..832ea31 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,6 +77,7 @@ 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;

@@ -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,97 @@ 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, num;
+ 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) {
+ /* get a number for the reaction string */
+ num = sql_get_next_number(conn, ra.str[i]);
+ if (num < 0) {
+ sql_print_error(conn, num);
+ sql_close_database(conn);
+ return -4;
+ }
+ if (num > SQL_OFFSET)
+ asprintf(&cmd, "react=%u", num - SQL_OFFSET);
+ else
+ asprintf(&cmd, "react=%u", num);
+ if (cmd) {
+ rc = audit_rule_fieldpair_data(&rule_new,
+ cmd, flags);
+ if (rc < 0) {
+ audit_number_to_errmsg(rc, NULL);
+ sql_close_database(conn);
+ free(cmd);
+ return -4;
+ }
+ free(cmd);
+ } else {
+ fprintf(stderr,
+ "Out of memory adding reaction\n");
+ 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) {
+ rc = sql_reaction_to_number(conn, ra.str[i], &num);
+ if (rc < 0) {
+ sql_print_error(conn, rc);
+ sql_close_database(conn);
+ return -4;
+ }
+
+ asprintf(&cmd, "react=%u", num);
+ if (cmd) {
+ rc = audit_rule_fieldpair_data(&rule_new,
+ cmd, flags);
+ if (rc < 0) {
+ audit_number_to_errmsg(rc, NULL);
+ sql_close_database(conn);
+ free(cmd);
+ return -4;
+ }
+ free(cmd);
+ }
+ }
+ }
+ sql_close_database(conn);
+ }
+
if (retval == -1 && errno == ECONNREFUSED)
fprintf(stderr, "The audit system is disabled\n");
return retval;
@@ -1022,6 +1168,7 @@ static int fileopt(const char *file)

/* Parse it */
if (reset_vars()) {
+ free_vars();
fclose(f);
return -1;
}
@@ -1045,6 +1192,8 @@ static int fileopt(const char *file)
return -1;
}
}
+ } else {
+ free_vars();
}
lineno++;
}
@@ -1086,12 +1235,12 @@ int main(int argc, char *argv[])
return 0;
} else {
if (reset_vars()) {
- free(rule_new);
+ free_vars();
return 1;
}
retval = setopt(argc, 0, argv);
if (retval == -3) {
- free(rule_new);
+ free_vars();
return 0;
}
}
@@ -1102,11 +1251,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 +1281,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 +1304,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 +1332,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 +1464,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 +1569,25 @@ 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) {
+ 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 +1625,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