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

Commit 69df9632 authored by shafik's avatar shafik
Browse files

Mitigate Intent Capturing vulnerability

ResolverActivity now identifies that the intent is a browsable intent,
and thus omits the Always button and replaces it with a settings button
tha can be used to configure the user's wanted behaviour.
Also prints out a message explaining that the user is giving the app
an access to open URLs from a specific host.

Bug: 116610086
Test: manually tested on device (Pixel 2XL) - will add unit test to
document behaviour

Change-Id: I81988b9a4d082bc1e6491186d39532fd283f2ede
parent 938e0f56
Loading
Loading
Loading
Loading
+96 −22
Original line number Diff line number Diff line
@@ -101,6 +101,7 @@ public class ResolverActivity extends Activity {
    private AbsListView mAdapterView;
    private Button mAlwaysButton;
    private Button mOnceButton;
    private Button mSettingsButton;
    private View mProfileView;
    private int mIconDpi;
    private int mLastSelected = AbsListView.INVALID_POSITION;
@@ -112,6 +113,7 @@ public class ResolverActivity extends Activity {
    private String mReferrerPackage;
    private CharSequence mTitle;
    private int mDefaultTitleResId;
    private boolean mUseLayoutForBrowsables;

    // Whether or not this activity supports choosing a default handler for the intent.
    private boolean mSupportsAlwaysUseOption;
@@ -192,6 +194,12 @@ public class ResolverActivity extends Activity {
                com.android.internal.R.string.whichHomeApplicationNamed,
                com.android.internal.R.string.whichHomeApplicationLabel);

        // SpR.id.buttonecial titles for BROWSABLE components
        public static final int BROWSABLE_TITLE_RES =
                com.android.internal.R.string.whichGiveAccessToApplication;
        public static final int BROWSABLE_NAMED_TITLE_RES =
                com.android.internal.R.string.whichGiveAccessToApplicationNamed;

        public final String action;
        public final int titleRes;
        public final int namedTitleRes;
@@ -283,7 +291,6 @@ public class ResolverActivity extends Activity {
        mPackageMonitor.register(this, getMainLooper(), false);
        mRegistered = true;
        mReferrerPackage = getReferrerPackageName();
        mSupportsAlwaysUseOption = supportsAlwaysUseOption;

        final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
        mIconDpi = am.getLauncherLargeIconDensity();
@@ -293,6 +300,14 @@ public class ResolverActivity extends Activity {
        mTitle = title;
        mDefaultTitleResId = defaultTitleRes;

        mUseLayoutForBrowsables = getTargetIntent() == null
                ? false
                : getTargetIntent().hasCategory(Intent.CATEGORY_BROWSABLE);

        // We don't want to support Always Use if browsable layout is being used,
        // as to mitigate Intent Capturing vulnerability
        mSupportsAlwaysUseOption = supportsAlwaysUseOption && !mUseLayoutForBrowsables;

        mIconFactory = IconDrawableFactory.newInstance(this, true);
        if (configureContentView(mIntents, initialIntents, rList)) {
            return;
@@ -448,13 +463,23 @@ public class ResolverActivity extends Activity {
        mSafeForwardingMode = safeForwarding;
    }

    protected CharSequence getTitleForAction(String action, int defaultTitleRes) {
        final ActionTitle title = mResolvingHome ? ActionTitle.HOME : ActionTitle.forAction(action);
    protected CharSequence getTitleForAction(Intent intent, int defaultTitleRes) {
        final ActionTitle title = mResolvingHome
                ? ActionTitle.HOME
                : ActionTitle.forAction(intent.getAction());

        // While there may already be a filtered item, we can only use it in the title if the list
        // is already sorted and all information relevant to it is already in the list.
        final boolean named = mAdapter.getFilteredPosition() >= 0;
        if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
            return getString(defaultTitleRes);
        } else if (intent.hasCategory(Intent.CATEGORY_BROWSABLE)) {
            // If the Intent is BROWSABLE then we need to warn the user that
            // they're giving access for the activity to open URLs from this specific host
            return named
                    ? getString(ActionTitle.BROWSABLE_NAMED_TITLE_RES, intent.getData().getHost(),
                    mAdapter.getFilteredItem().getDisplayLabel())
                    : getString(ActionTitle.BROWSABLE_TITLE_RES, intent.getData().getHost());
        } else {
            return named
                    ? getString(title.namedTitleRes, mAdapter.getFilteredItem().getDisplayLabel())
@@ -555,7 +580,7 @@ public class ResolverActivity extends Activity {
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        resetAlwaysOrOnceButtonBar();
        resetButtonBar();
    }

    private boolean hasManagedProfile() {
@@ -607,11 +632,23 @@ public class ResolverActivity extends Activity {

    public void onButtonClick(View v) {
        final int id = v.getId();
        startSelected(mAdapter.hasFilteredItem() ?
                        mAdapter.getFilteredPosition():
                        mAdapterView.getCheckedItemPosition(),
                id == R.id.button_always,
                !mAdapter.hasFilteredItem());
        int which = mAdapter.hasFilteredItem()
                ? mAdapter.getFilteredPosition()
                : mAdapterView.getCheckedItemPosition();
        boolean hasIndexBeenFiltered = !mAdapter.hasFilteredItem();
        if (id == R.id.button_app_settings) {
            showSettingsForSelected(which, hasIndexBeenFiltered);
        } else {
            startSelected(which, id == R.id.button_always, hasIndexBeenFiltered);
        }
    }

    private void showSettingsForSelected(int which, boolean hasIndexBeenFiltered) {
        ResolveInfo ri = mAdapter.resolveInfoForPosition(which, hasIndexBeenFiltered);
        Intent in = new Intent().setAction(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS)
                .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
                .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
        startActivity(in);
    }

    public void startSelected(int which, boolean always, boolean hasIndexBeenFiltered) {
@@ -995,7 +1032,7 @@ public class ResolverActivity extends Activity {
        adapterView.setOnItemClickListener(listener);
        adapterView.setOnItemLongClickListener(listener);

        if (mSupportsAlwaysUseOption) {
        if (mSupportsAlwaysUseOption || mUseLayoutForBrowsables) {
            listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
        }

@@ -1017,7 +1054,7 @@ public class ResolverActivity extends Activity {

        CharSequence title = mTitle != null
                ? mTitle
                : getTitleForAction(getTargetIntent().getAction(), mDefaultTitleResId);
                : getTitleForAction(getTargetIntent(), mDefaultTitleResId);

        if (!TextUtils.isEmpty(title)) {
            final TextView titleView = findViewById(R.id.title);
@@ -1051,18 +1088,49 @@ public class ResolverActivity extends Activity {
        }
    }

    public void resetAlwaysOrOnceButtonBar() {
        if (mSupportsAlwaysUseOption) {
    private void resetButtonBar() {
        if (!mSupportsAlwaysUseOption && !mUseLayoutForBrowsables) {
            return;
        }
        final ViewGroup buttonLayout = findViewById(R.id.button_bar);
        if (buttonLayout != null) {
            buttonLayout.setVisibility(View.VISIBLE);
                mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
            mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
            mSettingsButton = (Button) buttonLayout.findViewById(R.id.button_app_settings);
            mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);

            if (mUseLayoutForBrowsables) {
                resetSettingsOrOnceButtonBar();
            } else {
                resetAlwaysOrOnceButtonBar();
            }
        } else {
            Log.e(TAG, "Layout unexpectedly does not have a button bar");
        }
    }

    private void resetSettingsOrOnceButtonBar() {
        //unsetting always button
        mAlwaysButton.setVisibility(View.GONE);

        // When the items load in, if an item was already selected,
        // enable the buttons
        if (mAdapterView != null
                && mAdapterView.getCheckedItemPosition() != ListView.INVALID_POSITION) {
            mSettingsButton.setEnabled(true);
            mOnceButton.setEnabled(true);
        }
    }

    private void resetAlwaysOrOnceButtonBar() {
        // This check needs to be made because layout with default
        // doesn't have a settings button
        if (mSettingsButton != null) {
            //unsetting always button
            mSettingsButton.setVisibility(View.GONE);
            mSettingsButton = null;
        }

        if (useLayoutWithDefault()
                && mAdapter.getFilteredPosition() != ListView.INVALID_POSITION) {
            setAlwaysButtonEnabled(true, mAdapter.getFilteredPosition(), false);
@@ -1625,7 +1693,7 @@ public class ResolverActivity extends Activity {
                    @Override
                    public void run() {
                        setTitleAndIcon();
                        resetAlwaysOrOnceButtonBar();
                        resetButtonBar();
                        onListRebuilt();
                        mPostListReadyRunnable = null;
                    }
@@ -1986,8 +2054,14 @@ public class ResolverActivity extends Activity {
            final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
            if (!useLayoutWithDefault()
                    && (!hasValidSelection || mLastSelected != checkedPos)
                    && mAlwaysButton != null) {
                    && (mAlwaysButton != null || mSettingsButton != null)) {
                if (mSettingsButton != null) {
                    // this implies that the layout for browsables is being used
                    mSettingsButton.setEnabled(true);
                } else {
                    // this implies that mAlwaysButton != null
                    setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
                }
                mOnceButton.setEnabled(hasValidSelection);
                if (hasValidSelection) {
                    mAdapterView.smoothScrollToPosition(checkedPos);
+12 −0
Original line number Diff line number Diff line
@@ -129,6 +129,18 @@
            android:enabled="false"
            android:text="@string/activity_resolver_use_always"
            android:onClick="onButtonClick" />

        <Button
            android:id="@+id/button_app_settings"
            android:layout_width="wrap_content"
            android:layout_gravity="end"
            android:maxLines="2"
            android:minHeight="@dimen/alert_dialog_button_bar_height"
            style="?attr/buttonBarPositiveButtonStyle"
            android:layout_height="wrap_content"
            android:enabled="false"
            android:text="@string/activity_resolver_app_settings"
            android:onClick="onButtonClick" />
    </LinearLayout>

</com.android.internal.widget.ResolverDrawerLayout>
+12 −0
Original line number Diff line number Diff line
@@ -3066,6 +3066,14 @@
    <string name="whichViewApplicationNamed">Open with %1$s</string>
    <!-- Label for a link to a intent resolver dialog to view something -->
    <string name="whichViewApplicationLabel">Open</string>
    <!-- Title of intent resolver dialog when selecting a viewer application that opens URI
         [CHAR LIMIT=128]. -->
    <string name="whichGiveAccessToApplication">Give access to open <xliff:g id="host" example="mail.google.com">%1$s</xliff:g> links with</string>
    <!-- Title of intent resolver dialog when selecting a viewer application that opens URI
         and a previously used application is known [CHAR LIMIT=128]. -->
    <string name="whichGiveAccessToApplicationNamed">Give access to open <xliff:g id="host" example="mail.google.com">%1$s</xliff:g> links with <xliff:g id="application" example="Gmail">%2$s</xliff:g></string>
    <!-- Label for a link to an intent resolver dialog to open URI [CHAR LIMIT=16] -->
    <string name="whichGiveAccessToApplicationLabel">Give access</string>
    <!-- Title of intent resolver dialog when selecting an editor application to run. -->
    <string name="whichEditApplication">Edit with</string>
    <!-- Title of intent resolver dialog when selecting an editor application to run
@@ -4156,6 +4164,10 @@
         from the activity resolver to use just this once. [CHAR LIMIT=25] -->
    <string name="activity_resolver_use_once">Just once</string>

    <!-- Title for a button to choose to go to
          'Open by Default' app settings. [CHAR LIMIT=25] -->
    <string name="activity_resolver_app_settings">Settings</string>

    <!-- Text for the toast that is shown when the user clicks on a launcher that
         doesn't support the work profile. [CHAR LIMIT=100] -->
    <string name="activity_resolver_work_profiles_support">%1$s doesn\'t support work profile</string>
+5 −0
Original line number Diff line number Diff line
@@ -2153,6 +2153,7 @@
  <java-symbol type="id" name="resolver_list" />
  <java-symbol type="id" name="button_once" />
  <java-symbol type="id" name="button_always" />
  <java-symbol type="id" name="button_app_settings" />
  <java-symbol type="integer" name="config_globalActionsKeyTimeout" />
  <java-symbol type="integer" name="config_maxResolverActivityColumns" />
  <java-symbol type="array" name="config_notificationSignalExtractors" />
@@ -2508,11 +2509,15 @@
  <java-symbol type="bool" name="config_use_voip_mode_for_ims" />
  <java-symbol type="attr" name="touchscreenBlocksFocus" />
  <java-symbol type="layout" name="resolver_list_with_default" />
  <java-symbol type="string" name="activity_resolver_app_settings" />
  <java-symbol type="string" name="whichApplicationNamed" />
  <java-symbol type="string" name="whichApplicationLabel" />
  <java-symbol type="string" name="whichViewApplication" />
  <java-symbol type="string" name="whichViewApplicationNamed" />
  <java-symbol type="string" name="whichViewApplicationLabel" />
  <java-symbol type="string" name="whichGiveAccessToApplication" />
  <java-symbol type="string" name="whichGiveAccessToApplicationNamed" />
  <java-symbol type="string" name="whichGiveAccessToApplicationLabel" />
  <java-symbol type="string" name="whichEditApplication" />
  <java-symbol type="string" name="whichEditApplicationNamed" />
  <java-symbol type="string" name="whichEditApplicationLabel" />