diff options
| author | 2020-04-17 00:19:52 -0700 | |
|---|---|---|
| committer | 2020-04-17 14:52:06 -0700 | |
| commit | 151a5643bd75775f88df2722daab3a1042f52699 (patch) | |
| tree | 38bb976c4f5783a52be3f2b0f4157db8fb77e61c | |
| parent | 0fcd767d5f547d1c2adc0ea4d22ada36415b5527 (diff) | |
Add Sideloaded detector
A helper class to detect if any package is unsafe or sideloaded.
A package is considered unsafe if not a system app and not installed
through trusted sources.
The usage will be added in the following cls.
Bug: 154263570
Test: Added UnitTest
Change-Id: Ifffbe64ae95029427aeca4a997bc440dbdc2d3d6
3 files changed, 305 insertions, 0 deletions
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index bf1bf38cdb92..5679306b3001 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -76,6 +76,11 @@ <item>com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator</item> </string-array> + <!-- List of package names that are allowed sources of app installation. --> + <string-array name="config_allowedAppInstallSources" translatable="false"> + <item>com.android.vending</item> + </string-array> + <!-- SystemUI Services: The classes of the stuff to start. --> <string-array name="config_systemUIServiceComponents" translatable="false"> <item>com.android.systemui.util.NotificationChannels</item> diff --git a/packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java b/packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java new file mode 100644 index 000000000000..c0dbb5879d7d --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.sideloaded.car; + +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.pm.ApplicationInfo; +import android.content.pm.InstallSourceInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.os.UserHandle; +import android.util.Log; + +import com.android.systemui.R; +import com.android.systemui.car.CarDeviceProvisionedController; +import com.android.systemui.dagger.qualifiers.Main; + +import java.util.Arrays; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * A class that detects unsafe apps. + * An app is considered safe if is a system app or installed through whitelisted sources. + */ +@Singleton +public class CarSideLoadedAppDetector { + private static final String TAG = "CarSideLoadedDetector"; + + private final PackageManager mPackageManager; + private final CarDeviceProvisionedController mCarDeviceProvisionedController; + private final List<String> mAllowedAppInstallSources; + + @Inject + public CarSideLoadedAppDetector(@Main Resources resources, PackageManager packageManager, + CarDeviceProvisionedController deviceProvisionedController) { + mAllowedAppInstallSources = Arrays.asList( + resources.getStringArray(R.array.config_allowedAppInstallSources)); + mPackageManager = packageManager; + mCarDeviceProvisionedController = deviceProvisionedController; + } + + boolean hasUnsafeInstalledApps() { + int userId = mCarDeviceProvisionedController.getCurrentUser(); + + List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser( + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + userId); + for (PackageInfo info : packages) { + if (info.applicationInfo == null) { + Log.w(TAG, info.packageName + " does not have application info."); + return true; + } + + if (!isSafe(info.applicationInfo)) { + return true; + } + } + return false; + } + + boolean isSafe(@NonNull ActivityManager.StackInfo stackInfo) { + ComponentName componentName = stackInfo.topActivity; + if (componentName == null) { + Log.w(TAG, "Stack info does not have top activity: " + stackInfo.stackId); + return false; + } + return isSafe(componentName.getPackageName()); + } + + private boolean isSafe(@NonNull String packageName) { + if (packageName == null) { + return false; + } + + ApplicationInfo applicationInfo; + try { + int userId = mCarDeviceProvisionedController.getCurrentUser(); + applicationInfo = mPackageManager.getApplicationInfoAsUser(packageName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + UserHandle.of(userId)); + + if (applicationInfo == null) { + Log.e(TAG, packageName + " did not have an application info!"); + return false; + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Could not get application info for package:" + packageName, e); + return false; + } + + return isSafe(applicationInfo); + } + + private boolean isSafe(@NonNull ApplicationInfo applicationInfo) { + String packageName = applicationInfo.packageName; + + if (applicationInfo.isSystemApp() || applicationInfo.isUpdatedSystemApp()) { + return true; + } + + String initiatingPackageName; + try { + InstallSourceInfo sourceInfo = mPackageManager.getInstallSourceInfo(packageName); + initiatingPackageName = sourceInfo.getInitiatingPackageName(); + if (initiatingPackageName == null) { + Log.w(TAG, packageName + " does not have an installer name."); + return false; + } + + return mAllowedAppInstallSources.contains(initiatingPackageName); + } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) { + return false; + } + } +} diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java new file mode 100644 index 000000000000..aebb0e005019 --- /dev/null +++ b/packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.sideloaded.car; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.pm.ApplicationInfo; +import android.content.pm.InstallSourceInfo; +import android.content.pm.PackageManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableResources; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.car.CarDeviceProvisionedController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class CarSideLoadedAppDetectorTest extends SysuiTestCase { + + private static final String SAFE_VENDOR = "com.safe.vendor"; + private static final String UNSAFE_VENDOR = "com.unsafe.vendor"; + private static final String APP_PACKAGE_NAME = "com.test"; + private static final String APP_CLASS_NAME = ".TestClass"; + + private CarSideLoadedAppDetector mSideLoadedAppDetector; + + @Mock + private PackageManager mPackageManager; + @Mock + private CarDeviceProvisionedController mCarDeviceProvisionedController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + TestableResources testableResources = mContext.getOrCreateTestableResources(); + String[] allowedAppInstallSources = new String[] {SAFE_VENDOR}; + testableResources.addOverride(R.array.config_allowedAppInstallSources, + allowedAppInstallSources); + + mSideLoadedAppDetector = new CarSideLoadedAppDetector(testableResources.getResources(), + mPackageManager, + mCarDeviceProvisionedController); + } + + @Test + public void isSafe_systemApp_returnsTrue() throws Exception { + ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); + stackInfo.topActivity = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); + + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = APP_PACKAGE_NAME; + applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + + when(mPackageManager.getApplicationInfoAsUser(eq(APP_PACKAGE_NAME), anyInt(), any())) + .thenReturn(applicationInfo); + + assertThat(mSideLoadedAppDetector.isSafe(stackInfo)).isTrue(); + } + + @Test + public void isSafe_updatedSystemApp_returnsTrue() throws Exception { + ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); + stackInfo.topActivity = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); + + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = APP_PACKAGE_NAME; + applicationInfo.flags = ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; + + when(mPackageManager.getApplicationInfoAsUser(eq(APP_PACKAGE_NAME), anyInt(), any())) + .thenReturn(applicationInfo); + + assertThat(mSideLoadedAppDetector.isSafe(stackInfo)).isTrue(); + } + + @Test + public void isSafe_nonSystemApp_withSafeSource_returnsTrue() throws Exception { + InstallSourceInfo sourceInfo = new InstallSourceInfo(SAFE_VENDOR, + /* initiatingPackageSigningInfo= */null, + /* originatingPackageName= */ null, + /* installingPackageName= */ null); + ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); + stackInfo.topActivity = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); + + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = APP_PACKAGE_NAME; + + when(mPackageManager.getApplicationInfoAsUser(eq(APP_PACKAGE_NAME), anyInt(), any())) + .thenReturn(applicationInfo); + when(mPackageManager.getInstallSourceInfo(APP_PACKAGE_NAME)).thenReturn(sourceInfo); + + assertThat(mSideLoadedAppDetector.isSafe(stackInfo)).isTrue(); + } + + @Test + public void isSafe_nonSystemApp_withUnsafeSource_returnsFalse() throws Exception { + InstallSourceInfo sourceInfo = new InstallSourceInfo(UNSAFE_VENDOR, + /* initiatingPackageSigningInfo= */null, + /* originatingPackageName= */ null, + /* installingPackageName= */ null); + ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); + stackInfo.topActivity = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); + + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = APP_PACKAGE_NAME; + + when(mPackageManager.getApplicationInfoAsUser(eq(APP_PACKAGE_NAME), anyInt(), any())) + .thenReturn(applicationInfo); + when(mPackageManager.getInstallSourceInfo(APP_PACKAGE_NAME)).thenReturn(sourceInfo); + + assertThat(mSideLoadedAppDetector.isSafe(stackInfo)).isFalse(); + } + + @Test + public void isSafe_nonSystemApp_withoutSource_returnsFalse() throws Exception { + InstallSourceInfo sourceInfo = new InstallSourceInfo(null, + /* initiatingPackageSigningInfo= */null, + /* originatingPackageName= */ null, + /* installingPackageName= */ null); + ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); + stackInfo.topActivity = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); + + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = APP_PACKAGE_NAME; + + when(mPackageManager.getApplicationInfoAsUser(eq(APP_PACKAGE_NAME), anyInt(), any())) + .thenReturn(applicationInfo); + when(mPackageManager.getInstallSourceInfo(APP_PACKAGE_NAME)).thenReturn(sourceInfo); + + assertThat(mSideLoadedAppDetector.isSafe(stackInfo)).isFalse(); + } +} |