Allow heap profiling of certain app domains on user builds
This patch extends the current debug-specific rules to cover user
builds. As a reminder, on user, the target process fork-execs a private
heapprofd process, which then performs stack unwinding & talking to the
central tracing daemon while staying in the target's domain. The central
heapprofd daemon is only responsible for identifying targets & sending
the activation signal. On the other hand, on debug, the central
heapprofd can handle all processes directly, so the necessary SELinux
capabilities depend on the build type.
These rules are necessary but not sufficient for profiling. For zygote
children, the libc triggering logic will also check for the app to
either be debuggable, or go/profileable.
For more context, see go/heapprofd-security & go/heapprofd-design.
Note that I've had to split this into two separate macros, as
exec_no_trans - which is necessary on user, but nice-to-have on debug -
conflicts with a lot of neverallows (e.g. HALs and system_server) for
the wider whitelisting that we do on debug builds.
Test: built & flashed on {blueline-userdebug, blueline-user}, activated profiling of whitelisted/not domains & checked for lack of denials in logcat.
Bug: 120409382
Change-Id: Id0defc3105b99f777bcee2046d9894a2b39c6a29
diff --git a/private/domain.te b/private/domain.te
index 326e62a..dda8f21 100644
--- a/private/domain.te
+++ b/private/domain.te
@@ -10,7 +10,8 @@
# heap profiling, as initialization will fail if it does not have the
# necessary SELinux permissions.
get_prop(domain, heapprofd_prop);
-userdebug_or_eng(`can_profile_heap({
+# Allow heap profiling on debug builds.
+userdebug_or_eng(`can_profile_heap_userdebug_or_eng({
domain
-bpfloader
-init
diff --git a/private/ephemeral_app.te b/private/ephemeral_app.te
index 3500c0f..9a6a300 100644
--- a/private/ephemeral_app.te
+++ b/private/ephemeral_app.te
@@ -49,6 +49,10 @@
allow ephemeral_app traced_tmpfs:file { read write getattr map };
unix_socket_connect(ephemeral_app, traced_producer, traced)
+# Allow heap profiling if the app opts in by being marked
+# profileable/debuggable.
+can_profile_heap(ephemeral_app)
+
# allow ephemeral apps to use UDP sockets provided by the system server but not
# modify them other than to connect
allow ephemeral_app system_server:udp_socket {
diff --git a/private/heapprofd.te b/private/heapprofd.te
index 5a17990..7f8d8d6 100644
--- a/private/heapprofd.te
+++ b/private/heapprofd.te
@@ -1,33 +1,47 @@
-# Android Heap Profiler Daemon go/heapprofd
+# Android heap profiling daemon. go/heapprofd.
+#
+# On user builds, this daemon is responsible for receiving the initial
+# profiling configuration, finding matching target processes (if profiling by
+# process name), and sending the activation signal to them (+ setting system
+# properties for new processes to start profiling from startup). When profiling
+# is triggered in a process, it spawns a private heapprofd subprocess (in its
+# own SELinux domain), which will exclusively handle profiling of its parent.
+#
+# On debug builds, this central daemon performs profiling for all target
+# processes (which talk directly to this daemon).
type heapprofd_exec, exec_type, file_type, system_file_type;
init_daemon_domain(heapprofd)
set_prop(heapprofd, heapprofd_prop);
-userdebug_or_eng(`
- # TODO(fmayer): We will also need this on user to read /proc/<pid>/cmdline
- # and send signals.
- typeattribute heapprofd mlstrustedsubject;
- # Allow to send signal to processes.
- # This excludes SIGKILL, SIGSTOP and SIGCHLD,
- # which are controlled by separate permissions.
- allow heapprofd self:capability kill;
+# Necessary for /proc/[pid]/cmdline access & sending signals.
+typeattribute heapprofd mlstrustedsubject;
- # Executables and libraries.
- # These are needed to read the ELF binary data needed for unwinding.
+# Allow sending signals to processes. This excludes SIGKILL, SIGSTOP and
+# SIGCHLD, which are controlled by separate permissions.
+allow heapprofd self:capability kill;
+
+# When scanning /proc/[pid]/cmdline to find matching processes for by-name
+# profiling, only whitelisted domains will be allowed by SELinux. Avoid
+# spamming logs with denials for entries that we can not access.
+dontaudit heapprofd domain:dir { search open };
+
+# Write trace data to the Perfetto traced daemon. This requires connecting to
+# its producer socket and obtaining a (per-process) tmpfs fd.
+allow heapprofd traced:fd use;
+allow heapprofd traced_tmpfs:file { read write getattr map };
+unix_socket_connect(heapprofd, traced_producer, traced)
+
+# When handling profiling for all processes, heapprofd needs to read
+# executables/libraries/etc to do stack unwinding.
+userdebug_or_eng(`
r_dir_file(heapprofd, system_file_type)
r_dir_file(heapprofd, apk_data_file)
r_dir_file(heapprofd, dalvikcache_data_file)
r_dir_file(heapprofd, vendor_file_type)
')
-# Write trace data to the Perfetto traced damon. This requires connecting to its
-# producer socket and obtaining a (per-process) tmpfs fd.
-allow heapprofd traced:fd use;
-allow heapprofd traced_tmpfs:file { read write getattr map };
-unix_socket_connect(heapprofd, traced_producer, traced)
-
never_profile_heap(`{
bpfloader
init
diff --git a/private/isolated_app.te b/private/isolated_app.te
index 3443dc4..017f46b 100644
--- a/private/isolated_app.te
+++ b/private/isolated_app.te
@@ -60,6 +60,10 @@
allow isolated_app traced_tmpfs:file { read write getattr map };
unix_socket_connect(isolated_app, traced_producer, traced)
+# Allow heap profiling if the main app has been marked as profileable or
+# debuggable.
+can_profile_heap(isolated_app)
+
#####
##### Neverallow
#####
diff --git a/private/priv_app.te b/private/priv_app.te
index b6828f0..9232bd0 100644
--- a/private/priv_app.te
+++ b/private/priv_app.te
@@ -144,6 +144,10 @@
allow priv_app traced_tmpfs:file { read write getattr map };
unix_socket_connect(priv_app, traced_producer, traced)
+# Allow heap profiling if the app opts in by being marked
+# profileable/debuggable.
+can_profile_heap(priv_app)
+
# suppress denials for non-API accesses.
dontaudit priv_app exec_type:file getattr;
dontaudit priv_app device:dir read;
diff --git a/private/untrusted_app_all.te b/private/untrusted_app_all.te
index ba70751..a4af4e7 100644
--- a/private/untrusted_app_all.te
+++ b/private/untrusted_app_all.te
@@ -123,6 +123,10 @@
allow untrusted_app_all traced_tmpfs:file { read write getattr map };
unix_socket_connect(untrusted_app_all, traced_producer, traced)
+# Allow heap profiling if the app opts in by being marked
+# profileable/debuggable.
+can_profile_heap(untrusted_app_all)
+
# allow untrusted apps to use UDP sockets provided by the system server but not
# modify them other than to connect
allow untrusted_app_all system_server:udp_socket {
diff --git a/public/te_macros b/public/te_macros
index 149d5ac..ca6070b 100644
--- a/public/te_macros
+++ b/public/te_macros
@@ -647,31 +647,66 @@
###################################
# can_profile_heap(domain)
-# never_profile_heap(domain)
+# Allow processes within the domain to have their heap profiled by heapprofd.
#
-# Opt in our out of heap profiling.
-# This will allow a heap profiling daemon to read this
-# process' address space in order to support unwinding.
-#
+# Note that profiling is performed differently between debug and user builds.
+# This macro covers both user and debug builds, but see
+# can_profile_heap_userdebug_or_eng for a variant that can be used when
+# allowing profiling for a domain only on debug builds, without granting
+# the exec permission. The exec permission is necessary for user builds, but
+# only a nice-to-have for development and testing purposes on debug builds.
define(`can_profile_heap', `
+ # Allow central daemon to send signal for client initialization.
+ allow heapprofd $1:process signal;
+
+ # Allow executing a private heapprofd process to handle profiling on
+ # user builds (also debug builds for testing & development purposes).
+ allow $1 heapprofd_exec:file rx_file_perms;
+
+ # Allow directory & file read to the central heapprofd daemon, as it scans
+ # /proc/[pid]/cmdline for by-process-name profiling configs.
+ # Note that this excludes /proc/[pid]/mem, as it requires ptrace capabilities.
+ allow heapprofd $1:file r_file_perms;
+ allow heapprofd $1:dir r_dir_perms;
+
+ # On debug builds, central daemon can handle profiling of all processes
+ # directly.
userdebug_or_eng(`
- # RT signal for client initialization.
- allow heapprofd $1:process signal;
- # Connect to heapprofd service.
+ # Allow connecting to the daemon.
unix_socket_connect($1, heapprofd, heapprofd)
- # To receive file descriptor.
+ # Allow daemon to use the passed fds.
+ allow heapprofd $1:fd use;
+ ')
+')
+
+###################################
+# can_profile_heap_userdebug_or_eng(domain)
+# Allow processes within the domain to have their heap profiled by heapprofd on
+# debug builds only.
+#
+# Only necessary when can_profile_heap cannot be applied, see its description
+# for rationale.
+define(`can_profile_heap_userdebug_or_eng', `
+ userdebug_or_eng(`
+ # Allow central daemon to send signal for client initialization.
+ allow heapprofd $1:process signal;
+ # Allow connecting to the daemon.
+ unix_socket_connect($1, heapprofd, heapprofd)
+ # Allow daemon to use the passed fds.
allow heapprofd $1:fd use;
# To read from the received file descriptors.
# /proc/[pid]/maps and /proc/[pid]/mem have the same SELinux label as the
# process they relate to.
allow heapprofd $1:file r_file_perms;
- # This is needed to search the /proc/[pid] directory.
+ # Allow searching the /proc/[pid] directory for cmdline.
allow heapprofd $1:dir r_dir_perms;
- allow heapprofd $1:process signal;
')
')
+###################################
+# never_profile_heap(domain)
+# Opt out of heap profiling by heapprofd.
define(`never_profile_heap', `
neverallow heapprofd $1:file read;
neverallow heapprofd $1:process signal;