diff options
Diffstat (limited to 'framework/src/audit/src/auditctl.c')
-rw-r--r-- | framework/src/audit/src/auditctl.c | 1472 |
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 */ + } + } +} + |