| Index: content/public/android/java/src/org/chromium/content/browser/installedapp/InstalledAppProviderImpl.java
|
| diff --git a/content/public/android/java/src/org/chromium/content/browser/installedapp/InstalledAppProviderImpl.java b/content/public/android/java/src/org/chromium/content/browser/installedapp/InstalledAppProviderImpl.java
|
| index 6258aa3baeb8cfb5bb4d5209238e6069d9b29c28..fd2f72def84df73ba35a93089f576c6b74df99f9 100644
|
| --- a/content/public/android/java/src/org/chromium/content/browser/installedapp/InstalledAppProviderImpl.java
|
| +++ b/content/public/android/java/src/org/chromium/content/browser/installedapp/InstalledAppProviderImpl.java
|
| @@ -10,12 +10,14 @@ import android.content.pm.PackageManager;
|
| import android.content.pm.PackageManager.NameNotFoundException;
|
| import android.content.res.Resources;
|
| import android.os.AsyncTask;
|
| +import android.util.Pair;
|
|
|
| import org.json.JSONArray;
|
| import org.json.JSONException;
|
| import org.json.JSONObject;
|
|
|
| import org.chromium.base.Log;
|
| +import org.chromium.base.ThreadUtils;
|
| import org.chromium.base.VisibleForTesting;
|
| import org.chromium.installedapp.mojom.InstalledAppProvider;
|
| import org.chromium.installedapp.mojom.RelatedApplication;
|
| @@ -79,15 +81,24 @@ public class InstalledAppProviderImpl implements InstalledAppProvider {
|
|
|
| // Use an AsyncTask to execute the installed/related checks on a background thread (so as
|
| // not to block the UI thread).
|
| - new AsyncTask<Void, Void, RelatedApplication[]>() {
|
| + new AsyncTask<Void, Void, Pair<RelatedApplication[], Integer>>() {
|
| @Override
|
| - protected RelatedApplication[] doInBackground(Void... unused) {
|
| + protected Pair<RelatedApplication[], Integer> doInBackground(Void... unused) {
|
| return filterInstalledAppsOnBackgroundThread(relatedApps, frameUrl);
|
| }
|
|
|
| @Override
|
| - protected void onPostExecute(RelatedApplication[] installedApps) {
|
| - callback.call(installedApps);
|
| + protected void onPostExecute(Pair<RelatedApplication[], Integer> result) {
|
| + final RelatedApplication[] installedApps = result.first;
|
| + int delayMillis = result.second;
|
| + // Before calling the callback, delay for the amount of time that has been
|
| + // calculated in |delayMillis|.
|
| + delayThenRun(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + callback.call(installedApps);
|
| + }
|
| + }, delayMillis);
|
| }
|
| }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
| }
|
| @@ -105,11 +116,14 @@ public class InstalledAppProviderImpl implements InstalledAppProvider {
|
| *
|
| * @param relatedApps A list of applications to be filtered.
|
| * @param frameUrl The URL of the frame this operation was called from.
|
| - * @return A subsequence of applications that meet the criteria.
|
| + * @return Pair of: A subsequence of applications that meet the criteria, and, the total amount
|
| + * of time in ms that should be delayed before returning to the user, to mask the
|
| + * installed state of the requested apps.
|
| */
|
| - private RelatedApplication[] filterInstalledAppsOnBackgroundThread(
|
| + private Pair<RelatedApplication[], Integer> filterInstalledAppsOnBackgroundThread(
|
| RelatedApplication[] relatedApps, URI frameUrl) {
|
| ArrayList<RelatedApplication> installedApps = new ArrayList<RelatedApplication>();
|
| + int delayMillis = 0;
|
| PackageManager pm = mContext.getPackageManager();
|
| for (RelatedApplication app : relatedApps) {
|
| // If the package is of type "play", it is installed, and the origin is associated with
|
| @@ -118,15 +132,34 @@ public class InstalledAppProviderImpl implements InstalledAppProvider {
|
| // between the app not being installed and the origin not being associated with the app
|
| // (otherwise, arbitrary websites would be able to test whether un-associated apps are
|
| // installed on the user's device).
|
| - if (app.platform.equals(RELATED_APP_PLATFORM_ANDROID) && app.id != null
|
| - && isAppInstalledAndAssociatedWithOrigin(app.id, frameUrl, pm)) {
|
| - installedApps.add(app);
|
| + if (app.platform.equals(RELATED_APP_PLATFORM_ANDROID) && app.id != null) {
|
| + delayMillis += calculateDelayForPackageMs(app.id);
|
| + if (isAppInstalledAndAssociatedWithOrigin(app.id, frameUrl, pm)) {
|
| + installedApps.add(app);
|
| + }
|
| }
|
| }
|
|
|
| RelatedApplication[] installedAppsArray = new RelatedApplication[installedApps.size()];
|
| installedApps.toArray(installedAppsArray);
|
| - return installedAppsArray;
|
| + return Pair.create(installedAppsArray, delayMillis);
|
| + }
|
| +
|
| + /**
|
| + * Determines how long to artifically delay for, for a particular package name.
|
| + */
|
| + private int calculateDelayForPackageMs(String packageName) {
|
| + // Important timing-attack prevention measure: delay by a pseudo-random amount of time, to
|
| + // add significant noise to the time taken to check whether this app is installed and
|
| + // related. Otherwise, it would be possible to tell whether a non-related app is installed,
|
| + // based on the time this operation takes.
|
| + //
|
| + // Generate a 16-bit hash based on a unique device ID + the package name.
|
| + short hash = PackageHash.hashForPackage(packageName);
|
| +
|
| + // The time delay is the low 10 bits of the hash in 100ths of a ms (between 0 and 10ms).
|
| + int delayHundredthsOfMs = hash & 0x3ff;
|
| + return delayHundredthsOfMs / 100;
|
| }
|
|
|
| /**
|
| @@ -137,7 +170,7 @@ public class InstalledAppProviderImpl implements InstalledAppProvider {
|
| * @param frameUrl Returns false if the Android package does not declare association with the
|
| * origin of this URL. Can be null.
|
| */
|
| - private static boolean isAppInstalledAndAssociatedWithOrigin(
|
| + private boolean isAppInstalledAndAssociatedWithOrigin(
|
| String packageName, URI frameUrl, PackageManager pm) {
|
| if (frameUrl == null) return false;
|
|
|
| @@ -275,4 +308,17 @@ public class InstalledAppProviderImpl implements InstalledAppProvider {
|
| return assetUrl.getScheme().equals(frameUrl.getScheme())
|
| && assetUrl.getAuthority().equals(frameUrl.getAuthority());
|
| }
|
| +
|
| + /**
|
| + * Runs a Runnable task after a given delay.
|
| + *
|
| + * Protected and non-static for testing.
|
| + *
|
| + * @param r The Runnable that will be executed.
|
| + * @param delayMillis The delay (in ms) until the Runnable will be executed.
|
| + * @return True if the Runnable was successfully placed into the message queue.
|
| + */
|
| + protected void delayThenRun(Runnable r, long delayMillis) {
|
| + ThreadUtils.postOnUiThreadDelayed(r, delayMillis);
|
| + }
|
| }
|
|
|