Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.etesync.syncadapter.syncadapter

import android.content.ComponentName
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

/**
* Guards the exported surface of the sync and account services, as declared in the *merged*
* AndroidManifest.xml.
*
* Sync adapter services must stay unexported: the system server binds them either way (it is
* exempt from the exported check), while exporting them hands the ISyncAdapter Binder to any
* local app, which can then crash the sync process with a malformed transaction.
* See https://github.com/etesync/android/issues/295.
*/
class ExportedComponentsTest {

private val context: Context
get() = InstrumentationRegistry.getInstrumentation().targetContext

private fun isExported(cls: Class<*>) =
context.packageManager.getServiceInfo(ComponentName(context, cls), 0).exported

@Test
fun syncAdapterServicesAreNotExported() {
for (cls in arrayOf(
CalendarsSyncAdapterService::class.java,
ContactsSyncAdapterService::class.java,
AddressBooksSyncAdapterService::class.java,
TasksSyncAdapterService::class.java,
TasksOrgSyncAdapterService::class.java)) {
assertFalse("${cls.simpleName} must not be exported: any local app could then bind it " +
"and crash the sync process (etesync/android#295)", isExported(cls))
}
}

@Test
fun accountAuthenticatorServiceIsNotExported() {
assertFalse("AccountAuthenticatorService is bound by the system and must not be exported",
isExported(AccountAuthenticatorService::class.java))
}

@Test
fun nullAuthenticatorServiceIsExported() {
// Since Android 11 this one has to stay exported, otherwise Google Contacts doesn't show
// the address book accounts.
assertTrue("NullAuthenticatorService must stay exported so that other apps can see the " +
"address book accounts", isExported(NullAuthenticatorService::class.java))
}
}
24 changes: 14 additions & 10 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,19 @@
android:resource="@xml/account_authenticator"/>
</service>

<!--
Sync adapter services must NOT be exported. They are bound by the system server
(SyncManager), which is exempt from the exported check, so the sync framework keeps
working without it. Exporting them hands the ISyncAdapter Binder to any local app,
which can then crash the sync process with a malformed transaction (NPE in
AbstractThreadedSyncAdapter.handleOnUnsyncableAccount).
See https://github.com/etesync/android/issues/295.
-->

<!-- Normal account -->
<service
android:name=".syncadapter.CalendarsSyncAdapterService"
android:exported="true"
tools:ignore="ExportedService">
android:exported="false">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
Expand All @@ -104,8 +112,7 @@
</service>
<service
android:name=".syncadapter.TasksSyncAdapterService"
android:exported="true"
tools:ignore="ExportedService">
android:exported="false">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
</intent-filter>
Expand All @@ -117,8 +124,7 @@

<service
android:name=".syncadapter.TasksOrgSyncAdapterService"
android:exported="true"
tools:ignore="ExportedService">
android:exported="false">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
</intent-filter>
Expand Down Expand Up @@ -149,8 +155,7 @@
android:multiprocess="false"/>
<service
android:name=".syncadapter.AddressBooksSyncAdapterService"
android:exported="true"
tools:ignore="ExportedService">
android:exported="false">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
</intent-filter>
Expand All @@ -162,8 +167,7 @@

<service
android:name=".syncadapter.ContactsSyncAdapterService"
android:exported="true"
tools:ignore="ExportedService">
android:exported="false">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
</intent-filter>
Expand Down