diff options
Diffstat (limited to 'framework/src/audit/lib/libaudit.c')
-rw-r--r-- | framework/src/audit/lib/libaudit.c | 1606 |
1 files changed, 1606 insertions, 0 deletions
diff --git a/framework/src/audit/lib/libaudit.c b/framework/src/audit/lib/libaudit.c new file mode 100644 index 00000000..6311b813 --- /dev/null +++ b/framework/src/audit/lib/libaudit.c @@ -0,0 +1,1606 @@ +/* libaudit.c -- + * Copyright 2004-2009,2012,2014 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; 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 <stdarg.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <ctype.h> +#include <time.h> +#include <pwd.h> +#include <grp.h> +#include <errno.h> +#include <sys/poll.h> +#include <sys/utsname.h> +#include <sys/stat.h> +#include <fcntl.h> /* O_NOFOLLOW needs gnu defined */ +#include <limits.h> /* for PATH_MAX */ +#include <sys/stat.h> +#include <sys/types.h> + +#include "libaudit.h" +#include "private.h" +#include "errormsg.h" + +/* #defines for the audit failure query */ +#define CONFIG_FILE "/etc/libaudit.conf" + +/* Local prototypes */ +struct nv_pair +{ + const char *name; + const char *value; +}; + +struct kw_pair +{ + const char *name; + int (*parser)(const char *, int); +}; + +struct nv_list +{ + const char *name; + int option; +}; + +struct libaudit_conf +{ + auditfail_t failure_action; +}; + +static const struct nv_list failure_actions[] = +{ + {"ignore", FAIL_IGNORE }, + {"log", FAIL_LOG }, + {"terminate", FAIL_TERMINATE }, + { NULL, 0 } +}; + +int _audit_permadded = 0; +int _audit_archadded = 0; +int _audit_syscalladded = 0; +unsigned int _audit_elf = 0U; +static struct libaudit_conf config; + +static int audit_failure_parser(const char *val, int line); +static int audit_name_to_uid(const char *name, uid_t *uid); +static int audit_name_to_gid(const char *name, gid_t *gid); + +static const struct kw_pair keywords[] = +{ + {"failure_action", audit_failure_parser }, + { NULL, NULL } +}; + +static int audit_priority(int xerrno) +{ + /* If they've compiled their own kernel and did not include + * the audit susbsystem, they will get ECONNREFUSED. We'll + * demote the message to debug so its not lost entirely. */ + if (xerrno == ECONNREFUSED) + return LOG_DEBUG; + else + return LOG_WARNING; +} + +int audit_request_status(int fd) +{ + int rc = audit_send(fd, AUDIT_GET, NULL, 0); + if (rc < 0) + audit_msg(audit_priority(errno), + "Error sending status request (%s)", strerror(-rc)); + return rc; +} +hidden_def(audit_request_status) + +/* + * Set everything to its default value + */ +static void clear_config(void) +{ + config.failure_action = FAIL_IGNORE; +} + +/* Get 1 line from file */ +static char *get_line(FILE *f, char *buf, size_t len) +{ + if (fgets(buf, len, f)) { + /* remove newline */ + char *ptr = strchr(buf, 0x0a); + if (ptr) + *ptr = 0; + return buf; + } + return NULL; +} + +static int nv_split(char *buf, struct nv_pair *nv) +{ + /* Get the name part */ + char *ptr, *saved=NULL; + + nv->name = NULL; + nv->value = NULL; + ptr = audit_strsplit_r(buf, &saved); + if (ptr == NULL) + return 0; /* If there's nothing, go to next line */ + if (ptr[0] == '#') + return 0; /* If there's a comment, go to next line */ + nv->name = ptr; + + /* Check for a '=' */ + ptr = audit_strsplit_r(NULL, &saved); + if (ptr == NULL) + return 1; + if (strcmp(ptr, "=") != 0) + return 2; + + /* get the value */ + ptr = audit_strsplit_r(NULL, &saved); + if (ptr == NULL) + return 1; + nv->value = ptr; + + /* Make sure there's nothing else */ + ptr = audit_strsplit_r(NULL, &saved); + if (ptr) + return 1; + + /* Everything is OK */ + return 0; +} + +static const struct kw_pair *kw_lookup(const char *val) +{ + int i = 0; + while (keywords[i].name != NULL) { + if (strcasecmp(keywords[i].name, val) == 0) + break; + i++; + } + return &keywords[i]; +} + +static int audit_failure_parser(const char *val, int line) +{ + int i; + + audit_msg(LOG_DEBUG, "audit_failure_parser called with: %s", val); + for (i=0; failure_actions[i].name != NULL; i++) { + if (strcasecmp(val, failure_actions[i].name) == 0) { + config.failure_action = failure_actions[i].option; + return 0; + } + } + audit_msg(LOG_ERR, "Option %s not found - line %d", val, line); + return 1; +} + +/* + * Read the /etc/libaudit.conf file and all tunables. + */ +static int load_libaudit_config(const char *path) +{ + int fd, rc, lineno = 1; + struct stat st; + FILE *f; + char buf[128]; + + /* open the file */ + rc = open(path, O_NOFOLLOW|O_RDONLY); + if (rc < 0) { + if (errno != ENOENT) { + audit_msg(LOG_ERR, "Error opening %s (%s)", + path, strerror(errno)); + return 1; + } + audit_msg(LOG_WARNING, + "Config file %s doesn't exist, skipping", path); + return 0; + } + fd = rc; + + /* check the file's permissions: owned by root, not world writable, + * not symlink. + */ + audit_msg(LOG_DEBUG, "Config file %s opened for parsing", path); + if (fstat(fd, &st) < 0) { + audit_msg(LOG_ERR, "Error fstat'ing %s (%s)", + path, strerror(errno)); + close(fd); + return 1; + } + if (st.st_uid != 0) { + audit_msg(LOG_ERR, "Error - %s isn't owned by root", path); + close(fd); + return 1; + } + if ((st.st_mode & S_IWOTH) == S_IWOTH) { + audit_msg(LOG_ERR, "Error - %s is world writable", path); + close(fd); + return 1; + } + if (!S_ISREG(st.st_mode)) { + audit_msg(LOG_ERR, "Error - %s is not a regular file", path); + close(fd); + return 1; + } + + /* it's ok, read line by line */ + f = fdopen(fd, "rm"); + if (f == NULL) { + audit_msg(LOG_ERR, "Error - fdopen failed (%s)", + strerror(errno)); + close(fd); + return 1; + } + + while (get_line(f, buf, sizeof(buf))) { + // convert line into name-value pair + const struct kw_pair *kw; + struct nv_pair nv; + rc = nv_split(buf, &nv); + switch (rc) { + case 0: // fine + break; + case 1: // not the right number of tokens. + audit_msg(LOG_ERR, + "Wrong number of arguments for line %d in %s", + lineno, path); + break; + case 2: // no '=' sign + audit_msg(LOG_ERR, + "Missing equal sign for line %d in %s", + lineno, path); + break; + default: // something else went wrong... + audit_msg(LOG_ERR, + "Unknown error for line %d in %s", + lineno, path); + break; + } + if (nv.name == NULL) { + lineno++; + continue; + } + if (nv.value == NULL) { + fclose(f); + return 1; + } + + /* identify keyword or error */ + kw = kw_lookup(nv.name); + if (kw->name == NULL) { + audit_msg(LOG_ERR, + "Unknown keyword \"%s\" in line %d of %s", + nv.name, lineno, path); + fclose(f); + return 1; + } + + /* dispatch to keyword's local parser */ + rc = kw->parser(nv.value, lineno); + if (rc != 0) { + fclose(f); + return 1; // local parser puts message out + } + + lineno++; + } + + fclose(f); + return 0; +} + + +/* + * This function is called to get the value of the failure_action + * tunable stored in /etc/libaudit.conf. The function returns 1 if + * the tunable is not found or there is an error. If the tunable is found, + * 0 is returned the the tunable value is saved in the failmode parameter. + */ +int get_auditfail_action(auditfail_t *failmode) +{ + clear_config(); + + if (load_libaudit_config(CONFIG_FILE)) { + *failmode = config.failure_action; + return 1; + } + + *failmode = config.failure_action; + return 0; +} + +int audit_set_enabled(int fd, uint32_t enabled) +{ + int rc; + struct audit_status s; + + memset(&s, 0, sizeof(s)); + s.mask = AUDIT_STATUS_ENABLED; + s.enabled = enabled; + rc = audit_send(fd, AUDIT_SET, &s, sizeof(s)); + if (rc < 0) + audit_msg(audit_priority(errno), + "Error sending enable request (%s)", strerror(-rc)); + return rc; +} + +/* + * This function will return 0 if auditing is NOT enabled and + * 1 if enabled, and -1 on error. + */ +int audit_is_enabled(int fd) +{ + int rc; + + if (fd < 0) + return 0; + + if ((rc = audit_request_status(fd)) > 0) { + struct audit_reply rep; + int i; + int timeout = 40; /* tenths of seconds */ + struct pollfd pfd[1]; + + pfd[0].fd = fd; + pfd[0].events = POLLIN; + + for (i = 0; i < timeout; i++) { + do { + rc = poll(pfd, 1, 100); + } while (rc < 0 && errno == EINTR); + + rc = audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING,0); + if (rc > 0) { + /* If we get done or error, break out */ + if (rep.type == NLMSG_DONE || + rep.type == NLMSG_ERROR) + break; + + /* If its not status, keep looping */ + if (rep.type != AUDIT_GET) + continue; + + /* Found it... */ + return rep.status->enabled; + } + } + } + if (rc == -ECONNREFUSED) { + /* This is here to let people that build their own kernel + and disable the audit system get in. ECONNREFUSED is + issued by the kernel when there is "no on listening". */ + return 0; + } else if (rc == -EPERM && getuid() != 0) { + /* If we get this, then the kernel supports auditing + * but we don't have enough privilege to write to the + * socket. Therefore, we have already been authenticated + * and we are a common user. Just act as though auditing + * is not enabled. Any other error we take seriously. + * This is here basically to satisfy Xscreensaver. */ + return 0; + } + return -1; +} + +int audit_set_failure(int fd, uint32_t failure) +{ + int rc; + struct audit_status s; + + memset(&s, 0, sizeof(s)); + s.mask = AUDIT_STATUS_FAILURE; + s.failure = failure; + rc = audit_send(fd, AUDIT_SET, &s, sizeof(s)); + if (rc < 0) + audit_msg(audit_priority(errno), + "Error sending failure mode request (%s)", + strerror(-rc)); + return rc; +} + +/* + * This function returns -1 on error and 1 on success. + */ +int audit_set_pid(int fd, uint32_t pid, rep_wait_t wmode) +{ + struct audit_status s; + struct audit_reply rep; + struct pollfd pfd[1]; + int rc; + + memset(&s, 0, sizeof(s)); + s.mask = AUDIT_STATUS_PID; + s.pid = pid; + rc = audit_send(fd, AUDIT_SET, &s, sizeof(s)); + if (rc < 0) { + audit_msg(audit_priority(errno), + "Error setting audit daemon pid (%s)", + strerror(-rc)); + return rc; + } + if (wmode == WAIT_NO) + return 1; + + /* Now we'll see if there's any reply message. This only + happens on error. It is not fatal if there is no message. + As a matter of fact, we don't do anything with the message + besides gobble it. */ + pfd[0].fd = fd; + pfd[0].events = POLLIN; + do { + rc = poll(pfd, 1, 100); /* .1 second */ + } while (rc < 0 && errno == EINTR); + + (void)audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING, 0); + return 1; +} + +int audit_set_rate_limit(int fd, uint32_t limit) +{ + int rc; + struct audit_status s; + + memset(&s, 0, sizeof(s)); + s.mask = AUDIT_STATUS_RATE_LIMIT; + s.rate_limit = limit; + rc = audit_send(fd, AUDIT_SET, &s, sizeof(s)); + if (rc < 0) + audit_msg(audit_priority(errno), + "Error sending rate limit request (%s)", + strerror(-rc)); + return rc; +} + +int audit_set_backlog_limit(int fd, uint32_t limit) +{ + int rc; + struct audit_status s; + + memset(&s, 0, sizeof(s)); + s.mask = AUDIT_STATUS_BACKLOG_LIMIT; + s.backlog_limit = limit; + rc = audit_send(fd, AUDIT_SET, &s, sizeof(s)); + if (rc < 0) + audit_msg(audit_priority(errno), + "Error sending backlog limit request (%s)", + strerror(-rc)); + return rc; +} + +int audit_set_backlog_wait_time(int fd, uint32_t bwt) +{ + int rc = -1; +#if HAVE_DECL_AUDIT_VERSION_BACKLOG_WAIT_TIME + struct audit_status s; + + memset(&s, 0, sizeof(s)); + s.mask = AUDIT_STATUS_BACKLOG_WAIT_TIME; + s.backlog_wait_time = bwt; + rc = audit_send(fd, AUDIT_SET, &s, sizeof(s)); + if (rc < 0) + audit_msg(audit_priority(errno), + "Error sending backlog limit request (%s)", + strerror(-rc)); +#endif + return rc; +} + +int audit_set_feature(int fd, unsigned feature, unsigned value, unsigned lock) +{ +#if HAVE_DECL_AUDIT_FEATURE_VERSION + int rc; + struct audit_features f; + + memset(&f, 0, sizeof(f)); + f.mask = AUDIT_FEATURE_TO_MASK(feature); + if (value) + f.features = AUDIT_FEATURE_TO_MASK(feature); + if (lock) + f.lock = AUDIT_FEATURE_TO_MASK(feature); + rc = audit_send(fd, AUDIT_SET_FEATURE, &f, sizeof(f)); + if (rc < 0) + audit_msg(audit_priority(errno), + "Error setting feature (%s)", + strerror(-rc)); + return rc; +#else + errno = EINVAL; + return -1; +#endif +} +hidden_def(audit_set_feature) + +int audit_request_features(int fd) +{ +#if HAVE_DECL_AUDIT_FEATURE_VERSION + int rc; + struct audit_features f; + + memset(&f, 0, sizeof(f)); + rc = audit_send(fd, AUDIT_GET_FEATURE, &f, sizeof(f)); + if (rc < 0) + audit_msg(audit_priority(errno), + "Error getting feature (%s)", + strerror(-rc)); + return rc; +#else + errno = EINVAL; + return -1; +#endif +} +hidden_def(audit_request_features) + +extern int audit_set_loginuid_immutable(int fd) +{ +#if HAVE_DECL_AUDIT_FEATURE_VERSION + return audit_set_feature(fd, AUDIT_FEATURE_LOGINUID_IMMUTABLE, 1, 1); +#else + errno = EINVAL; + return -1; +#endif +} + +int audit_request_rules_list_data(int fd) +{ + int rc = audit_send(fd, AUDIT_LIST_RULES, NULL, 0); + if (rc < 0 && rc != -EINVAL) + audit_msg(audit_priority(errno), + "Error sending rule list data request (%s)", + strerror(-rc)); + return rc; +} + +int audit_request_signal_info(int fd) +{ + int rc = audit_send(fd, AUDIT_SIGNAL_INFO, NULL, 0); + if (rc < 0) + audit_msg(LOG_WARNING, + "Error sending signal_info request (%s)", + strerror(-rc)); + return rc; +} + +int audit_update_watch_perms(struct audit_rule_data *rule, int perms) +{ + int i, done=0; + + if (rule->field_count < 1) + return -1; + + // First see if we have an entry we are updating + for (i=0; i< rule->field_count; i++) { + if (rule->fields[i] == AUDIT_PERM) { + rule->values[i] = perms; + done = 1; + } + } + if (!done) { + // If not check to see if we have room to add a field + if (rule->field_count >= (AUDIT_MAX_FIELDS - 1)) + return -2; + + // Add the perm + rule->fields[rule->field_count] = AUDIT_PERM; + rule->fieldflags[rule->field_count] = AUDIT_EQUAL; + rule->values[rule->field_count] = perms; + rule->field_count++; + } + return 0; +} + +int audit_add_watch(struct audit_rule_data **rulep, const char *path) +{ + return audit_add_watch_dir(AUDIT_WATCH, rulep, path); +} + +int audit_add_dir(struct audit_rule_data **rulep, const char *path) +{ + return audit_add_watch_dir(AUDIT_DIR, rulep, path); +} + +int audit_add_watch_dir(int type, struct audit_rule_data **rulep, + const char *path) +{ + size_t len = strlen(path); + struct audit_rule_data *rule = *rulep; + + if (rule && rule->field_count) { + audit_msg(LOG_ERR, "Rule is not empty\n"); + return -1; + } + if (type != AUDIT_WATCH && type != AUDIT_DIR) { + audit_msg(LOG_ERR, "Invalid type used\n"); + return -1; + } + + *rulep = realloc(rule, len + sizeof(*rule)); + if (*rulep == NULL) { + free(rule); + audit_msg(LOG_ERR, "Cannot realloc memory!\n"); + return -1; + } + rule = *rulep; + memset(rule, 0, len + sizeof(*rule)); + + rule->flags = AUDIT_FILTER_EXIT; + rule->action = AUDIT_ALWAYS; + audit_rule_syscallbyname_data(rule, "all"); + rule->field_count = 2; + rule->fields[0] = type; + rule->values[0] = len; + rule->fieldflags[0] = AUDIT_EQUAL; + rule->buflen = len; + memcpy(&rule->buf[0], path, len); + + // Default to all permissions + rule->fields[1] = AUDIT_PERM; + rule->fieldflags[1] = AUDIT_EQUAL; + rule->values[1] = AUDIT_PERM_READ | AUDIT_PERM_WRITE | + AUDIT_PERM_EXEC | AUDIT_PERM_ATTR; + + _audit_permadded = 1; + + return 0; +} +hidden_def(audit_add_watch_dir) + +int audit_add_rule_data(int fd, struct audit_rule_data *rule, + int flags, int action) +{ + int rc; + + if (flags == AUDIT_FILTER_ENTRY) { + audit_msg(LOG_WARNING, "Use of entry filter is deprecated"); + return -2; + } + rule->flags = flags; + rule->action = action; + rc = audit_send(fd, AUDIT_ADD_RULE, rule, + sizeof(struct audit_rule_data) + rule->buflen); + if (rc < 0) + audit_msg(audit_priority(errno), + "Error sending add rule data request (%s)", + errno == EEXIST ? + "Rule exists" : strerror(-rc)); + return rc; +} + +int audit_delete_rule_data(int fd, struct audit_rule_data *rule, + int flags, int action) +{ + int rc; + + if (flags == AUDIT_FILTER_ENTRY) { + audit_msg(LOG_WARNING, "Use of entry filter is deprecated"); + return -2; + } + rule->flags = flags; + rule->action = action; + rc = audit_send(fd, AUDIT_DEL_RULE, rule, + sizeof(struct audit_rule_data) + rule->buflen); + if (rc < 0) { + if (rc == -ENOENT) + audit_msg(LOG_WARNING, + "Error sending delete rule request (No rule matches)"); + else + audit_msg(audit_priority(errno), + "Error sending delete rule data request (%s)", + strerror(-rc)); + } + return rc; +} + +/* + * This function is part of the directory auditing code + */ +int audit_trim_subtrees(int fd) +{ + int rc = audit_send(fd, AUDIT_TRIM, NULL, 0); + if (rc < 0) + audit_msg(audit_priority(errno), + "Error sending trim subtrees command (%s)", + strerror(-rc)); + return rc; +} + +/* + * This function is part of the directory auditing code + */ +int audit_make_equivalent(int fd, const char *mount_point, + const char *subtree) +{ + int rc; + size_t len1 = strlen(mount_point); + size_t len2 = strlen(subtree); + struct { + uint32_t sizes[2]; + unsigned char buf[]; + } *cmd = malloc(sizeof(*cmd) + len1 + len2); + + memset(cmd, 0, sizeof(*cmd) + len1 + len2); + + cmd->sizes[0] = len1; + cmd->sizes[1] = len2; + memcpy(&cmd->buf[0], mount_point, len1); + memcpy(&cmd->buf[len1], subtree, len2); + + rc = audit_send(fd, AUDIT_MAKE_EQUIV, cmd, sizeof(*cmd) + len1 + len2); + if (rc < 0) + audit_msg(audit_priority(errno), + "Error sending make_equivalent command (%s)", + strerror(-rc)); + free(cmd); + return rc; +} + +/* + * This function will retreive the loginuid or -1 if there + * is an error. + */ +uid_t audit_getloginuid(void) +{ + uid_t uid; + int len, in; + char buf[16]; + + errno = 0; + in = open("/proc/self/loginuid", O_NOFOLLOW|O_RDONLY); + if (in < 0) + return -1; + do { + len = read(in, buf, sizeof(buf)); + } while (len < 0 && errno == EINTR); + close(in); + if (len < 0 || len >= sizeof(buf)) + return -1; + buf[len] = 0; + errno = 0; + uid = strtol(buf, 0, 10); + if (errno) + return -1; + else + return uid; +} + +/* + * This function returns 0 on success and 1 on failure + */ +int audit_setloginuid(uid_t uid) +{ + char loginuid[16]; + int o, count, rc = 0; + + errno = 0; + count = snprintf(loginuid, sizeof(loginuid), "%u", uid); + o = open("/proc/self/loginuid", O_NOFOLLOW|O_WRONLY|O_TRUNC); + if (o >= 0) { + int block, offset = 0; + + while (count > 0) { + block = write(o, &loginuid[offset], (unsigned)count); + + if (block < 0) { + if (errno == EINTR) + continue; + audit_msg(LOG_ERR, "Error writing loginuid"); + close(o); + return 1; + } + offset += block; + count -= block; + } + close(o); + } else { + audit_msg(LOG_ERR, "Error opening /proc/self/loginuid"); + rc = 1; + } + return rc; +} + +int audit_rule_syscall_data(struct audit_rule_data *rule, int scall) +{ + int word = AUDIT_WORD(scall); + int bit = AUDIT_BIT(scall); + + if (word >= (AUDIT_BITMASK_SIZE-1)) + return -1; + rule->mask[word] |= bit; + return 0; +} +hidden_def(audit_rule_syscall_data) + +int audit_rule_syscallbyname_data(struct audit_rule_data *rule, + const char *scall) +{ + int nr, i; + int machine; + + if (!strcmp(scall, "all")) { + for (i = 0; i < (AUDIT_BITMASK_SIZE-1); i++) + rule->mask[i] = ~0; + return 0; + } + if (!_audit_elf) + machine = audit_detect_machine(); + else + machine = audit_elf_to_machine(_audit_elf); + if (machine < 0) + return -2; + nr = audit_name_to_syscall(scall, machine); + if (nr < 0) { + if (isdigit(scall[0])) + nr = strtol(scall, NULL, 0); + } + if (nr >= 0) + return audit_rule_syscall_data(rule, nr); + return -1; +} +hidden_def(audit_rule_syscallbyname_data) + +int audit_rule_interfield_comp_data(struct audit_rule_data **rulep, + const char *pair, + int flags) { + const char *f = pair; + char *v; + int op; + int field1, field2; + struct audit_rule_data *rule = *rulep; + + if (f == NULL) + return -1; + + if (rule->field_count >= (AUDIT_MAX_FIELDS - 1)) + return -28; + + /* look for 2-char operators first + then look for 1-char operators afterwards + when found, null out the bytes under the operators to split + and set value pointer just past operator bytes + */ + if ( (v = strstr(pair, "!=")) ) { + *v++ = '\0'; + *v++ = '\0'; + op = AUDIT_NOT_EQUAL; + } else if ( (v = strstr(pair, "=")) ) { + *v++ = '\0'; + op = AUDIT_EQUAL; + } else { + return -13; + } + + if (*f == 0) + return -24; + + if (*v == 0) + return -25; + + if ((field1 = audit_name_to_field(f)) < 0) + return -26; + + if ((field2 = audit_name_to_field(v)) < 0) + return -27; + + /* Interfield comparison can only be in exit filter */ + if (flags != AUDIT_FILTER_EXIT) + return -7; + + // It should always be AUDIT_FIELD_COMPARE + rule->fields[rule->field_count] = AUDIT_FIELD_COMPARE; + rule->fieldflags[rule->field_count] = op; + /* oh god, so many permutations */ + switch (field1) + { + /* UID comparison */ + case AUDIT_EUID: + switch(field2) { + case AUDIT_LOGINUID: + rule->values[rule->field_count] = AUDIT_COMPARE_AUID_TO_EUID; + break; + case AUDIT_FSUID: + rule->values[rule->field_count] = AUDIT_COMPARE_EUID_TO_FSUID; + break; + case AUDIT_OBJ_UID: + rule->values[rule->field_count] = AUDIT_COMPARE_EUID_TO_OBJ_UID; + break; + case AUDIT_SUID: + rule->values[rule->field_count] = AUDIT_COMPARE_EUID_TO_SUID; + break; + case AUDIT_UID: + rule->values[rule->field_count] = AUDIT_COMPARE_UID_TO_EUID; + break; + default: + return -1; + } + break; + case AUDIT_FSUID: + switch(field2) { + case AUDIT_LOGINUID: + rule->values[rule->field_count] = AUDIT_COMPARE_AUID_TO_FSUID; + break; + case AUDIT_EUID: + rule->values[rule->field_count] = AUDIT_COMPARE_EUID_TO_FSUID; + break; + case AUDIT_OBJ_UID: + rule->values[rule->field_count] = AUDIT_COMPARE_FSUID_TO_OBJ_UID; + break; + case AUDIT_SUID: + rule->values[rule->field_count] = AUDIT_COMPARE_SUID_TO_FSUID; + break; + case AUDIT_UID: + rule->values[rule->field_count] = AUDIT_COMPARE_UID_TO_FSUID; + break; + default: + return -1; + } + break; + case AUDIT_LOGINUID: + switch(field2) { + case AUDIT_EUID: + rule->values[rule->field_count] = AUDIT_COMPARE_AUID_TO_EUID; + break; + case AUDIT_FSUID: + rule->values[rule->field_count] = AUDIT_COMPARE_AUID_TO_FSUID; + break; + case AUDIT_OBJ_UID: + rule->values[rule->field_count] = AUDIT_COMPARE_AUID_TO_OBJ_UID; + break; + case AUDIT_SUID: + rule->values[rule->field_count] = AUDIT_COMPARE_AUID_TO_SUID; + break; + case AUDIT_UID: + rule->values[rule->field_count] = AUDIT_COMPARE_UID_TO_AUID; + break; + default: + return -1; + } + break; + case AUDIT_SUID: + switch(field2) { + case AUDIT_LOGINUID: + rule->values[rule->field_count] = AUDIT_COMPARE_AUID_TO_SUID; + break; + case AUDIT_EUID: + rule->values[rule->field_count] = AUDIT_COMPARE_EUID_TO_SUID; + break; + case AUDIT_FSUID: + rule->values[rule->field_count] = AUDIT_COMPARE_SUID_TO_FSUID; + break; + case AUDIT_OBJ_UID: + rule->values[rule->field_count] = AUDIT_COMPARE_SUID_TO_OBJ_UID; + break; + case AUDIT_UID: + rule->values[rule->field_count] = AUDIT_COMPARE_UID_TO_SUID; + break; + default: + return -1; + } + break; + case AUDIT_OBJ_UID: + switch(field2) { + case AUDIT_LOGINUID: + rule->values[rule->field_count] = AUDIT_COMPARE_AUID_TO_OBJ_UID; + break; + case AUDIT_EUID: + rule->values[rule->field_count] = AUDIT_COMPARE_EUID_TO_OBJ_UID; + break; + case AUDIT_FSUID: + rule->values[rule->field_count] = AUDIT_COMPARE_FSUID_TO_OBJ_UID; + break; + case AUDIT_UID: + rule->values[rule->field_count] = AUDIT_COMPARE_UID_TO_OBJ_UID; + break; + case AUDIT_SUID: + rule->values[rule->field_count] = AUDIT_COMPARE_SUID_TO_OBJ_UID; + break; + default: + return -1; + } + break; + case AUDIT_UID: + switch(field2) { + case AUDIT_LOGINUID: + rule->values[rule->field_count] = AUDIT_COMPARE_UID_TO_AUID; + break; + case AUDIT_EUID: + rule->values[rule->field_count] = AUDIT_COMPARE_UID_TO_EUID; + break; + case AUDIT_FSUID: + rule->values[rule->field_count] = AUDIT_COMPARE_UID_TO_FSUID; + break; + case AUDIT_OBJ_UID: + rule->values[rule->field_count] = AUDIT_COMPARE_UID_TO_OBJ_UID; + break; + case AUDIT_SUID: + rule->values[rule->field_count] = AUDIT_COMPARE_UID_TO_SUID; + break; + default: + return -1; + } + break; + + /* GID comparisons */ + case AUDIT_EGID: + switch(field2) { + case AUDIT_FSGID: + rule->values[rule->field_count] = AUDIT_COMPARE_EGID_TO_FSGID; + break; + case AUDIT_GID: + rule->values[rule->field_count] = AUDIT_COMPARE_GID_TO_EGID; + break; + case AUDIT_OBJ_GID: + rule->values[rule->field_count] = AUDIT_COMPARE_EGID_TO_OBJ_GID; + break; + case AUDIT_SGID: + rule->values[rule->field_count] = AUDIT_COMPARE_EGID_TO_SGID; + break; + default: + return -1; + } + break; + case AUDIT_FSGID: + switch(field2) { + case AUDIT_SGID: + rule->values[rule->field_count] = AUDIT_COMPARE_SGID_TO_FSGID; + break; + case AUDIT_GID: + rule->values[rule->field_count] = AUDIT_COMPARE_GID_TO_FSGID; + break; + case AUDIT_OBJ_GID: + rule->values[rule->field_count] = AUDIT_COMPARE_FSGID_TO_OBJ_GID; + break; + case AUDIT_EGID: + rule->values[rule->field_count] = AUDIT_COMPARE_EGID_TO_FSGID; + break; + default: + return -1; + } + break; + case AUDIT_GID: + switch(field2) { + case AUDIT_EGID: + rule->values[rule->field_count] = AUDIT_COMPARE_GID_TO_EGID; + break; + case AUDIT_FSGID: + rule->values[rule->field_count] = AUDIT_COMPARE_GID_TO_FSGID; + break; + case AUDIT_OBJ_GID: + rule->values[rule->field_count] = AUDIT_COMPARE_GID_TO_OBJ_GID; + break; + case AUDIT_SGID: + rule->values[rule->field_count] = AUDIT_COMPARE_GID_TO_SGID; + break; + default: + return -1; + } + break; + case AUDIT_OBJ_GID: + switch(field2) { + case AUDIT_EGID: + rule->values[rule->field_count] = AUDIT_COMPARE_EGID_TO_OBJ_GID; + break; + case AUDIT_FSGID: + rule->values[rule->field_count] = AUDIT_COMPARE_FSGID_TO_OBJ_GID; + break; + case AUDIT_GID: + rule->values[rule->field_count] = AUDIT_COMPARE_GID_TO_OBJ_GID; + break; + case AUDIT_SGID: + rule->values[rule->field_count] = AUDIT_COMPARE_SGID_TO_OBJ_GID; + break; + default: + return -1; + } + break; + case AUDIT_SGID: + switch(field2) { + case AUDIT_FSGID: + rule->values[rule->field_count] = AUDIT_COMPARE_SGID_TO_FSGID; + break; + case AUDIT_GID: + rule->values[rule->field_count] = AUDIT_COMPARE_GID_TO_SGID; + break; + case AUDIT_OBJ_GID: + rule->values[rule->field_count] = AUDIT_COMPARE_SGID_TO_OBJ_GID; + break; + case AUDIT_EGID: + rule->values[rule->field_count] = AUDIT_COMPARE_EGID_TO_SGID; + break; + default: + return -1; + } + break; + default: + return -1; + break; + } + rule->field_count++; + return 0; +} + +int audit_determine_machine(const char *arch) +{ // What do we want? i686, x86_64, ia64 or b64, b32 + int machine; + unsigned int bits = 0; + + if (strcasecmp("b64", arch) == 0) { + bits = __AUDIT_ARCH_64BIT; + machine = audit_detect_machine(); + } else if (strcasecmp("b32", arch) == 0) { + bits = ~__AUDIT_ARCH_64BIT; + machine = audit_detect_machine(); + } else { + machine = audit_name_to_machine(arch); + if (machine < 0) { + // See if its numeric + unsigned int ival; + errno = 0; + ival = strtoul(arch, NULL, 16); + if (errno) + return -4; + machine = audit_elf_to_machine(ival); + } + } + + if (machine < 0) + return -4; + + /* Here's where we fixup the machine. For example, they give + * x86_64 & want 32 bits we translate that to i686. */ + if (bits == ~__AUDIT_ARCH_64BIT && machine == MACH_86_64) + machine = MACH_X86; + else if (bits == ~__AUDIT_ARCH_64BIT && machine == MACH_PPC64) + machine = MACH_PPC; + else if (bits == ~__AUDIT_ARCH_64BIT && machine == MACH_S390X) + machine = MACH_S390; + else if (bits == ~__AUDIT_ARCH_64BIT && machine == MACH_AARCH64) + machine = MACH_ARM; + + /* Check for errors - return -6 + * We don't allow 32 bit machines to specify 64 bit. */ + switch (machine) + { + case MACH_X86: + if (bits == __AUDIT_ARCH_64BIT) + return -6; + break; + case MACH_IA64: + if (bits == ~__AUDIT_ARCH_64BIT) + return -6; + break; + case MACH_PPC: + if (bits == __AUDIT_ARCH_64BIT) + return -6; + break; + case MACH_S390: + if (bits == __AUDIT_ARCH_64BIT) + return -6; + break; +#ifdef WITH_ARM + case MACH_ARM: + if (bits == __AUDIT_ARCH_64BIT) + return -6; // Deadcode - just incase of mistake + break; +#endif +#ifdef WITH_AARCH64 + case MACH_AARCH64: + if (bits && bits != __AUDIT_ARCH_64BIT) + return -6; + break; +#endif + case MACH_86_64: /* fallthrough */ + case MACH_PPC64: /* fallthrough */ + case MACH_PPC64LE: /* fallthrough */ + case MACH_S390X: /* fallthrough */ + break; + default: + return -6; + } + return machine; +} + +int audit_rule_fieldpair_data(struct audit_rule_data **rulep, const char *pair, + int flags) +{ + const char *f = pair; + char *v; + int op; + int field; + int vlen; + int offset; + struct audit_rule_data *rule = *rulep; + + if (f == NULL) + return -1; + + if (rule->field_count >= (AUDIT_MAX_FIELDS - 1)) + return -28; + + /* look for 2-char operators first + then look for 1-char operators afterwards + when found, null out the bytes under the operators to split + and set value pointer just past operator bytes + */ + if ( (v = strstr(pair, "!=")) ) { + *v++ = '\0'; + *v++ = '\0'; + op = AUDIT_NOT_EQUAL; + } else if ( (v = strstr(pair, ">=")) ) { + *v++ = '\0'; + *v++ = '\0'; + op = AUDIT_GREATER_THAN_OR_EQUAL; + } else if ( (v = strstr(pair, "<=")) ) { + *v++ = '\0'; + *v++ = '\0'; + op = AUDIT_LESS_THAN_OR_EQUAL; + } else if ( (v = strstr(pair, "&=")) ) { + *v++ = '\0'; + *v++ = '\0'; + op = AUDIT_BIT_TEST; + } else if ( (v = strstr(pair, "=")) ) { + *v++ = '\0'; + op = AUDIT_EQUAL; + } else if ( (v = strstr(pair, ">")) ) { + *v++ = '\0'; + op = AUDIT_GREATER_THAN; + } else if ( (v = strstr(pair, "<")) ) { + *v++ = '\0'; + op = AUDIT_LESS_THAN; + } else if ( (v = strstr(pair, "&")) ) { + *v++ = '\0'; + op = AUDIT_BIT_MASK; + } + + if (v == NULL) + return -1; + + if (*f == 0) + return -22; + + if (*v == 0) + return -20; + + if ((field = audit_name_to_field(f)) < 0) + return -2; + + /* Exclude filter can be used only with MSGTYPE field */ + if (flags == AUDIT_FILTER_EXCLUDE && field != AUDIT_MSGTYPE) + return -12; + + rule->fields[rule->field_count] = field; + rule->fieldflags[rule->field_count] = op; + switch (field) + { + case AUDIT_UID: + case AUDIT_EUID: + case AUDIT_SUID: + case AUDIT_FSUID: + case AUDIT_LOGINUID: + case AUDIT_OBJ_UID: + // Do positive & negative separate for 32 bit systems + vlen = strlen(v); + if (isdigit((char)*(v))) + rule->values[rule->field_count] = + strtoul(v, NULL, 0); + else if (vlen >= 2 && *(v)=='-' && + (isdigit((char)*(v+1)))) + rule->values[rule->field_count] = + strtol(v, NULL, 0); + else { + if (strcmp(v, "unset") == 0) + rule->values[rule->field_count] = + 4294967295; + else if (audit_name_to_uid(v, + &rule->values[rule->field_count])) { + audit_msg(LOG_ERR, "Unknown user: %s", + v); + return -2; + } + } + break; + case AUDIT_GID: + case AUDIT_EGID: + case AUDIT_SGID: + case AUDIT_FSGID: + case AUDIT_OBJ_GID: + if (isdigit((char)*(v))) + rule->values[rule->field_count] = + strtol(v, NULL, 0); + else { + if (audit_name_to_gid(v, + &rule->values[rule->field_count])) { + audit_msg(LOG_ERR, "Unknown group: %s", + v); + return -2; + } + } + break; + case AUDIT_EXIT: + if (flags != AUDIT_FILTER_EXIT) + return -7; + vlen = strlen(v); + if (isdigit((char)*(v))) + rule->values[rule->field_count] = + strtol(v, NULL, 0); + else if (vlen >= 2 && *(v)=='-' && + (isdigit((char)*(v+1)))) + rule->values[rule->field_count] = + strtol(v, NULL, 0); + else { + rule->values[rule->field_count] = + audit_name_to_errno(v); + if (rule->values[rule->field_count] == 0) + return -15; + } + break; + case AUDIT_MSGTYPE: + if (flags != AUDIT_FILTER_EXCLUDE && + flags != AUDIT_FILTER_USER) + return -9; + + if (isdigit((char)*(v))) + rule->values[rule->field_count] = + strtol(v, NULL, 0); + else + if (audit_name_to_msg_type(v) > 0) + rule->values[rule->field_count] = + audit_name_to_msg_type(v); + else + return -8; + break; + /* These next few are strings */ + case AUDIT_OBJ_USER: + case AUDIT_OBJ_ROLE: + case AUDIT_OBJ_TYPE: + case AUDIT_OBJ_LEV_LOW: + case AUDIT_OBJ_LEV_HIGH: + case AUDIT_WATCH: + case AUDIT_DIR: + /* Watch & object filtering is invalid on anything + * but exit */ + if (flags != AUDIT_FILTER_EXIT) + return -7; + if (field == AUDIT_WATCH || field == AUDIT_DIR) + _audit_permadded = 1; + + /* fallthrough */ + case AUDIT_SUBJ_USER: + case AUDIT_SUBJ_ROLE: + case AUDIT_SUBJ_TYPE: + case AUDIT_SUBJ_SEN: + case AUDIT_SUBJ_CLR: + case AUDIT_FILTERKEY: + if (field == AUDIT_FILTERKEY && !(_audit_syscalladded || _audit_permadded)) + return -19; + vlen = strlen(v); + if (field == AUDIT_FILTERKEY && + vlen > AUDIT_MAX_KEY_LEN) + return -11; + else if (vlen > PATH_MAX) + return -11; + rule->values[rule->field_count] = vlen; + offset = rule->buflen; + rule->buflen += vlen; + *rulep = realloc(rule, sizeof(*rule) + rule->buflen); + if (*rulep == NULL) { + free(rule); + audit_msg(LOG_ERR, "Cannot realloc memory!\n"); + return -3; + } else { + rule = *rulep; + } + strncpy(&rule->buf[offset], v, vlen); + + break; + case AUDIT_ARCH: + if (_audit_syscalladded) + return -3; + if (!(op == AUDIT_NOT_EQUAL || op == AUDIT_EQUAL)) + return -13; + if (isdigit((char)*(v))) { + int machine; + + errno = 0; + _audit_elf = strtoul(v, NULL, 0); + if (errno) + return -5; + + // Make sure we have a valid mapping + machine = audit_elf_to_machine(_audit_elf); + if (machine < 0) + return -5; + } + else { + const char *arch=v; + unsigned int machine, elf; + machine = audit_determine_machine(arch); + /* OK, we have the machine type, now convert + to elf. */ + elf = audit_machine_to_elf(machine); + if (elf == 0) + return -5; + + _audit_elf = elf; + } + rule->values[rule->field_count] = _audit_elf; + _audit_archadded = 1; + break; + case AUDIT_PERM: + if (flags != AUDIT_FILTER_EXIT) + return -7; + else if (op != AUDIT_EQUAL) + return -13; + else { + unsigned int i, len, val = 0; + + len = strlen(v); + if (len > 4) + return -11; + + for (i = 0; i < len; i++) { + switch (tolower(v[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: + return -14; + } + } + rule->values[rule->field_count] = val; + } + break; + case AUDIT_FILETYPE: + if (!(flags == AUDIT_FILTER_EXIT || flags == AUDIT_FILTER_ENTRY)) + return -17; + rule->values[rule->field_count] = + audit_name_to_ftype(v); + if ((int)rule->values[rule->field_count] < 0) { + return -16; + } + break; + case AUDIT_ARG0...AUDIT_ARG3: + vlen = strlen(v); + if (isdigit((char)*(v))) + rule->values[rule->field_count] = + strtoul(v, NULL, 0); + else if (vlen >= 2 && *(v)=='-' && + (isdigit((char)*(v+1)))) + rule->values[rule->field_count] = + strtol(v, NULL, 0); + else + return -21; + break; + case AUDIT_DEVMAJOR...AUDIT_INODE: + case AUDIT_SUCCESS: + if (flags != AUDIT_FILTER_EXIT) + return -7; + /* fallthrough */ + default: + if (field == AUDIT_INODE) { + if (!(op == AUDIT_NOT_EQUAL || + op == AUDIT_EQUAL)) + return -13; + } + + if (field == AUDIT_PPID && !(flags == AUDIT_FILTER_EXIT + || flags == AUDIT_FILTER_ENTRY)) + return -17; + + if (!isdigit((char)*(v))) + return -21; + + if (field == AUDIT_INODE) + rule->values[rule->field_count] = + strtoul(v, NULL, 0); + else + rule->values[rule->field_count] = + strtol(v, NULL, 0); + break; + } + rule->field_count++; + return 0; +} + +void audit_rule_free_data(struct audit_rule_data *rule) +{ + free(rule); +} + +static int audit_name_to_uid(const char *name, uid_t *uid) +{ + struct passwd *pw; + + pw = getpwnam(name); + if (pw == NULL) + return 1; + + memset(pw->pw_passwd, ' ', strlen(pw->pw_passwd)); + *uid = pw->pw_uid; + return 0; +} + +static int audit_name_to_gid(const char *name, gid_t *gid) +{ + struct group *gr; + + gr = getgrnam(name); + if (gr == NULL) + return 1; + + *gid = gr->gr_gid; + return 0; +} + +int audit_detect_machine(void) +{ + struct utsname uts; + if (uname(&uts) == 0) +// strcpy(uts.machine, "x86_64"); + return audit_name_to_machine(uts.machine); + return -1; +} +hidden_def(audit_detect_machine) + +#ifndef NO_TABLES +void audit_number_to_errmsg(int errnumber, const char *opt) +{ + unsigned int i; + + for (i = 0; i < sizeof(err_msgtab)/sizeof(struct msg_tab); i++) { + if (err_msgtab[i].key == errnumber) { + switch (err_msgtab[i].position) + { + case 0: + fprintf(stderr, "%s\n", + err_msgtab[i].cvalue); + break; + case 1: + fprintf(stderr, "%s %s\n", opt, + err_msgtab[i].cvalue); + break; + case 2: + fprintf(stderr, "%s %s\n", + err_msgtab[i].cvalue, opt); + break; + default: + break; + } + return; + } + } +} +#endif |