Re: [PATCH] mapping of reactions

Juraj Hlista
2010-04-02T12:15:19Z

> 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 <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 | 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 <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, 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 <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..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 <juro.hlista@gmail.com>
+ */
+
+#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 <juro.hlista@gmail.com>
+ */
+
+#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