The useful result of this log-access audit is the test boundary: revoking READ_LOGS must be checked alongside runtime log requests and the caller SELinux domain. The fifteen grants are an observation from one LineageOS installation. They should not be generalized to every ROM or treated as evidence that each app actually read every log entry.
Logcat is system-wide. A normal app can’t read another app’s memory, but an app holding READ_LOGS reads the system log buffers in plain text. I discovered that on this LineageOS build, READ_LOGS was auto-granted at install. Its protection level is signature|privileged|development. The development flag allows a shell to grant it, and this ROM was silently auto-granting it upon manifest declaration.
I started by revoking the permission from all third-party apps with a root script:
for p in $(pm list packages -3 | cut -d: -f2); do
if dumpsys package "$p" 2>/dev/null | grep -q "android.permission.READ_LOGS: granted=true"; then
pm revoke "$p" android.permission.READ_LOGS
fi
done
I also denied third-party requests on the runtime path. I hooked LogcatManagerService inside system_server. Since the decision runs on a handler thread instead of a Binder transaction, I couldn’t use Binder.getCallingUid(). I extracted the UID from the LogAccessRequest object.
XposedBridge.hookAllMethods(lms, "processNewLogAccessRequest", new XC_MethodHook() {
@Override protected void beforeHookedMethod(MethodHookParam p) {
try {
Object req = (p.args != null && p.args.length > 0) ? p.args[0] : null;
if (req == null) return;
int uid = XposedHelpers.getIntField(req, "mUid");
if (!isThirdPartyAppId(uid)) return;
XposedHelpers.callMethod(p.thisObject, "declineRequest", req);
p.setResult(null);
} catch (Throwable ignored) {}
}
});
Finally, I stripped all my own XposedBridge.log statements from my modules.
My first check was misleading: su 10253 -c 'logcat' returned 329 lines, even after revocation. su <uid> keeps the privileged SELinux domain, bypassing logd’s check. With the app’s actual context, the command returned no lines:
su 10253 -z u:r:untrusted_app:s0 -c 'logcat -d -t 200' | wc -l -> 0 (denied)
Logd enforces access via SELinux and caller UID. The untrusted_app domain is what logd actually gates. To measure the app’s access limits, the test has to use its actual SELinux domain.
The separate app-context audit explains why changing UID alone is insufficient.
Comments