<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: vw2x</title>
    <description>The latest articles on DEV Community by vw2x (@vwww-droid).</description>
    <link>https://dev.to/vwww-droid</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3944132%2F3b4ebd48-dba0-43d1-9e27-a44f55a876b3.png</url>
      <title>DEV Community: vw2x</title>
      <link>https://dev.to/vwww-droid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vwww-droid"/>
    <language>en</language>
    <item>
      <title>Detecting root, emulators, and scrcpy-like projection through an Android audit-log side channel</title>
      <dc:creator>vw2x</dc:creator>
      <pubDate>Thu, 21 May 2026 13:53:29 +0000</pubDate>
      <link>https://dev.to/vwww-droid/detecting-root-emulators-and-scrcpy-like-projection-through-an-android-audit-log-side-channel-5322</link>
      <guid>https://dev.to/vwww-droid/detecting-root-emulators-and-scrcpy-like-projection-through-an-android-audit-log-side-channel-5322</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;This post is about an Android audit-log information leak fixed by the AOSP change &lt;a href="https://android-review.googlesource.com/c/platform/system/logging/+/3725346" rel="noopener noreferrer"&gt;Hide procfs related audit messages from appdomain&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Inside a restricted third-party Android app sandbox, even when the app cannot directly read another process under &lt;code&gt;/proc/&amp;lt;pid&amp;gt;&lt;/code&gt;, touching procfs may trigger SELinux audit logs. If those logs are visible through logcat, the &lt;code&gt;tcontext&lt;/code&gt; field can reveal the SELinux domain of the target process.&lt;/p&gt;

&lt;p&gt;In other words, this is a detection technique based on an audit-log side channel.&lt;/p&gt;

&lt;p&gt;I tried three scenarios with this idea:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Root environment: observe Magisk-related domains through &lt;code&gt;tcontext=u:r:magisk:s0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Emulator environment: observe emulator traits through &lt;code&gt;qemu_props&lt;/code&gt;, &lt;code&gt;goldfish&lt;/code&gt;, and &lt;code&gt;ranchu&lt;/code&gt; related domains.&lt;/li&gt;
&lt;li&gt;Possible active screen projection: observe consecutive high-PID &lt;code&gt;u:r:shell:s0&lt;/code&gt; targets that look like scrcpy-driven shell automation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The experiments use the open-source &lt;a href="https://github.com/vwww-droid/Mira" rel="noopener noreferrer"&gt;Mira&lt;/a&gt; framework for runtime risk analysis.&lt;/p&gt;

&lt;p&gt;Compared with repeatedly packaging, installing, and triggering an app, Mira lets an AI iterate shell probes directly. For this research, tuning PID ranges, logcat match rules, and scan windows was quick and low effort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The leak mechanism, AOSP fix, and evasion discussion are at the end of this article.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduction
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Magisk root environment
&lt;/h3&gt;

&lt;p&gt;I asked an AI to call Mira MCP and run a chunked scan. The scan started at PID &lt;code&gt;900&lt;/code&gt;, touched &lt;code&gt;/proc/&amp;lt;pid&amp;gt;&lt;/code&gt; in 25-PID windows, and stopped after a hit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Magisk audit side-channel scan]
scan pid=900-924
scan pid=925-949
scan pid=950-974
scan pid=975-999
scan pid=1000-1024
scan pid=1025-1049
hit_window=1025-1049
05-20 21:48:56.216 ... avc: denied { getattr } for comm="sh" path="/proc/1028" dev="proc" ... scontext=u:r:untrusted_app_27:s0:... tcontext=u:r:magisk:s0 tclass=dir permissive=0 app=com.vwww.mira
05-20 21:48:56.216 ... avc: denied { getattr } for comm="sh" path="/proc/1029" dev="proc" ... scontext=u:r:untrusted_app_27:s0:... tcontext=u:r:magisk:s0 tclass=dir permissive=0 app=com.vwww.mira
05-20 21:48:56.219 ... avc: denied { getattr } for comm="sh" path="/proc/1048" dev="proc" ... scontext=u:r:untrusted_app_27:s0:... tcontext=u:r:magisk:s0 tclass=dir permissive=0 app=com.vwww.mira
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwemxf9ctxbru2eyfrlzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwemxf9ctxbru2eyfrlzi.png" alt="u:magisk:s0" width="799" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the app sandbox touches procfs, the audit log directly exposes &lt;code&gt;tcontext=u:r:magisk:s0&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  AVD
&lt;/h3&gt;

&lt;p&gt;AVD means the emulator bundled with Android Studio. This demo uses an Android 13 image from Android Studio on an Apple Silicon Mac.&lt;/p&gt;

&lt;p&gt;In the Magisk section, I used AI-driven MCP calls to demonstrate how fast Mira can iterate. After understanding the mechanism, manually using the controlled third-party shell is often faster.&lt;/p&gt;

&lt;p&gt;First, use adb to inspect candidate processes and names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ adb shell
emu64a:/ &lt;span class="nv"&gt;$ &lt;/span&gt;ps &lt;span class="nt"&gt;-e&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;qemu
root           158     1 10780188  2184 0                   0 S qemu-props
emu64a:/ &lt;span class="err"&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then probe it. The &lt;code&gt;qemu_props&lt;/code&gt; trait appears.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjrjfrqsxiivrbhaksfp6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjrjfrqsxiivrbhaksfp6.png" alt="qemu_props" width="800" height="108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Other emulator-related candidates can also be found:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ adb shell
emu64a:/ &lt;span class="nv"&gt;$ &lt;/span&gt;ps &lt;span class="nt"&gt;-e&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;qemu
root           158     1 10780188  2184 0                   0 S qemu-props
emu64a:/ &lt;span class="nv"&gt;$ &lt;/span&gt;ps &lt;span class="nt"&gt;-e&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;goldfish
root           152     2       0      0 0                   0 S &lt;span class="o"&gt;[&lt;/span&gt;irq/46-goldfish]
media          317     1 11086960  4008 0                   0 S android.hardware.media.c2@1.0-service-goldfish
radio          370     1 11113696  3372 0                   0 S libgoldfish-rild
emu64a:/ &lt;span class="nv"&gt;$ &lt;/span&gt;ps &lt;span class="nt"&gt;-e&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;anchu
gps            777     1 10949284  2056 0                   0 S android.hardware.gnss@2.0-service.ranchu
emu64a:/ &lt;span class="err"&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are less obvious than &lt;code&gt;qemu&lt;/code&gt;, but they can also be triggered in some images.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gdmkg6v555kq5l4vs8f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gdmkg6v555kq5l4vs8f.png" alt="more emulator-related candidates" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  scrcpy projection
&lt;/h3&gt;

&lt;p&gt;After Android 9 tightened permissions, detecting scrcpy became difficult because scrcpy hides its runtime traces well. Audit logs provide another angle.&lt;/p&gt;

&lt;p&gt;From the scrcpy source code, newer scrcpy versions start through adb shell, launch &lt;code&gt;app_process&lt;/code&gt;, run scrcpy's jar logic, and delete &lt;code&gt;/data/local/tmp/scrcpy-server.jar&lt;/code&gt; after startup. That leaves no stable file artifact.&lt;/p&gt;

&lt;p&gt;Following the same method as AVD, inspect from adb shell first. During projection, the projection service appears as a tight &lt;code&gt;sh -&amp;gt; app_process -&amp;gt; app_process&lt;/code&gt; PID cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;emu64a:/ $ ps -e | grep shell
shell          351     1 11105424  7368 0                   0 S adbd
shell          508   351 10789444  2236 __arm64_sys_nanosleep 0 S process-tracker
shell         5615   351 10815908  3168 __do_sys_rt_sigsuspend 0 S sh
shell         6212   351 10858824  3256 unix_stream_data_wait 0 S abb

shell        27769   351 10797476  2632 __do_sys_rt_sigsuspend 0 S sh
shell        27771 27769 14129832 135132 do_epoll_wait      0 S app_process
shell        27800 27771 13609408 102484 pipe_read          0 S app_process
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The audit log does not directly expose &lt;code&gt;app_process&lt;/code&gt;, but it can show several nearby high-PID targets with &lt;code&gt;u:r:shell:s0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuatdg06999nxf7pridva.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuatdg06999nxf7pridva.png" alt="high-PID targets with u:r:shell:s0" width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The start-stop comparison also matched expectations. After stopping projection, scanning the same high-PID range returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;START=10000 END=18000 CHUNK=100 STEP=100
no_shell_domain_hit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A normal user with adb enabled but without scrcpy projection usually will not have this high-PID cluster. Therefore, &lt;strong&gt;three consecutive nearby &lt;code&gt;u:r:shell:s0&lt;/code&gt; targets&lt;/strong&gt; can be used as a strategy for detecting suspected scrcpy-like projection. This still needs online-environment validation. Here it is presented as a research direction.&lt;/p&gt;

&lt;p&gt;To hide this trace, one would need either a patched system or a root-level plugin to hook this framework path. That moves the problem into root-evasion territory and often introduces new traits, raising the attacker's cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scan stability
&lt;/h3&gt;

&lt;p&gt;This side channel should not be scanned with a careless wide range. &lt;strong&gt;In experiments, a single PID hit could succeed while a wide-window scan missed it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are two main reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SELinux audit logs are rate-limited.&lt;/li&gt;
&lt;li&gt;A large window creates many unrelated denials. If the target PID is late in the window, useful lines may be buried by noise.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Prefer small or overlapping windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;START&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1000
&lt;span class="nv"&gt;END&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2500
&lt;span class="nv"&gt;CHUNK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10
&lt;span class="nv"&gt;STEP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10
&lt;span class="nv"&gt;WAIT_SEC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="nv"&gt;LOG_TAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;400
&lt;span class="nv"&gt;MATCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'tcontext=u:r:qemu_props:s0|tcontext=u:r:[^ ]*(goldfish|ranchu|qemu)[^ ]*:s0'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;CHUNK=50&lt;/code&gt; misses, lower it to &lt;code&gt;CHUNK=10&lt;/code&gt;, or use an overlapping strategy such as &lt;code&gt;CHUNK=50 STEP=25&lt;/code&gt;. Do not only increase &lt;code&gt;sleep&lt;/code&gt;, because the failure is usually not log latency, but audit rate limiting and window noise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Execution syntax details
&lt;/h3&gt;

&lt;p&gt;Touching &lt;code&gt;/proc/&amp;lt;pid&amp;gt;&lt;/code&gt; from the current shell is not necessarily equivalent to touching it from a new &lt;code&gt;sh script.sh&lt;/code&gt; child shell.&lt;/p&gt;

&lt;p&gt;Recommended execution models:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Touch from the current shell.&lt;/li&gt;
&lt;li&gt;Or write a file and load it in the current shell with &lt;code&gt;. script.sh&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  AOSP original leak path analysis
&lt;/h2&gt;

&lt;p&gt;The core code path is &lt;code&gt;system/logging/logd/LogAudit.cpp&lt;/code&gt;, specifically &lt;code&gt;LogAudit::logPrint&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After logd receives an audit message, it first formats the message as a string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;va_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vasprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;va_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, &lt;code&gt;str&lt;/code&gt; already contains raw audit information such as &lt;code&gt;dev="proc"&lt;/code&gt;, &lt;code&gt;scontext&lt;/code&gt;, &lt;code&gt;tcontext&lt;/code&gt;, &lt;code&gt;tclass&lt;/code&gt;, &lt;code&gt;comm&lt;/code&gt;, and &lt;code&gt;path&lt;/code&gt;. For this article, the important combination is &lt;code&gt;dev="proc"&lt;/code&gt; plus &lt;code&gt;tcontext&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The code then looks for the &lt;code&gt;pid=&lt;/code&gt; field in the audit string and calls &lt;code&gt;pidToUid&lt;/code&gt; to resolve the UID that triggered the audit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;pid_str&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;" pid="&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pidptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strstr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid_str&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;android&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;pidToUid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That UID is later passed to the logging system. For procfs audit events triggered by an app, the parsed UID falls under &lt;code&gt;AID_APP_START&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The original code calls &lt;code&gt;auditParse(str, uid)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;denial_metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auditParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;auditParse&lt;/code&gt; parses &lt;code&gt;scontext&lt;/code&gt;, &lt;code&gt;tcontext&lt;/code&gt;, and &lt;code&gt;tclass&lt;/code&gt;, and tries to match bug metadata. More importantly, when the UID belongs to the app range, it appends the app package name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;AID_APP_START&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;AID_APP_END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;uidname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;android&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;uidToName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uidname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" app="&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;uidname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uidname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So logd does more than forward the raw audit string. It enriches the message for debugging. That is useful for system diagnostics, but when the log is visible to apps, it makes the side channel easier to read.&lt;/p&gt;

&lt;p&gt;The code then writes the audit string and appended metadata into the events buffer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logbuf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG_ID_EVENTS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="k"&gt;reinterpret_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                 &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_len&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;UINT16_MAX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint16_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;message_len&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UINT16_MAX&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;uid&lt;/code&gt;, &lt;code&gt;pid&lt;/code&gt;, and &lt;code&gt;tid&lt;/code&gt; used here come from the previous parsing step. In other words, this audit event enters the logging system with information related to the triggering app.&lt;/p&gt;

&lt;p&gt;Later, the code also builds a main-buffer log line. It parses &lt;code&gt;comm="..."&lt;/code&gt; from the audit string, builds a new log message, and appends denial metadata.&lt;/p&gt;

&lt;p&gt;The result is that the same procfs denial may enter both the main and events buffers. If the app side can read the relevant log surface, &lt;code&gt;tcontext&lt;/code&gt; becomes visible.&lt;/p&gt;

&lt;p&gt;The root cause can be summarized as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hidepid=2&lt;/code&gt; protects the normal procfs read surface, but the original logd path forwards diagnostics from failed procfs accesses into an app-visible logging surface.&lt;/p&gt;

&lt;p&gt;Therefore, the app does not need to directly read &lt;code&gt;/proc/&amp;lt;pid&amp;gt;&lt;/code&gt;. It only needs to touch &lt;code&gt;/proc/&amp;lt;pid&amp;gt;&lt;/code&gt; to trigger a &lt;code&gt;getattr&lt;/code&gt; denial, then read &lt;code&gt;tcontext&lt;/code&gt; from logcat and infer the target process's SELinux domain.&lt;/p&gt;

&lt;p&gt;The information flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;App touches /proc/&amp;lt;pid&amp;gt;
  -&amp;gt; SELinux denies the access
  -&amp;gt; kernel produces an audit record
  -&amp;gt; audit record reaches logd through netlink
  -&amp;gt; logd writes it into main or events log buffers
  -&amp;gt; app-side logcat sees tcontext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not bypass SELinux permission checks. It abuses diagnostic information produced after the permission check fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  AOSP patch author's fix
&lt;/h2&gt;

&lt;p&gt;Patch 3725346 adds the following logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Hide procfs related audit messages from appdomain to prevent selinux context leak&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;AID_APP_START&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;strstr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dev=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;proc&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The filter has two conditions.&lt;/p&gt;

&lt;p&gt;First, &lt;code&gt;uid &amp;gt;= AID_APP_START&lt;/code&gt;. The filter targets audit events triggered by app UIDs, not every system audit event. This avoids breaking diagnostics for system services and native system processes.&lt;/p&gt;

&lt;p&gt;Second, &lt;code&gt;strstr(str, "dev=\"proc\"")&lt;/code&gt;. The filter is scoped to procfs-related audit events. It does not broadly filter all SELinux denials. It specifically blocks the path that leaks other process domains through &lt;code&gt;/proc/&amp;lt;pid&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fix:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does not change SELinux decisions.&lt;/li&gt;
&lt;li&gt;Does not change the procfs permission model.&lt;/li&gt;
&lt;li&gt;Does not discard all system debugging information.&lt;/li&gt;
&lt;li&gt;Blocks app-triggered procfs audit records from entering the main and events log buffers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It fixes the forwarding boundary in logd. Access can still fail, and the system can still audit, but apps can no longer observe other processes' &lt;code&gt;tcontext&lt;/code&gt; through log buffers.&lt;/p&gt;

&lt;h2&gt;
  
  
  SELinux audit structure
&lt;/h2&gt;

&lt;p&gt;A typical SELinux denial looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;avc: denied { getattr } for comm="sh" path="/proc/1030" dev="proc" ... scontext=u:r:untrusted_app:s0 ... tcontext=u:r:magisk:s0 tclass=dir permissive=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Why it matters here&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;avc: denied&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SELinux denied access&lt;/td&gt;
&lt;td&gt;Shows that the denial path was reached&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{ getattr }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Access operation&lt;/td&gt;
&lt;td&gt;Directory metadata probing is enough&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Target path&lt;/td&gt;
&lt;td&gt;Points to &lt;code&gt;/proc/&amp;lt;pid&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dev="proc"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Filesystem source&lt;/td&gt;
&lt;td&gt;Identifies procfs-related audit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scontext&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Source security domain&lt;/td&gt;
&lt;td&gt;Usually the app sandbox domain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tcontext&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Target security domain&lt;/td&gt;
&lt;td&gt;The core leaked side-channel field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tclass&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Target object class&lt;/td&gt;
&lt;td&gt;For example &lt;code&gt;dir&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;AOSP SELinux docs state that SELinux denials enter &lt;code&gt;dmesg&lt;/code&gt; and &lt;code&gt;logcat&lt;/code&gt;, and that &lt;code&gt;scontext&lt;/code&gt;, &lt;code&gt;tcontext&lt;/code&gt;, and &lt;code&gt;tclass&lt;/code&gt; describe the source, target, and target object class. The design goal is to help system developers debug policy issues, but under certain log visibility conditions it becomes an information side channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evasion and patching
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ZN-AuditPatch
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/aviraxp/ZN-AuditPatch" rel="noopener noreferrer"&gt;ZN-AuditPatch&lt;/a&gt; filters known root-related target contexts such as &lt;code&gt;magisk&lt;/code&gt; and &lt;code&gt;su&lt;/code&gt;, then rewrites them into a fixed &lt;code&gt;priv_app&lt;/code&gt; context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;constexpr&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string_view&lt;/span&gt; &lt;span class="n"&gt;target_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tcontext=u:r:priv_app:s0:c512,c768"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;constexpr&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string_view&lt;/span&gt; &lt;span class="n"&gt;source_contexts&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"tcontext=u:r:su:s0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"tcontext=u:r:magisk:s0"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for a narrow root-domain detector, but it is weak once the detection surface is generalized. A third-party app can still look for other leaked domains, and the fixed replacement string can itself become a fingerprint.&lt;/p&gt;

&lt;h3&gt;
  
  
  PatchAudit
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/vwww-droid/PatchAudit" rel="noopener noreferrer"&gt;PatchAudit&lt;/a&gt; follows the same general direction, but changes the matching and replacement strategy.&lt;/p&gt;

&lt;p&gt;First, it does not only match &lt;code&gt;magisk&lt;/code&gt; or &lt;code&gt;su&lt;/code&gt;. Any audit message that has &lt;code&gt;tcontext=&lt;/code&gt; and looks procfs-related through &lt;code&gt;dev="proc"&lt;/code&gt; or &lt;code&gt;path="/proc/"&lt;/code&gt; becomes a rewrite candidate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;is_procfs_audit_with_target_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;has_tcontext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tcontext="&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;is_procfs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dev=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;proc&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
        &lt;span class="n"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"path=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;/proc/"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;has_tcontext&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;is_procfs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, the fake &lt;code&gt;priv_app&lt;/code&gt; MLS categories are not hard-coded. They are generated once per &lt;code&gt;logd&lt;/code&gt; lifecycle and remain stable during that lifecycle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;g_fake_tcontext&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"u:r:priv_app:s0:c512,c768"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;init_fake_tcontext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_seed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xffU&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;256U&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;snprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;g_fake_tcontext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g_fake_tcontext&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s"&gt;"u:r:priv_app:s0:c%u,c%u,c512,c768"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;high&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value is not randomized for every log line. It is stable for the current &lt;code&gt;logd&lt;/code&gt; lifetime, which looks more natural than high-frequency per-line randomness.&lt;/p&gt;

&lt;p&gt;On an arm64 rooted test device, this hid the procfs audit side channels described in this article. However, in a scrcpy scenario without root-level intervention, the high-PID &lt;code&gt;u:r:shell:s0&lt;/code&gt; cluster can still be detected. That is why the detection method remains useful as a practical risk signal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- tcontext=u:r:magisk:s0
&lt;/span&gt;&lt;span class="gi"&gt;+ tcontext=u:r:priv_app:s0:c115,c371,c512,c768
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other device families have not been systematically tested. If there are issues, they can be reported to PatchAudit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source cases and scripts
&lt;/h2&gt;

&lt;p&gt;This post is distilled from the following Mira cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/vwww-droid/Mira/blob/main/knowledge/cases/zh/2026/2026-05-19-android-proc-audit-magisk-sidechannel.md" rel="noopener noreferrer"&gt;Android proc audit side channel detects Magisk SELinux context&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vwww-droid/Mira/blob/main/knowledge/cases/zh/2026/2026-05-20-android-emulator-proc-audit-sidechannel.md" rel="noopener noreferrer"&gt;Android emulator proc audit side channel exposes qemu SELinux context&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vwww-droid/Mira/blob/main/knowledge/cases/zh/2026/2026-05-20-android-high-pid-shell-audit-sidechannel-scrcpy.md" rel="noopener noreferrer"&gt;Android high-PID shell proc audit side channel suggests scrcpy projection&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Companion scripts, with parameters such as PID ranges and wait times expected to be tuned per device:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/vwww-droid/Mira/blob/main/tools/android/mira-proc-audit-sidechannel.sh" rel="noopener noreferrer"&gt;mira-proc-audit-sidechannel.sh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vwww-droid/Mira/blob/main/tools/android/mira-emulator-audit-sidechannel.sh" rel="noopener noreferrer"&gt;mira-emulator-audit-sidechannel.sh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vwww-droid/Mira/blob/main/tools/android/mira-high-pid-shell-audit-sidechannel.sh" rel="noopener noreferrer"&gt;mira-high-pid-shell-audit-sidechannel.sh&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Mira is mainly a debugging entry point here. The detection logic does not need to be hard-coded into the app first. It is better to validate the idea with shell scripts, then decide whether it is stable enough to become a reusable case or rule based on results from different devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;This side channel does not bypass SELinux. It relies on diagnostics generated after SELinux correctly denies access. That makes the bug interesting: the protected data path is blocked, but the failure report can still reveal enough context to classify the target process.&lt;/p&gt;

&lt;p&gt;For defenders, the AOSP fix closes the cleanest leak path by filtering app-triggered procfs audit messages before they reach app-visible log buffers. For researchers, the pattern is still useful as a reminder to inspect diagnostic surfaces, not only successful read APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;AOSP Gerrit: &lt;a href="https://android-review.googlesource.com/c/platform/system/logging/+/3725346" rel="noopener noreferrer"&gt;Hide procfs related audit messages from appdomain&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AOSP Docs: &lt;a href="https://source.android.com/docs/security/features/selinux/validate" rel="noopener noreferrer"&gt;Validate SELinux&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AOSP Docs: &lt;a href="https://source.android.com/docs/core/tests/debug/understanding-logging" rel="noopener noreferrer"&gt;Understand logging&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aviraxp/ZN-AuditPatch" rel="noopener noreferrer"&gt;ZN-AuditPatch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vwww-droid/PatchAudit" rel="noopener noreferrer"&gt;PatchAudit&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>android</category>
      <category>ai</category>
      <category>security</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
