Fix confused deputy vulnerability in termsActivity to access terms_disclaimer Uri only if the calling app has the permissions 1-P doc at: go/termsDisclaimerVulnerability Before: https://hsv.googleplex.com/5163551739084800 After: https://hsv.googleplex.com/5207829722955776 https://paste.googleplex.com/5643054726512640 Flag: EXEMPT bug fix Bug: 299928772 Test: Manual using test app provided by the reporter (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5d67902f6a7498d016ee588d6c00710fb2d3ab98) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:68ff8bc959c104f1803e8b5e04ee180aaa542992) Merged-In: I4c5ab64cb770c61db1cedc5169a4b8cdf0a4b0bd Change-Id: I4c5ab64cb770c61db1cedc5169a4b8cdf0a4b0bd
diff --git a/src/com/android/managedprovisioning/parser/DisclaimersParserImpl.java b/src/com/android/managedprovisioning/parser/DisclaimersParserImpl.java index fd20705..542bd7c 100644 --- a/src/com/android/managedprovisioning/parser/DisclaimersParserImpl.java +++ b/src/com/android/managedprovisioning/parser/DisclaimersParserImpl.java
@@ -20,10 +20,13 @@ import static com.android.managedprovisioning.common.StoreUtils.DIR_PROVISIONING_PARAMS_FILE_CACHE; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import androidx.annotation.Nullable; +import android.os.Binder; import android.text.TextUtils; import com.android.managedprovisioning.common.ProvisionLogger; @@ -33,6 +36,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Parser for {@link EXTRA_PROVISIONING_DISCLAIMERS} into {@link DisclaimersParam} @@ -40,6 +44,9 @@ */ public class DisclaimersParserImpl implements DisclaimerParser { private static final int MAX_LENGTH = 3; + private static final String SCHEME_ANDROID_RESOURCE = "android.resource"; + private static final String SCHEME_CONTENT = "content"; + private final Context mContext; private final long mProvisioningId; @@ -75,7 +82,17 @@ ProvisionLogger.logw("Null disclaimer content uri in " + i + " element"); continue; } - + try { + validateUriSchemeAndPermission(uri); + } catch (SecurityException e) { + ProvisionLogger.loge( + "Skipping disclaimer in " + + i + + " element due to URI validation failure: " + + e.getMessage(), + e); + continue; + } File disclaimerFile = saveDisclaimerContentIntoFile(uri, i); if (disclaimerFile == null) { @@ -90,6 +107,41 @@ } /** + * Validates a {@link Uri} extra pointing to disclaimer content. + * + * <p>It checks that the URI scheme is one of {@code content} ({@link + * android.content.ContentResolver#SCHEME_CONTENT}) or {@code + * android.resource} ({@link + * android.content.ContentResolver#SCHEME_ANDROID_RESOURCE}). If a {@code + * content:} URI is passed, it also checks that the caller has grant read + * permission ({@link Intent#FLAG_GRANT_READ_URI_PERMISSION}). + * + * @throws SecurityException if the URI scheme is invalid or the caller + * does not have permission to access the URI. + */ + private void validateUriSchemeAndPermission(Uri uri) throws SecurityException { + ProvisionLogger.logd("validateUriSchemeAndPermission: " + uri); + String scheme = uri.getScheme(); + if (!Objects.equals(scheme, SCHEME_ANDROID_RESOURCE) + && !Objects.equals(scheme, SCHEME_CONTENT)) { + String errorMessage = "Invalid URI scheme: " + scheme; + throw new SecurityException(errorMessage); + } + int permissionCheck = + mContext.checkUriPermission( + uri, + Binder.getCallingPid(), + Binder.getCallingUid(), + Intent.FLAG_GRANT_READ_URI_PERMISSION); + + if (permissionCheck != PackageManager.PERMISSION_GRANTED) { + String errorMessage = "Caller does not have permission to access" + + " disclaimer URI: " + uri; + throw new SecurityException(errorMessage); + } + } + + /** * @return {@link File} if the uri content is saved into the file successfully. Otherwise, * return null. */