# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1992, 2010, Oracle and/or its affiliates. All rights reserved. # # Copyright (c) 2018, Joyent, Inc. PROG = auditd MANIFEST = auditd.xml SVCMETHOD = svc-auditd include $(SRC)/cmd/Makefile.cmd ROOTMANIFESTDIR = $(ROOTSVCSYSTEM) LIBBSM = $(SRC)/lib/libbsm/common AUDITD = $(SRC)/cmd/auditd CPPFLAGS += -D_REENTRANT CPPFLAGS += -I$(LIBBSM) -I$(AUDITD) CERRWARN += -Wno-parentheses SMOFF += macros # Hammerhead: -ldl for dlopen/dlsym of audit plugin modules LDLIBS += -lbsm -lsecdb -ldl OBJS = auditd.o doorway.o queue.o SRCS = $(OBJS:%.o=%.c) POFILE = $(PROG).po MSGFILES = $(SRCS) .KEEP_STATE: all: $(PROG) install: all $(ROOTUSRSBINPROG) \ $(ROOTMANIFEST) $(ROOTSVCMETHOD) $(PROG): $(SRCS) $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) lint: lint_SRCS $(POFILE): $(MSGFILES) $(BUILDPO.msgfiles) _msg: $(MSGDOMAINPOFILE) clean: $(RM) $(OBJS) check: $(CHKMANIFEST) include $(SRC)/cmd/Makefile.targ include $(SRC)/Makefile.msg.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1992, 2010, Oracle and/or its affiliates. All rights reserved. */ /* Audit daemon server */ /* * These routines make up the audit daemon server. This daemon, called * auditd, handles the user level parts of auditing. It receives buffered * audit records (usually one or more per buffer, potentially less than * one) and passes them to one or more plugins for processing. * * The major interrupts are SIGHUP (start over), SIGTERM (start shutting down), * SIGALRM (quit), and SIGUSR1 (start a new audit log file). SIGTERM is also * used for the child to tell the parent that audit is ready. * * Configuration data comes from audit service configuration * (AUDITD_FMRI/smf(7)) and the auditon system call. * * The major errors are EBUSY (auditing is already in use) and EINTR * (one of the above signals was received). File space errors are * handled by the audit_binfile plugin */ /* #define DEBUG - define for debug messages to be generated */ /* #define MEM_TEST - define to generate core dump on exit */ #define DEBUG 0 #define MEM_TEST 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "plugin.h" #include #include #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SUNW_OST_OSCMD" #endif /* * After we get a SIGTERM, we want to set a timer for 2 seconds * and let c2audit write as many records as it can until the timer * goes off (at which point it returns to auditd with SIGALRM). * If any other signals are received during that time, we call * __audit_dowarn() to indicate that the queue may not have been fully * flushed. */ #define ALRM_TIME 2 #define SLEEP_TIME 20 /* # of seconds to sleep in all hard loop */ static plugin_t *binfile = NULL; static int turn_audit_on = AUC_AUDITING; static int turn_audit_off = AUC_NOAUDIT; static int running = 1; /* * GLOBALS: */ plugin_t *plugin_head = NULL; static thr_data_t main_thr; /* auditd thread (0) */ pthread_mutex_t plugin_mutex; /* for plugin_t list */ static int caught_alrm = 0; /* number of SIGALRMs pending */ static int caught_readc = 0; /* number of SIGHUPs pending */ static int caught_term = 0; /* number of SIGTERMs pending */ static int caught_nextd = 0; /* number of SIGUSR1s pending */ static int reset_list = 1; /* 1 to re-read audit configuration */ static int reset_file = 1; /* 1 to close/open binary log */ static int auditing_set = 0; /* 1 if auditon(A_SETCOND, on... */ static void my_sleep(); static void *signal_thread(void *); static void loadauditlist(); static void block_signals(); static int do_sethost(); static void conf_to_kernel(); static void scf_to_kernel_qctrl(); static void scf_to_kernel_policy(); /* * err_exit() - exit function after the unsuccessful call to auditon(); * prints_out / saves_via_syslog the necessary error messages. */ static void err_exit(char *msg) { if (msg != NULL) { DPRINT((dbfp, "%s\n", msg)); __audit_syslog("auditd", LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_DAEMON, LOG_ALERT, msg); free(msg); } else { DPRINT((dbfp, "the memory allocation failed\n")); __audit_syslog("auditd", LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_DAEMON, LOG_ALERT, gettext("no memory")); } auditd_thread_close(); auditd_exit(1); } /* common exit function */ void auditd_exit(int status) { #if MEM_TEST DPRINT((dbfp, "mem_test intentional abort (status=%d)\n", status)); abort(); #endif DPRINT((dbfp, "%ld exit status = %d auditing_set = %d\n", getpid(), status, auditing_set)); if (auditing_set) (void) auditon(A_SETCOND, (caddr_t)&turn_audit_off, sizeof (int)); #if DEBUG (void) fclose(dbfp); #endif exit(status); } /* ARGSUSED */ int main(int argc, char *argv[]) { auditinfo_addr_t as_null; /* audit state to set */ au_id_t auid; pthread_t tid; plugin_t *p; pid_t pid; #if DEBUG #if MEM_TEST char *envp; #endif if (dbfp == NULL) { dbfp = __auditd_debug_file_open(); } #endif (void) setsid(); /* Internationalization */ (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); /* * Set the audit host-id. */ if (do_sethost() != 0) { __audit_dowarn("nostart", "", 0); auditd_exit(1); } /* * Turn off all auditing for this process. */ if (getaudit_addr(&as_null, sizeof (as_null)) == -1) { __audit_dowarn("nostart", "", 0); auditd_exit(1); } as_null.ai_mask.as_success = 0; as_null.ai_mask.as_failure = 0; (void) setaudit_addr(&as_null, sizeof (as_null)); auid = AU_NOAUDITID; (void) setauid(&auid); /* * Set the audit state flag to AUDITING. */ if (auditon(A_SETCOND, (caddr_t)&turn_audit_on, sizeof (int)) != 0) { DPRINT((dbfp, "auditon(A_SETCOND...) failed (exit)\n")); __audit_dowarn("nostart", "", 0); auditd_exit(1); } block_signals(); /* * wait for "ready" signal before exit -- for greenline */ if (fork()) { sigset_t set; int signal_caught = 0; (void) sigemptyset(&set); (void) sigaddset(&set, SIGTERM); while (signal_caught != SIGTERM) signal_caught = sigwait(&set); DPRINT((dbfp, "init complete: parent can now exit\n")); auditd_exit(0); } pid = getppid(); auditing_set = 1; #if DEBUG && MEM_TEST envp = getenv("UMEM_DEBUG"); if (envp != NULL) DPRINT((dbfp, "UMEM_DEBUG=%s\n", envp)); envp = getenv("UMEM_LOGGING"); if (envp != NULL) DPRINT((dbfp, "UMEM_LOGGING=%s\n", envp)); #endif DPRINT((dbfp, "auditd pid=%ld\n", getpid())); /* thread 0 sync */ (void) pthread_mutex_init(&(main_thr.thd_mutex), NULL); (void) pthread_cond_init(&(main_thr.thd_cv), NULL); (void) pthread_mutex_init(&plugin_mutex, NULL); /* * Set up a separate thread for signal handling. */ if (pthread_create(&tid, NULL, signal_thread, NULL)) { (void) fprintf(stderr, gettext( "auditd can't create a thread\n")); auditd_exit(1); } /* * Set the umask so that only audit or other users in the audit group * can get to the files created by auditd. */ (void) umask(007); if (__logpost("")) { /* Cannot unlink pointer to audit.log(5) file */ DPRINT((dbfp, "logpost failed\n")); auditd_exit(1); } /* * Here is the main body of the audit daemon. running == 0 means that * after flushing out the audit queue, it is time to exit in response * to SIGTERM. */ while (running) { /* * Read auditd / auditd plugins related configuration from * smf(7) repository and create plugin lists. * * loadauditlist() and auditd_thread_init() are called * while under the plugin_mutex lock to avoid a race * with unload_plugin(). */ if (reset_list || reset_file) { if (reset_list) { conf_to_kernel(); scf_to_kernel_qctrl(); scf_to_kernel_policy(); (void) pthread_mutex_lock(&plugin_mutex); loadauditlist(); } else { (void) pthread_mutex_lock(&plugin_mutex); } if (auditd_thread_init()) { auditd_thread_close(); /* continue; wait for audit -s */ } (void) pthread_mutex_unlock(&plugin_mutex); if (reset_list && reset_file) { (void) printf(gettext("auditd started\n")); } else { (void) printf(gettext("auditd refreshed\n")); } reset_list = 0; reset_file = 0; } /* * tell parent I'm running whether or not the initialization * actually worked. The failure case is to wait for an * audit -n or audit -s to fix the problem. */ if (pid != 0) { (void) kill(pid, SIGTERM); pid = 0; } /* * thread_signal() signals main (this thread) when * it has received a signal. */ DPRINT((dbfp, "main thread is waiting for signal\n")); (void) pthread_mutex_lock(&(main_thr.thd_mutex)); if (!(caught_readc || caught_term || caught_alrm || caught_nextd)) (void) pthread_cond_wait(&(main_thr.thd_cv), &(main_thr.thd_mutex)); (void) pthread_mutex_unlock(&(main_thr.thd_mutex)); /* * Got here because a signal came in. * Since we may have gotten more than one, we assume a * priority scheme with SIGALRM being the most * significant. */ if (caught_alrm) { /* * We have returned from our timed wait for * c2audit to calm down. We need to really shut * down here. */ caught_alrm = 0; running = 0; /* shut down now */ } else if (caught_term) { /* * we are going to shut down, but need to * allow time for the audit queues in * c2audit and for the threads to empty. */ p = plugin_head; while (p != NULL) { DPRINT((dbfp, "signalling thread %d\n", p->plg_tid)); (void) pthread_mutex_lock(&(p->plg_mutex)); p->plg_removed = 1; if (p->plg_initialized) (void) pthread_cond_signal( &(p->plg_cv)); (void) pthread_mutex_unlock(&(p->plg_mutex)); p = p->plg_next; } caught_alrm = 0; caught_readc = 0; caught_term = 0; caught_nextd = 0; DPRINT((dbfp, "main thread is pausing before exit.\n")); (void) pthread_mutex_lock(&(main_thr.thd_mutex)); caught_alrm = 0; (void) alarm(ALRM_TIME); while (!caught_alrm) (void) pthread_cond_wait(&(main_thr.thd_cv), &(main_thr.thd_mutex)); (void) pthread_mutex_unlock(&(main_thr.thd_mutex)); running = 0; /* Close down auditing and exit */ } else if (caught_readc) { /* * if both hup and usr1 are caught, the logic in * loadauditlist() results in hup winning. The * result will be that the audit file is not rolled * over unless audit configuration actually changed. * * They want to reread the audit configuration from * smf(7) repository (AUDITD_FMRI). Set reset_list * which will return us to the main while loop in the * main routine. */ caught_readc = 0; reset_list = 1; } else if (caught_nextd) { /* * This is a special case for the binfile plugin. * (audit -n) NULL out kvlist so binfile won't * re-read audit configuration. */ caught_nextd = 0; reset_file = 1; if (binfile != NULL) { _kva_free(binfile->plg_kvlist); binfile->plg_kvlist = NULL; binfile->plg_reopen = 1; } } } /* end while (running) */ auditd_thread_close(); auditd_exit(0); return (0); } /* * my_sleep - sleep for SLEEP_TIME seconds but only accept the signals * that we want to accept. (Premature termination just means the * caller retries more often, not a big deal.) */ static void my_sleep() { DPRINT((dbfp, "auditd: sleeping for 20 seconds\n")); /* * Set timer to "sleep" */ (void) alarm(SLEEP_TIME); DPRINT((dbfp, "main thread is waiting for SIGALRM before exit.\n")); (void) pthread_mutex_lock(&(main_thr.thd_mutex)); (void) pthread_cond_wait(&(main_thr.thd_cv), &(main_thr.thd_mutex)); (void) pthread_mutex_unlock(&(main_thr.thd_mutex)); if (caught_term) { DPRINT((dbfp, "normal SIGTERM exit\n")); /* * Exit, as requested. */ auditd_thread_close(); } if (caught_readc) reset_list = 1; /* Reread the audit configuration */ caught_readc = 0; caught_nextd = 0; } /* * search for $ISA/ in path and replace it with "" if auditd * is 32 bit, else "sparcv9/" The plugin $ISA must match however * auditd was compiled. */ static void isa_ified(char *path, char **newpath) { char *p, *q; if (((p = strchr(path, '$')) != NULL) && (strncmp("$ISA/", p, 5) == 0)) { (void) memcpy(*newpath, path, p - path); q = *newpath + (p - path); #ifdef __sparcv9 q += strlcpy(q, "sparcv9/", avail_length); #endif (void) strcpy(q, p + 5); } else *newpath = path; } /* * init_plugin first searches the existing plugin list to see if the plugin * already has been defined; if not, it creates it and links it into the list. * It returns a pointer to the found or created struct. Note, that * (manual/unsupported) change of path property in audit service configuration * for given plugin will cause a miss. */ /* * for 64 bits, the path name can grow 3 bytes (minus 5 for the * removed "$ISA" and plus 8 for the added "sparcv9/" */ #define ISA_GROW 8 - 5 static plugin_t * init_plugin(char *name, kva_t *list, int cnt_flag) { plugin_t *p, *q; char filepath[MAXPATHLEN + 1 + ISA_GROW]; char *path = filepath; if (*name != '/') { #ifdef __sparcv9 (void) strcpy(filepath, "/usr/lib/security/sparcv9/"); #else (void) strcpy(filepath, "/usr/lib/security/"); #endif if (strlcat(filepath, name, MAXPATHLEN) >= MAXPATHLEN) return (NULL); } else { if (strlen(name) > MAXPATHLEN + ISA_GROW) return (NULL); isa_ified(name, &path); } p = plugin_head; q = plugin_head; while (p != NULL) { if (p->plg_path != NULL) { if (strcmp(p->plg_path, path) == 0) { p->plg_removed = 0; p->plg_to_be_removed = 0; p->plg_cnt = cnt_flag; _kva_free(p->plg_kvlist); p->plg_kvlist = _kva_dup(list); if (list != NULL && p->plg_kvlist == NULL) { err_exit(NULL); } p->plg_reopen = 1; DPRINT((dbfp, "reusing %s\n", p->plg_path)); return (p); } } q = p; p = p->plg_next; } DPRINT((dbfp, "creating new plugin structure for %s\n", path)); p = malloc(sizeof (plugin_t)); if (p == NULL) { perror("auditd"); return (NULL); } if (q == NULL) plugin_head = p; else q->plg_next = p; p->plg_next = NULL; p->plg_initialized = 0; p->plg_reopen = 1; p->plg_tid = 0; p->plg_removed = 0; p->plg_to_be_removed = 0; p->plg_tossed = 0; p->plg_queued = 0; p->plg_output = 0; p->plg_sequence = 1; p->plg_last_seq_out = 0; p->plg_path = strdup(path); p->plg_kvlist = _kva_dup(list); p->plg_cnt = cnt_flag; p->plg_retry_time = SLEEP_TIME; p->plg_qmax = 0; p->plg_save_q_copy = NULL; if (list != NULL && p->plg_kvlist == NULL || p->plg_path == NULL) { err_exit(NULL); } DPRINT((dbfp, "created plugin: %s\n", path)); return (p); } /* * loadauditlist() - read the auditd plugin configuration from smf(7) and * prepare appropriate plugin related structures (plugin_t). Set cnt policy here * based on currently active policy settings. (future could have a policy = * {+|-}cnt entry per plugin with auditconfig providing the default) */ static void loadauditlist() { char *value; char *endptr; plugin_t *p; uint32_t policy; int cnt_flag; struct au_qctrl kqmax; scf_plugin_kva_node_t *plugin_kva_ll; scf_plugin_kva_node_t *plugin_kva_ll_head; if (auditon(A_GETPOLICY, (char *)&policy, 0) == -1) { DPRINT((dbfp, "auditon(A_GETPOLICY...) failed (exit)\n")); __audit_dowarn("auditoff", "", 0); auditd_thread_close(); auditd_exit(1); } cnt_flag = ((policy & AUDIT_CNT) != 0) ? 1 : 0; DPRINT((dbfp, "loadauditlist: policy is to %s\n", (cnt_flag == 1) ? "continue" : "block")); #if DEBUG { int acresult; if (auditon(A_GETCOND, (caddr_t)&acresult, sizeof (int)) != 0) { DPRINT((dbfp, "auditon(A_GETCOND...) failed (exit)\n")); } DPRINT((dbfp, "audit cond = %d (1 is on)\n", acresult)); } #endif if (auditon(A_GETQCTRL, (char *)&kqmax, sizeof (struct au_qctrl)) != 0) { DPRINT((dbfp, "auditon(A_GETQCTRL...) failed (exit)\n")); __audit_dowarn("auditoff", "", 0); auditd_thread_close(); auditd_exit(1); } kqmax.aq_hiwater *= 5; /* RAM is cheaper in userspace */ DPRINT((dbfp, "auditd: reading audit configuration\n")); p = plugin_head; /* * two-step on setting p->plg_removed because the input thread * in doorway.c uses p->plg_removed to decide if the plugin is * active. */ while (p != NULL) { DPRINT((dbfp, "loadauditlist: %p, %s previously created\n", (void *)p, p->plg_path)); p->plg_to_be_removed = 1; /* tentative removal */ p = p->plg_next; } if (!do_getpluginconfig_scf(NULL, &plugin_kva_ll)) { DPRINT((dbfp, "Could not get plugin configuration.\n")); auditd_thread_close(); auditd_exit(1); } plugin_kva_ll_head = plugin_kva_ll; while (plugin_kva_ll != NULL) { DPRINT((dbfp, "loadauditlist: starting with %s", plugin_kva_ll->plugin_name)); /* skip inactive plugins */ value = kva_match(plugin_kva_ll->plugin_kva, PLUGIN_ACTIVE); if (strcmp(value, "1") != 0) { DPRINT((dbfp, " (inactive:%s) skipping..\n", value)); plugin_kva_ll = plugin_kva_ll->next; continue; } DPRINT((dbfp, " (active)\n")); value = kva_match(plugin_kva_ll->plugin_kva, PLUGIN_PATH); DPRINT((dbfp, "loadauditlist: have an entry for %s (%s)\n", plugin_kva_ll->plugin_name, value)); p = init_plugin(value, plugin_kva_ll->plugin_kva, cnt_flag); if (p == NULL) { DPRINT((dbfp, "Unsuccessful plugin_t " "initialization.\n")); my_sleep(); continue; } if (strcmp(plugin_kva_ll->plugin_name, "audit_binfile") == 0) { binfile = p; } p->plg_qmax = kqmax.aq_hiwater; /* default */ value = kva_match(plugin_kva_ll->plugin_kva, PLUGIN_QSIZE); if (value != NULL) { long tmp; tmp = strtol(value, &endptr, 10); if (*endptr == '\0' && tmp != 0) { p->plg_qmax = tmp; } } DPRINT((dbfp, "%s queue max = %d\n", p->plg_path, p->plg_qmax)); plugin_kva_ll = plugin_kva_ll->next; } p = plugin_head; while (p != NULL) { DPRINT((dbfp, "loadauditlist: %s remove flag=%d; cnt=%d\n", p->plg_path, p->plg_to_be_removed, p->plg_cnt)); p->plg_removed = p->plg_to_be_removed; p = p->plg_next; } plugin_kva_ll_free(plugin_kva_ll_head); } /* * block signals -- thread-specific blocking of the signals expected * by the main thread. */ static void block_signals() { sigset_t set; (void) sigfillset(&set); (void) pthread_sigmask(SIG_BLOCK, &set, NULL); } /* * signal_thread is the designated signal catcher. It wakes up the * main thread whenever it receives a signal and then goes back to * sleep; it does not exit. The global variables caught_* let * the main thread which signal was received. * * The thread is created with all signals blocked. */ static void * signal_thread(void *arg __unused) { sigset_t set; int signal_caught; DPRINT((dbfp, "the signal thread is thread %d\n", pthread_self())); (void) sigemptyset(&set); (void) sigaddset(&set, SIGALRM); (void) sigaddset(&set, SIGTERM); (void) sigaddset(&set, SIGHUP); (void) sigaddset(&set, SIGUSR1); for (;;) { signal_caught = sigwait(&set); switch (signal_caught) { case SIGALRM: caught_alrm++; DPRINT((dbfp, "caught SIGALRM\n")); break; case SIGTERM: caught_term++; DPRINT((dbfp, "caught SIGTERM\n")); break; case SIGHUP: caught_readc++; DPRINT((dbfp, "caught SIGHUP\n")); break; case SIGUSR1: caught_nextd++; DPRINT((dbfp, "caught SIGUSR1\n")); break; default: DPRINT((dbfp, "caught unexpected signal: %d\n", signal_caught)); break; } (void) pthread_cond_signal(&(main_thr.thd_cv)); } return (NULL); } /* * do_sethost - do auditon(2) to set the audit host-id. * Returns 0 if success or -1 otherwise. */ static int do_sethost(void) { au_tid_addr_t *termid; auditinfo_addr_t audit_info; char msg[512]; if (adt_load_hostname(NULL, (adt_termid_t **)&termid) < 0) { (void) snprintf(msg, sizeof (msg), "unable to get local " "IP address: %s", strerror(errno)); goto fail; } /* Get current kernel audit info, and fill in the IP address */ if (auditon(A_GETKAUDIT, (caddr_t)&audit_info, sizeof (audit_info)) < 0) { (void) snprintf(msg, sizeof (msg), "unable to get kernel " "audit info: %s", strerror(errno)); goto fail; } audit_info.ai_termid = *termid; /* Update the kernel audit info with new IP address */ if (auditon(A_SETKAUDIT, (caddr_t)&audit_info, sizeof (audit_info)) < 0) { (void) snprintf(msg, sizeof (msg), "unable to set kernel " "audit info: %s", strerror(errno)); goto fail; } free(termid); return (0); fail: free(termid); __audit_syslog("auditd", LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_DAEMON, LOG_ALERT, msg); return (-1); } /* * conf_to_kernel() - configure the event to class mapping; see also * auditconfig(8) -conf option. */ static void conf_to_kernel(void) { au_event_ent_t *evp; int i; char *msg; au_evclass_map_t ec; au_stat_t as; if (auditon(A_GETSTAT, (caddr_t)&as, 0) != 0) { (void) asprintf(&msg, gettext("Audit module does not appear " "to be loaded.")); err_exit(msg); } i = 0; setauevent(); while ((evp = getauevent()) != NULL) { if (evp->ae_number <= as.as_numevent) { ++i; ec.ec_number = evp->ae_number; ec.ec_class = evp->ae_class; if (auditon(A_SETCLASS, (caddr_t)&ec, sizeof (ec)) != 0) { (void) asprintf(&msg, gettext("Could not configure kernel audit " "event to class mappings.")); err_exit(msg); } } } endauevent(); DPRINT((dbfp, "configured %d kernel events.\n", i)); } /* * scf_to_kernel_qctrl() - update the kernel queue control parameters */ static void scf_to_kernel_qctrl(void) { struct au_qctrl act_qctrl; struct au_qctrl cfg_qctrl; char *msg; if (!do_getqctrl_scf(&cfg_qctrl)) { (void) asprintf(&msg, gettext("Unable to gather audit queue " "control parameters from the SMF repository.")); err_exit(msg); } DPRINT((dbfp, "will check and set qctrl parameters:\n")); DPRINT((dbfp, "\thiwater: %d\n", cfg_qctrl.aq_hiwater)); DPRINT((dbfp, "\tlowater: %d\n", cfg_qctrl.aq_lowater)); DPRINT((dbfp, "\tbufsz: %d\n", cfg_qctrl.aq_bufsz)); DPRINT((dbfp, "\tdelay: %ld\n", cfg_qctrl.aq_delay)); if (auditon(A_GETQCTRL, (caddr_t)&act_qctrl, 0) != 0) { (void) asprintf(&msg, gettext("Could not retrieve " "audit queue controls from kernel.")); err_exit(msg); } /* overwrite the default (zeros) from the qctrl configuration */ if (cfg_qctrl.aq_hiwater == 0) { cfg_qctrl.aq_hiwater = act_qctrl.aq_hiwater; DPRINT((dbfp, "hiwater changed to active value: %u\n", cfg_qctrl.aq_hiwater)); } if (cfg_qctrl.aq_lowater == 0) { cfg_qctrl.aq_lowater = act_qctrl.aq_lowater; DPRINT((dbfp, "lowater changed to active value: %u\n", cfg_qctrl.aq_lowater)); } if (cfg_qctrl.aq_bufsz == 0) { cfg_qctrl.aq_bufsz = act_qctrl.aq_bufsz; DPRINT((dbfp, "bufsz changed to active value: %u\n", cfg_qctrl.aq_bufsz)); } if (cfg_qctrl.aq_delay == 0) { cfg_qctrl.aq_delay = act_qctrl.aq_delay; DPRINT((dbfp, "delay changed to active value: %ld\n", cfg_qctrl.aq_delay)); } if (auditon(A_SETQCTRL, (caddr_t)&cfg_qctrl, 0) != 0) { (void) asprintf(&msg, gettext("Could not configure audit queue controls.")); err_exit(msg); } DPRINT((dbfp, "qctrl parameters set\n")); } /* * scf_to_kernel_policy() - update the audit service policies */ static void scf_to_kernel_policy(void) { uint32_t policy; char *msg; if (!do_getpolicy_scf(&policy)) { (void) asprintf(&msg, gettext("Unable to get audit policy " "configuration from the SMF repository.")); err_exit(msg); } if (auditon(A_SETPOLICY, (caddr_t)&policy, 0) != 0) { (void) asprintf(&msg, gettext("Could not update active policy settings.")); err_exit(msg); } DPRINT((dbfp, "kernel policy settings updated\n")); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Copyright 2022 Tintri by DDN, Inc. All rights reserved. */ /* * Threads: * * auditd is thread 0 and does signal handling * * input() is a door server that receives binary audit records and * queues them for handling by an instance of process() for conversion to syslog * message(s). There is one process thread per plugin. * * Queues: * * Each plugin has a buffer pool and and queue for feeding the * the process threads. The input thread moves buffers from the pool * to the queue and the process thread puts them back. * * Another pool, b_pool, contains buffers referenced by each of the * process queues; this is to minimize the number of buffer copies * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* libbsm */ #include "plugin.h" #include #include "queue.h" #define DEBUG 0 /* gettext() obfuscation routine for lint */ #ifdef __lint #define gettext(x) x #endif #if DEBUG static FILE *dbfp; #define DUMP(w, x, y, z) dump_state(w, x, y, z) #define DPRINT(x) { (void) fprintf x; } #else #define DUMP(w, x, y, z) #define DPRINT(x) #endif #define FATAL_MESSAGE_LEN 256 #define MIN_RECORD_SIZE (size_t)25 #define INPUT_MIN 2 #define THRESHOLD_PCT 75 #define DEFAULT_BUF_SZ (size_t)250 #define BASE_PRIORITY 10 /* 0 - 20 valid for user, time share */ #define HIGH_PRIORITY BASE_PRIORITY - 1 static thr_data_t in_thr; /* input thread locks and data */ static int doorfd = -1; static int largest_queue = INPUT_MIN; static au_queue_t b_pool; static int b_allocated = 0; static pthread_mutex_t b_alloc_lock; static pthread_mutex_t b_refcnt_lock; static void input(void *, void *, int, door_desc_t *, int); static void *process(void *); static audit_q_t *qpool_withdraw(plugin_t *); static void qpool_init(plugin_t *, int); static void qpool_return(plugin_t *, audit_q_t *); static void qpool_close(plugin_t *); static audit_rec_t *bpool_withdraw(char *, size_t, size_t); static void bpool_init(); static void bpool_return(audit_rec_t *); /* * warn_or_fatal() -- log daemon error and (optionally) exit */ static void warn_or_fatal(int fatal, char *parting_shot) { char *severity; char message[512]; if (fatal) severity = gettext("fatal error"); else severity = gettext("warning"); (void) snprintf(message, 512, "%s: %s", severity, parting_shot); __audit_syslog("auditd", LOG_PID | LOG_ODELAY | LOG_CONS, LOG_DAEMON, LOG_ALERT, message); DPRINT((dbfp, "auditd warn_or_fatal %s: %s\n", severity, parting_shot)); if (fatal) auditd_exit(1); } /* Internal to doorway.c errors... */ #define INTERNAL_LOAD_ERROR -1 #define INTERNAL_SYS_ERROR -2 #define INTERNAL_CONFIG_ERROR -3 /* * report_error -- handle errors returned by plugin * * rc is plugin's return code if it is a non-negative value, * otherwise it is a doorway.c code about a plugin. */ static void report_error(int rc, char *error_text, char *plugin_path) { char rcbuf[100]; /* short error name string */ char message[FATAL_MESSAGE_LEN]; int bad_count = 0; char *name; char empty[] = ".."; boolean_t warn = B_FALSE, discard = B_FALSE; static int no_plug = 0; static int no_load = 0; static int no_thread; static int no_memory = 0; static int invalid = 0; static int retry = 0; static int fail = 0; name = plugin_path; if (error_text == NULL) error_text = empty; if (name == NULL) name = empty; switch (rc) { case INTERNAL_LOAD_ERROR: warn = B_TRUE; bad_count = ++no_load; (void) strcpy(rcbuf, "load_error"); break; case INTERNAL_SYS_ERROR: warn = B_TRUE; bad_count = ++no_thread; (void) strcpy(rcbuf, "sys_error"); break; case INTERNAL_CONFIG_ERROR: warn = B_TRUE; bad_count = ++no_plug; (void) strcpy(rcbuf, "config_error"); name = strdup("--"); break; case AUDITD_SUCCESS: break; case AUDITD_NO_MEMORY: /* no_memory */ warn = B_TRUE; bad_count = ++no_memory; (void) strcpy(rcbuf, "no_memory"); break; case AUDITD_INVALID: /* invalid */ warn = B_TRUE; bad_count = ++invalid; (void) strcpy(rcbuf, "invalid"); break; case AUDITD_RETRY: warn = B_TRUE; bad_count = ++retry; (void) strcpy(rcbuf, "retry"); break; case AUDITD_COMM_FAIL: /* comm_fail */ (void) strcpy(rcbuf, "comm_fail"); break; case AUDITD_FATAL: /* failure */ warn = B_TRUE; bad_count = ++fail; (void) strcpy(rcbuf, "failure"); break; case AUDITD_DISCARD: /* discarded - shouldn't get here */ /* Don't report this one; it's a non-error. */ discard = B_TRUE; (void) strcpy(rcbuf, "discarded"); break; default: (void) strcpy(rcbuf, "error"); break; } DPRINT((dbfp, "report_error(%d - %s): %s\n\t%s\n", bad_count, name, rcbuf, error_text)); if (warn) __audit_dowarn2("plugin", name, rcbuf, error_text, bad_count); else if (!discard) { (void) snprintf(message, FATAL_MESSAGE_LEN, gettext("audit plugin %s reported error = \"%s\": %s\n"), name, rcbuf, error_text); warn_or_fatal(0, message); } } static size_t getlen(char *buf) { adr_t adr; char tokenid; uint32_t len; adr.adr_now = buf; adr.adr_stream = buf; adrm_char(&adr, &tokenid, 1); if ((tokenid == AUT_OHEADER) || (tokenid == AUT_HEADER32) || (tokenid == AUT_HEADER32_EX) || (tokenid == AUT_HEADER64) || (tokenid == AUT_HEADER64_EX)) { adrm_u_int32(&adr, &len, 1); return (len); } DPRINT((dbfp, "getlen() is not looking at a header token\n")); return (0); } /* * load_function - call dlsym() to resolve the function address */ static int load_function(plugin_t *p, char *name, auditd_rc_t (**func)()) { *func = (auditd_rc_t (*)())dlsym(p->plg_dlptr, name); if (*func == NULL) { char message[FATAL_MESSAGE_LEN]; char *errmsg = dlerror(); (void) snprintf(message, FATAL_MESSAGE_LEN, gettext("dlsym failed %s: error %s"), name, errmsg != NULL ? errmsg : gettext("Unknown error\n")); warn_or_fatal(0, message); return (-1); } return (0); } /* * load the auditd plug in */ static int load_plugin(plugin_t *p) { struct stat64 stat; int fd; int fail = 0; /* * Stat the file so we can check modes and ownerships */ if ((fd = open(p->plg_path, O_NONBLOCK | O_RDONLY)) != -1) { if ((fstat64(fd, &stat) == -1) || (!S_ISREG(stat.st_mode))) fail = 1; } else fail = 1; if (fail) { char message[FATAL_MESSAGE_LEN]; (void) snprintf(message, FATAL_MESSAGE_LEN, gettext("auditd plugin: stat(%s) failed: %s\n"), p->plg_path, strerror(errno)); warn_or_fatal(0, message); return (-1); } /* * Check the ownership of the file */ if (stat.st_uid != (uid_t)0) { char message[FATAL_MESSAGE_LEN]; (void) snprintf(message, FATAL_MESSAGE_LEN, gettext( "auditd plugin: Owner of the module %s is not root\n"), p->plg_path); warn_or_fatal(0, message); return (-1); } /* * Check the modes on the file */ if (stat.st_mode&S_IWGRP) { char message[FATAL_MESSAGE_LEN]; (void) snprintf(message, FATAL_MESSAGE_LEN, gettext("auditd plugin: module %s writable by group\n"), p->plg_path); warn_or_fatal(0, message); return (-1); } if (stat.st_mode&S_IWOTH) { char message[FATAL_MESSAGE_LEN]; (void) snprintf(message, FATAL_MESSAGE_LEN, gettext("auditd plugin: module %s writable by world\n"), p->plg_path); warn_or_fatal(0, message); return (-1); } /* * Open the plugin */ p->plg_dlptr = dlopen(p->plg_path, RTLD_LAZY); if (p->plg_dlptr == NULL) { char message[FATAL_MESSAGE_LEN]; char *errmsg = dlerror(); (void) snprintf(message, FATAL_MESSAGE_LEN, gettext("plugin load %s failed: %s\n"), p->plg_path, errmsg != NULL ? errmsg : gettext("Unknown error\n")); warn_or_fatal(0, message); return (-1); } if (load_function(p, "auditd_plugin", &(p->plg_fplugin))) return (-1); if (load_function(p, "auditd_plugin_open", &(p->plg_fplugin_open))) return (-1); if (load_function(p, "auditd_plugin_close", &(p->plg_fplugin_close))) return (-1); return (0); } /* * unload_plugin() unlinks and frees the plugin_t structure after * freeing buffers and structures that hang off it. It also dlcloses * the referenced plugin. The return is the next entry, which may be NULL * * hold plugin_mutex for this call */ static plugin_t * unload_plugin(plugin_t *p) { plugin_t *q, **r; assert(pthread_mutex_trylock(&plugin_mutex) != 0); DPRINT((dbfp, "unload_plugin: removing %s\n", p->plg_path)); _kva_free(p->plg_kvlist); /* _kva_free accepts NULL */ qpool_close(p); /* qpool_close accepts NULL pool, queue */ DPRINT((dbfp, "unload_plugin: %s structure removed\n", p->plg_path)); (void) dlclose(p->plg_dlptr); DPRINT((dbfp, "unload_plugin: %s dlclosed\n", p->plg_path)); free(p->plg_path); (void) pthread_mutex_destroy(&(p->plg_mutex)); (void) pthread_cond_destroy(&(p->plg_cv)); q = plugin_head; r = &plugin_head; while (q != NULL) { if (q == p) { *r = p->plg_next; free(p); break; } r = &(q->plg_next); q = q->plg_next; } return (*r); } /* * process return values from plugin_open * * presently no attribute is defined. */ /* ARGSUSED */ static void open_return(plugin_t *p, char *attrval) { } /* * auditd_thread_init * - create threads * - load plugins * * auditd_thread_init is called at auditd startup with an initial list * of plugins and again each time audit catches a SIGHUP or SIGUSR1. */ int auditd_thread_init() { int threshold; auditd_rc_t rc; plugin_t *p; char *open_params; char *error_string; int plugin_count = 0; static int threads_ready = 0; if (!threads_ready) { struct sched_param param; #if DEBUG dbfp = __auditd_debug_file_open(); #endif doorfd = door_create((void(*)())input, 0, DOOR_REFUSE_DESC | DOOR_NO_CANCEL); if (doorfd < 0) return (1); /* can't create door -> fatal */ param.sched_priority = BASE_PRIORITY; (void) pthread_setschedparam(pthread_self(), SCHED_OTHER, ¶m); /* input door server */ (void) pthread_mutex_init(&(in_thr.thd_mutex), NULL); (void) pthread_cond_init(&(in_thr.thd_cv), NULL); in_thr.thd_waiting = 0; bpool_init(); } p = plugin_head; while (p != NULL) { if (p->plg_removed) { DPRINT((dbfp, "start removing %s\n", p->plg_path)); /* tell process(p) to exit and dlclose */ (void) pthread_cond_signal(&(p->plg_cv)); } else if (!p->plg_initialized) { DPRINT((dbfp, "start initial load of %s\n", p->plg_path)); if (load_plugin(p)) { report_error(INTERNAL_LOAD_ERROR, gettext("dynamic load failed"), p->plg_path); p = unload_plugin(p); continue; } open_params = NULL; error_string = NULL; if ((rc = p->plg_fplugin_open( p->plg_kvlist, &open_params, &error_string)) != AUDITD_SUCCESS) { report_error(rc, error_string, p->plg_path); free(error_string); p = unload_plugin(p); continue; } open_return(p, open_params); p->plg_reopen = 0; threshold = ((p->plg_qmax * THRESHOLD_PCT) + 99) / 100; p->plg_qmin = INPUT_MIN; DPRINT((dbfp, "calling qpool_init for %s with qmax=%d\n", p->plg_path, p->plg_qmax)); qpool_init(p, threshold); audit_queue_init(&(p->plg_queue)); p->plg_initialized = 1; (void) pthread_mutex_init(&(p->plg_mutex), NULL); (void) pthread_cond_init(&(p->plg_cv), NULL); p->plg_waiting = 0; if (pthread_create(&(p->plg_tid), NULL, process, p)) { report_error(INTERNAL_SYS_ERROR, gettext("thread creation failed"), p->plg_path); p = unload_plugin(p); continue; } } else if (p->plg_reopen) { DPRINT((dbfp, "reopen %s\n", p->plg_path)); error_string = NULL; if ((rc = p->plg_fplugin_open(p->plg_kvlist, &open_params, &error_string)) != AUDITD_SUCCESS) { report_error(rc, error_string, p->plg_path); free(error_string); p = unload_plugin(p); continue; } open_return(p, open_params); p->plg_reopen = 0; DPRINT((dbfp, "%s qmax=%d\n", p->plg_path, p->plg_qmax)); } p->plg_q_threshold = ((p->plg_qmax * THRESHOLD_PCT) + 99) / 100; p = p->plg_next; plugin_count++; } if (plugin_count == 0) { report_error(INTERNAL_CONFIG_ERROR, gettext("No plugins are configured"), NULL); return (-1); } if (!threads_ready) { /* unleash the kernel */ rc = auditdoor(doorfd); DPRINT((dbfp, "%d returned from auditdoor.\n", rc)); if (rc != 0) return (1); /* fatal */ threads_ready = 1; } return (0); } /* * Door invocations that are in progress during a * door_revoke() invocation are allowed to complete normally. * -- man page for door_revoke() */ void auditd_thread_close() { if (doorfd == -1) return; (void) door_revoke(doorfd); doorfd = -1; } /* * qpool_init() sets up pool for queue entries (audit_q_t) * */ static void qpool_init(plugin_t *p, int threshold) { int i; audit_q_t *node; audit_queue_init(&(p->plg_pool)); DPRINT((dbfp, "qpool_init(%d) max, min, threshhold = %d, %d, %d\n", p->plg_tid, p->plg_qmax, p->plg_qmin, threshold)); if (p->plg_qmax > largest_queue) largest_queue = p->plg_qmax; p->plg_q_threshold = threshold; for (i = 0; i < p->plg_qmin; i++) { node = malloc(sizeof (audit_q_t)); if (node == NULL) warn_or_fatal(1, gettext("no memory\n")); /* doesn't return */ audit_enqueue(&p->plg_pool, node); } } /* * bpool_init() sets up pool and queue for record entries (audit_rec_t) * */ static void bpool_init() { int i; audit_rec_t *node; audit_queue_init(&b_pool); (void) pthread_mutex_init(&b_alloc_lock, NULL); (void) pthread_mutex_init(&b_refcnt_lock, NULL); for (i = 0; i < INPUT_MIN; i++) { node = malloc(AUDIT_REC_HEADER + DEFAULT_BUF_SZ); if (node == NULL) warn_or_fatal(1, gettext("no memory\n")); /* doesn't return */ node->abq_buf_len = DEFAULT_BUF_SZ; node->abq_data_len = 0; audit_enqueue(&b_pool, node); (void) pthread_mutex_lock(&b_alloc_lock); b_allocated++; (void) pthread_mutex_unlock(&b_alloc_lock); } } /* * qpool_close() discard queue and pool for a discontinued plugin * * there is no corresponding bpool_close() since it would only * be called as auditd is going down. */ static void qpool_close(plugin_t *p) { audit_q_t *q_node; audit_rec_t *b_node; if (!p->plg_initialized) return; while (audit_dequeue(&(p->plg_pool), (void *)&q_node) == 0) { free(q_node); } audit_queue_destroy(&(p->plg_pool)); while (audit_dequeue(&(p->plg_queue), (void *)&q_node) == 0) { b_node = audit_release(&b_refcnt_lock, q_node->aqq_data); if (b_node != NULL) audit_enqueue(&b_pool, b_node); free(q_node); } audit_queue_destroy(&(p->plg_queue)); } /* * qpool_withdraw */ static audit_q_t * qpool_withdraw(plugin_t *p) { audit_q_t *node; int rc; /* get a buffer from the pool, if any */ rc = audit_dequeue(&(p->plg_pool), (void *)&node); if (rc == 0) return (node); /* * the pool is empty: allocate a new element */ node = malloc(sizeof (audit_q_t)); if (node == NULL) warn_or_fatal(1, gettext("no memory\n")); /* doesn't return */ return (node); } /* * bpool_withdraw -- gets a buffer and fills it * */ static audit_rec_t * bpool_withdraw(char *buffer, size_t buff_size, size_t request_size) { audit_rec_t *node; int rc; size_t new_length; new_length = (request_size > DEFAULT_BUF_SZ) ? request_size : DEFAULT_BUF_SZ; /* get a buffer from the pool, if any */ rc = audit_dequeue(&b_pool, (void *)&node); DPRINT((dbfp, "bpool_withdraw buf length=%d," " requested size=%d, dequeue rc=%d\n", new_length, request_size, rc)); if (rc == 0) { DPRINT((dbfp, "bpool_withdraw node=%p (pool=%d)\n", (void *)node, audit_queue_size(&b_pool))); if (new_length > node->abq_buf_len) { node = realloc(node, AUDIT_REC_HEADER + new_length); if (node == NULL) warn_or_fatal(1, gettext("no memory\n")); /* no return */ } } else { /* * the pool is empty: allocate a new element */ (void) pthread_mutex_lock(&b_alloc_lock); if (b_allocated >= largest_queue) { (void) pthread_mutex_unlock(&b_alloc_lock); DPRINT((dbfp, "bpool_withdraw is over max (pool=%d)\n", audit_queue_size(&b_pool))); return (NULL); } (void) pthread_mutex_unlock(&b_alloc_lock); node = malloc(AUDIT_REC_HEADER + new_length); if (node == NULL) warn_or_fatal(1, gettext("no memory\n")); /* no return */ (void) pthread_mutex_lock(&b_alloc_lock); b_allocated++; (void) pthread_mutex_unlock(&b_alloc_lock); DPRINT((dbfp, "bpool_withdraw node=%p (alloc=%d, pool=%d)\n", (void *)node, b_allocated, audit_queue_size(&b_pool))); } assert(request_size <= new_length); (void) memcpy(node->abq_buffer, buffer, buff_size); node->abq_data_len = buff_size; node->abq_buf_len = new_length; node->abq_ref_count = 0; return (node); } /* * qpool_return() moves queue nodes back to the pool queue. * * if the pool is over max, the node is discarded instead. */ static void qpool_return(plugin_t *p, audit_q_t *node) { int qpool_size; int q_size; #if DEBUG uint64_t sequence = node->aqq_sequence; #endif qpool_size = audit_queue_size(&(p->plg_pool)); q_size = audit_queue_size(&(p->plg_queue)); if (qpool_size + q_size > p->plg_qmax) free(node); else audit_enqueue(&(p->plg_pool), node); DPRINT((dbfp, "qpool_return(%d): seq=%llu, q size=%d," " pool size=%d (total alloc=%d), threshhold=%d\n", p->plg_tid, sequence, q_size, qpool_size, q_size + qpool_size, p->plg_q_threshold)); } /* * bpool_return() moves queue nodes back to the pool queue. */ static void bpool_return(audit_rec_t *node) { #if DEBUG audit_rec_t *copy = node; #endif node = audit_release(&b_refcnt_lock, node); /* decrement ref cnt */ if (node != NULL) { /* NULL if ref cnt is not zero */ audit_enqueue(&b_pool, node); DPRINT((dbfp, "bpool_return: requeue %p (allocated=%d," " pool size=%d)\n", (void *)node, b_allocated, audit_queue_size(&b_pool))); } #if DEBUG else { DPRINT((dbfp, "bpool_return: decrement count for %p (allocated=%d," " pool size=%d)\n", (void *)copy, b_allocated, audit_queue_size(&b_pool))); } #endif } #if DEBUG static void dump_state(char *src, plugin_t *p, uint64_t count, char *msg) { struct sched_param param; int policy; /* * count is message sequence */ (void) pthread_getschedparam(p->plg_tid, &policy, ¶m); (void) fprintf(dbfp, "%7s(%d/%llu) %11s:" " input_in_wait=%d" " priority=%d" " queue size=%d pool size=%d" "\n\t" "process wait=%d" " tossed=%d" " queued=%d" " written=%d" "\n", src, p->plg_tid, count, msg, in_thr.thd_waiting, param.sched_priority, audit_queue_size(&(p->plg_queue)), audit_queue_size(&(p->plg_pool)), p->plg_waiting, p->plg_tossed, p->plg_queued, p->plg_output); (void) fflush(dbfp); } #endif /* * policy_is_block: return 1 if the continue policy is off for any active * plugin, else 0 */ static int policy_is_block() { plugin_t *p; (void) pthread_mutex_lock(&plugin_mutex); p = plugin_head; while (p != NULL) { if (p->plg_cnt == 0) { (void) pthread_mutex_unlock(&plugin_mutex); DPRINT((dbfp, "policy_is_block: policy is to block\n")); return (1); } p = p->plg_next; } (void) pthread_mutex_unlock(&plugin_mutex); DPRINT((dbfp, "policy_is_block: policy is to continue\n")); return (0); } /* * policy_update() -- the kernel has received a policy change. * Presently, the only policy auditd cares about is AUDIT_CNT */ static void policy_update(uint32_t newpolicy) { plugin_t *p; DPRINT((dbfp, "policy change: %X\n", newpolicy)); (void) pthread_mutex_lock(&plugin_mutex); p = plugin_head; while (p != NULL) { p->plg_cnt = (newpolicy & AUDIT_CNT) ? 1 : 0; (void) pthread_cond_signal(&(p->plg_cv)); DPRINT((dbfp, "policy changed for thread %d\n", p->plg_tid)); p = p->plg_next; } (void) pthread_mutex_unlock(&plugin_mutex); } /* * queue_buffer() inputs a buffer and queues for each active plugin if * it represents a complete audit record. Otherwise it builds a * larger buffer to hold the record and take successive buffers from * c2audit to build a complete record; then queues it for each plugin. * * return 0 if data is queued (or damaged and tossed). If resources * are not available, return 0 if all active plugins have the cnt * policy set, else 1. 0 is also returned if the input is a control * message. (aub_buf is aligned on a 64 bit boundary, so casting * it to an integer works just fine.) */ static int queue_buffer(au_dbuf_t *kl) { plugin_t *p; audit_rec_t *b_copy; audit_q_t *q_copy; boolean_t referenced = 0; static char *invalid_msg = "invalid audit record discarded"; static char *invalid_control = "invalid audit control discarded"; static audit_rec_t *alt_b_copy = NULL; static size_t alt_length; static size_t alt_offset; /* * the buffer may be a kernel -> auditd message. (only * the policy change message exists so far.) */ if ((kl->aub_type & AU_DBUF_NOTIFY) != 0) { uint32_t control; control = kl->aub_type & ~AU_DBUF_NOTIFY; switch (control) { case AU_DBUF_POLICY: /* LINTED */ policy_update(*(uint32_t *)kl->aub_buf); break; case AU_DBUF_SHUTDOWN: (void) kill(getpid(), SIGTERM); DPRINT((dbfp, "AU_DBUF_SHUTDOWN message\n")); break; default: warn_or_fatal(0, gettext(invalid_control)); break; } return (0); } /* * The test for valid continuation/completion may fail. Need to * assume the failure was earlier and that this buffer may * be a valid first or complete buffer after discarding the * incomplete record */ if (alt_b_copy != NULL) { if ((kl->aub_type == AU_DBUF_FIRST) || (kl->aub_type == AU_DBUF_COMPLETE)) { DPRINT((dbfp, "copy is not null, partial is %d\n", kl->aub_type)); bpool_return(alt_b_copy); warn_or_fatal(0, gettext(invalid_msg)); alt_b_copy = NULL; } } if (alt_b_copy != NULL) { /* continue collecting a long record */ if (kl->aub_size + alt_offset > alt_length) { bpool_return(alt_b_copy); alt_b_copy = NULL; warn_or_fatal(0, gettext(invalid_msg)); return (0); } (void) memcpy(alt_b_copy->abq_buffer + alt_offset, kl->aub_buf, kl->aub_size); alt_offset += kl->aub_size; if (kl->aub_type == AU_DBUF_MIDDLE) return (0); b_copy = alt_b_copy; alt_b_copy = NULL; b_copy->abq_data_len = alt_length; } else if (kl->aub_type == AU_DBUF_FIRST) { /* first buffer of a multiple buffer record */ alt_length = getlen(kl->aub_buf); if ((alt_length < MIN_RECORD_SIZE) || (alt_length <= kl->aub_size)) { warn_or_fatal(0, gettext(invalid_msg)); return (0); } alt_b_copy = bpool_withdraw(kl->aub_buf, kl->aub_size, alt_length); if (alt_b_copy == NULL) return (policy_is_block()); alt_offset = kl->aub_size; return (0); } else { /* one buffer, one record -- the basic case */ if (kl->aub_type != AU_DBUF_COMPLETE) { DPRINT((dbfp, "copy is null, partial is %d\n", kl->aub_type)); warn_or_fatal(0, gettext(invalid_msg)); return (0); /* tossed */ } b_copy = bpool_withdraw(kl->aub_buf, kl->aub_size, kl->aub_size); if (b_copy == NULL) return (policy_is_block()); } (void) pthread_mutex_lock(&plugin_mutex); p = plugin_head; while (p != NULL) { if (!p->plg_removed) { /* * Link the record buffer to the input queues. * To avoid a race, it is necessary to wait * until all reference count increments * are complete before queueing q_copy. */ audit_incr_ref(&b_refcnt_lock, b_copy); q_copy = qpool_withdraw(p); q_copy->aqq_sequence = p->plg_sequence++; q_copy->aqq_data = b_copy; p->plg_save_q_copy = q_copy; /* enqueue below */ referenced = 1; } else p->plg_save_q_copy = NULL; p = p->plg_next; } /* * now that the reference count is updated, queue it. */ if (referenced) { p = plugin_head; while ((p != NULL) && (p->plg_save_q_copy != NULL)) { audit_enqueue(&(p->plg_queue), p->plg_save_q_copy); (void) pthread_cond_signal(&(p->plg_cv)); p->plg_queued++; p = p->plg_next; } } else bpool_return(b_copy); (void) pthread_mutex_unlock(&plugin_mutex); return (0); } /* * wait_a_while() -- timed wait in the door server to allow output * time to catch up. */ static void wait_a_while() { struct timespec delay = {0, 500000000}; /* 1/2 second */; (void) pthread_mutex_lock(&(in_thr.thd_mutex)); in_thr.thd_waiting = 1; (void) pthread_cond_reltimedwait_np(&(in_thr.thd_cv), &(in_thr.thd_mutex), &delay); in_thr.thd_waiting = 0; (void) pthread_mutex_unlock(&(in_thr.thd_mutex)); } /* * adjust_priority() -- check queue and pools and adjust the priority * for process() accordingly. If we're way ahead of output, do a * timed wait as well. */ static void adjust_priority() { int queue_near_full; plugin_t *p; int queue_size; struct sched_param param; queue_near_full = 0; (void) pthread_mutex_lock(&plugin_mutex); p = plugin_head; while (p != NULL) { queue_size = audit_queue_size(&(p->plg_queue)); if (queue_size > p->plg_q_threshold) { if (p->plg_priority != HIGH_PRIORITY) { p->plg_priority = param.sched_priority = HIGH_PRIORITY; (void) pthread_setschedparam(p->plg_tid, SCHED_OTHER, ¶m); } if (queue_size > p->plg_qmax - p->plg_qmin) { queue_near_full = 1; break; } } p = p->plg_next; } (void) pthread_mutex_unlock(&plugin_mutex); if (queue_near_full) { DPRINT((dbfp, "adjust_priority: input taking a short break\n")); wait_a_while(); DPRINT((dbfp, "adjust_priority: input back from my break\n")); } } /* * input() is a door server; it blocks if any plugins have full queues * with the continue policy off. (auditconfig -setpolicy -cnt) * * input() is called synchronously from c2audit and is NOT * reentrant due to the (unprotected) static variables in * queue_buffer(). If multiple clients are created, a context * structure will be required for queue_buffer. * * timedwait is used when input() gets too far ahead of process(); * the wait terminates either when the set time expires or when * process() signals that it has nearly caught up. */ /* ARGSUSED */ static void input(void *cookie, void *argp, int arg_size, door_desc_t *dp, int n_descriptors) { int is_blocked; plugin_t *p; #if DEBUG int loop_count = 0; static int call_counter = 0; #endif if (argp == NULL) { warn_or_fatal(0, gettext("invalid data received from c2audit\n")); goto input_exit; } DPRINT((dbfp, "%d input new buffer: length=%u, " "partial=%u, arg_size=%d\n", ++call_counter, ((au_dbuf_t *)argp)->aub_size, ((au_dbuf_t *)argp)->aub_type, arg_size)); if (((au_dbuf_t *)argp)->aub_size < 1) { warn_or_fatal(0, gettext("invalid data length received from c2audit\n")); goto input_exit; } /* * is_blocked is true only if one or more plugins have "no * continue" (-cnt) set and one of those has a full queue. * All plugins block until success is met. */ for (;;) { DPRINT((dbfp, "%d input is calling queue_buffer\n", call_counter)); is_blocked = queue_buffer((au_dbuf_t *)argp); if (!is_blocked) { adjust_priority(); break; } else { DPRINT((dbfp, "%d input blocked (loop=%d)\n", call_counter, loop_count)); wait_a_while(); DPRINT((dbfp, "%d input unblocked (loop=%d)\n", call_counter, loop_count)); } #if DEBUG loop_count++; #endif } input_exit: p = plugin_head; while (p != NULL) { (void) pthread_cond_signal(&(p->plg_cv)); p = p->plg_next; } ((au_dbuf_t *)argp)->aub_size = 0; /* return code */ (void) door_return(argp, sizeof (uint64_t), NULL, 0); } /* * process() -- pass a buffer to a plugin */ static void * process(void *arg) { plugin_t *p = arg; int rc; audit_rec_t *b_node; audit_q_t *q_node; auditd_rc_t plugrc; char *error_string; struct timespec delay; int sendsignal; int queue_len; struct sched_param param; static boolean_t once = B_FALSE; DPRINT((dbfp, "%s is thread %d\n", p->plg_path, p->plg_tid)); p->plg_priority = param.sched_priority = BASE_PRIORITY; (void) pthread_setschedparam(p->plg_tid, SCHED_OTHER, ¶m); delay.tv_nsec = 0; for (;;) { while (audit_dequeue(&(p->plg_queue), (void *)&q_node) != 0) { DUMP("process", p, p->plg_last_seq_out, "blocked"); (void) pthread_cond_signal(&(in_thr.thd_cv)); (void) pthread_mutex_lock(&(p->plg_mutex)); p->plg_waiting++; (void) pthread_cond_wait(&(p->plg_cv), &(p->plg_mutex)); p->plg_waiting--; (void) pthread_mutex_unlock(&(p->plg_mutex)); if (p->plg_removed) goto plugin_removed; DUMP("process", p, p->plg_last_seq_out, "unblocked"); } #if DEBUG if (q_node->aqq_sequence != p->plg_last_seq_out + 1) (void) fprintf(dbfp, "process(%d): buffer sequence=%llu but prev=%llu\n", p->plg_tid, q_node->aqq_sequence, p->plg_last_seq_out); #endif error_string = NULL; b_node = q_node->aqq_data; retry_mode: plugrc = p->plg_fplugin(b_node->abq_buffer, b_node->abq_data_len, q_node->aqq_sequence, &error_string); if (p->plg_removed) goto plugin_removed; #if DEBUG p->plg_last_seq_out = q_node->aqq_sequence; #endif switch (plugrc) { case AUDITD_RETRY: if (!once) { report_error(plugrc, error_string, p->plg_path); once = B_TRUE; } free(error_string); error_string = NULL; DPRINT((dbfp, "process(%d) AUDITD_RETRY returned." " cnt=%d (if 1, enter retry)\n", p->plg_tid, p->plg_cnt)); if (p->plg_cnt) /* if cnt is on, lose the buffer */ break; delay.tv_sec = p->plg_retry_time; (void) pthread_mutex_lock(&(p->plg_mutex)); p->plg_waiting++; (void) pthread_cond_reltimedwait_np(&(p->plg_cv), &(p->plg_mutex), &delay); p->plg_waiting--; (void) pthread_mutex_unlock(&(p->plg_mutex)); DPRINT((dbfp, "left retry mode for %d\n", p->plg_tid)); goto retry_mode; case AUDITD_SUCCESS: p->plg_output++; once = B_FALSE; break; default: report_error(plugrc, error_string, p->plg_path); free(error_string); error_string = NULL; break; } /* end switch */ bpool_return(b_node); qpool_return(p, q_node); sendsignal = 0; queue_len = audit_queue_size(&(p->plg_queue)); (void) pthread_mutex_lock(&(in_thr.thd_mutex)); if (in_thr.thd_waiting && (queue_len > p->plg_qmin) && (queue_len < p->plg_q_threshold)) sendsignal = 1; (void) pthread_mutex_unlock(&(in_thr.thd_mutex)); if (sendsignal) { (void) pthread_cond_signal(&(in_thr.thd_cv)); /* * sched_yield(); does not help * performance and in artificial tests * (high sustained volume) appears to * hurt by adding wide variability in * the results. */ } else if ((p->plg_priority < BASE_PRIORITY) && (queue_len < p->plg_q_threshold)) { p->plg_priority = param.sched_priority = BASE_PRIORITY; (void) pthread_setschedparam(p->plg_tid, SCHED_OTHER, ¶m); } } /* end for (;;) */ plugin_removed: DUMP("process", p, p->plg_last_seq_out, "exit"); error_string = NULL; if ((rc = p->plg_fplugin_close(&error_string)) != AUDITD_SUCCESS) report_error(rc, error_string, p->plg_path); free(error_string); (void) pthread_mutex_lock(&plugin_mutex); (void) unload_plugin(p); (void) pthread_mutex_unlock(&plugin_mutex); return (NULL); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _PLUGIN_H #define _PLUGIN_H #ifdef __cplusplus extern "C" { #endif #include #include "queue.h" typedef struct thd { pthread_cond_t thd_cv; pthread_mutex_t thd_mutex; int thd_waiting; } thr_data_t; typedef struct plg plugin_t; struct plg { boolean_t plg_initialized; /* if threads, pools created */ boolean_t plg_reopen; /* call auditd_plugin_open */ /* * removed is 1 if last read of audit configuration didn't list this * plugin or the plugin is marked as "inactive"; it needs to be removed. */ boolean_t plg_removed; /* plugin removed */ boolean_t plg_to_be_removed; /* tentative removal state */ char *plg_path; /* plugin path */ void *plg_dlptr; /* dynamic lib pointer */ auditd_rc_t (*plg_fplugin)(const char *, size_t, uint64_t, char **); auditd_rc_t (*plg_fplugin_open)(const kva_t *, char **, char **); auditd_rc_t (*plg_fplugin_close)(char **); kva_t *plg_kvlist; /* plugin inputs */ size_t plg_qmax; /* max queue size */ size_t plg_qmin; /* min queue size */ uint64_t plg_sequence; /* buffer counter */ uint64_t plg_last_seq_out; /* buffer counter (debug) */ uint32_t plg_tossed; /* discards (debug) */ uint32_t plg_queued; /* count buffers queued */ uint32_t plg_output; /* count of buffers output */ int plg_priority; /* current priority */ au_queue_t plg_pool; /* buffer pool */ au_queue_t plg_queue; /* queue drawn from pool */ int plg_q_threshold; /* max preallocated queue */ audit_q_t *plg_save_q_copy; /* tmp holding for a record */ pthread_t plg_tid; /* thread id */ pthread_cond_t plg_cv; pthread_mutex_t plg_mutex; int plg_waiting; /* output thread wait state */ int plg_cnt; /* continue policy */ int plg_retry_time; /* retry (seconds) */ plugin_t *plg_next; /* null is end of list */ }; int auditd_thread_init(); void auditd_thread_close(); void auditd_exit(int); extern plugin_t *plugin_head; extern pthread_mutex_t plugin_mutex; #ifdef __cplusplus } #endif #endif /* _PLUGIN_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include "queue.h" #include #include #include "plugin.h" #define DEBUG 0 #if DEBUG extern FILE *dbfp; extern FILE *__auditd_debug_file_open(); #define DPRINT(x) { (void) fprintf x; } #else #define DPRINT(x) #endif void audit_queue_init(au_queue_t *q) { q->auq_head = NULL; q->auq_tail = NULL; (void) pthread_mutex_init(&q->auq_lock, NULL); q->auq_count = 0; #if DEBUG if (dbfp == NULL) { dbfp = __auditd_debug_file_open(); } #endif } /* * enqueue() caller creates queue entry */ void audit_enqueue(au_queue_t *q, void *p) { (void) pthread_mutex_lock(&q->auq_lock); DPRINT((dbfp, "enqueue0(%p): p=%p, head=%p, tail=%p, count=%d\n", (void *)q, (void *)p, (void *)q->auq_head, (void *)q->auq_tail, q->auq_count)); if (q->auq_head == NULL) q->auq_head = p; else { DPRINT((dbfp, "\tindirect tail=%p\n", (void *)&(((audit_link_t *)(q->auq_tail))->aln_next))); ((audit_link_t *)(q->auq_tail))->aln_next = p; } q->auq_tail = p; ((audit_link_t *)p)->aln_next = NULL; q->auq_count++; DPRINT((dbfp, "enqueue1(%p): p=%p, head=%p, tail=%p, " "count=%d, pnext=%p\n", (void *)q, (void *)p, (void *)q->auq_head, (void *)q->auq_tail, q->auq_count, (void *)((audit_link_t *)p)->aln_next)); (void) pthread_mutex_unlock(&q->auq_lock); } /* * audit_dequeue() returns entry; caller is responsible for free */ int audit_dequeue(au_queue_t *q, void **p) { (void) pthread_mutex_lock(&q->auq_lock); if ((*p = q->auq_head) == NULL) { DPRINT((dbfp, "dequeue1(%p): p=%p, head=%p, " "tail=%p, count=%d\n", (void *)q, (void *)*p, (void *)q->auq_head, (void *)q->auq_tail, q->auq_count)); (void) pthread_mutex_unlock(&q->auq_lock); return (1); } q->auq_count--; /* if *p is the last, next is NULL */ q->auq_head = ((audit_link_t *)*p)->aln_next; DPRINT((dbfp, "dequeue0(%p): p=%p, head=%p, tail=%p, " "count=%d, pnext=%p\n", (void *)q, (void *)*p, (void *)q->auq_head, (void *)q->auq_tail, q->auq_count, (void *)((audit_link_t *)*p)->aln_next)); (void) pthread_mutex_unlock(&q->auq_lock); return (0); } /* * increment ref count */ void audit_incr_ref(pthread_mutex_t *l, audit_rec_t *p) { (void) pthread_mutex_lock(l); p->abq_ref_count++; DPRINT((dbfp, "incr_ref: p=%p, count=%d\n", (void *)p, p->abq_ref_count)); (void) pthread_mutex_unlock(l); } /* * decrement reference count; if it reaches zero, * return a pointer to it. Otherwise, return NULL. */ audit_rec_t * audit_release(pthread_mutex_t *l, audit_rec_t *p) { assert(p != NULL); (void) pthread_mutex_lock(l); DPRINT((dbfp, "release: p=%p , count=%d\n", (void *)p, p->abq_ref_count)); if (--(p->abq_ref_count) > 0) { (void) pthread_mutex_unlock(l); return (NULL); } (void) pthread_mutex_unlock(l); return (p); } int audit_queue_size(au_queue_t *q) { int size; (void) pthread_mutex_lock(&q->auq_lock); size = q->auq_count; (void) pthread_mutex_unlock(&q->auq_lock); return (size); } void audit_queue_destroy(au_queue_t *q) { (void) pthread_mutex_destroy(&q->auq_lock); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * */ #ifndef _QUEUE_H #define _QUEUE_H #ifdef __cplusplus extern "C" { #endif #include #include typedef struct aln audit_link_t; struct aln { audit_link_t *aln_next; }; /* one audit_rec_t per audit record */ typedef struct abq audit_rec_t; struct abq { audit_link_t abq_l; int abq_ref_count; size_t abq_buf_len; /* space allocated */ size_t abq_data_len; /* space used */ char abq_buffer[1]; /* variable length */ }; #define AUDIT_REC_HEADER offsetof(audit_rec_t, abq_buffer[0]) /* one audit_q_t entry per audit record per plugin */ typedef struct aqq audit_q_t; /* plugin queued data */ struct aqq { audit_link_t aqq_l; audit_rec_t *aqq_data; uint64_t aqq_sequence; }; /* queue head */ typedef struct auq au_queue_t; struct auq { void *auq_head; void *auq_tail; int auq_count; pthread_mutex_t auq_lock; }; int audit_dequeue(au_queue_t *, void **); void audit_queue_destroy(au_queue_t *); void audit_enqueue(au_queue_t *, void *); int audit_queue_size(au_queue_t *); void audit_queue_init(au_queue_t *); audit_rec_t *audit_release(pthread_mutex_t *, audit_rec_t *); void audit_incr_ref(pthread_mutex_t *, audit_rec_t *); #ifdef __cplusplus } #endif #endif /* _QUEUE_H */ #! /sbin/sh # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. # . /lib/svc/share/smf_include.sh AUDIT=/usr/sbin/audit AUDITCONFIG=/usr/sbin/auditconfig AUDITD=/usr/sbin/auditd AWK=/bin/awk EGREP=/bin/egrep MV=/bin/mv PKILL=/usr/bin/pkill SLEEP=/bin/sleep SVCADM=/sbin/svcadm SVCCFG=/sbin/svccfg SVCS=/usr/bin/svcs AUDIT_STARTUP=/etc/security/audit_startup AUDITD_FMRI="system/auditd:default" # # main - the execution starts there. main() { # # Do the basic argument inspection and take the appropriate action. case "$SMF_METHOD" in start) do_common do_start ;; refresh) do_common do_refresh ;; *) if [ -z "$SMF_METHOD" ]; then echo "$0: No SMF method defined." else echo "$0: Unsupported SMF method: $SMF_METHOD." fi exit $SMF_EXIT_ERR_NOSMF ;; esac } # # do_common - executes all the code common to all supported service methods. do_common() { # # If the audit state is "disabled" auditconfig returns non-zero exit # status unless the c2audit module is loaded; if c2audit is loaded, # "disabled" becomes "noaudit" early in the boot cycle and "auditing" # only after auditd starts. AUDITCOND="`$AUDITCONFIG -getcond 2>/dev/null`" if [ $? -ne 0 ]; then # The decision whether to start # auditing is driven by bsmconv(8) / bsmunconv(8) echo "$0: Unable to get current kernel auditing condition." $SVCADM mark maintenance $AUDITD_FMRI exit $SMF_EXIT_MON_OFFLINE fi # # In a non-global zone, auditd is started/refreshed only if the # "perzone" audit policy has been set. if smf_is_nonglobalzone; then $AUDITCONFIG -t -getpolicy | \ $EGREP "perzone|all" 1>/dev/null 2>&1 if [ $? -eq 1 ]; then echo "$0: auditd(8) is not configured to run in" echo " a local zone, perzone policy not set" \ "(see auditconfig(8))." $SVCADM disable $AUDITD_FMRI $SLEEP 5 & exit $SMF_EXIT_OK fi fi # # Validate the audit service configuration val_err="`$AUDIT -v 2>&1`" if [ $? -ne 0 ]; then echo "$0: audit service misconfiguration detected (${val_err})" $SVCADM mark maintenance $AUDITD_FMRI exit $SMF_EXIT_MON_OFFLINE fi } # # do_start - service start method helper. do_start() { # # The transition of the audit_startup(8) has to be performed. if [ -f "$AUDIT_STARTUP" ]; then if [ -x "$AUDIT_STARTUP" ]; then $AUDIT_STARTUP else echo "$0: Unable to execute $AUDIT_STARTUP" $SVCADM mark maintenance $AUDITD_FMRI exit $SMF_EXIT_MON_OFFLINE fi echo "$0: Transition of audit_startup(8) started." $MV $AUDIT_STARTUP $AUDIT_STARTUP._transitioned_ if [ $? -ne 0 ]; then # Unable to perform the backup of $AUDIT_STARTUP echo "$0: The $AUDIT_STARTUP was not moved to" echo " $AUDIT_STARTUP._transitioned_" fi # # Refreshing service to make the newly created properties # available for any other consequent svcprop(1). $SVCCFG -s $AUDITD_FMRI refresh if [ $? -ne 0 ]; then echo "$0: Refresh of $AUDITD_FMRI configuration failed." $SVCADM mark maintenance $AUDITD_FMRI exit $SMF_EXIT_ERR_CONFIG fi echo "$0: Transition of audit_startup(8) finished." fi # # Daemon forks, parent exits when child says it's ready. exec $AUDITD } # # do_refresh - service refresh method helper. do_refresh() { # # The refresh capability is available only for those systems # with already transformed audit_startup(8) into $AUDITD_FMRI # service properties. See do_start() for more information. if [ ! -f "$AUDIT_STARTUP" ]; then # # Find the contract_id. contract_id=`$SVCS -l $AUDITD_FMRI | \ $AWK '/^contract_id/ {print $2}'` if [ -z "${contract_id}" ]; then echo "$0: Service $AUDITD_FMRI has no associated" \ "contract. Service cannot be refreshed." exit $SMF_EXIT_ERR_FATAL fi # # signal to auditd(8): $PKILL -HUP -c ${contract_id} if [ $? -ne 0 ]; then echo "$0: SIGHUP was not successfully delivered to" \ "the related contract (${contract_id}/err:$?)." $SVCADM mark maintenance $AUDITD_FMRI exit $SMF_EXIT_ERR_FATAL fi $SLEEP 5 & else echo "$0: Service refresh method not supported on systems" \ "without converted audit_startup(8) into auditd service" \ "SMF configuration. Clear the service (svcadm(8))." $SVCADM mark maintenance $AUDITD_FMRI exit $SMF_EXIT_ERR_CONFIG fi } # # Call main() to start the own script execution. main