diff options
| author | 2024-09-13 16:00:36 +0000 | |
|---|---|---|
| committer | 2024-09-13 16:00:36 +0000 | |
| commit | e32aa3fd1266f265ef5fa08ec89e4865c09b86ad (patch) | |
| tree | ccdae43d020786193d3ccd9c13afbc576a449d43 | |
| parent | 83ba985faed6ca3caad8be4bfdac84b80f8137f7 (diff) | |
| parent | 5029ea510d6d09ff972197616105fb2746e91409 (diff) | |
Merge "Implement logging for ContentOrFileUriEventReported" into main
3 files changed, 188 insertions, 36 deletions
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java index 195e91cf5716..49825f16ca94 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java @@ -64,13 +64,36 @@ public interface UriGrantsManagerInternal { String targetPkg, int targetUserId); /** - * Same as {@link #checkGrantUriPermissionFromIntent(Intent, int, String, int)}, but with an - * extra parameter {@code requireContentUriPermissionFromCaller}, which is the value from {@link - * android.R.attr#requireContentUriPermissionFromCaller} attribute. + * Same as {@link #checkGrantUriPermissionFromIntent(Intent, int, String, int)}, but with: + * - {@code requireContentUriPermissionFromCaller}, which is the value from {@link + * android.R.attr#requireContentUriPermissionFromCaller} attribute. + * - {@code requestHashCode}, which is required to differentiate activity launches for logging + * ContentOrFileUriEventReported message. */ NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid, String targetPkg, int targetUserId, - @RequiredContentUriPermission int requireContentUriPermissionFromCaller); + @RequiredContentUriPermission int requireContentUriPermissionFromCaller, + int requestHashCode); + + /** + * Notify that an activity launch request has been completed and perform the following actions: + * - If the activity launch was unsuccessful, then clean up all the collected the content URIs + * that were passed during that launch. + * - If the activity launch was successful, then log cog content URIs that were passed during + * that launch. Specifically: + * - The caller didn't have read permission to them. + * - The activity's {@link android.R.attr#requireContentUriPermissionFromCaller} was set to + * "none". + * + * <p>Note that: + * - The API has to be called after + * {@link #checkGrantUriPermissionFromIntent(Intent, int, String, int, int, int)} was called. + * - The API is not idempotent, i.e. content URIs may be logged only once because the API clears + * the content URIs after logging. + */ + void notifyActivityLaunchRequestCompleted(int requestHashCode, boolean isSuccessfulLaunch, + String intentAction, int callingUid, String callingActivityName, int calleeUid, + String calleeActivityName, boolean isStartActivityForResult); /** * Extend a previously calculated set of permissions grants to the given diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index a581b083f645..3479b6c926da 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -24,6 +24,7 @@ import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION; import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; import static android.content.pm.ActivityInfo.CONTENT_URI_PERMISSION_NONE; +import static android.content.pm.ActivityInfo.CONTENT_URI_PERMISSION_READ; import static android.content.pm.ActivityInfo.CONTENT_URI_PERMISSION_READ_OR_WRITE; import static android.content.pm.ActivityInfo.isRequiredContentUriPermissionRead; import static android.content.pm.ActivityInfo.isRequiredContentUriPermissionWrite; @@ -39,6 +40,8 @@ import static android.os.Process.SYSTEM_UID; import static android.os.Process.myUid; import static com.android.internal.util.XmlUtils.writeBooleanAttribute; +import static com.android.internal.util.FrameworkStatsLog.CONTENT_OR_FILE_URI_EVENT_REPORTED; +import static com.android.internal.util.FrameworkStatsLog.CONTENT_OR_FILE_URI_EVENT_REPORTED__EVENT_TYPE__CONTENT_URI_WITHOUT_CALLER_READ_PERMISSION; import static com.android.server.uri.UriGrantsManagerService.H.PERSIST_URI_GRANTS_MSG; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; @@ -78,6 +81,7 @@ import android.os.UserHandle; import android.provider.Downloads; import android.text.format.DateUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; @@ -86,6 +90,7 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.Preconditions; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -153,6 +158,22 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements private final SparseArray<ArrayMap<GrantUri, UriPermission>> mGrantedUriPermissions = new SparseArray<>(); + /** + * Global map of activity launches to sets of passed content URIs. Specifically: + * - The caller didn't have read permission to them. + * - The callee activity's {@link android.R.attr#requireContentUriPermissionFromCaller} was set + * to "none". + * + * <p>This map is used for logging the ContentOrFileUriEventReported message. + * + * <p>The launch id is the ActivityStarter.Request#hashCode and has to be received from + * ActivityStarter to {@link #checkGrantUriPermissionFromIntentUnlocked(int, String, Intent, + * int, NeededUriGrants, int, Integer, Integer)}. + */ + @GuardedBy("mLaunchToContentUrisWithoutCallerReadPermission") + private final SparseArray<ArraySet<Uri>> mLaunchToContentUrisWithoutCallerReadPermission = + new SparseArray<>(); + private UriGrantsManagerService() { this(SystemServiceManager.ensureSystemDir(), "uri-grants"); } @@ -613,7 +634,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements /** Like checkGrantUriPermission, but takes an Intent. */ private NeededUriGrants checkGrantUriPermissionFromIntentUnlocked(int callingUid, String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId, - @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller) { + @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller, + Integer requestHashCode) { if (DEBUG) Slog.v(TAG, "Checking URI perm to data=" + (intent != null ? intent.getData() : null) + " clip=" + (intent != null ? intent.getClipData() : null) @@ -635,8 +657,9 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements } if (android.security.Flags.contentUriPermissionApis()) { - enforceRequireContentUriPermissionFromCallerOnIntentExtraStream(intent, contentUserHint, - mode, callingUid, requireContentUriPermissionFromCaller); + enforceRequireContentUriPermissionFromCallerOnIntentExtraStreamUnlocked(intent, + contentUserHint, mode, callingUid, requireContentUriPermissionFromCaller, + requestHashCode); } Uri data = intent.getData(); @@ -660,8 +683,9 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements if (data != null) { GrantUri grantUri = GrantUri.resolve(contentUserHint, data, mode); if (android.security.Flags.contentUriPermissionApis()) { - enforceRequireContentUriPermissionFromCaller(requireContentUriPermissionFromCaller, - grantUri, callingUid); + enforceRequireContentUriPermissionFromCallerUnlocked( + requireContentUriPermissionFromCaller, grantUri, callingUid, + requestHashCode); } targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg, grantUri, mode, targetUid); @@ -678,8 +702,9 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements if (uri != null) { GrantUri grantUri = GrantUri.resolve(contentUserHint, uri, mode); if (android.security.Flags.contentUriPermissionApis()) { - enforceRequireContentUriPermissionFromCaller( - requireContentUriPermissionFromCaller, grantUri, callingUid); + enforceRequireContentUriPermissionFromCallerUnlocked( + requireContentUriPermissionFromCaller, grantUri, callingUid, + requestHashCode); } targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg, grantUri, mode, targetUid); @@ -694,7 +719,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements if (clipIntent != null) { NeededUriGrants newNeeded = checkGrantUriPermissionFromIntentUnlocked( callingUid, targetPkg, clipIntent, mode, needed, targetUserId, - requireContentUriPermissionFromCaller); + requireContentUriPermissionFromCaller, requestHashCode); if (newNeeded != null) { needed = newNeeded; } @@ -706,17 +731,32 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements return needed; } - private void enforceRequireContentUriPermissionFromCaller( + private void enforceRequireContentUriPermissionFromCallerUnlocked( @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller, - GrantUri grantUri, int uid) { - // Ignore if requireContentUriPermissionFromCaller hasn't been set or the URI is a + GrantUri grantUri, int callingUid, Integer requestHashCode) { + // Exit early if requireContentUriPermissionFromCaller hasn't been set or the URI is a // non-content URI. if (requireContentUriPermissionFromCaller == null || requireContentUriPermissionFromCaller == CONTENT_URI_PERMISSION_NONE || !ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) { + tryAddingContentUriWithoutCallerReadPermissionWhenAttributeIsNoneUnlocked( + requireContentUriPermissionFromCaller, grantUri, callingUid, requestHashCode); return; } + final boolean hasPermission = hasRequireContentUriPermissionFromCallerUnlocked( + requireContentUriPermissionFromCaller, grantUri, callingUid); + + if (!hasPermission) { + throw new SecurityException("You can't launch this activity because you don't have the" + + " required " + ActivityInfo.requiredContentUriPermissionToShortString( + requireContentUriPermissionFromCaller) + " access to " + grantUri.uri); + } + } + + private boolean hasRequireContentUriPermissionFromCallerUnlocked( + @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller, + GrantUri grantUri, int uid) { final boolean readMet = !isRequiredContentUriPermissionRead( requireContentUriPermissionFromCaller) || checkContentUriPermissionFullUnlocked(grantUri, uid, @@ -727,26 +767,48 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements || checkContentUriPermissionFullUnlocked(grantUri, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - boolean hasPermission = - requireContentUriPermissionFromCaller == CONTENT_URI_PERMISSION_READ_OR_WRITE - ? (readMet || writeMet) : (readMet && writeMet); + return requireContentUriPermissionFromCaller == CONTENT_URI_PERMISSION_READ_OR_WRITE + ? (readMet || writeMet) : (readMet && writeMet); + } - if (!hasPermission) { - throw new SecurityException("You can't launch this activity because you don't have the" - + " required " + ActivityInfo.requiredContentUriPermissionToShortString( - requireContentUriPermissionFromCaller) + " access to " + grantUri.uri); + private void tryAddingContentUriWithoutCallerReadPermissionWhenAttributeIsNoneUnlocked( + @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller, + GrantUri grantUri, int callingUid, Integer requestHashCode) { + // We're interested in requireContentUriPermissionFromCaller that is set to + // CONTENT_URI_PERMISSION_NONE and content URIs. Hence, ignore if + // requireContentUriPermissionFromCaller is not set to CONTENT_URI_PERMISSION_NONE or the + // URI is a non-content URI. + if (requireContentUriPermissionFromCaller == null + || requireContentUriPermissionFromCaller != CONTENT_URI_PERMISSION_NONE + || !ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme()) + || requestHashCode == null) { + return; + } + + if (!hasRequireContentUriPermissionFromCallerUnlocked(CONTENT_URI_PERMISSION_READ, grantUri, + callingUid)) { + synchronized (mLaunchToContentUrisWithoutCallerReadPermission) { + if (mLaunchToContentUrisWithoutCallerReadPermission.get(requestHashCode) == null) { + mLaunchToContentUrisWithoutCallerReadPermission + .put(requestHashCode, new ArraySet<>()); + } + mLaunchToContentUrisWithoutCallerReadPermission.get(requestHashCode) + .add(grantUri.uri); + } } } - private void enforceRequireContentUriPermissionFromCallerOnIntentExtraStream(Intent intent, - int contentUserHint, int mode, int callingUid, - @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller) { + private void enforceRequireContentUriPermissionFromCallerOnIntentExtraStreamUnlocked( + Intent intent, int contentUserHint, int mode, int callingUid, + @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller, + Integer requestHashCode) { try { final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri.class); if (uri != null) { final GrantUri grantUri = GrantUri.resolve(contentUserHint, uri, mode); - enforceRequireContentUriPermissionFromCaller( - requireContentUriPermissionFromCaller, grantUri, callingUid); + enforceRequireContentUriPermissionFromCallerUnlocked( + requireContentUriPermissionFromCaller, grantUri, callingUid, + requestHashCode); } } catch (BadParcelableException e) { Slog.w(TAG, "Failed to unparcel an URI in EXTRA_STREAM, skipping" @@ -759,8 +821,9 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements if (uris != null) { for (int i = uris.size() - 1; i >= 0; i--) { final GrantUri grantUri = GrantUri.resolve(contentUserHint, uris.get(i), mode); - enforceRequireContentUriPermissionFromCaller( - requireContentUriPermissionFromCaller, grantUri, callingUid); + enforceRequireContentUriPermissionFromCallerUnlocked( + requireContentUriPermissionFromCaller, grantUri, callingUid, + requestHashCode); } } } catch (BadParcelableException e) { @@ -769,6 +832,37 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements } } + private void notifyActivityLaunchRequestCompletedUnlocked(Integer requestHashCode, + boolean isSuccessfulLaunch, String intentAction, int callingUid, + String callingActivityName, int calleeUid, String calleeActivityName, + boolean isStartActivityForResult) { + ArraySet<Uri> contentUris; + synchronized (mLaunchToContentUrisWithoutCallerReadPermission) { + contentUris = mLaunchToContentUrisWithoutCallerReadPermission.get(requestHashCode); + mLaunchToContentUrisWithoutCallerReadPermission.remove(requestHashCode); + } + if (!isSuccessfulLaunch || contentUris == null) return; + + final String[] authorities = new String[contentUris.size()]; + final String[] schemes = new String[contentUris.size()]; + for (int i = contentUris.size() - 1; i >= 0; i--) { + Uri uri = contentUris.valueAt(i); + authorities[i] = uri.getAuthority(); + schemes[i] = uri.getScheme(); + } + FrameworkStatsLog.write(CONTENT_OR_FILE_URI_EVENT_REPORTED, + CONTENT_OR_FILE_URI_EVENT_REPORTED__EVENT_TYPE__CONTENT_URI_WITHOUT_CALLER_READ_PERMISSION, + intentAction, + callingUid, + callingActivityName, + calleeUid, + calleeActivityName, + isStartActivityForResult, + String.join(",", authorities), + String.join(",", schemes), + /* uri_mime_type */ null); + } + @GuardedBy("mLock") private void readGrantedUriPermissionsLocked() { if (DEBUG) Slog.v(TAG, "readGrantedUriPermissions()"); @@ -1645,23 +1739,36 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements public NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid, String targetPkg, int targetUserId) { return internalCheckGrantUriPermissionFromIntent(intent, callingUid, targetPkg, - targetUserId, /* requireContentUriPermissionFromCaller */ null); + targetUserId, /* requireContentUriPermissionFromCaller */ null, + /* requestHashCode */ null); } @Override public NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid, - String targetPkg, int targetUserId, int requireContentUriPermissionFromCaller) { + String targetPkg, int targetUserId, int requireContentUriPermissionFromCaller, + int requestHashCode) { return internalCheckGrantUriPermissionFromIntent(intent, callingUid, targetPkg, - targetUserId, requireContentUriPermissionFromCaller); + targetUserId, requireContentUriPermissionFromCaller, requestHashCode); + } + + @Override + public void notifyActivityLaunchRequestCompleted(int requestHashCode, + boolean isSuccessfulLaunch, String intentAction, int callingUid, + String callingActivityName, int calleeUid, String calleeActivityName, + boolean isStartActivityForResult) { + UriGrantsManagerService.this.notifyActivityLaunchRequestCompletedUnlocked( + requestHashCode, isSuccessfulLaunch, intentAction, callingUid, + callingActivityName, calleeUid, calleeActivityName, + isStartActivityForResult); } private NeededUriGrants internalCheckGrantUriPermissionFromIntent(Intent intent, int callingUid, String targetPkg, int targetUserId, - @Nullable Integer requireContentUriPermissionFromCaller) { + @Nullable Integer requireContentUriPermissionFromCaller, Integer requestHashCode) { final int mode = (intent != null) ? intent.getFlags() : 0; return UriGrantsManagerService.this.checkGrantUriPermissionFromIntentUnlocked( callingUid, targetPkg, intent, mode, null, targetUserId, - requireContentUriPermissionFromCaller); + requireContentUriPermissionFromCaller, requestHashCode); } @Override diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 1822a80c2f95..bc11bacf8200 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -603,7 +603,8 @@ class ActivityStarter { .checkGrantUriPermissionFromIntent(intent, resolvedCallingUid, activityInfo.applicationInfo.packageName, UserHandle.getUserId(activityInfo.applicationInfo.uid), - activityInfo.requireContentUriPermissionFromCaller); + activityInfo.requireContentUriPermissionFromCaller, + /* requestHashCode */ this.hashCode()); } else { intentGrants = supervisor.mService.mUgmInternal .checkGrantUriPermissionFromIntent(intent, resolvedCallingUid, @@ -717,6 +718,9 @@ class ActivityStarter { * @return The starter result. */ int execute() { + // Required for logging ContentOrFileUriEventReported in the finally block. + String callerActivityName = null; + ActivityRecord launchingRecord = null; try { onExecutionStarted(); @@ -737,6 +741,7 @@ class ActivityStarter { ? Binder.getCallingUid() : mRequest.realCallingUid; launchingState = mSupervisor.getActivityMetricsLogger().notifyActivityLaunching( mRequest.intent, caller, callingUid); + callerActivityName = caller != null ? caller.info.name : null; } if (mRequest.intent != null) { @@ -812,7 +817,7 @@ class ActivityStarter { final ActivityOptions originalOptions = mRequest.activityOptions != null ? mRequest.activityOptions.getOriginalOptions() : null; // Only track the launch time of activity that will be resumed. - final ActivityRecord launchingRecord = mDoResume ? mLastStartActivityRecord : null; + launchingRecord = mDoResume ? mLastStartActivityRecord : null; // If the new record is the one that started, a new activity has created. final boolean newActivityCreated = mStartActivity == launchingRecord; // Notify ActivityMetricsLogger that the activity has launched. @@ -828,6 +833,23 @@ class ActivityStarter { return getExternalResult(res); } } finally { + // Notify UriGrantsManagerService that activity launch completed. Required for logging + // the ContentOrFileUriEventReported message. + mSupervisor.mService.mUgmInternal.notifyActivityLaunchRequestCompleted( + mRequest.hashCode(), + // isSuccessfulLaunch + launchingRecord != null, + // Intent action + mRequest.intent != null ? mRequest.intent.getAction() : null, + mRequest.realCallingUid, + callerActivityName, + // Callee UID + mRequest.activityInfo != null + ? mRequest.activityInfo.applicationInfo.uid : INVALID_UID, + // Callee Activity name + mRequest.activityInfo != null ? mRequest.activityInfo.name : null, + // isStartActivityForResult + launchingRecord != null && launchingRecord.resultTo != null); onExecutionComplete(); } } |