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
This commit is contained in:
Babak 2020-04-17 00:19:52 -07:00
parent 0fcd767d5f
commit 151a5643bd
3 changed files with 305 additions and 0 deletions

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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();
}
}