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
This commit is contained in:
Nick Kralevich 2019-04-09 10:59:39 -07:00
parent b142458ab3
commit be5e446791
7 changed files with 124 additions and 147 deletions

View file

@ -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",

74
logd/auditctl.cpp Normal file
View file

@ -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 <android-base/parseint.h>
#include <error.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#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<uint32_t>(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;
}

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -10,6 +10,7 @@ phony {
phony {
name: "shell_and_utilities_system",
required: [
"auditctl",
"awk",
"bzip2",
"grep",