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:
parent
0fcd767d5f
commit
151a5643bd
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user