From be5e44679146d333c20e28bf99c52d168f422626 Mon Sep 17 00:00:00 2001 From: Nick Kralevich Date: Tue, 9 Apr 2019 10:59:39 -0700 Subject: [PATCH] introduce auditctl and use it to configure SELinux throttling In an effort to ensure that our development community does not introduce new code without corresponding SELinux changes, Android closely monitors the number of SELinux denials which occur during boot. This monitoring occurs both in treehugger, as well as various dashboards. If SELinux denials are dropped during early boot, this could result in non-determinism for the various SELinux treehugger tests. Introduce /system/bin/auditctl. This tool, model after https://linux.die.net/man/8/auditctl , allows for configuring the throttling rate for the kernel auditing system. Remove any throttling from early boot. This will hopefully reduce treehugger flakiness by making denial generation more predictible during early boot. Reapply the throttling at boot complete, to avoid denial of service attacks against the auditing subsystem. Delete pre-existing unittests for logd / SELinux integration. It's intended that all throttling decisions be made in the kernel, and shouldn't be a concern of logd. Bug: 118815957 Test: Perform an operation which generates lots of SELinux denials, and count how many occur before and after the time period. Change-Id: I6c787dbdd4a28208dc854b543e1727ae92e5eeed --- logd/Android.bp | 18 +++++ logd/auditctl.cpp | 74 +++++++++++++++++ logd/libaudit.c | 11 ++- logd/libaudit.h | 13 ++- logd/logd.rc | 11 +++ logd/tests/logd_test.cpp | 143 --------------------------------- shell_and_utilities/Android.bp | 1 + 7 files changed, 124 insertions(+), 147 deletions(-) create mode 100644 logd/auditctl.cpp diff --git a/logd/Android.bp b/logd/Android.bp index 360f2fe53..9b8625821 100644 --- a/logd/Android.bp +++ b/logd/Android.bp @@ -80,6 +80,24 @@ cc_binary { cflags: ["-Werror"], } +cc_binary { + name: "auditctl", + + srcs: ["auditctl.cpp"], + + static_libs: [ + "liblogd", + ], + + shared_libs: ["libbase"], + + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + "-Wconversion" + ], +} prebuilt_etc { name: "logtagd.rc", diff --git a/logd/auditctl.cpp b/logd/auditctl.cpp new file mode 100644 index 000000000..98bb02dbe --- /dev/null +++ b/logd/auditctl.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include "libaudit.h" + +static void usage(const char* cmdline) { + fprintf(stderr, "Usage: %s [-r rate]\n", cmdline); +} + +static void do_update_rate(uint32_t rate) { + int fd = audit_open(); + if (fd == -1) { + error(EXIT_FAILURE, errno, "Unable to open audit socket"); + } + int result = audit_rate_limit(fd, rate); + close(fd); + if (result < 0) { + fprintf(stderr, "Can't update audit rate limit: %d\n", result); + exit(EXIT_FAILURE); + } +} + +int main(int argc, char* argv[]) { + uint32_t rate = 0; + bool update_rate = false; + int opt; + + while ((opt = getopt(argc, argv, "r:")) != -1) { + switch (opt) { + case 'r': + if (!android::base::ParseUint(optarg, &rate)) { + error(EXIT_FAILURE, errno, "Invalid Rate"); + } + update_rate = true; + break; + default: /* '?' */ + usage(argv[0]); + exit(EXIT_FAILURE); + } + } + + // In the future, we may add other options to auditctl + // so this if statement will expand. + // if (!update_rate && !update_backlog && !update_whatever) ... + if (!update_rate) { + fprintf(stderr, "Nothing to do\n"); + usage(argv[0]); + exit(EXIT_FAILURE); + } + + if (update_rate) { + do_update_rate(rate); + } + + return 0; +} diff --git a/logd/libaudit.c b/logd/libaudit.c index 9d9a85742..f452c71ab 100644 --- a/logd/libaudit.c +++ b/logd/libaudit.c @@ -160,8 +160,7 @@ int audit_setup(int fd, pid_t pid) { * and the the mask set to AUDIT_STATUS_PID */ status.pid = pid; - status.mask = AUDIT_STATUS_PID | AUDIT_STATUS_RATE_LIMIT; - status.rate_limit = AUDIT_RATE_LIMIT; /* audit entries per second */ + status.mask = AUDIT_STATUS_PID; /* Let the kernel know this pid will be registering for audit events */ rc = audit_send(fd, AUDIT_SET, &status, sizeof(status)); @@ -188,6 +187,14 @@ int audit_open() { return socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_AUDIT); } +int audit_rate_limit(int fd, uint32_t limit) { + struct audit_status status; + memset(&status, 0, sizeof(status)); + status.mask = AUDIT_STATUS_RATE_LIMIT; + status.rate_limit = limit; /* audit entries per second */ + return audit_send(fd, AUDIT_SET, &status, sizeof(status)); +} + int audit_get_reply(int fd, struct audit_message* rep, reply_t block, int peek) { ssize_t len; int flags; diff --git a/logd/libaudit.h b/logd/libaudit.h index 2a93ea361..b4a92a8a3 100644 --- a/logd/libaudit.h +++ b/logd/libaudit.h @@ -89,8 +89,17 @@ extern int audit_get_reply(int fd, struct audit_message* rep, reply_t block, */ extern int audit_setup(int fd, pid_t pid); -/* Max audit messages per second */ -#define AUDIT_RATE_LIMIT 5 +/** + * Throttle kernel messages at the provided rate + * @param fd + * The fd returned by a call to audit_open() + * @param rate + * The rate, in messages per second, above which the kernel + * should drop audit messages. + * @return + * This function returns 0 on success, -errno on error. + */ +extern int audit_rate_limit(int fd, uint32_t limit); __END_DECLS diff --git a/logd/logd.rc b/logd/logd.rc index c740ecfce..438419ad6 100644 --- a/logd/logd.rc +++ b/logd/logd.rc @@ -16,8 +16,19 @@ service logd-reinit /system/bin/logd --reinit group logd writepid /dev/cpuset/system-background/tasks +# Limit SELinux denial generation to 5/second +service logd-auditctl /system/bin/auditctl -r 5 + oneshot + disabled + user logd + group logd + capabilities AUDIT_CONTROL + on fs write /dev/event-log-tags "# content owned by logd " chown logd logd /dev/event-log-tags chmod 0644 /dev/event-log-tags + +on property:sys.boot_completed=1 + start logd-auditctl diff --git a/logd/tests/logd_test.cpp b/logd/tests/logd_test.cpp index 7d7a22f92..447b06731 100644 --- a/logd/tests/logd_test.cpp +++ b/logd/tests/logd_test.cpp @@ -39,7 +39,6 @@ #endif #include "../LogReader.h" // pickup LOGD_SNDTIMEO -#include "../libaudit.h" // pickup AUDIT_RATE_LIMIT_* #ifdef __ANDROID__ static void send_to_control(char* buf, size_t len) { @@ -1065,145 +1064,3 @@ TEST(logd, multiple_test_3) { TEST(logd, multiple_test_10) { __android_log_btwrite_multiple__helper(10); } - -#ifdef __ANDROID__ -// returns violating pid -static pid_t sepolicy_rate(unsigned rate, unsigned num) { - pid_t pid = fork(); - - if (pid) { - siginfo_t info = {}; - if (TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED))) return -1; - if (info.si_status) return -1; - return pid; - } - - // We may have DAC, but let's not have MAC - if ((setcon("u:object_r:shell:s0") < 0) && (setcon("u:r:shell:s0") < 0)) { - int save_errno = errno; - security_context_t context; - getcon(&context); - if (strcmp(context, "u:r:shell:s0")) { - fprintf(stderr, "setcon(\"u:r:shell:s0\") failed @\"%s\" %s\n", - context, strerror(save_errno)); - freecon(context); - _exit(-1); - // NOTREACHED - return -1; - } - } - - // The key here is we are root, but we are in u:r:shell:s0, - // and the directory does not provide us DAC access - // (eg: 0700 system system) so we trigger the pair dac_override - // and dac_read_search on every try to get past the message - // de-duper. We will also rotate the file name in the directory - // as another measure. - static const char file[] = "/data/drm/cannot_access_directory_%u"; - static const unsigned avc_requests_per_access = 2; - - rate /= avc_requests_per_access; - useconds_t usec; - if (rate == 0) { - rate = 1; - usec = 2000000; - } else { - usec = (1000000 + (rate / 2)) / rate; - } - num = (num + (avc_requests_per_access / 2)) / avc_requests_per_access; - - if (usec < 2) usec = 2; - - while (num > 0) { - if (access(android::base::StringPrintf(file, num).c_str(), F_OK) == 0) { - _exit(-1); - // NOTREACHED - return -1; - } - usleep(usec); - --num; - } - _exit(0); - // NOTREACHED - return -1; -} - -static constexpr int background_period = 10; - -static int count_avc(pid_t pid) { - int count = 0; - - // pid=-1 skip as pid is in error - if (pid == (pid_t)-1) return count; - - // pid=0 means we want to report the background count of avc: activities - struct logger_list* logger_list = - pid ? android_logger_list_alloc( - ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 0, pid) - : android_logger_list_alloc_time( - ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, - log_time(android_log_clockid()) - - log_time(background_period, 0), - 0); - if (!logger_list) return count; - struct logger* logger = android_logger_open(logger_list, LOG_ID_EVENTS); - if (!logger) { - android_logger_list_close(logger_list); - return count; - } - for (;;) { - log_msg log_msg; - - if (android_logger_list_read(logger_list, &log_msg) <= 0) break; - - if ((log_msg.entry.pid != pid) || (log_msg.entry.len < (4 + 1 + 8)) || - (log_msg.id() != LOG_ID_EVENTS)) - continue; - - char* eventData = log_msg.msg(); - if (!eventData) continue; - - uint32_t tag = get4LE(eventData); - if (tag != AUDITD_LOG_TAG) continue; - - if (eventData[4] != EVENT_TYPE_STRING) continue; - - // int len = get4LE(eventData + 4 + 1); - log_msg.buf[LOGGER_ENTRY_MAX_LEN] = '\0'; - const char* cp = strstr(eventData + 4 + 1 + 4, "): avc: denied"); - if (!cp) continue; - - ++count; - } - - android_logger_list_close(logger_list); - - return count; -} -#endif - -TEST(logd, sepolicy_rate_limiter) { -#ifdef __ANDROID__ - int background_selinux_activity_too_high = count_avc(0); - if (background_selinux_activity_too_high > 2) { - GTEST_LOG_(ERROR) << "Too much background selinux activity " - << background_selinux_activity_too_high * 60 / - background_period - << "/minute on the device, this test\n" - << "can not measure the functionality of the " - << "sepolicy rate limiter. Expect test to\n" - << "fail as this device is in a bad state, " - << "but is not strictly a unit test failure."; - } - - static const int rate = AUDIT_RATE_LIMIT; - static const int duration = 2; - // Two seconds of sustained denials. Depending on the overlap in the time - // window that the kernel is considering vs what this test is considering, - // allow some additional denials to prevent a flaky test. - EXPECT_LE(count_avc(sepolicy_rate(rate, rate * duration)), - rate * duration + rate); -#else - GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif -} diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp index f01a8c7ec..3bc3883a5 100644 --- a/shell_and_utilities/Android.bp +++ b/shell_and_utilities/Android.bp @@ -10,6 +10,7 @@ phony { phony { name: "shell_and_utilities_system", required: [ + "auditctl", "awk", "bzip2", "grep",