-
-
Notifications
You must be signed in to change notification settings - Fork 444
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix potential Privilege Escalation via Content Provider (CVE-2018-9492)…
… (#2466) Co-authored-by: Markus Hintersteiner <[email protected]> close undefined
- Loading branch information
Showing
5 changed files
with
154 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
...re/src/main/java/io/sentry/android/core/internal/util/ContentProviderSecurityChecker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package io.sentry.android.core.internal.util; | ||
|
||
import android.annotation.SuppressLint; | ||
import android.content.ContentProvider; | ||
import android.net.Uri; | ||
import android.os.Build; | ||
import io.sentry.NoOpLogger; | ||
import io.sentry.android.core.BuildInfoProvider; | ||
import org.jetbrains.annotations.ApiStatus; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
@ApiStatus.Internal | ||
public final class ContentProviderSecurityChecker { | ||
|
||
private final @NotNull BuildInfoProvider buildInfoProvider; | ||
|
||
public ContentProviderSecurityChecker() { | ||
this(new BuildInfoProvider(NoOpLogger.getInstance())); | ||
} | ||
|
||
public ContentProviderSecurityChecker(final @NotNull BuildInfoProvider buildInfoProvider) { | ||
this.buildInfoProvider = buildInfoProvider; | ||
} | ||
|
||
/** | ||
* Protects against "Privilege Escalation via Content Provider" (CVE-2018-9492). | ||
* | ||
* <p>Throws a SecurityException if the security check is breached. | ||
* | ||
* <p>See https://www.cvedetails.com/cve/CVE-2018-9492/ and | ||
* https://github.com/getsentry/sentry-java/issues/2460 | ||
* | ||
* <p>Call this function in the {@link ContentProvider#query(Uri, String[], String, String[], | ||
* String)} function. | ||
* | ||
* <p>This should be invoked regardless of whether there is data to query or not. The attack is | ||
* not contained to the specific provider but rather the entire system. | ||
* | ||
* <p>This blocks the attacker by only allowing the app itself (not other apps) to "query" the | ||
* provider. | ||
* | ||
* <p>The vulnerability is specific to un-patched versions of Android 8 and 9 (API 26 to 28). | ||
* Therefore, this security check is limited to those versions to mitigate risk of regression. | ||
*/ | ||
@SuppressLint("NewApi") | ||
public void checkPrivilegeEscalation(ContentProvider contentProvider) { | ||
final int sdkVersion = buildInfoProvider.getSdkInfoVersion(); | ||
if (sdkVersion >= Build.VERSION_CODES.O && sdkVersion <= Build.VERSION_CODES.P) { | ||
|
||
String callingPackage = contentProvider.getCallingPackage(); | ||
String appPackage = contentProvider.getContext().getPackageName(); | ||
if (callingPackage != null && callingPackage.equals(appPackage)) { | ||
return; | ||
} | ||
throw new SecurityException("Provider does not allow for granting of Uri permissions"); | ||
} | ||
} | ||
} |
91 changes: 91 additions & 0 deletions
91
.../src/test/java/io/sentry/android/core/internal/util/ContentProviderSecurityCheckerTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package io.sentry.android.core.internal.util | ||
|
||
import android.content.ContentProvider | ||
import android.content.Context | ||
import android.os.Build | ||
import io.sentry.android.core.BuildInfoProvider | ||
import org.mockito.kotlin.mock | ||
import org.mockito.kotlin.verifyNoInteractions | ||
import org.mockito.kotlin.whenever | ||
import kotlin.test.Test | ||
import kotlin.test.assertFailsWith | ||
|
||
class ContentProviderSecurityCheckerTest { | ||
|
||
private class Fixture { | ||
val buildInfoProvider = mock<BuildInfoProvider>() | ||
|
||
fun getSut( | ||
sdkVersion: Int = Build.VERSION_CODES.O | ||
): ContentProviderSecurityChecker { | ||
whenever(buildInfoProvider.sdkInfoVersion).thenReturn(sdkVersion) | ||
|
||
return ContentProviderSecurityChecker( | ||
buildInfoProvider | ||
) | ||
} | ||
} | ||
|
||
private val fixture = Fixture() | ||
|
||
@Test | ||
fun `When sdk version is less than vulnerable versions, security check is not performed`() { | ||
val contentProvider = mock<ContentProvider>() | ||
|
||
fixture.getSut(Build.VERSION_CODES.N_MR1).checkPrivilegeEscalation(contentProvider) | ||
|
||
verifyNoInteractions(contentProvider) | ||
} | ||
|
||
@Test | ||
fun `When sdk version is greater than vulnerable versions, security check is not performed`() { | ||
val contentProvider = mock<ContentProvider>() | ||
|
||
fixture.getSut(Build.VERSION_CODES.Q).checkPrivilegeEscalation(contentProvider) | ||
|
||
verifyNoInteractions(contentProvider) | ||
} | ||
|
||
@Test | ||
fun `When calling package is null, security check exception is thrown`() { | ||
val contentProvider = mock<ContentProvider>() | ||
|
||
contentProvider.mockPackages(null) | ||
|
||
assertFailsWith<SecurityException> { | ||
fixture.getSut().checkPrivilegeEscalation(contentProvider) | ||
} | ||
} | ||
|
||
@Test | ||
fun `When calling package does not match app package, security check exception is thrown`() { | ||
val contentProvider = mock<ContentProvider>() | ||
|
||
contentProvider.mockPackages("{$APP_PACKAGE}.attacker") | ||
|
||
assertFailsWith<SecurityException> { | ||
fixture.getSut().checkPrivilegeEscalation(contentProvider) | ||
} | ||
} | ||
|
||
@Test | ||
fun `When calling package matches app package, no security exception thrown`() { | ||
val contentProvider = mock<ContentProvider>() | ||
|
||
contentProvider.mockPackages(APP_PACKAGE) | ||
|
||
fixture.getSut().checkPrivilegeEscalation(contentProvider) | ||
|
||
// No exception! | ||
} | ||
} | ||
|
||
private fun ContentProvider.mockPackages(callingPackage: String?) { | ||
whenever(this.callingPackage).thenReturn(callingPackage) | ||
|
||
val context = mock<Context>() | ||
whenever(this.context).thenReturn(context) | ||
whenever(context.packageName).thenReturn(APP_PACKAGE) | ||
} | ||
|
||
private const val APP_PACKAGE = "com.app" |