/*- * Copyright (c) 2008-2009 Apple Inc. * Copyright (c) 2016 Robert N. M. Watson * All rights reserved. * * Portions of this software were developed by BAE Systems, the University of * Cambridge Computer Laboratory, and Memorial University under DARPA/AFRL * contract FA8650-15-C-7558 ("CADETS"), as part of the DARPA Transparent * Computing (TC) research program. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #ifdef HAVE_FULL_QUEUE_H #include #else /* !HAVE_FULL_QUEUE_H */ #include #endif /* !HAVE_FULL_QUEUE_H */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __APPLE__ #include #ifndef __BSM_INTERNAL_NOTIFY_KEY #define __BSM_INTERNAL_NOTIFY_KEY "com.apple.audit.change" #endif /* __BSM_INTERNAL_NOTIFY_KEY */ #endif /* __APPLE__ */ /* * XXX This is temporary until this is moved to and shared with * the kernel. */ #ifndef AUDIT_HARD_LIMIT_FREE_BLOCKS #define AUDIT_HARD_LIMIT_FREE_BLOCKS 4 #endif /* * Number of seconds to January 1, 2000 */ #define JAN_01_2000 946598400 struct dir_ent { char *dirname; uint8_t softlim; uint8_t hardlim; TAILQ_ENTRY(dir_ent) dirs; }; static TAILQ_HEAD(, dir_ent) dir_q; struct audit_trail { time_t at_time; char *at_path; off_t at_size; TAILQ_ENTRY(audit_trail) at_trls; }; static int auditd_minval = -1; static int auditd_dist = 0; static char auditd_host[MAXHOSTNAMELEN]; static int auditd_hostlen = -1; static char *auditd_errmsg[] = { "no error", /* ADE_NOERR ( 0) */ "could not parse audit_control(5) file", /* ADE_PARSE ( 1) */ "auditon(2) failed", /* ADE_AUDITON ( 2) */ "malloc(3) failed", /* ADE_NOMEM ( 3) */ "all audit log directories over soft limit", /* ADE_SOFTLIM ( 4) */ "all audit log directories over hard limit", /* ADE_HARDLIM ( 5) */ "could not create file name string", /* ADE_STRERR ( 6) */ "could not open audit record", /* ADE_AU_OPEN ( 7) */ "could not close audit record", /* ADE_AU_CLOSE ( 8) */ "could not set active audit session state", /* ADE_SETAUDIT ( 9) */ "auditctl(2) failed (trail still swapped)", /* ADE_ACTL (10) */ "auditctl(2) failed (trail not swapped)", /* ADE_ACTLERR (11) */ "could not swap audit trail file", /* ADE_SWAPERR (12) */ "could not rename crash recovery file", /* ADE_RENAME (13) */ "could not read 'current' link file", /* ADE_READLINK (14) */ "could not create 'current' link file", /* ADE_SYMLINK (15) */ "invalid argument", /* ADE_INVAL (16) */ "could not resolve hostname to address", /* ADE_GETADDR (17) */ "address family not supported", /* ADE_ADDRFAM (18) */ "error expiring audit trail files", /* ADE_EXPIRE (19) */ }; #define MAXERRCODE (sizeof(auditd_errmsg) / sizeof(auditd_errmsg[0])) #define NA_EVENT_STR_SIZE 128 #define POL_STR_SIZE 128 /* * Look up and return the error string for the given audit error code. */ const char * auditd_strerror(int errcode) { int idx = -errcode; if (idx < 0 || idx > (int)MAXERRCODE) return ("Invalid auditd error code"); return (auditd_errmsg[idx]); } /* * Free our local list of directory names and init list. */ static void free_dir_q(void) { struct dir_ent *d1, *d2; d1 = TAILQ_FIRST(&dir_q); while (d1 != NULL) { d2 = TAILQ_NEXT(d1, dirs); free(d1->dirname); free(d1); d1 = d2; } TAILQ_INIT(&dir_q); } /* * Concat the directory name to the given file name. * XXX We should affix the hostname also */ static char * affixdir(char *name, struct dir_ent *dirent) { char *fn = NULL; /* * Sanity check on file name. */ if (strlen(name) != FILENAME_LEN) { errno = EINVAL; return (NULL); } /* * If the host is set then also add the hostname to the filename. */ if (auditd_hostlen != -1) asprintf(&fn, "%s/%s.%s", dirent->dirname, name, auditd_host); else asprintf(&fn, "%s/%s", dirent->dirname, name); return (fn); } /* * Insert the directory entry in the list by the way they are ordered in * audit_control(5). Move the entries that are over the soft and hard limits * toward the tail. */ static void insert_orderly(struct dir_ent *denew) { struct dir_ent *dep; TAILQ_FOREACH(dep, &dir_q, dirs) { if (dep->softlim == 1 && denew->softlim == 0) { TAILQ_INSERT_BEFORE(dep, denew, dirs); return; } if (dep->hardlim == 1 && denew->hardlim == 0) { TAILQ_INSERT_BEFORE(dep, denew, dirs); return; } } TAILQ_INSERT_TAIL(&dir_q, denew, dirs); } /* * Get the min percentage of free blocks from audit_control(5) and that * value in the kernel. Return: * ADE_NOERR on success, * ADE_PARSE error parsing audit_control(5), */ int auditd_set_dist(void) { int ret; ret = getacdist(); if (ret < 0) return (ADE_PARSE); auditd_dist = ret; return (ADE_NOERR); } /* * Get the host from audit_control(5) and set it in the audit kernel * information. Return: * ADE_NOERR on success. * ADE_PARSE error parsing audit_control(5). * ADE_AUDITON error getting/setting auditon(2) value. * ADE_GETADDR error getting address info for host. * ADE_ADDRFAM un-supported address family. */ int auditd_set_host(void) { struct sockaddr_in6 *sin6; struct sockaddr_in *sin; struct addrinfo *res; struct auditinfo_addr aia; int error, ret = ADE_NOERR; if (getachost(auditd_host, sizeof(auditd_host)) != 0) { ret = ADE_PARSE; /* * To maintain reverse compatability with older audit_control * files, simply drop a warning if the host parameter has not * been set. However, we will explicitly disable the * generation of extended audit header by passing in a zeroed * termid structure. */ bzero(&aia, sizeof(aia)); aia.ai_termid.at_type = AU_IPv4; error = audit_set_kaudit(&aia, sizeof(aia)); if (error < 0 && errno != ENOSYS) ret = ADE_AUDITON; return (ret); } auditd_hostlen = strlen(auditd_host); error = getaddrinfo(auditd_host, NULL, NULL, &res); if (error) return (ADE_GETADDR); switch (res->ai_family) { case PF_INET6: sin6 = (struct sockaddr_in6 *) res->ai_addr; bcopy(&sin6->sin6_addr.s6_addr, &aia.ai_termid.at_addr[0], sizeof(struct in6_addr)); aia.ai_termid.at_type = AU_IPv6; break; case PF_INET: sin = (struct sockaddr_in *) res->ai_addr; bcopy(&sin->sin_addr.s_addr, &aia.ai_termid.at_addr[0], sizeof(struct in_addr)); aia.ai_termid.at_type = AU_IPv4; break; default: /* Un-supported address family in host parameter. */ errno = EAFNOSUPPORT; return (ADE_ADDRFAM); } if (audit_set_kaudit(&aia, sizeof(aia)) < 0) ret = ADE_AUDITON; return (ret); } /* * Get the min percentage of free blocks from audit_control(5) and that * value in the kernel. Return: * ADE_NOERR on success, * ADE_PARSE error parsing audit_control(5), * ADE_AUDITON error getting/setting auditon(2) value. */ int auditd_set_minfree(void) { au_qctrl_t qctrl; if (getacmin(&auditd_minval) != 0) return (ADE_PARSE); if (audit_get_qctrl(&qctrl, sizeof(qctrl)) != 0) return (ADE_AUDITON); if (qctrl.aq_minfree != auditd_minval) { qctrl.aq_minfree = auditd_minval; if (audit_set_qctrl(&qctrl, sizeof(qctrl)) != 0) return (ADE_AUDITON); } return (0); } /* * Convert a trailname into a timestamp (seconds). Return 0 if the conversion * was successful. */ static int trailname_to_tstamp(char *fn, time_t *tstamp) { struct tm tm; char ts[TIMESTAMP_LEN + 1]; char *p; *tstamp = 0; /* * Get the ending time stamp. */ if ((p = strchr(fn, '.')) == NULL) return (1); strlcpy(ts, ++p, sizeof(ts)); if (strlen(ts) != POSTFIX_LEN) return (1); bzero(&tm, sizeof(tm)); /* seconds (0-60) */ p = ts + POSTFIX_LEN - 2; tm.tm_sec = atol(p); if (tm.tm_sec < 0 || tm.tm_sec > 60) return (1); /* minutes (0-59) */ *p = '\0'; p -= 2; tm.tm_min = atol(p); if (tm.tm_min < 0 || tm.tm_min > 59) return (1); /* hours (0 - 23) */ *p = '\0'; p -= 2; tm.tm_hour = atol(p); if (tm.tm_hour < 0 || tm.tm_hour > 23) return (1); /* day of month (1-31) */ *p = '\0'; p -= 2; tm.tm_mday = atol(p); if (tm.tm_mday < 1 || tm.tm_mday > 31) return (1); /* month (0 - 11) */ *p = '\0'; p -= 2; tm.tm_mon = atol(p) - 1; if (tm.tm_mon < 0 || tm.tm_mon > 11) return (1); /* year (year - 1900) */ *p = '\0'; p -= 4; tm.tm_year = atol(p) - 1900; if (tm.tm_year < 0) return (1); *tstamp = timegm(&tm); return (0); } /* * Remove audit trails files according to the expiration conditions. Returns: * ADE_NOERR on success or there is nothing to do. * ADE_PARSE if error parsing audit_control(5). * ADE_NOMEM if could not allocate memory. * ADE_READLINK if could not read link file. * ADE_EXPIRE if there was an unexpected error. */ int auditd_expire_trails(int (*warn_expired)(char *)) { int andflg, len, ret = ADE_NOERR; size_t expire_size, total_size = 0L; time_t expire_age, oldest_time, current_time = time(NULL); struct dir_ent *traildir; struct audit_trail *at; char *afnp, *pn; TAILQ_HEAD(au_trls_head, audit_trail) head = TAILQ_HEAD_INITIALIZER(head); struct stat stbuf; char activefn[MAXPATHLEN]; /* * Read the expiration conditions. If no conditions then return no * error. */ if (getacexpire(&andflg, &expire_age, &expire_size) < 0) return (ADE_PARSE); if (!expire_age && !expire_size) return (ADE_NOERR); /* * Read the 'current' trail file name. Trim off directory path. */ activefn[0] = '\0'; len = readlink(AUDIT_CURRENT_LINK, activefn, MAXPATHLEN - 1); if (len < 0) return (ADE_READLINK); if ((afnp = strrchr(activefn, '/')) != NULL) afnp++; /* * Build tail queue of the trail files. */ TAILQ_FOREACH(traildir, &dir_q, dirs) { DIR *dirp; struct dirent *dp; dirp = opendir(traildir->dirname); while ((dp = readdir(dirp)) != NULL) { time_t tstamp = 0; struct audit_trail *new; /* * Quickly filter non-trail files. */ if (dp->d_namlen < FILENAME_LEN || dp->d_name[POSTFIX_LEN] != '.') continue; if (asprintf(&pn, "%s/%s", traildir->dirname, dp->d_name) < 0) { ret = ADE_NOMEM; break; } if (stat(pn, &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) { free(pn); continue; } total_size += stbuf.st_size; /* * If this is the 'current' audit trail then * don't add it to the tail queue. */ if (NULL != afnp && strcmp(dp->d_name, afnp) == 0) { free(pn); continue; } /* * Get the ending time stamp encoded in the trail * name. If we can't read it or if it is older * than Jan 1, 2000 then use the mtime. */ if (trailname_to_tstamp(dp->d_name, &tstamp) != 0 || tstamp < JAN_01_2000) tstamp = stbuf.st_mtime; /* * If the time stamp is older than Jan 1, 2000 then * update the mtime of the trail file to the current * time. This is so we don't prematurely remove a trail * file that was created while the system clock reset * to the * "beginning of time" but later the system * clock is set to the correct current time. */ if (current_time >= JAN_01_2000 && tstamp < JAN_01_2000) { struct timeval tv[2]; tstamp = stbuf.st_mtime = current_time; TIMESPEC_TO_TIMEVAL(&tv[0], &stbuf.st_atimespec); TIMESPEC_TO_TIMEVAL(&tv[1], &stbuf.st_mtimespec); utimes(pn, tv); } /* * Allocate and populate the new entry. */ new = malloc(sizeof(*new)); if (NULL == new) { free(pn); ret = ADE_NOMEM; break; } new->at_time = tstamp; new->at_size = stbuf.st_size; new->at_path = pn; /* * Check to see if we have a new head. Otherwise, * walk the tailq from the tail first and do a simple * insertion sort. */ if (TAILQ_EMPTY(&head) || new->at_time <= TAILQ_FIRST(&head)->at_time) { TAILQ_INSERT_HEAD(&head, new, at_trls); continue; } TAILQ_FOREACH_REVERSE(at, &head, au_trls_head, at_trls) if (new->at_time >= at->at_time) { TAILQ_INSERT_AFTER(&head, at, new, at_trls); break; } } closedir(dirp); } oldest_time = current_time - expire_age; /* * Expire trail files, oldest (mtime) first, if the given * conditions are met. */ at = TAILQ_FIRST(&head); while (NULL != at) { struct audit_trail *at_next = TAILQ_NEXT(at, at_trls); if (andflg) { if ((expire_size && total_size > expire_size) && (expire_age && at->at_time < oldest_time)) { if (warn_expired) (*warn_expired)(at->at_path); if (unlink(at->at_path) < 0) ret = ADE_EXPIRE; total_size -= at->at_size; } } else { if ((expire_size && total_size > expire_size) || (expire_age && at->at_time < oldest_time)) { if (warn_expired) (*warn_expired)(at->at_path); if (unlink(at->at_path) < 0) ret = ADE_EXPIRE; total_size -= at->at_size; } } free(at->at_path); free(at); at = at_next; } return (ret); } /* * Parses the "dir" entry in audit_control(5) into an ordered list. Also, will * set the minfree and host values if not already set. Arguments include * function pointers to audit_warn functions for soft and hard limits. Returns: * ADE_NOERR on success, * ADE_PARSE error parsing audit_control(5), * ADE_AUDITON error getting/setting auditon(2) value, * ADE_NOMEM error allocating memory, * ADE_SOFTLIM if all the directories are over the soft limit, * ADE_HARDLIM if all the directories are over the hard limit, */ int auditd_read_dirs(int (*warn_soft)(char *), int (*warn_hard)(char *)) { char cur_dir[MAXNAMLEN]; struct dir_ent *dirent; struct statfs sfs; int err; char soft, hard; int tcnt = 0; int scnt = 0; int hcnt = 0; if (auditd_minval == -1 && (err = auditd_set_minfree()) != 0) return (err); if (auditd_hostlen == -1) auditd_set_host(); /* * Init directory q. Force a re-read of the file the next time. */ free_dir_q(); endac(); /* * Read the list of directories into an ordered linked list * admin's preference, then those over soft limit and, finally, * those over the hard limit. * * XXX We should use the reentrant interfaces once they are * available. */ while (getacdir(cur_dir, MAXNAMLEN) >= 0) { if (statfs(cur_dir, &sfs) < 0) continue; /* XXX should warn */ soft = (sfs.f_bfree < (sfs.f_blocks * auditd_minval / 100 )) ? 1 : 0; hard = (sfs.f_bfree < AUDIT_HARD_LIMIT_FREE_BLOCKS) ? 1 : 0; if (soft) { if (warn_soft) (*warn_soft)(cur_dir); scnt++; } if (hard) { if (warn_hard) (*warn_hard)(cur_dir); hcnt++; } dirent = (struct dir_ent *) malloc(sizeof(struct dir_ent)); if (dirent == NULL) return (ADE_NOMEM); dirent->softlim = soft; dirent->hardlim = hard; dirent->dirname = (char *) malloc(MAXNAMLEN); if (dirent->dirname == NULL) { free(dirent); return (ADE_NOMEM); } strlcpy(dirent->dirname, cur_dir, MAXNAMLEN); insert_orderly(dirent); tcnt++; } if (hcnt == tcnt) return (ADE_HARDLIM); if (scnt == tcnt) return (ADE_SOFTLIM); return (0); } void auditd_close_dirs(void) { free_dir_q(); auditd_minval = -1; auditd_hostlen = -1; } /* * Process the audit event file, obtaining a class mapping for each event, and * set that mapping into the kernel. Return: * n number of event mappings that were successfully processed, * ADE_NOMEM if there was an error allocating memory. * * Historically, this code only set up the in-kernel class mapping. On * systems with an in-kernel event-to-name mapping, it also now installs that, * as it is iterating over the event list anyway. Failures there will be * ignored as not all kernels support the feature. */ int auditd_set_evcmap(void) { au_event_ent_t ev, *evp; au_evclass_map_t evc_map; au_evname_map_t evn_map; int ctr = 0; /* * XXX There's a risk here that the BSM library will return NULL * for an event when it can't properly map it to a class. In that * case, we will not process any events beyond the one that failed, * but should. We need a way to get a count of the events. */ ev.ae_name = (char *)malloc(AU_EVENT_NAME_MAX); ev.ae_desc = (char *)malloc(AU_EVENT_DESC_MAX); if (ev.ae_name == NULL || ev.ae_desc == NULL) { if (ev.ae_name != NULL) free(ev.ae_name); return (ADE_NOMEM); } /* * XXXRW: Currently we have no way to remove mappings from the kernel * when they are removed from the file-based mappings. */ evp = &ev; setauevent(); while ((evp = getauevent_r(evp)) != NULL) { /* * Set the event-to-name mapping entry. If there's not room * in the in-kernel string, then we skip the entry. Possibly * better than truncating...? */ if (strlcpy(evn_map.en_name, evp->ae_name, sizeof(evn_map.en_name)) < sizeof(evn_map.en_name)) { evn_map.en_number = evp->ae_number; (void)audit_set_event(&evn_map, sizeof(evn_map)); } /* * Set the event-to-class mapping entry. */ evc_map.ec_number = evp->ae_number; evc_map.ec_class = evp->ae_class; if (audit_set_class(&evc_map, sizeof(evc_map)) == 0) ctr++; } endauevent(); free(ev.ae_name); free(ev.ae_desc); return (ctr); } /* * Get the non-attributable event string and set the kernel mask. Return: * ADE_NOERR on success, * ADE_PARSE error parsing audit_control(5), * ADE_AUDITON error setting the mask using auditon(2). */ int auditd_set_namask(void) { au_mask_t aumask; char naeventstr[NA_EVENT_STR_SIZE]; if (getacna(naeventstr, NA_EVENT_STR_SIZE) != 0 || getauditflagsbin(naeventstr, &aumask) != 0) return (ADE_PARSE); if (audit_set_kmask(&aumask, sizeof(aumask)) != 0) return (ADE_AUDITON); return (ADE_NOERR); } /* * Set the audit control policy if a policy is configured in audit_control(5), * implement the policy. However, if one isn't defined or if there is an error * parsing the control file, set AUDIT_CNT to avoid leaving the system in a * fragile state. Return: * ADE_NOERR on success, * ADE_PARSE error parsing audit_control(5), * ADE_AUDITON error setting policy using auditon(2). */ int auditd_set_policy(void) { int policy; char polstr[POL_STR_SIZE]; if (getacpol(polstr, POL_STR_SIZE) != 0 || au_strtopol(polstr, &policy) != 0) { policy = AUDIT_CNT; if (audit_set_policy(&policy) != 0) return (ADE_AUDITON); return (ADE_PARSE); } if (audit_set_policy(&policy) != 0) return (ADE_AUDITON); return (ADE_NOERR); } /* * Set trail rotation size. Return: * ADE_NOERR on success, * ADE_PARSE error parsing audit_control(5), * ADE_AUDITON error setting file size using auditon(2). */ int auditd_set_fsize(void) { size_t filesz; au_fstat_t au_fstat; /* * Set trail rotation size. */ if (getacfilesz(&filesz) != 0) return (ADE_PARSE); bzero(&au_fstat, sizeof(au_fstat)); au_fstat.af_filesz = filesz; if (audit_set_fsize(&au_fstat, sizeof(au_fstat)) != 0) return (ADE_AUDITON); return (ADE_NOERR); } /* * Set trail rotation size. Return: * ADE_NOERR on success, * ADE_PARSE error parsing audit_control(5), * ADE_AUDITON error setting queue size using auditon(2). */ int auditd_set_qsize(void) { int qsz; au_qctrl_t au_qctrl; /* * Set trail rotation size. */ if (getacqsize(&qsz) != 0) return (ADE_PARSE); if (audit_get_qctrl(&au_qctrl, sizeof(au_qctrl)) != 0) return (ADE_AUDITON); if (qsz != USE_DEFAULT_QSZ) au_qctrl.aq_hiwater = qsz; if (audit_set_qctrl(&au_qctrl, sizeof(au_qctrl)) != 0) return (ADE_AUDITON); return (ADE_NOERR); } static void inject_dist(const char *fromname, char *toname, size_t tonamesize) { char *ptr; ptr = strrchr(fromname, '/'); assert(ptr != NULL); assert(ptr - fromname < (ssize_t)tonamesize); strlcpy(toname, fromname, ptr - fromname + 1); strlcat(toname, "/dist/", tonamesize); strlcat(toname, ptr + 1, tonamesize); } static int auditdist_link(const char *filename) { char fname[MAXPATHLEN]; if (auditd_dist) { inject_dist(filename, fname, sizeof(fname)); /* Ignore errors. */ (void) link(filename, fname); } return (0); } int auditd_rename(const char *fromname, const char *toname) { char fname[MAXPATHLEN], tname[MAXPATHLEN]; if (auditd_dist) { inject_dist(fromname, fname, sizeof(fname)); inject_dist(toname, tname, sizeof(tname)); /* Ignore errors. */ (void) rename(fname, tname); } return (rename(fromname, toname)); } /* * Create the new audit file with appropriate permissions and ownership. * Call auditctl(2) for this file. * Try to clean up if something goes wrong. * *errorp is modified only on auditctl(2) failure. */ static int open_trail(char *fname, gid_t gid, int *errorp) { int fd; /* XXXPJD: What should we do if the file already exists? */ fd = open(fname, O_RDONLY | O_CREAT, S_IRUSR); if (fd < 0) return (-1); if (fchown(fd, -1, gid) < 0 || fchmod(fd, S_IRUSR | S_IRGRP) < 0) { (void) close(fd); (void) unlink(fname); return (-1); } (void) close(fd); if (auditctl(fname) < 0) { *errorp = errno; (void) unlink(fname); return (-1); } (void) auditdist_link(fname); return (0); } /* * Create the new audit trail file, swap with existing audit file. Arguments * include timestamp for the filename, a pointer to a string for returning the * new file name, GID for trail file, and audit_warn function pointer for * 'getacdir()' errors. Returns: * ADE_NOERR on success, * ADE_STRERR if the file name string could not be created, * ADE_SWAPERR if the audit trail file could not be swapped, * ADE_ACTL if the auditctl(2) call failed but file swap still * successful. * ADE_ACTLERR if the auditctl(2) call failed and file swap failed. * ADE_SYMLINK if symlink(2) failed updating the current link. */ int auditd_swap_trail(char *TS, char **newfile, gid_t gid, int (*warn_getacdir)(char *)) { char timestr[FILENAME_LEN + 1]; char *fn; struct dir_ent *dirent; int saverrno = 0; if (strlen(TS) != TIMESTAMP_LEN || snprintf(timestr, sizeof(timestr), "%s.%s", TS, NOT_TERMINATED) < 0) { errno = EINVAL; return (ADE_STRERR); } /* Try until we succeed. */ TAILQ_FOREACH(dirent, &dir_q, dirs) { if (dirent->hardlim) continue; if ((fn = affixdir(timestr, dirent)) == NULL) return (ADE_STRERR); /* * Create the file and pass to the kernel if all went well. */ if (open_trail(fn, gid, &saverrno) == 0) { /* Success. */ *newfile = fn; if (saverrno) { /* * auditctl() failed but still * successful. Return errno and "soft" * error. */ errno = saverrno; return (ADE_ACTL); } return (ADE_NOERR); } /* * auditctl failed setting log file. Try again. */ /* * Tell the administrator about lack of permissions for dir. */ if (warn_getacdir != NULL) (*warn_getacdir)(dirent->dirname); } if (saverrno) { errno = saverrno; return (ADE_ACTLERR); } else return (ADE_SWAPERR); } /* * Mask calling process from being audited. Returns: * ADE_NOERR on success, * ADE_SETAUDIT if setaudit(2) fails. */ #ifdef __APPLE__ int auditd_prevent_audit(void) { auditinfo_addr_t aia; /* * To prevent event feedback cycles and avoid audit becoming stalled if * auditing is suspended we mask this processes events from being * audited. We allow the uid, tid, and mask fields to be implicitly * set to zero, but do set the audit session ID to the PID. * * XXXRW: Is there more to it than this? */ bzero(&aia, sizeof(aia)); aia.ai_asid = AU_ASSIGN_ASID; aia.ai_termid.at_type = AU_IPv4; if (setaudit_addr(&aia, sizeof(aia)) != 0) return (ADE_SETAUDIT); return (ADE_NOERR); } #else int auditd_prevent_audit(void) { auditinfo_t ai; /* * To prevent event feedback cycles and avoid audit becoming stalled if * auditing is suspended we mask this processes events from being * audited. We allow the uid, tid, and mask fields to be implicitly * set to zero, but do set the audit session ID to the PID. * * XXXRW: Is there more to it than this? */ bzero(&ai, sizeof(ai)); ai.ai_asid = getpid(); if (setaudit(&ai) != 0) return (ADE_SETAUDIT); return (ADE_NOERR); } #endif /* !__APPLE__ */ /* * Generate and submit audit record for audit startup or shutdown. The event * argument can be AUE_audit_recovery, AUE_audit_startup or * AUE_audit_shutdown. The path argument will add a path token, if not NULL. * Returns: * AUE_NOERR on success, * ADE_NOMEM if memory allocation fails, * ADE_AU_OPEN if au_open(3) fails, * ADE_AU_CLOSE if au_close(3) fails. */ int auditd_gen_record(int event, char *path) { int aufd; uid_t uid; pid_t pid; char *autext = NULL; token_t *tok; struct auditinfo_addr aia; if (event == AUE_audit_startup) asprintf(&autext, "%s::Audit startup", getprogname()); else if (event == AUE_audit_shutdown) asprintf(&autext, "%s::Audit shutdown", getprogname()); else if (event == AUE_audit_recovery) asprintf(&autext, "%s::Audit recovery", getprogname()); else return (ADE_INVAL); if (autext == NULL) return (ADE_NOMEM); if ((aufd = au_open()) == -1) { free(autext); return (ADE_AU_OPEN); } bzero(&aia, sizeof(aia)); uid = getuid(); pid = getpid(); if ((tok = au_to_subject32_ex(uid, geteuid(), getegid(), uid, getgid(), pid, pid, &aia.ai_termid)) != NULL) au_write(aufd, tok); if ((tok = au_to_text(autext)) != NULL) au_write(aufd, tok); free(autext); if (path != NULL && (tok = au_to_path(path)) != NULL) au_write(aufd, tok); if ((tok = au_to_return32(0, 0)) != NULL) au_write(aufd, tok); if (au_close(aufd, 1, event) == -1) return (ADE_AU_CLOSE); return (ADE_NOERR); } /* * Check for a 'current' symlink and do crash recovery, if needed. Create a new * 'current' symlink. The argument 'curfile' is the file the 'current' symlink * should point to. Returns: * ADE_NOERR on success, * ADE_AU_OPEN if au_open(3) fails, * ADE_AU_CLOSE if au_close(3) fails. * ADE_RENAME if error renaming audit trail file, * ADE_READLINK if error reading the 'current' link, * ADE_SYMLINK if error creating 'current' link. */ int auditd_new_curlink(char *curfile) { int len, err; char *ptr; char *path = NULL; struct stat sb; char recoveredname[MAXPATHLEN]; char newname[MAXPATHLEN]; /* * Check to see if audit was shutdown properly. If not, clean up, * recover previous audit trail file, and generate audit record. */ len = readlink(AUDIT_CURRENT_LINK, recoveredname, sizeof(recoveredname) - 1); if (len > 0) { /* 'current' exist but is it pointing at a valid file? */ recoveredname[len++] = '\0'; if (stat(recoveredname, &sb) == 0) { /* Yes, rename it to a crash recovery file. */ strlcpy(newname, recoveredname, sizeof(newname)); if ((ptr = strstr(newname, NOT_TERMINATED)) != NULL) { memcpy(ptr, CRASH_RECOVERY, POSTFIX_LEN); if (auditd_rename(recoveredname, newname) != 0) return (ADE_RENAME); } else return (ADE_STRERR); path = newname; } /* 'current' symlink is (now) invalid so remove it. */ (void) unlink(AUDIT_CURRENT_LINK); /* Note the crash recovery in current audit trail */ err = auditd_gen_record(AUE_audit_recovery, path); if (err) return (err); } if (len < 0 && errno != ENOENT) return (ADE_READLINK); if (symlink(curfile, AUDIT_CURRENT_LINK) != 0) return (ADE_SYMLINK); return (0); } /* * Do just what we need to quickly start auditing. Assume no system logging or * notify. Return: * 0 on success, * -1 on failure. */ int audit_quick_start(void) { int err; char *newfile = NULL; time_t tt; char TS[TIMESTAMP_LEN + 1]; int ret = 0; /* * Mask auditing of this process. */ if (auditd_prevent_audit() != 0) return (-1); /* * Read audit_control and get log directories. */ err = auditd_read_dirs(NULL, NULL); if (err != ADE_NOERR && err != ADE_SOFTLIM) return (-1); /* * Setup trail file distribution. */ (void) auditd_set_dist(); /* * Create a new audit trail log. */ if (getTSstr(tt, TS, sizeof(TS)) != 0) return (-1); err = auditd_swap_trail(TS, &newfile, getgid(), NULL); if (err != ADE_NOERR && err != ADE_ACTL) { ret = -1; goto out; } /* * Add the current symlink and recover from crash, if needed. */ if (auditd_new_curlink(newfile) != 0) { ret = -1; goto out; } /* * At this point auditing has started so generate audit start-up record. */ if (auditd_gen_record(AUE_audit_startup, NULL) != 0) { ret = -1; goto out; } /* * Configure the audit controls. */ (void) auditd_set_evcmap(); (void) auditd_set_namask(); (void) auditd_set_policy(); (void) auditd_set_fsize(); (void) auditd_set_minfree(); (void) auditd_set_host(); out: if (newfile != NULL) free(newfile); return (ret); } /* * Shut down auditing quickly. Assumes that is only called on system shutdown. * Returns: * 0 on success, * -1 on failure. */ int audit_quick_stop(void) { int len; int cond; char *ptr; time_t tt; char oldname[MAXPATHLEN]; char newname[MAXPATHLEN]; char TS[TIMESTAMP_LEN + 1]; /* * Auditing already disabled? */ if (audit_get_cond(&cond) != 0) return (-1); if (cond == AUC_NOAUDIT) return (0); /* * Generate audit shutdown record. */ (void) auditd_gen_record(AUE_audit_shutdown, NULL); /* * Shutdown auditing in the kernel. */ cond = AUC_DISABLED; if (audit_set_cond(&cond) != 0) return (-1); #ifdef __BSM_INTERNAL_NOTIFY_KEY notify_post(__BSM_INTERNAL_NOTIFY_KEY); #endif /* * Rename last audit trail and remove 'current' link. */ len = readlink(AUDIT_CURRENT_LINK, oldname, sizeof(oldname) - 1); if (len < 0) return (-1); oldname[len++] = '\0'; if (getTSstr(tt, TS, sizeof(TS)) != 0) return (-1); strlcpy(newname, oldname, sizeof(newname)); if ((ptr = strstr(newname, NOT_TERMINATED)) != NULL) { memcpy(ptr, TS, POSTFIX_LEN); if (auditd_rename(oldname, newname) != 0) return (-1); } else return (-1); (void) unlink(AUDIT_CURRENT_LINK); return (0); }