Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cc045c8c authored by Sumedh Sen's avatar Sumedh Sen
Browse files

Perform user restriction check before creating install session

If a user is restricted with DISALLOW_INSTALL_APPS restriction,
PackageInstallerSession throws a SecurityException causing PIA to crash.
Perform restriction checks before PIA creates an install session

Test: atest CtsPackageInstallTestCases:UserRestrictionInstallTest
Bug: 280441684
Change-Id: I9e6b9a0e56eea122b17b4e75a5496d158b15eefe
parent f3192865
Loading
Loading
Loading
Loading
+112 −6
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionFor

import android.Manifest;
import android.app.Activity;
import android.app.DialogFragment;
import android.app.admin.DevicePolicyManager;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -31,6 +33,8 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Process;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
@@ -45,16 +49,24 @@ import java.util.Arrays;
 * it.
 */
public class InstallStart extends Activity {
    private static final String LOG_TAG = InstallStart.class.getSimpleName();
    private static final String TAG = InstallStart.class.getSimpleName();

    private static final String DOWNLOADS_AUTHORITY = "downloads";

    private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = 1;
    private static final int DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER = 2;
    private PackageManager mPackageManager;
    private UserManager mUserManager;
    private boolean mAbortInstall = false;

    private final boolean mLocalLOGV = false;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPackageManager = getPackageManager();
        mUserManager = getSystemService(UserManager.class);

        Intent intent = getIntent();
        String callingPackage = getCallingPackage();
        String callingAttributionTag = null;
@@ -80,8 +92,7 @@ public class InstallStart extends Activity {
        // Uid of the source package, coming from ActivityManager
        int callingUid = getLaunchedFromUid();
        if (callingUid == Process.INVALID_UID) {
            // Cannot reach ActivityManager. Aborting install.
            Log.e(LOG_TAG, "Could not determine the launching uid.");
            Log.e(TAG, "Could not determine the launching uid.");
        }
        // Uid of the source package, with a preference to uid from ApplicationInfo
        final int originatingUid = sourceInfo != null ? sourceInfo.uid : callingUid;
@@ -104,12 +115,12 @@ public class InstallStart extends Activity {
                && originatingUid != Process.INVALID_UID) {
            final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
            if (targetSdkVersion < 0) {
                Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
                Log.w(TAG, "Cannot get target sdk version for uid " + originatingUid);
                // Invalid originating uid supplied. Abort install.
                mAbortInstall = true;
            } else if (targetSdkVersion >= Build.VERSION_CODES.O && !isUidRequestingPermission(
                    originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
                Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
                Log.e(TAG, "Requesting uid " + originatingUid + " needs to declare permission "
                        + Manifest.permission.REQUEST_INSTALL_PACKAGES);
                mAbortInstall = true;
            }
@@ -119,6 +130,8 @@ public class InstallStart extends Activity {
            mAbortInstall = true;
        }

        checkDevicePolicyRestriction();

        final String installerPackageNameFromIntent = getIntent().getStringExtra(
                Intent.EXTRA_INSTALLER_PACKAGE_NAME);
        if (installerPackageNameFromIntent != null) {
@@ -126,7 +139,7 @@ public class InstallStart extends Activity {
            if (!TextUtils.equals(installerPackageNameFromIntent, callingPkgName)
                    && mPackageManager.checkPermission(Manifest.permission.INSTALL_PACKAGES,
                    callingPkgName) != PackageManager.PERMISSION_GRANTED) {
                Log.e(LOG_TAG, "The given installer package name " + installerPackageNameFromIntent
                Log.e(TAG, "The given installer package name " + installerPackageNameFromIntent
                        + " is invalid. Remove it.");
                EventLog.writeEvent(0x534e4554, "236687884", getLaunchedFromUid(),
                        "Invalid EXTRA_INSTALLER_PACKAGE_NAME");
@@ -270,4 +283,97 @@ public class InstallStart extends Activity {
        int installerUid = packageInstaller.getSessionInfo(sessionId).getInstallerUid();
        return (originatingUid == Process.ROOT_UID) || (originatingUid == installerUid);
    }

    private void checkDevicePolicyRestriction() {
        // Check for install apps user restriction first.
        final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
                UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
        if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
            if (mLocalLOGV) Log.i(TAG, "install not allowed: " + UserManager.DISALLOW_INSTALL_APPS);
            mAbortInstall = true;
            showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
            return;
        } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
            if (mLocalLOGV) {
                Log.i(TAG, "install not allowed by admin; showing "
                        + Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
            }
            mAbortInstall = true;
            startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
            return;
        }

        final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
        final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource(
                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle());
        final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM
                & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);
        if (systemRestriction != 0) {
            if (mLocalLOGV) Log.i(TAG, "Showing DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER");
            mAbortInstall = true;
            showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
        } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
            mAbortInstall = true;
            startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
        } else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
            mAbortInstall = true;
            startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
        }
    }

    /**
     * Replace any dialog shown by the dialog with the one for the given {@link #createDialog(int)}.
     *
     * @param id The dialog type to add
     */
    private void showDialogInner(int id) {
        if (mLocalLOGV) Log.i(TAG, "showDialogInner(" + id + ")");
        DialogFragment currentDialog =
                (DialogFragment) getFragmentManager().findFragmentByTag("dialog");
        if (currentDialog != null) {
            currentDialog.dismissAllowingStateLoss();
        }

        DialogFragment newDialog = createDialog(id);
        if (newDialog != null) {
            getFragmentManager().beginTransaction()
                    .add(newDialog, "dialog").commitAllowingStateLoss();
        }
    }

    /**
     * Create a new dialog.
     *
     * @param id The id of the dialog (determines dialog type)
     *
     * @return The dialog
     */
    private DialogFragment createDialog(int id) {
        if (mLocalLOGV) Log.i(TAG, "createDialog(" + id + ")");
        switch (id) {
            case DLG_INSTALL_APPS_RESTRICTED_FOR_USER:
                return PackageUtil.SimpleErrorDialog.newInstance(
                        R.string.install_apps_user_restriction_dlg_text);
            case DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER:
                return PackageUtil.SimpleErrorDialog.newInstance(
                        R.string.unknown_apps_user_restriction_dlg_text);
        }
        return null;
    }

    private void startAdminSupportDetailsActivity(String restriction) {
        if (mLocalLOGV) Log.i(TAG, "startAdminSupportDetailsActivity(): " + restriction);

        // If the given restriction is set by an admin, display information about the
        // admin enforcing the restriction for the affected user.
        final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
        final Intent showAdminSupportDetailsIntent = dpm.createAdminSupportIntent(restriction);
        if (showAdminSupportDetailsIntent != null) {
            if (mLocalLOGV) Log.i(TAG, "starting " + showAdminSupportDetailsIntent);
            startActivity(showAdminSupportDetailsIntent);
        } else {
            if (mLocalLOGV) Log.w(TAG, "not intent for " + restriction);
        }
    }
}
+8 −102
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import android.app.AlertDialog;
import android.app.AppOpsManager;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.admin.DevicePolicyManager;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
@@ -56,7 +55,6 @@ import android.widget.Button;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.StringRes;

import java.io.File;
import java.util.ArrayList;
@@ -123,13 +121,11 @@ public class PackageInstallerActivity extends AlertActivity {

    // Dialog identifiers used in showDialog
    private static final int DLG_BASE = 0;
    private static final int DLG_PACKAGE_ERROR = DLG_BASE + 2;
    private static final int DLG_OUT_OF_SPACE = DLG_BASE + 3;
    private static final int DLG_INSTALL_ERROR = DLG_BASE + 4;
    private static final int DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER = DLG_BASE + 5;
    private static final int DLG_ANONYMOUS_SOURCE = DLG_BASE + 6;
    private static final int DLG_EXTERNAL_SOURCE_BLOCKED = DLG_BASE + 7;
    private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = DLG_BASE + 8;
    private static final int DLG_PACKAGE_ERROR = DLG_BASE + 1;
    private static final int DLG_OUT_OF_SPACE = DLG_BASE + 2;
    private static final int DLG_INSTALL_ERROR = DLG_BASE + 3;
    private static final int DLG_ANONYMOUS_SOURCE = DLG_BASE + 4;
    private static final int DLG_EXTERNAL_SOURCE_BLOCKED = DLG_BASE + 5;

    // If unknown sources are temporary allowed
    private boolean mAllowUnknownSources;
@@ -218,19 +214,13 @@ public class PackageInstallerActivity extends AlertActivity {
        if (mLocalLOGV) Log.i(TAG, "createDialog(" + id + ")");
        switch (id) {
            case DLG_PACKAGE_ERROR:
                return SimpleErrorDialog.newInstance(R.string.Parse_error_dlg_text);
                return PackageUtil.SimpleErrorDialog.newInstance(R.string.Parse_error_dlg_text);
            case DLG_OUT_OF_SPACE:
                return OutOfSpaceDialog.newInstance(
                        mPm.getApplicationLabel(mPkgInfo.applicationInfo));
            case DLG_INSTALL_ERROR:
                return InstallErrorDialog.newInstance(
                        mPm.getApplicationLabel(mPkgInfo.applicationInfo));
            case DLG_INSTALL_APPS_RESTRICTED_FOR_USER:
                return SimpleErrorDialog.newInstance(
                        R.string.install_apps_user_restriction_dlg_text);
            case DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER:
                return SimpleErrorDialog.newInstance(
                        R.string.unknown_apps_user_restriction_dlg_text);
            case DLG_EXTERNAL_SOURCE_BLOCKED:
                return ExternalSourcesBlockedDialog.newInstance(mOriginatingPackage);
            case DLG_ANONYMOUS_SOURCE:
@@ -524,68 +514,16 @@ public class PackageInstallerActivity extends AlertActivity {
    }

    /**
     * Check if it is allowed to install the package and initiate install if allowed. If not allowed
     * show the appropriate dialog.
     * Check if it is allowed to install the package and initiate install if allowed.
     */
    private void checkIfAllowedAndInitiateInstall() {
        // Check for install apps user restriction first.
        final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
                UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
        if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
            if (mLocalLOGV) Log.i(TAG, "install not allowed: " + UserManager.DISALLOW_INSTALL_APPS);
            showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
            return;
        } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
            if (mLocalLOGV) {
                Log.i(TAG, "install not allowed by admin; showing "
                        + Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
            }
            startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
            finish();
            return;
        }

        if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
            if (mLocalLOGV) Log.i(TAG, "install allowed");
            initiateInstall();
        } else {
            // Check for unknown sources restrictions.
            final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
                    UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
            final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource(
                    UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle());
            final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM
                    & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);
            if (systemRestriction != 0) {
                if (mLocalLOGV) Log.i(TAG, "Showing DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER");
                showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
            } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
                startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
            } else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
                startAdminSupportDetailsActivity(
                        UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
        } else {
            handleUnknownSources();
        }
    }
    }

    private void startAdminSupportDetailsActivity(String restriction) {
        if (mLocalLOGV) Log.i(TAG, "startAdminSupportDetailsActivity(): " + restriction);

        // If the given restriction is set by an admin, display information about the
        // admin enforcing the restriction for the affected user.
        final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
        final Intent showAdminSupportDetailsIntent = dpm.createAdminSupportIntent(restriction);
        if (showAdminSupportDetailsIntent != null) {
            if (mLocalLOGV) Log.i(TAG, "starting " + showAdminSupportDetailsIntent);
            startActivity(showAdminSupportDetailsIntent);
        } else {
            if (mLocalLOGV) Log.w(TAG, "not intent for " + restriction);
        }

        finish();
    }

    private void handleUnknownSources() {
        if (mOriginatingPackage == null) {
@@ -761,38 +699,6 @@ public class PackageInstallerActivity extends AlertActivity {
        finish();
    }

    /**
     * A simple error dialog showing a message
     */
    public static class SimpleErrorDialog extends DialogFragment {
        private static final String MESSAGE_KEY =
                SimpleErrorDialog.class.getName() + "MESSAGE_KEY";

        static SimpleErrorDialog newInstance(@StringRes int message) {
            SimpleErrorDialog dialog = new SimpleErrorDialog();

            Bundle args = new Bundle();
            args.putInt(MESSAGE_KEY, message);
            dialog.setArguments(args);

            return dialog;
        }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return new AlertDialog.Builder(getActivity())
                    .setMessage(getArguments().getInt(MESSAGE_KEY))
                    .setPositiveButton(R.string.ok, (dialog, which) -> getActivity().finish())
                    .create();
        }

        @Override
        public void onCancel(DialogInterface dialog) {
            getActivity().setResult(Activity.RESULT_CANCELED);
            getActivity().finish();
        }
    }

    /**
     * Dialog to show when the source of apk can not be identified
     */
+38 −0
Original line number Diff line number Diff line
@@ -18,12 +18,17 @@
package com.android.packageinstaller;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
import android.view.View;
@@ -32,6 +37,7 @@ import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;

import java.io.Closeable;
import java.io.File;
@@ -207,4 +213,36 @@ public class PackageUtil {
            }
        }
    }

    /**
     * A simple error dialog showing a message
     */
    public static class SimpleErrorDialog extends DialogFragment {
        private static final String MESSAGE_KEY =
                SimpleErrorDialog.class.getName() + "MESSAGE_KEY";

        static SimpleErrorDialog newInstance(@StringRes int message) {
            SimpleErrorDialog dialog = new SimpleErrorDialog();

            Bundle args = new Bundle();
            args.putInt(MESSAGE_KEY, message);
            dialog.setArguments(args);

            return dialog;
        }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return new AlertDialog.Builder(getActivity())
                    .setMessage(getArguments().getInt(MESSAGE_KEY))
                    .setPositiveButton(R.string.ok, (dialog, which) -> getActivity().finish())
                    .create();
        }

        @Override
        public void onCancel(DialogInterface dialog) {
            getActivity().setResult(Activity.RESULT_CANCELED);
            getActivity().finish();
        }
    }
}