/*-1* Copyright 2018 Aniket Pandey2*3* Redistribution and use in source and binary forms, with or without4* modification, are permitted provided that the following conditions5* are met:6* 1. Redistributions of source code must retain the above copyright7* notice, this list of conditions and the following disclaimer.8* 2. Redistributions in binary form must reproduce the above copyright9* notice, this list of conditions and the following disclaimer in the10* documentation and/or other materials provided with the distribution.11*12* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND13* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE14* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE15* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE16* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL17* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS18* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF19* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)20* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT21* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY22* SUCH DAMAGE.23*/2425#include <sys/types.h>26#include <sys/extattr.h>27#include <sys/ioctl.h>2829#include <bsm/libbsm.h>30#include <bsm/auditd_lib.h>31#include <security/audit/audit_ioctl.h>3233#include <atf-c.h>34#include <errno.h>35#include <fcntl.h>36#include <stdlib.h>37#include <string.h>38#include <time.h>39#include <unistd.h>4041#include "utils.h"4243/*44* Checks the presence of "auditregex" in auditpipe(4) after the45* corresponding system call has been triggered.46*/47static bool48get_records(const char *auditregex, FILE *pipestream)49{50uint8_t *buff;51tokenstr_t token;52ssize_t size = 1024;53char membuff[size];54char del[] = ",";55int reclen, bytes = 0;56FILE *memstream;5758/*59* Open a stream on 'membuff' (address to memory buffer) for storing60* the audit records in the default mode.'reclen' is the length of the61* available records from auditpipe which is passed to the functions62* au_fetch_tok(3) and au_print_flags_tok(3) for further use.63*/64ATF_REQUIRE((memstream = fmemopen(membuff, size, "w")) != NULL);65ATF_REQUIRE((reclen = au_read_rec(pipestream, &buff)) != -1);6667/*68* Iterate through each BSM token, extracting the bits that are69* required to start processing the token sequences.70*/71while (bytes < reclen) {72if (au_fetch_tok(&token, buff + bytes, reclen - bytes) == -1) {73perror("au_read_rec");74atf_tc_fail("Incomplete Audit Record");75}7677/* Print the tokens as they are obtained, in the default form */78au_print_flags_tok(memstream, &token, del, AU_OFLAG_NONE);79fputc(',', memstream);80bytes += token.len;81}8283free(buff);84ATF_REQUIRE_EQ(0, fclose(memstream));85return (atf_utils_grep_string("%s", membuff, auditregex));86}8788/*89* Override the system-wide audit mask settings in /etc/security/audit_control90* and set the auditpipe's maximum allowed queue length limit91*/92static void93set_preselect_mode(int filedesc, au_mask_t *fmask)94{95int qlimit_max;96int fmode = AUDITPIPE_PRESELECT_MODE_LOCAL;9798/* Set local preselection mode for auditing */99if (ioctl(filedesc, AUDITPIPE_SET_PRESELECT_MODE, &fmode) < 0)100atf_tc_fail("Preselection mode: %s", strerror(errno));101102/* Set local preselection flag corresponding to the audit_event */103if (ioctl(filedesc, AUDITPIPE_SET_PRESELECT_FLAGS, fmask) < 0)104atf_tc_fail("Preselection flag: %s", strerror(errno));105106/* Set local preselection flag for non-attributable audit_events */107if (ioctl(filedesc, AUDITPIPE_SET_PRESELECT_NAFLAGS, fmask) < 0)108atf_tc_fail("Preselection naflag: %s", strerror(errno));109110/* Query the maximum possible queue length limit for auditpipe */111if (ioctl(filedesc, AUDITPIPE_GET_QLIMIT_MAX, &qlimit_max) < 0)112atf_tc_fail("Query max-limit: %s", strerror(errno));113114/* Set the queue length limit as obtained from previous step */115if (ioctl(filedesc, AUDITPIPE_SET_QLIMIT, &qlimit_max) < 0)116atf_tc_fail("Set max-qlimit: %s", strerror(errno));117118/* This removes any outstanding record on the auditpipe */119if (ioctl(filedesc, AUDITPIPE_FLUSH) < 0)120atf_tc_fail("Auditpipe flush: %s", strerror(errno));121}122123/*124* Get the corresponding audit_mask for class-name "name" then set the125* success and failure bits for fmask to be used as the ioctl argument126*/127static au_mask_t128get_audit_mask(const char *name)129{130au_mask_t fmask;131au_class_ent_t *class;132133ATF_REQUIRE((class = getauclassnam(name)) != NULL);134fmask.am_success = class->ac_class;135fmask.am_failure = class->ac_class;136return (fmask);137}138139/*140* Loop until the auditpipe returns something, check if it is what141* we want, else repeat the procedure until ppoll(2) times out.142*/143static void144check_auditpipe(struct pollfd fd[], const char *auditregex, FILE *pipestream)145{146struct timespec currtime, endtime, timeout;147148/* Set the expire time for poll(2) while waiting for syscall audit */149ATF_REQUIRE_EQ(0, clock_gettime(CLOCK_MONOTONIC, &endtime));150/* Set limit to 30 seconds total and ~10s without an event. */151endtime.tv_sec += 30;152153for (;;) {154/* Update the time left for auditpipe to return any event */155ATF_REQUIRE_EQ(0, clock_gettime(CLOCK_MONOTONIC, &currtime));156timespecsub(&endtime, &currtime, &timeout);157timeout.tv_sec = MIN(timeout.tv_sec, 9);158if (timeout.tv_sec < 0) {159atf_tc_fail("%s not found in auditpipe within the "160"time limit", auditregex);161}162163switch (ppoll(fd, 1, &timeout, NULL)) {164/* ppoll(2) returns, check if it's what we want */165case 1:166if (fd[0].revents & POLLIN) {167if (get_records(auditregex, pipestream))168return;169} else {170atf_tc_fail("Auditpipe returned an "171"unknown event %#x", fd[0].revents);172}173break;174175/* poll(2) timed out */176case 0:177atf_tc_fail("%s not found in auditpipe within the "178"time limit", auditregex);179break;180181/* poll(2) standard error */182case -1:183atf_tc_fail("Poll: %s", strerror(errno));184break;185186default:187atf_tc_fail("Poll returned too many file descriptors");188}189}190}191192/*193* Wrapper functions around static "check_auditpipe"194*/195static void196check_audit_startup(struct pollfd fd[], const char *auditrgx, FILE *pipestream){197check_auditpipe(fd, auditrgx, pipestream);198}199200void201check_audit(struct pollfd fd[], const char *auditrgx, FILE *pipestream) {202check_auditpipe(fd, auditrgx, pipestream);203204/* Teardown: /dev/auditpipe's instance opened for this test-suite */205ATF_REQUIRE_EQ(0, fclose(pipestream));206}207208void209skip_if_extattr_not_supported(const char *path)210{211ssize_t result;212213/*214* Some file systems (e.g. tmpfs) do not support extattr, so we need215* skip tests that use extattrs. To detect this we can check whether216* the extattr_list_file returns EOPNOTSUPP.217*/218result = extattr_list_file(path, EXTATTR_NAMESPACE_USER, NULL, 0);219if (result == -1 && errno == EOPNOTSUPP) {220atf_tc_skip("File system does not support extattrs.");221}222}223224static bool225is_auditd_running(void)226{227int trigger;228int err;229230/*231* AUDIT_TRIGGER_INITIALIZE is a no-op message on FreeBSD and can232* therefore be used to check whether auditd has already been started.233* This is significantly cheaper than running `service auditd onestatus`234* for each test case. It is also slightly less racy since it will only235* return true once auditd() has opened the trigger file rather than236* just when the pidfile has been created.237*/238trigger = AUDIT_TRIGGER_INITIALIZE;239err = auditon(A_SENDTRIGGER, &trigger, sizeof(trigger));240if (err == 0) {241fprintf(stderr, "auditd(8) is running.\n");242return (true);243} else {244/*245* A_SENDTRIGGER returns ENODEV if auditd isn't listening,246* all other error codes indicate a fatal error.247*/248ATF_REQUIRE_MSG(errno == ENODEV,249"Unexpected error from auditon(2): %s", strerror(errno));250return (false);251}252253}254255FILE *256setup(struct pollfd fd[], const char *name)257{258au_mask_t fmask, nomask;259FILE *pipestream;260fmask = get_audit_mask(name);261nomask = get_audit_mask("no");262263ATF_REQUIRE((fd[0].fd = open("/dev/auditpipe", O_RDONLY)) != -1);264ATF_REQUIRE((pipestream = fdopen(fd[0].fd, "r")) != NULL);265fd[0].events = POLLIN;266267/*268* Disable stream buffering for read operations from /dev/auditpipe.269* Otherwise it is possible that fread(3), called via au_read_rec(3),270* can store buffered data in user-space unbeknown to ppoll(2), which271* as a result, reports that /dev/auditpipe is empty.272*/273ATF_REQUIRE_EQ(0, setvbuf(pipestream, NULL, _IONBF, 0));274275/* Set local preselection audit_class as "no" for audit startup */276set_preselect_mode(fd[0].fd, &nomask);277if (!is_auditd_running()) {278fprintf(stderr, "Running audit_quick_start() for testing... ");279/*280* Previously, this test started auditd using281* `service auditd onestart`. However, there is a race condition282* there since service can return before auditd(8) has283* fully started (once the daemon parent process has forked)284* and this can cause check_audit_startup() to fail sometimes.285*286* In the CheriBSD CI this caused the first test executed by287* kyua (administrative:acct_failure) to fail every time, but288* subsequent ones would almost always succeed.289*290* To avoid this problem (and as a nice side-effect this speeds291* up the test quite a bit), we register this process as a292* "fake" auditd(8) using the audit_quick_start() function from293* libauditd.294*/295atf_utils_create_file("started_fake_auditd", "yes\n");296ATF_REQUIRE(atf_utils_file_exists("started_fake_auditd"));297ATF_REQUIRE_EQ_MSG(0, audit_quick_start(),298"Failed to start fake auditd: %m");299fprintf(stderr, "done.\n");300/* audit_quick_start() should log an audit start event. */301check_audit_startup(fd, "audit startup", pipestream);302/*303* If we exit cleanly shutdown audit_quick_start(), if not304* cleanup() will take care of it.305* This is not required, but makes it easier to run individual306* tests outside of kyua.307*/308atexit(cleanup);309}310311/* Set local preselection parameters specific to "name" audit_class */312set_preselect_mode(fd[0].fd, &fmask);313return (pipestream);314}315316void317cleanup(void)318{319if (atf_utils_file_exists("started_fake_auditd")) {320fprintf(stderr, "Running audit_quick_stop()... ");321if (audit_quick_stop() != 0) {322fprintf(stderr, "Failed to stop fake auditd: %m\n");323abort();324}325fprintf(stderr, "done.\n");326unlink("started_fake_auditd");327}328}329330331