[Android] Remote Access: protect control receiver < API 33
Duncan McNamara
git at videolan.org
Fri Oct 2 07:29:56 UTC 2026
vlc-android | branch: master | Duncan McNamara <dcn.mcnamara at gmail.com> | Wed Sep 23 14:12:24 2026 +0200| [225b82dbf0ec44c88b0e025b0ceed6089930980c] | committer: Nicolas Pomepuy
Remote Access: protect control receiver < API 33
On Android < 13, exported=false doesn't actually stop other apps
from reaching a dynamically-registered receiver, so the Remote
Access control broadcasts (start/stop/disable/restart) were
still open to anyone.
Add a signature permission and require it on that receiver. Our
own broadcasts already hold it, so nothing else changes.
> https://code.videolan.org/videolan/vlc-android/commit/225b82dbf0ec44c88b0e025b0ceed6089930980c
---
.../remote-access-server/src/main/AndroidManifest.xml | 6 ++++++
.../videolan/vlc/remoteaccessserver/RemoteAccessService.kt | 2 +-
.../src/main/java/org/videolan/resources/util/Extensions.kt | 12 ++++++++++--
3 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/application/remote-access-server/src/main/AndroidManifest.xml b/application/remote-access-server/src/main/AndroidManifest.xml
index a8f5d1feb3..0c67cde4eb 100644
--- a/application/remote-access-server/src/main/AndroidManifest.xml
+++ b/application/remote-access-server/src/main/AndroidManifest.xml
@@ -26,6 +26,12 @@
<uses-sdk tools:overrideLibrary="androidx.security"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
+ <!-- Restricts who can send the Remote Access control broadcasts (start/stop/restart/disable) -->
+ <permission
+ android:name="${applicationId}.permission.REMOTE_ACCESS_CONTROL"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="${applicationId}.permission.REMOTE_ACCESS_CONTROL" />
+
<application>
<service
android:name=".RemoteAccessService"
diff --git a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessService.kt b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessService.kt
index f333d9d4cf..1b88388ae3 100644
--- a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessService.kt
+++ b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessService.kt
@@ -147,7 +147,7 @@ class RemoteAccessService : LifecycleService(), CoroutineScope by MainScope() {
filter.addAction(ACTION_START_SERVER)
filter.addAction(ACTION_DISABLE_SERVER)
filter.addAction(ACTION_RESTART_SERVER)
- registerReceiverCompat(receiver, filter, false)
+ registerReceiverCompat(receiver, filter, false, "$packageName.permission.REMOTE_ACCESS_CONTROL")
}
private fun forceForeground() {
diff --git a/application/resources/src/main/java/org/videolan/resources/util/Extensions.kt b/application/resources/src/main/java/org/videolan/resources/util/Extensions.kt
index ce5af19613..fdd87b01f4 100644
--- a/application/resources/src/main/java/org/videolan/resources/util/Extensions.kt
+++ b/application/resources/src/main/java/org/videolan/resources/util/Extensions.kt
@@ -224,14 +224,22 @@ fun Service.startForegroundCompat(serviceNotificationId: NotificationIds, notifi
/**
* Use the new registerReceiver API when needed
*
+ * Below API 33 there is no equivalent of [Context.RECEIVER_NOT_EXPORTED]: passing
+ * `exported = false` on those versions still leaves the receiver reachable from other
+ * apps. If that matters for a given receiver, also pass [permission] so senders are
+ * required to hold it on every API level.
+ *
* @param receiver the receiver to register
* @param filter the filter to apply
* @param exported true if it needs to be exported
+ * @param permission a signature-level permission senders must hold, or null for none
*/
@SuppressLint("UnspecifiedRegisterReceiverFlag")
-fun Context.registerReceiverCompat(receiver: BroadcastReceiver, filter: IntentFilter, exported: Boolean) {
+fun Context.registerReceiverCompat(receiver: BroadcastReceiver, filter: IntentFilter, exported: Boolean, permission: String? = null) {
if (SDK_INT >= Build.VERSION_CODES.TIRAMISU)
- registerReceiver(receiver, filter, if (exported) Context.RECEIVER_EXPORTED else Context.RECEIVER_NOT_EXPORTED)
+ registerReceiver(receiver, filter, permission, null, if (exported) Context.RECEIVER_EXPORTED else Context.RECEIVER_NOT_EXPORTED)
+ else if (permission != null)
+ registerReceiver(receiver, filter, permission, null)
else
registerReceiver(receiver, filter)
}
More information about the Android
mailing list