/* 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 * Rickard E. (Rik) Faith */ #include "config.h" #include #include #include #include /* strdup needs xopen define */ #include #include #include #include #include #include #include #include #include /* For basename */ #include /* 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 Append rule to end of ist with ction\n" " -A Add rule at beginning of ist with ction\n" " -b 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 Delete rule from ist with 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 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 make subtree part of mount point's dir watches\n" " -r Set limit in messages/sec (0=none)\n" " -R 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 Insert watch at \n" " -W Remove watch at \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 to * the watch. When this potential watch reaches the kernel, it will resolve * down to (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 */ } } }