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:
parent
b142458ab3
commit
be5e446791
7 changed files with 124 additions and 147 deletions
|
@ -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
74
logd/auditctl.cpp
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
11
logd/logd.rc
11
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ phony {
|
|||
phony {
|
||||
name: "shell_and_utilities_system",
|
||||
required: [
|
||||
"auditctl",
|
||||
"awk",
|
||||
"bzip2",
|
||||
"grep",
|
||||
|
|
Loading…
Reference in a new issue