summaryrefslogtreecommitdiffstats
path: root/framework/src/audit/src/auditctl.c
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/audit/src/auditctl.c')
-rw-r--r--framework/src/audit/src/auditctl.c1472
1 files changed, 1472 insertions, 0 deletions
diff --git a/framework/src/audit/src/auditctl.c b/framework/src/audit/src/auditctl.c
new file mode 100644
index 00000000..334f8021
--- /dev/null
+++ b/framework/src/audit/src/auditctl.c
@@ -0,0 +1,1472 @@
+/* auditctl.c --
+ * Copyright 2004-2015 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * 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:
+ * Steve Grubb <sgrubb@redhat.com>
+ * Rickard E. (Rik) Faith <faith@redhat.com>
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h> /* strdup needs xopen define */
+#include <getopt.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <libgen.h> /* For basename */
+#include <limits.h> /* PATH_MAX */
+#include "libaudit.h"
+#include "auditctl-listing.h"
+#include "private.h"
+
+/* This define controls the size of the line that we will request when
+ * reading in rules from a file.
+ */
+#define LINE_SIZE 6144
+
+
+/* Global functions */
+static int handle_request(int status);
+static void get_reply(void);
+extern int delete_all_rules(int fd);
+
+/* Global vars */
+int list_requested = 0, interpret = 0;
+char key[AUDIT_MAX_KEY_LEN+1];
+const char key_sep[2] = { AUDIT_KEY_SEPARATOR, 0 };
+static int keylen;
+static int fd = -1;
+static int add = AUDIT_FILTER_UNSET, del = AUDIT_FILTER_UNSET, action = -1;
+static int ignore = 0, continue_error = 0;
+static int exclude = 0;
+static int multiple = 0;
+static struct audit_rule_data *rule_new = NULL;
+
+/*
+ * This function will reset everything used for each loop when loading
+ * a ruleset from a file.
+ */
+static int reset_vars(void)
+{
+ list_requested = 0;
+ _audit_syscalladded = 0;
+ _audit_permadded = 0;
+ _audit_archadded = 0;
+ _audit_elf = 0;
+ add = AUDIT_FILTER_UNSET;
+ del = AUDIT_FILTER_UNSET;
+ action = -1;
+ exclude = 0;
+ multiple = 0;
+
+ free(rule_new);
+ rule_new = malloc(sizeof(struct audit_rule_data));
+ memset(rule_new, 0, sizeof(struct audit_rule_data));
+ if (fd < 0) {
+ if ((fd = audit_open()) < 0) {
+ audit_msg(LOG_ERR, "Cannot open netlink audit socket");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void usage(void)
+{
+ printf(
+ "usage: auditctl [options]\n"
+ " -a <l,a> Append rule to end of <l>ist with <a>ction\n"
+ " -A <l,a> Add rule at beginning of <l>ist with <a>ction\n"
+ " -b <backlog> Set max number of outstanding audit buffers\n"
+ " allowed Default=64\n"
+ " -c Continue through errors in rules\n"
+ " -C f=f Compare collected fields if available:\n"
+ " Field name, operator(=,!=), field name\n"
+ " -d <l,a> Delete rule from <l>ist with <a>ction\n"
+ " l=task,exit,user,exclude\n"
+ " a=never,always\n"
+ " -D Delete all rules and watches\n"
+ " -e [0..2] Set enabled flag\n"
+ " -f [0..2] Set failure flag\n"
+ " 0=silent 1=printk 2=panic\n"
+ " -F f=v Build rule: field name, operator(=,!=,<,>,<=,\n"
+ " >=,&,&=) value\n"
+ " -h Help\n"
+ " -i Ignore errors when reading rules from file\n"
+ " -k <key> Set filter key on audit rule\n"
+ " -l List rules\n"
+ " -m text Send a user-space message\n"
+ " -p [r|w|x|a] Set permissions filter on watch\n"
+ " r=read, w=write, x=execute, a=attribute\n"
+ " -q <mount,subtree> make subtree part of mount point's dir watches\n"
+ " -r <rate> Set limit in messages/sec (0=none)\n"
+ " -R <file> read rules from file\n"
+ " -s Report status\n"
+ " -S syscall Build rule: syscall name or number\n"
+ " -t Trim directory watches\n"
+ " -v Version\n"
+ " -w <path> Insert watch at <path>\n"
+ " -W <path> Remove watch at <path>\n"
+ " --loginuid-immutable Make loginuids unchangeable once set\n"
+ " --backlog_wait_time Set the kernel backlog_wait_time\n"
+ );
+}
+
+static int lookup_filter(const char *str, int *filter)
+{
+ if (strcmp(str, "task") == 0)
+ *filter = AUDIT_FILTER_TASK;
+ else if (strcmp(str, "entry") == 0)
+ *filter = AUDIT_FILTER_ENTRY;
+ else if (strcmp(str, "exit") == 0)
+ *filter = AUDIT_FILTER_EXIT;
+ else if (strcmp(str, "user") == 0)
+ *filter = AUDIT_FILTER_USER;
+ else if (strcmp(str, "exclude") == 0) {
+ *filter = AUDIT_FILTER_EXCLUDE;
+ exclude = 1;
+ } else
+ return 2;
+ return 0;
+}
+
+static int lookup_action(const char *str, int *act)
+{
+ if (strcmp(str, "never") == 0)
+ *act = AUDIT_NEVER;
+ else if (strcmp(str, "possible") == 0)
+ return 1;
+ else if (strcmp(str, "always") == 0)
+ *act = AUDIT_ALWAYS;
+ else
+ return 2;
+ return 0;
+}
+
+/*
+ * Returns 0 ok, 1 deprecated action, 2 rule error,
+ * 3 multiple rule insert/delete
+ */
+static int audit_rule_setup(char *opt, int *filter, int *act, int lineno)
+{
+ int rc;
+ char *p;
+
+ if (++multiple != 1)
+ return 3;
+
+ p = strchr(opt, ',');
+ if (p == NULL || strchr(p+1, ','))
+ return 2;
+ *p = 0;
+
+ /* Try opt both ways */
+ if (lookup_filter(opt, filter) == 2) {
+ rc = lookup_action(opt, act);
+ if (rc != 0) {
+ *p = ',';
+ return rc;
+ }
+ }
+
+ /* Repair the string */
+ *p = ',';
+ opt = p+1;
+
+ /* If flags are empty, p+1 must be the filter */
+ if (*filter == AUDIT_FILTER_UNSET)
+ lookup_filter(opt, filter);
+ else {
+ rc = lookup_action(opt, act);
+ if (rc != 0)
+ return rc;
+ }
+
+ /* Make sure we set both */
+ if (*filter == AUDIT_FILTER_UNSET || *act == -1)
+ return 2;
+
+ /* Consolidate rules on exit filter */
+ if (*filter == AUDIT_FILTER_ENTRY) {
+ *filter = AUDIT_FILTER_EXIT;
+ if (lineno)
+ audit_msg(LOG_INFO, "Warning - entry rules deprecated, changing to exit rule in line %d", lineno);
+ else
+ audit_msg(LOG_INFO,
+ "Warning - entry rules deprecated, changing to exit rule");
+ }
+
+ return 0;
+}
+
+/*
+ * This function will check the path before accepting it. It returns
+ * 1 on error and 0 on success.
+ */
+static int check_path(const char *path)
+{
+ char *ptr, *base;
+ size_t nlen;
+ size_t plen = strlen(path);
+ if (plen >= PATH_MAX) {
+ audit_msg(LOG_ERR, "The path passed for the watch is too big");
+ return 1;
+ }
+ if (path[0] != '/') {
+ audit_msg(LOG_ERR, "The path must start with '/'");
+ return 1;
+ }
+ ptr = strdup(path);
+ base = basename(ptr);
+ nlen = strlen(base);
+ free(ptr);
+ if (nlen > NAME_MAX) {
+ audit_msg(LOG_ERR, "The base name of the path is too big");
+ return 1;
+ }
+
+ /* These are warnings, not errors */
+ if (strstr(path, ".."))
+ audit_msg(LOG_WARNING,
+ "Warning - relative path notation is not supported");
+ if (strchr(path, '*') || strchr(path, '?'))
+ audit_msg(LOG_WARNING,
+ "Warning - wildcard notation is not supported");
+
+ return 0;
+}
+
+/*
+ * Setup a watch. The "name" of the watch in userspace will be the <path> to
+ * the watch. When this potential watch reaches the kernel, it will resolve
+ * down to <name> (of terminating file or directory).
+ * Returns a 1 on success & -1 on failure.
+ */
+static int audit_setup_watch_name(struct audit_rule_data **rulep, char *path)
+{
+ int type = AUDIT_WATCH;
+ size_t len;
+ struct stat buf;
+
+ if (check_path(path))
+ return -1;
+
+ // Trim trailing '/' should they exist
+ len = strlen(path);
+ if (len > 2 && path[len-1] == '/') {
+ while (path[len-1] == '/' && len > 1) {
+ path[len-1] = 0;
+ len--;
+ }
+ }
+ if (stat(path, &buf) == 0) {
+ if (S_ISDIR(buf.st_mode))
+ type = AUDIT_DIR;
+ }
+ /* FIXME: might want to check to see that rule is empty */
+ if (audit_add_watch_dir(type, rulep, path))
+ return -1;
+
+ return 1;
+}
+
+/*
+ * Setup a watch permissions.
+ * Returns a 1 on success & -1 on failure.
+ */
+static int audit_setup_perms(struct audit_rule_data *rule, const char *opt)
+{
+ unsigned int i, len, val = 0;
+
+ len = strlen(opt);
+ if (len > 4)
+ return -1;
+
+ for (i = 0; i < len; i++) {
+ switch (tolower(opt[i])) {
+ case 'r':
+ val |= AUDIT_PERM_READ;
+ break;
+ case 'w':
+ val |= AUDIT_PERM_WRITE;
+ break;
+ case 'x':
+ val |= AUDIT_PERM_EXEC;
+ break;
+ case 'a':
+ val |= AUDIT_PERM_ATTR;
+ break;
+ default:
+ audit_msg(LOG_ERR,
+ "Permission %c isn't supported",
+ opt[i]);
+ return -1;
+ }
+ }
+
+ if (audit_update_watch_perms(rule_new, val) == 0) {
+ _audit_permadded = 1;
+ return 1;
+ }
+ return -1;
+}
+
+/* 0 success, -1 failure */
+static int lookup_itype(const char *kind)
+{
+ if (strcmp(kind, "sys") == 0)
+ return 0;
+ if (strcmp(kind, "file") == 0)
+ return 0;
+ if (strcmp(kind, "exec") == 0)
+ return 0;
+ if (strcmp(kind, "mkexe") == 0)
+ return 0;
+ return -1;
+}
+
+/* 0 success, -1 failure */
+static int lookup_iseverity(const char *severity)
+{
+ if (strncmp(severity, "inf", 3) == 0)
+ return 0;
+ if (strncmp(severity, "low", 3) == 0)
+ return 0;
+ if (strncmp(severity, "med", 3) == 0)
+ return 0;
+ if (strncmp(severity, "hi", 2) == 0)
+ return 0;
+ return -1;
+}
+
+/* 0 success, -1 failure */
+static int check_ids_key(const char *k)
+{
+ char *ptr, *kindptr, *ratingptr;
+ char keyptr[AUDIT_MAX_KEY_LEN+1];
+
+ if (strlen(k) > AUDIT_MAX_KEY_LEN)
+ goto fail_exit;
+
+ strncpy(keyptr, k, sizeof(keyptr));
+ keyptr[AUDIT_MAX_KEY_LEN] = 0;
+ ptr = strchr(keyptr, '-'); // There has to be a - because strncmp
+ kindptr = ptr + 1;
+ if (*kindptr == 0)
+ goto fail_exit;
+
+ ptr = strchr(kindptr, '-');
+ if (ptr) {
+ *ptr = 0;
+ ratingptr = ptr +1;
+ } else // The rules are misconfigured
+ goto fail_exit;
+ if (*ratingptr == 0)
+ goto fail_exit;
+
+ if (lookup_itype(kindptr)) {
+ audit_msg(LOG_ERR, "ids key type is bad");
+ return -1;
+ }
+ if (lookup_iseverity(ratingptr)) {
+ audit_msg(LOG_ERR, "ids key severity is bad");
+ return -1;
+ }
+ return 0;
+
+fail_exit:
+ audit_msg(LOG_ERR, "ids key is bad");
+ return -1;
+}
+
+static int equiv_parse(char *optarg, char **mp, char **sub)
+{
+ char *ptr = strchr(optarg, ',');
+ if (ptr == NULL)
+ return -1; // no comma
+ *ptr = 0;
+ ptr++;
+ if (*ptr == 0)
+ return -1; // ends with comma
+ *mp = optarg;
+ *sub = ptr;
+ if (strchr(*sub, ','))
+ return -1; // too many commas
+ return 0;
+}
+
+int audit_request_rule_list(int fd)
+{
+ if (audit_request_rules_list_data(fd) > 0) {
+ list_requested = 1;
+ get_reply();
+ return 1;
+ }
+ return 0;
+}
+
+void check_rule_mismatch(int lineno, const char *option)
+{
+ struct audit_rule_data tmprule;
+ unsigned int old_audit_elf = _audit_elf;
+ int rc = 0;
+
+ switch (_audit_elf)
+ {
+ case AUDIT_ARCH_X86_64:
+ _audit_elf = AUDIT_ARCH_I386;
+ break;
+ case AUDIT_ARCH_PPC64:
+ _audit_elf = AUDIT_ARCH_PPC;
+ break;
+ case AUDIT_ARCH_S390X:
+ _audit_elf = AUDIT_ARCH_S390;
+ break;
+ }
+ memset(&tmprule, 0, sizeof(struct audit_rule_data));
+ audit_rule_syscallbyname_data(&tmprule, option);
+ if (memcmp(tmprule.mask, rule_new->mask, AUDIT_BITMASK_SIZE))
+ rc = 1;
+ _audit_elf = old_audit_elf;
+ if (rc) {
+ if (lineno)
+ audit_msg(LOG_WARNING, "WARNING - 32/64 bit syscall mismatch in line %d, you should specify an arch", lineno);
+ else
+ audit_msg(LOG_WARNING, "WARNING - 32/64 bit syscall mismatch, you should specify an arch");
+ }
+}
+
+int report_status(int fd)
+{
+ int retval;
+
+ retval = audit_request_status(fd);
+ if (retval == -1) {
+ if (errno == ECONNREFUSED)
+ fprintf(stderr, "The audit system is disabled\n");
+ return -1;
+ }
+ get_reply();
+ retval = audit_request_features(fd);
+ if (retval == -1) {
+ // errno is EINVAL if the kernel does support features API
+ if (errno == EINVAL)
+ return -2;
+ return -1;
+ }
+ get_reply();
+ return -2;
+}
+
+int parse_syscall(struct audit_rule_data *rule_new, const char *optarg)
+{
+ int retval = 0;
+ char *saved;
+
+ if (strchr(optarg, ',')) {
+ char *ptr, *tmp = strdup(optarg);
+ if (tmp == NULL)
+ return -1;
+ ptr = strtok_r(tmp, ",", &saved);
+ while (ptr) {
+ retval = audit_rule_syscallbyname_data(rule_new, ptr);
+ if (retval != 0) {
+ if (retval == -1) {
+ audit_msg(LOG_ERR,
+ "Syscall name unknown: %s",
+ ptr);
+ retval = -3; // error reported
+ }
+ break;
+ }
+ ptr = strtok_r(NULL, ",", &saved);
+ }
+ free(tmp);
+ return retval;
+ }
+
+ return audit_rule_syscallbyname_data(rule_new, optarg);
+}
+
+struct option long_opts[] =
+{
+ {"loginuid-immutable", 0, NULL, 1},
+ {"backlog_wait_time", 1, NULL, 2},
+ {NULL, 0, NULL, 0}
+};
+
+// FIXME: Change these to enums
+/*
+ * returns: -3 deprecated, -2 success - no reply, -1 error - noreply,
+ * 0 success - reply, > 0 success - rule
+ */
+static int setopt(int count, int lineno, char *vars[])
+{
+ int c;
+ int retval = 0, rc;
+
+ optind = 0;
+ opterr = 0;
+ key[0] = 0;
+ keylen = AUDIT_MAX_KEY_LEN;
+
+ while ((retval >= 0) && (c = getopt_long(count, vars,
+ "hicslDvtC:e:f:r:b:a:A:d:S:F:m:R:w:W:k:p:q:",
+ long_opts, NULL)) != EOF) {
+ int flags = AUDIT_FILTER_UNSET;
+ rc = 10; // Init to something impossible to see if unused.
+ switch (c) {
+ case 'h':
+ usage();
+ retval = -1;
+ break;
+ case 'i':
+ ignore = 1;
+ retval = -2;
+ break;
+ case 'c':
+ ignore = 1;
+ continue_error = 1;
+ retval = -2;
+ break;
+ case 's':
+ if (count > 3) {
+ audit_msg(LOG_ERR,
+ "Too many options for status command");
+ retval = -1;
+ break;
+ } else if (optind == 2 && count == 3) {
+ if (strcmp(vars[optind], "-i") == 0) {
+ interpret = 1;
+ count -= 1;
+ } else {
+ audit_msg(LOG_ERR,
+ "Only -i option is allowed");
+ retval = -1;
+ break;
+ }
+ }
+ retval = report_status(fd);
+ break;
+ case 'e':
+ if (optarg && ((strcmp(optarg, "0") == 0) ||
+ (strcmp(optarg, "1") == 0) ||
+ (strcmp(optarg, "2") == 0))) {
+ if (audit_set_enabled(fd, strtoul(optarg,NULL,0)) > 0)
+ audit_request_status(fd);
+ else
+ retval = -1;
+ } else {
+ audit_msg(LOG_ERR, "Enable must be 0, 1, or 2 was %s",
+ optarg);
+ retval = -1;
+ }
+ break;
+ case 'f':
+ if (optarg && ((strcmp(optarg, "0") == 0) ||
+ (strcmp(optarg, "1") == 0) ||
+ (strcmp(optarg, "2") == 0))) {
+ if (audit_set_failure(fd, strtoul(optarg,NULL,0)) > 0)
+ audit_request_status(fd);
+ else
+ return -1;
+ } else {
+ audit_msg(LOG_ERR, "Failure must be 0, 1, or 2 was %s",
+ optarg);
+ retval = -1;
+ }
+ break;
+ case 'r':
+ if (optarg && isdigit(optarg[0])) {
+ uint32_t rate;
+ errno = 0;
+ rate = strtoul(optarg,NULL,0);
+ if (errno) {
+ audit_msg(LOG_ERR, "Error converting rate");
+ return -1;
+ }
+ if (audit_set_rate_limit(fd, rate) > 0)
+ audit_request_status(fd);
+ else
+ return -1;
+ } else {
+ audit_msg(LOG_ERR,"Rate must be a numeric value was %s",
+ optarg);
+ retval = -1;
+ }
+ break;
+ case 'b':
+ if (optarg && isdigit(optarg[0])) {
+ uint32_t limit;
+ errno = 0;
+ limit = strtoul(optarg,NULL,0);
+ if (errno) {
+ audit_msg(LOG_ERR, "Error converting backlog");
+ return -1;
+ }
+ if (audit_set_backlog_limit(fd, limit) > 0)
+ audit_request_status(fd);
+ else
+ return -1;
+ } else {
+ audit_msg(LOG_ERR,
+ "Backlog must be a numeric value was %s",
+ optarg);
+ retval = -1;
+ }
+ break;
+ case 'l':
+ if (count > 4) {
+ audit_msg(LOG_ERR,
+ "Wrong number of options for list request");
+ retval = -1;
+ break;
+ }
+ if (count == 3) {
+ if (strcmp(vars[optind], "-i") == 0) {
+ interpret = 1;
+ count -= 1;
+ } else {
+ audit_msg(LOG_ERR,
+ "Only -k or -i options are allowed");
+ retval = -1;
+ }
+ } else if (count == 4) {
+ if (vars[optind] && strcmp(vars[optind], "-k") == 0) {
+ strncat(key, vars[3], keylen);
+ count -= 2;
+ } else {
+ audit_msg(LOG_ERR,
+ "Only -k or -i options are allowed");
+ retval = -1;
+ break;
+ }
+ }
+ if (audit_request_rule_list(fd)) {
+ list_requested = 1;
+ retval = -2;
+ } else
+ retval = -1;
+ break;
+ case 'a':
+ if (strstr(optarg, "task") && _audit_syscalladded) {
+ audit_msg(LOG_ERR,
+ "Syscall auditing requested for task list");
+ retval = -1;
+ } else {
+ rc = audit_rule_setup(optarg, &add, &action, lineno);
+ if (rc == 3) {
+ audit_msg(LOG_ERR,
+ "Multiple rule insert/delete operations are not allowed\n");
+ retval = -1;
+ } else if (rc == 2) {
+ audit_msg(LOG_ERR,
+ "Append rule - bad keyword %s",
+ optarg);
+ retval = -1;
+ } else if (rc == 1) {
+ audit_msg(LOG_ERR,
+ "Append rule - possible is deprecated");
+ return -3; /* deprecated - eat it */
+ } else
+ retval = 1; /* success - please send */
+ }
+ break;
+ case 'A':
+ if (strstr(optarg, "task") && _audit_syscalladded) {
+ audit_msg(LOG_ERR,
+ "Error: syscall auditing requested for task list");
+ retval = -1;
+ } else {
+ rc = audit_rule_setup(optarg, &add, &action, lineno);
+ if (rc == 3) {
+ audit_msg(LOG_ERR,
+ "Multiple rule insert/delete operations are not allowed");
+ retval = -1;
+ } else if (rc == 2) {
+ audit_msg(LOG_ERR,
+ "Add rule - bad keyword %s", optarg);
+ retval = -1;
+ } else if (rc == 1) {
+ audit_msg(LOG_WARNING,
+ "Append rule - possible is deprecated");
+ return -3; /* deprecated - eat it */
+ } else {
+ add |= AUDIT_FILTER_PREPEND;
+ retval = 1; /* success - please send */
+ }
+ }
+ break;
+ case 'd':
+ rc = audit_rule_setup(optarg, &del, &action, lineno);
+ if (rc == 3) {
+ audit_msg(LOG_ERR,
+ "Multiple rule insert/delete operations are not allowed");
+ retval = -1;
+ } else if (rc == 2) {
+ audit_msg(LOG_ERR, "Delete rule - bad keyword %s",
+ optarg);
+ retval = -1;
+ } else if (rc == 1) {
+ audit_msg(LOG_INFO,
+ "Delete rule - possible is deprecated");
+ return -3; /* deprecated - eat it */
+ } else
+ retval = 1; /* success - please send */
+ break;
+ case 'S': {
+ int unknown_arch = !_audit_elf;
+ /* Do some checking to make sure that we are not adding a
+ * syscall rule to a list that does not make sense. */
+ if (((add & (AUDIT_FILTER_MASK|AUDIT_FILTER_UNSET)) ==
+ AUDIT_FILTER_TASK || (del &
+ (AUDIT_FILTER_MASK|AUDIT_FILTER_UNSET)) ==
+ AUDIT_FILTER_TASK)) {
+ audit_msg(LOG_ERR,
+ "Error: syscall auditing being added to task list");
+ return -1;
+ } else if (((add & (AUDIT_FILTER_MASK|AUDIT_FILTER_UNSET)) ==
+ AUDIT_FILTER_USER || (del &
+ (AUDIT_FILTER_MASK|AUDIT_FILTER_UNSET)) ==
+ AUDIT_FILTER_USER)) {
+ audit_msg(LOG_ERR,
+ "Error: syscall auditing being added to user list");
+ return -1;
+ } else if (exclude) {
+ audit_msg(LOG_ERR,
+ "Error: syscall auditing cannot be put on exclude list");
+ return -1;
+ } else {
+ if (unknown_arch) {
+ int machine;
+ unsigned int elf;
+ machine = audit_detect_machine();
+ if (machine < 0) {
+ audit_msg(LOG_ERR,
+ "Error detecting machine type");
+ return -1;
+ }
+ elf = audit_machine_to_elf(machine);
+ if (elf == 0) {
+ audit_msg(LOG_ERR,
+ "Error looking up elf type %d",
+ machine);
+ return -1;
+ }
+ _audit_elf = elf;
+ }
+ }
+ rc = parse_syscall(rule_new, optarg);
+ switch (rc)
+ {
+ case 0:
+ _audit_syscalladded = 1;
+ if (unknown_arch && add != AUDIT_FILTER_UNSET)
+ check_rule_mismatch(lineno, optarg);
+ break;
+ case -1:
+ audit_msg(LOG_ERR, "Syscall name unknown: %s",
+ optarg);
+ retval = -1;
+ break;
+ case -2:
+ audit_msg(LOG_ERR, "Elf type unknown: 0x%x",
+ _audit_elf);
+ retval = -1;
+ break;
+ case -3: // Error reported - do nothing here
+ retval = -1;
+ break;
+ }}
+ break;
+ case 'F':
+ if (add != AUDIT_FILTER_UNSET)
+ flags = add & AUDIT_FILTER_MASK;
+ else if (del != AUDIT_FILTER_UNSET)
+ flags = del & AUDIT_FILTER_MASK;
+ // if the field is arch & there is a -t option...we
+ // can allow it
+ else if ((optind >= count) || (strstr(optarg, "arch=") == NULL)
+ || (strcmp(vars[optind], "-t") != 0)) {
+ audit_msg(LOG_ERR, "List must be given before field");
+ retval = -1;
+ break;
+ }
+
+ rc = audit_rule_fieldpair_data(&rule_new,optarg,flags);
+ if (rc != 0) {
+ audit_number_to_errmsg(rc, optarg);
+ retval = -1;
+ } else {
+ if (rule_new->fields[rule_new->field_count-1] ==
+ AUDIT_PERM)
+ _audit_permadded = 1;
+ }
+
+ break;
+ case 'C':
+ if (add != AUDIT_FILTER_UNSET)
+ flags = add & AUDIT_FILTER_MASK;
+ else if (del != AUDIT_FILTER_UNSET)
+ flags = del & AUDIT_FILTER_MASK;
+
+ rc = audit_rule_interfield_comp_data(&rule_new, optarg, flags);
+ if (rc != 0) {
+ audit_number_to_errmsg(rc, optarg);
+ retval = -1;
+ } else {
+ if (rule_new->fields[rule_new->field_count - 1] ==
+ AUDIT_PERM)
+ _audit_permadded = 1;
+ }
+ break;
+ case 'm':
+ if (count > 3) {
+ audit_msg(LOG_ERR,
+ "The -m option must be only the only option and takes 1 parameter");
+ retval = -1;
+ } else {
+ const char*s = optarg;
+ while (*s) {
+ if (*s < 32) {
+ audit_msg(LOG_ERR,
+ "Illegal character in audit event");
+ return -1;
+ }
+ s++;
+ }
+ if (audit_log_user_message( fd, AUDIT_USER,
+ optarg, NULL, NULL, NULL, 1) <= 0)
+ retval = -1;
+ else
+ return -2; // success - no reply for this
+ }
+ break;
+ case 'R':
+ audit_msg(LOG_ERR, "Error - nested rule files not supported");
+ retval = -1;
+ break;
+ case 'D':
+ if (count > 4 || count == 3) {
+ audit_msg(LOG_ERR,
+ "Wrong number of options for Delete all request");
+ retval = -1;
+ break;
+ }
+ if (count == 4) {
+ if (strcmp(vars[optind], "-k") == 0) {
+ strncat(key, vars[3], keylen);
+ count -= 2;
+ } else {
+ audit_msg(LOG_ERR,
+ "Only the -k option is allowed");
+ retval = -1;
+ break;
+ }
+ }
+ retval = delete_all_rules(fd);
+ if (retval == 0) {
+ (void)audit_request_rule_list(fd);
+ key[0] = 0;
+ retval = -2;
+ }
+ break;
+ case 'w':
+ if (add != AUDIT_FILTER_UNSET ||
+ del != AUDIT_FILTER_UNSET) {
+ audit_msg(LOG_ERR,
+ "watch option can't be given with a syscall");
+ retval = -1;
+ } else if (optarg) {
+ add = AUDIT_FILTER_EXIT;
+ action = AUDIT_ALWAYS;
+ _audit_syscalladded = 1;
+ retval = audit_setup_watch_name(&rule_new, optarg);
+ } else {
+ audit_msg(LOG_ERR, "watch option needs a path");
+ retval = -1;
+ }
+ break;
+ case 'W':
+ if (optarg) {
+ del = AUDIT_FILTER_EXIT;
+ action = AUDIT_ALWAYS;
+ _audit_syscalladded = 1;
+ retval = audit_setup_watch_name(&rule_new, optarg);
+ } else {
+ audit_msg(LOG_ERR, "watch option needs a path");
+ retval = -1;
+ }
+ break;
+ case 'k':
+ if (!(_audit_syscalladded || _audit_permadded ) ||
+ (add==AUDIT_FILTER_UNSET &&
+ del==AUDIT_FILTER_UNSET)) {
+ audit_msg(LOG_ERR,
+ "key option needs a watch or syscall given prior to it");
+ retval = -1;
+ } else if (!optarg) {
+ audit_msg(LOG_ERR, "key option needs a value");
+ retval = -1;
+ } else if ((strlen(optarg)+strlen(key)+(!!key[0])) >
+ AUDIT_MAX_KEY_LEN) {
+ audit_msg(LOG_ERR, "key option exceeds size limit");
+ retval = -1;
+ } else {
+ if (strncmp(optarg, "ids-", 4) == 0) {
+ if (check_ids_key(optarg)) {
+ retval = -1;
+ break;
+ }
+ }
+ if (strchr(optarg, AUDIT_KEY_SEPARATOR))
+ audit_msg(LOG_ERR,
+ "key %s has illegal character", optarg);
+ if (key[0]) { // Add the separator if we need to
+ strcat(key, key_sep);
+ keylen--;
+ }
+ strncat(key, optarg, keylen);
+ keylen = AUDIT_MAX_KEY_LEN - strlen(key);
+ }
+ break;
+ case 'p':
+ if (!add && !del) {
+ audit_msg(LOG_ERR,
+ "permission option needs a watch given prior to it");
+ retval = -1;
+ } else if (!optarg) {
+ audit_msg(LOG_ERR, "permission option needs a filter");
+ retval = -1;
+ } else
+ retval = audit_setup_perms(rule_new, optarg);
+ break;
+ case 'q':
+ if (_audit_syscalladded) {
+ audit_msg(LOG_ERR,
+ "Syscall auditing requested for make equivalent");
+ retval = -1;
+ } else {
+ char *mp, *sub;
+ retval = equiv_parse(optarg, &mp, &sub);
+ if (retval < 0) {
+ audit_msg(LOG_ERR,
+ "Error parsing equivalent parts");
+ retval = -1;
+ } else {
+ retval = audit_make_equivalent(fd, mp, sub);
+ if (retval <= 0) {
+ retval = -1;
+ } else
+ return -2; // success - no reply needed
+ }
+ }
+ break;
+ case 't':
+ retval = audit_trim_subtrees(fd);
+ if (retval <= 0)
+ retval = -1;
+ else
+ return -2; // success - no reply for this
+ break;
+ case 'v':
+ printf("auditctl version %s\n", VERSION);
+ retval = -2;
+ break;
+ // Now the long options
+ case 1:
+ retval = audit_set_loginuid_immutable(fd);
+ if (retval <= 0)
+ retval = -1;
+ else
+ return -2; // success - no reply for this
+ break;
+ case 2:
+#if HAVE_DECL_AUDIT_VERSION_BACKLOG_WAIT_TIME
+ if (optarg && isdigit(optarg[0])) {
+ uint32_t bwt;
+ errno = 0;
+ bwt = strtoul(optarg,NULL,0);
+ if (errno) {
+ audit_msg(LOG_ERR,
+ "Error converting backlog_wait_time");
+ return -1;
+ }
+ if (audit_set_backlog_wait_time(fd, bwt) > 0)
+ audit_request_status(fd);
+ else
+ return -1;
+ } else {
+ audit_msg(LOG_ERR,
+ "Backlog_wait_time must be a numeric value was %s",
+ optarg);
+ retval = -1;
+ }
+#else
+ audit_msg(LOG_ERR,
+ "backlog_wait_time is not supported on your kernel");
+ retval = -1;
+#endif
+ break;
+ default:
+ usage();
+ retval = -1;
+ break;
+ }
+ }
+ /* catch extra args or errors where the user types "- s" */
+ if (optind == 1)
+ retval = -1;
+ else if ((optind < count) && (retval != -1)) {
+ audit_msg(LOG_ERR, "parameter passed without an option given");
+ retval = -1;
+ }
+
+ /* See if we were adding a key */
+ if (key[0] && list_requested == 0) {
+ int flags = 0;
+ char *cmd=NULL;
+
+ /* Get the flag */
+ if (add != AUDIT_FILTER_UNSET)
+ flags = add & AUDIT_FILTER_MASK;
+ else if (del != AUDIT_FILTER_UNSET)
+ flags = del & AUDIT_FILTER_MASK;
+
+ /* Build the command */
+ if (asprintf(&cmd, "key=%s", key) < 0) {
+ cmd = NULL;
+ audit_msg(LOG_ERR, "Out of memory adding key");
+ retval = -1;
+ } else {
+ /* Add this to the rule */
+ int ret = audit_rule_fieldpair_data(&rule_new, cmd, flags);
+ if (ret < 0)
+ retval = -1;
+ free(cmd);
+ }
+ }
+ if (retval == -1 && errno == ECONNREFUSED)
+ audit_msg(LOG_ERR, "The audit system is disabled");
+ return retval;
+}
+
+static char *get_line(FILE *f, char *buf)
+{
+ if (fgets_unlocked(buf, LINE_SIZE, f)) {
+ /* remove newline */
+ char *ptr = strchr(buf, 0x0a);
+ if (ptr)
+ *ptr = 0;
+ return buf;
+ }
+ return NULL;
+}
+
+
+void preprocess(char *buf)
+{
+ unsigned int i = 0;
+ bool esc_ctx = false;
+
+ while (buf[i]) {
+ if (buf[i] == '\\' && esc_ctx == false)
+ esc_ctx = true;
+ else {
+ if (esc_ctx == true) {
+ if (buf[i] == ' ') {
+ buf[i] = 0x07;
+ buf[i - 1] = 0x07;
+ } else if (buf[i] == '\\') {
+ buf[i] = 0x04;
+ buf[i - 1] = 0x04;
+ }
+
+ esc_ctx = false;
+ }
+ }
+
+ i++;
+ }
+}
+
+
+void postprocess(char *buf)
+{
+ char *str = strdup(buf);
+ char *pos1 = str;
+ char *pos2 = buf;
+
+ if (!str)
+ return;
+
+ while (*pos1) {
+ if (*pos1 == 0x07) {
+ *pos2 = ' ';
+ pos1 += 2;
+ pos2++;
+ continue;
+ } else if (*pos1 == 0x04) {
+ *pos2 = '\\';
+ pos1 += 2;
+ pos2++;
+ continue;
+ }
+
+ *pos2 = *pos1;
+ pos2++;
+ pos1++;
+ }
+
+ *pos2 = 0;
+ free(str);
+}
+
+
+/*
+ * This function reads the given file line by line and executes the rule.
+ * It returns 0 if everything went OK, 1 if there are problems before reading
+ * the file and -1 on error conditions after executing some of the rules.
+ * It will abort reading the file if it encounters any problems.
+ */
+static int fileopt(const char *file)
+{
+ int i, tfd, rc, lineno = 1;
+ struct stat st;
+ FILE *f;
+ char buf[LINE_SIZE];
+
+ /* Does the file exist? */
+ rc = open(file, O_RDONLY);
+ if (rc < 0) {
+ if (errno != ENOENT) {
+ audit_msg(LOG_ERR,"Error opening %s (%s)",
+ file, strerror(errno));
+ return 1;
+ }
+ audit_msg(LOG_INFO, "file %s doesn't exist, skipping", file);
+ return 0;
+ }
+ tfd = rc;
+
+ /* Is the file permissions sane? */
+ if (fstat(tfd, &st) < 0) {
+ audit_msg(LOG_ERR, "Error fstat'ing %s (%s)",
+ file, strerror(errno));
+ close(tfd);
+ return 1;
+ }
+ if (st.st_uid != 0) {
+ audit_msg(LOG_ERR, "Error - %s isn't owned by root", file);
+ close(tfd);
+ return 1;
+ }
+ if ((st.st_mode & S_IWOTH) == S_IWOTH) {
+ audit_msg(LOG_ERR, "Error - %s is world writable", file);
+ close(tfd);
+ return 1;
+ }
+ if (!S_ISREG(st.st_mode)) {
+ audit_msg(LOG_ERR, "Error - %s is not a regular file", file);
+ close(tfd);
+ return 1;
+ }
+
+ f = fdopen(tfd, "rm");
+ if (f == NULL) {
+ audit_msg(LOG_ERR, "Error - fdopen failed (%s)",
+ strerror(errno));
+ close(tfd);
+ return 1;
+ }
+
+ /* Read until eof, lineno starts as 1 */
+ while (get_line(f, buf)) {
+ char *ptr, **fields;
+ int idx=0, nf = (strlen(buf)/3) + 3;
+
+ /* Weed out blank lines */
+ while (buf[idx] == ' ')
+ idx++;
+ if (buf[idx] == 0) {
+ lineno++;
+ continue;
+ }
+
+ preprocess(buf);
+ ptr = audit_strsplit(buf);
+ if (ptr == NULL)
+ break;
+
+ /* allow comments */
+ if (ptr[0] == '#') {
+ lineno++;
+ continue;
+ }
+ i = 0;
+ fields = malloc(nf * sizeof(char *));
+ fields[i++] = "auditctl";
+ fields[i++] = ptr;
+ while( (ptr=audit_strsplit(NULL)) && (i < nf-1)) {
+ postprocess(ptr);
+ fields[i++] = ptr;
+ }
+
+ fields[i] = NULL;
+
+ /* Parse it */
+ if (reset_vars()) {
+ free(fields);
+ fclose(f);
+ return -1;
+ }
+ rc = setopt(i, lineno, fields);
+ free(fields);
+
+ /* handle reply or send rule */
+ if (rc != -3) {
+ if (handle_request(rc) == -1) {
+ if (errno != ECONNREFUSED)
+ audit_msg(LOG_ERR,
+ "There was an error in line %d of %s",
+ lineno, file);
+ else {
+ audit_msg(LOG_ERR,
+ "The audit system is disabled");
+ fclose(f);
+ return 0;
+ }
+ if (ignore == 0) {
+ fclose(f);
+ return -1;
+ }
+ if (continue_error)
+ continue_error = -1;
+ }
+ }
+ lineno++;
+ }
+ fclose(f);
+ return 0;
+}
+
+/* Return 1 if ready, 0 otherwise */
+static int is_ready(int fd)
+{
+ if (audit_is_enabled(fd) == 2) {
+ audit_msg(LOG_ERR, "The audit system is in immutable mode,"
+ " no rule changes allowed");
+ return 0;
+ } else if (errno == ECONNREFUSED) {
+ audit_msg(LOG_ERR, "The audit system is disabled");
+ return 0;
+ }
+ return 1;
+}
+
+int main(int argc, char *argv[])
+{
+ int retval = 1;
+
+ set_aumessage_mode(MSG_STDERR, DBG_NO);
+
+ if (argc == 1) {
+ usage();
+ return 1;
+ }
+#ifndef DEBUG
+ /* Make sure we are root if we do anything except help */
+ if (!(argc == 2 && (strcmp(argv[1], "--help")==0 ||
+ strcmp(argv[1], "-h") == 0)) && (geteuid() != 0)) {
+ audit_msg(LOG_WARNING, "You must be root to run this program.");
+ return 4;
+ }
+#endif
+ /* Check where the rules are coming from: commandline or file */
+ if ((argc == 3) && (strcmp(argv[1], "-R") == 0)) {
+ // If reading a file, its most likely start up. Send problems
+ // to syslog where they will persist for later review
+ set_aumessage_mode(MSG_SYSLOG, DBG_NO);
+ fd = audit_open();
+ if (is_ready(fd) == 0)
+ return 0;
+ else if (fileopt(argv[2])) {
+ free(rule_new);
+ return 1;
+ } else {
+ free(rule_new);
+ if (continue_error < 0)
+ return 1;
+ return 0;
+ }
+ } else {
+ if (reset_vars()) {
+ free(rule_new);
+ return 1;
+ }
+ retval = setopt(argc, 0, argv);
+ if (retval == -3) {
+ free(rule_new);
+ return 0;
+ }
+ }
+
+ if (add != AUDIT_FILTER_UNSET || del != AUDIT_FILTER_UNSET) {
+ fd = audit_open();
+ if (is_ready(fd) == 0) {
+ free(rule_new);
+ return 0;
+ }
+ }
+ retval = handle_request(retval);
+ free(rule_new);
+ return retval;
+}
+
+/*
+ * This function is called after setopt to handle the return code.
+ * On entry, status = 0 means just get the reply. Greater than 0 means we
+ * are adding or deleting a rule or watch. -1 means an error occurred.
+ * -2 means everything is OK and no reply needed. Even if there's an
+ * error, we need to call this routine to close up the audit fd.
+ * The return code from this function is 0 success and -1 error.
+ */
+static int handle_request(int status)
+{
+ if (status == 0) {
+ if (_audit_syscalladded) {
+ audit_msg(LOG_ERR, "Error - no list specified");
+ return -1;
+ }
+ get_reply();
+ } else if (status == -2)
+ status = 0; // report success
+ else if (status > 0) {
+ int rc;
+ if (add != AUDIT_FILTER_UNSET) {
+ // if !task add syscall any if not specified
+ if ((add & AUDIT_FILTER_MASK) != AUDIT_FILTER_TASK &&
+ _audit_syscalladded != 1) {
+ audit_rule_syscallbyname_data(
+ rule_new, "all");
+ }
+ set_aumessage_mode(MSG_QUIET, DBG_NO);
+ rc = audit_add_rule_data(fd, rule_new, add, action);
+ set_aumessage_mode(MSG_STDERR, DBG_NO);
+ /* Retry for legacy kernels */
+ if (rc < 0) {
+ if (errno == EINVAL &&
+ rule_new->fields[0] == AUDIT_DIR) {
+ rule_new->fields[0] = AUDIT_WATCH;
+ rc = audit_add_rule_data(fd, rule_new,
+ add, action);
+ } else {
+ audit_msg(LOG_ERR,
+ "Error sending add rule data request (%s)",
+ errno == EEXIST ?
+ "Rule exists" : strerror(-rc));
+ }
+ }
+ }
+ else if (del != AUDIT_FILTER_UNSET) {
+ if ((del & AUDIT_FILTER_MASK) != AUDIT_FILTER_TASK &&
+ _audit_syscalladded != 1) {
+ audit_rule_syscallbyname_data(
+ rule_new, "all");
+ }
+ set_aumessage_mode(MSG_QUIET, DBG_NO);
+ rc = audit_delete_rule_data(fd, rule_new,
+ del, action);
+ set_aumessage_mode(MSG_STDERR, DBG_NO);
+ /* Retry for legacy kernels */
+ if (rc < 0) {
+ if (errno == EINVAL &&
+ rule_new->fields[0] == AUDIT_DIR) {
+ rule_new->fields[0] = AUDIT_WATCH;
+ rc = audit_delete_rule_data(fd,rule_new,
+ del, action);
+ } else {
+ audit_msg(LOG_ERR,
+ "Error sending delete rule data request (%s)",
+ errno == EEXIST ?
+ "Rule exists" : strerror(-rc));
+ }
+ }
+ } else {
+ usage();
+ audit_close(fd);
+ exit(1);
+ }
+ if (rc <= 0)
+ status = -1;
+ else
+ status = 0;
+ } else
+ status = -1;
+
+ if (!list_requested)
+ audit_close(fd);
+ fd = -1;
+ return status;
+}
+
+/*
+ * A reply from the kernel is expected. Get and display it.
+ */
+static void get_reply(void)
+{
+ int i, retval;
+ int timeout = 40; /* loop has delay of .1 - so this is 4 seconds */
+ struct audit_reply rep;
+ fd_set read_mask;
+ FD_ZERO(&read_mask);
+ FD_SET(fd, &read_mask);
+
+ // Reset printing counter
+ audit_print_init();
+
+ for (i = 0; i < timeout; i++) {
+ struct timeval t;
+
+ t.tv_sec = 0;
+ t.tv_usec = 100000; /* .1 second */
+ do {
+ retval=select(fd+1, &read_mask, NULL, NULL, &t);
+ } while (retval < 0 && errno == EINTR);
+ // We'll try to read just in case
+ retval = audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING, 0);
+ if (retval > 0) {
+ if (rep.type == NLMSG_ERROR && rep.error->error == 0) {
+ i = 0; /* reset timeout */
+ continue; /* This was an ack */
+ }
+
+ if ((retval = audit_print_reply(&rep, fd)) == 0)
+ break;
+ else
+ i = 0; /* If getting more, reset timeout */
+ }
+ }
+}
+