init: add builtin check for perf_event LSM hooks

Historically, the syscall was controlled by a system-wide
perf_event_paranoid sysctl, which is not flexible enough to allow only
specific processes to use the syscall. However, SELinux support for the
syscall has been upstreamed recently[1] (and is being backported to
Android R release common kernels).
[1] da97e18458

As the presence of these hooks is not guaranteed on all Android R
platforms (since we support upgrades while keeping an older kernel), we
need to test for the feature dynamically. The LSM hooks themselves have
no way of being detected directly, so we instead test for their effects,
so we perform several syscalls, and look for a specific success/failure
combination, corresponding to the platform's SELinux policy.

If hooks are detected, perf_event_paranoid is set to -1 (unrestricted),
as the SELinux policy is then sufficient to control access.

This is done within init for several reasons:
* CAP_SYS_ADMIN side-steps perf_event_paranoid, so the tests can be done
  if non-root users aren't allowed to use the syscall (the default).
* init is already the setter of the paranoid value (see init.rc), which
  is also a privileged operation.
* the test itself is simple (couple of syscalls), so having a dedicated
  test binary/domain felt excessive.

I decided to go through a new sysprop (set by a builtin test in
second-stage init), and keeping the actuation in init.rc. We can change
it to an immediate write to the paranoid value if a use-case comes up
that requires the decision to be made earlier in the init sequence.

Bug: 137092007
Change-Id: Ib13a31fee896f17a28910d993df57168a83a4b3d
This commit is contained in:
Ryan Savitski 2020-01-14 22:02:53 +00:00
parent acd17330df
commit f0f7e70186
4 changed files with 85 additions and 4 deletions

View file

@ -730,8 +730,8 @@ int SecondStageMain(int argc, char** argv) {
}
am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
am.QueueEventTrigger("early-init");
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...

View file

@ -18,14 +18,19 @@
#include <errno.h>
#include <fcntl.h>
#include <linux/perf_event.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <fstream>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/unique_fd.h>
using android::base::unique_fd;
using android::base::SetProperty;
namespace android {
namespace init {
@ -197,5 +202,61 @@ Result<void> SetKptrRestrictAction(const BuiltinArguments&) {
return {};
}
// Test for whether the kernel has SELinux hooks for the perf_event_open()
// syscall. If the hooks are present, we can stop using the other permission
// mechanism (perf_event_paranoid sysctl), and use only the SELinux policy to
// control access to the syscall. The hooks are expected on all Android R
// release kernels, but might be absent on devices that upgrade while keeping an
// older kernel.
//
// There is no direct/synchronous way of finding out that a syscall failed due
// to SELinux. Therefore we test for a combination of a success and a failure
// that are explained by the platform's SELinux policy for the "init" domain:
// * cpu-scoped perf_event is allowed
// * ioctl() on the event fd is disallowed with EACCES
//
// Since init has CAP_SYS_ADMIN, these tests are not affected by the system-wide
// perf_event_paranoid sysctl.
//
// If the SELinux hooks are detected, a special sysprop
// (sys.init.perf_lsm_hooks) is set, which translates to a modification of
// perf_event_paranoid (through init.rc sysprop actions).
//
// TODO(b/137092007): this entire test can be removed once the platform stops
// supporting kernels that precede the perf_event_open hooks (Android common
// kernels 4.4 and 4.9).
Result<void> TestPerfEventSelinuxAction(const BuiltinArguments&) {
// Use a trivial event that will be configured, but not started.
struct perf_event_attr pe = {
.type = PERF_TYPE_SOFTWARE,
.size = sizeof(struct perf_event_attr),
.config = PERF_COUNT_SW_TASK_CLOCK,
.disabled = 1,
.exclude_kernel = 1,
};
// Open the above event targeting cpu 0. (EINTR not possible.)
unique_fd fd(static_cast<int>(syscall(__NR_perf_event_open, &pe, /*pid=*/-1,
/*cpu=*/0,
/*group_fd=*/-1, /*flags=*/0)));
if (fd == -1) {
PLOG(ERROR) << "Unexpected perf_event_open error";
return {};
}
int ioctl_ret = ioctl(fd, PERF_EVENT_IOC_RESET);
if (ioctl_ret != -1) {
// Success implies that the kernel doesn't have the hooks.
return {};
} else if (errno != EACCES) {
PLOG(ERROR) << "Unexpected perf_event ioctl error";
return {};
}
// Conclude that the SELinux hooks are present.
SetProperty("sys.init.perf_lsm_hooks", "1");
return {};
}
} // namespace init
} // namespace android

View file

@ -29,6 +29,7 @@ namespace init {
Result<void> MixHwrngIntoLinuxRngAction(const BuiltinArguments&);
Result<void> SetMmapRndBitsAction(const BuiltinArguments&);
Result<void> SetKptrRestrictAction(const BuiltinArguments&);
Result<void> TestPerfEventSelinuxAction(const BuiltinArguments&);
} // namespace init
} // namespace android

View file

@ -936,14 +936,33 @@ on property:sys.sysctl.extra_free_kbytes=*
on property:sys.sysctl.tcp_def_init_rwnd=*
write /proc/sys/net/ipv4/tcp_default_init_rwnd ${sys.sysctl.tcp_def_init_rwnd}
on property:security.perf_harden=0
# perf_event_open syscall security:
# Newer kernels have the ability to control the use of the syscall via SELinux
# hooks. init tests for this, and sets sys_init.perf_lsm_hooks to 1 if the
# kernel has the hooks. In this case, the system-wide perf_event_paranoid
# sysctl is set to -1 (unrestricted use), and the SELinux policy is used for
# controlling access. On older kernels, the paranoid value is the only means of
# controlling access. It is normally 3 (allow only root), but the shell user
# can lower it to 1 (allowing thread-scoped pofiling) via security.perf_harden.
on property:sys.init.perf_lsm_hooks=1
write /proc/sys/kernel/perf_event_paranoid -1
on property:security.perf_harden=0 && property:sys.init.perf_lsm_hooks=""
write /proc/sys/kernel/perf_event_paranoid 1
on property:security.perf_harden=1 && property:sys.init.perf_lsm_hooks=""
write /proc/sys/kernel/perf_event_paranoid 3
# Additionally, simpleperf profiler uses debug.* and security.perf_harden
# sysprops to be able to indirectly set these sysctls.
on property:security.perf_harden=0
write /proc/sys/kernel/perf_event_max_sample_rate ${debug.perf_event_max_sample_rate:-100000}
write /proc/sys/kernel/perf_cpu_time_max_percent ${debug.perf_cpu_time_max_percent:-25}
write /proc/sys/kernel/perf_event_mlock_kb ${debug.perf_event_mlock_kb:-516}
# Default values.
on property:security.perf_harden=1
write /proc/sys/kernel/perf_event_paranoid 3
write /proc/sys/kernel/perf_event_max_sample_rate 100000
write /proc/sys/kernel/perf_cpu_time_max_percent 25
write /proc/sys/kernel/perf_event_mlock_kb 516
# on shutdown
# In device's init.rc, this trigger can be used to do device-specific actions