1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wm; 18 19 import static android.os.Process.INVALID_UID; 20 21 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; 22 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; 23 24 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; 25 import static org.xmlpull.v1.XmlPullParser.END_TAG; 26 import static org.xmlpull.v1.XmlPullParser.START_TAG; 27 28 import android.content.ClipData; 29 import android.content.ContentProvider; 30 import android.content.ContentResolver; 31 import android.content.Intent; 32 import android.net.Uri; 33 import android.os.BadParcelableException; 34 import android.os.IBinder; 35 import android.os.UserHandle; 36 import android.util.ArraySet; 37 import android.util.Slog; 38 39 import com.android.internal.util.XmlUtils; 40 import com.android.modules.utils.TypedXmlPullParser; 41 import com.android.modules.utils.TypedXmlSerializer; 42 import com.android.server.uri.GrantUri; 43 44 import org.xmlpull.v1.XmlPullParserException; 45 46 import java.io.IOException; 47 import java.util.ArrayList; 48 import java.util.WeakHashMap; 49 50 /** 51 * Represents the state of activity callers. Used by {@link ActivityRecord}. 52 * @hide 53 */ 54 final class ActivityCallerState { 55 private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityCallerState" : TAG_ATM; 56 57 // XML tags for CallerInfo 58 private static final String ATTR_CALLER_UID = "caller_uid"; 59 private static final String ATTR_CALLER_PACKAGE = "caller_package"; 60 private static final String ATTR_CALLER_IS_SHARE_ENABLED = "caller_is_share_enabled"; 61 private static final String TAG_READABLE_CONTENT_URI = "readable_content_uri"; 62 private static final String TAG_WRITABLE_CONTENT_URI = "writable_content_uri"; 63 private static final String TAG_INACCESSIBLE_CONTENT_URI = "inaccessible_content_uri"; 64 private static final String ATTR_SOURCE_USER_ID = "source_user_id"; 65 private static final String ATTR_URI = "uri"; 66 private static final String ATTR_PREFIX = "prefix"; 67 68 // Map for storing CallerInfo instances 69 private final WeakHashMap<IBinder, CallerInfo> mCallerTokenInfoMap = new WeakHashMap<>(); 70 71 final ActivityTaskManagerService mAtmService; 72 ActivityCallerState(ActivityTaskManagerService service)73 ActivityCallerState(ActivityTaskManagerService service) { 74 mAtmService = service; 75 } 76 getCallerInfoOrNull(IBinder callerToken)77 CallerInfo getCallerInfoOrNull(IBinder callerToken) { 78 return mCallerTokenInfoMap.getOrDefault(callerToken, null); 79 } 80 hasCaller(IBinder callerToken)81 boolean hasCaller(IBinder callerToken) { 82 return getCallerInfoOrNull(callerToken) != null; 83 } 84 getUid(IBinder callerToken)85 int getUid(IBinder callerToken) { 86 CallerInfo callerInfo = getCallerInfoOrNull(callerToken); 87 return callerInfo != null ? callerInfo.mUid : INVALID_UID; 88 } 89 getPackage(IBinder callerToken)90 String getPackage(IBinder callerToken) { 91 CallerInfo callerInfo = getCallerInfoOrNull(callerToken); 92 return callerInfo != null ? callerInfo.mPackageName : null; 93 } 94 isShareIdentityEnabled(IBinder callerToken)95 boolean isShareIdentityEnabled(IBinder callerToken) { 96 CallerInfo callerInfo = getCallerInfoOrNull(callerToken); 97 return callerInfo != null ? callerInfo.mIsShareIdentityEnabled : false; 98 } 99 add(IBinder callerToken, CallerInfo callerInfo)100 void add(IBinder callerToken, CallerInfo callerInfo) { 101 mCallerTokenInfoMap.put(callerToken, callerInfo); 102 } 103 computeCallerInfo(IBinder callerToken, Intent intent, int callerUid, String callerPackageName, boolean isCallerShareIdentityEnabled)104 void computeCallerInfo(IBinder callerToken, Intent intent, int callerUid, 105 String callerPackageName, boolean isCallerShareIdentityEnabled) { 106 final CallerInfo callerInfo = new CallerInfo(callerUid, callerPackageName, 107 isCallerShareIdentityEnabled); 108 mCallerTokenInfoMap.put(callerToken, callerInfo); 109 110 final ArraySet<Uri> contentUris = getContentUrisFromIntent(intent); 111 for (int i = contentUris.size() - 1; i >= 0; i--) { 112 final Uri contentUri = contentUris.valueAt(i); 113 114 final boolean hasRead = addContentUriIfUidHasPermission(contentUri, callerUid, 115 Intent.FLAG_GRANT_READ_URI_PERMISSION, callerInfo.mReadableContentUris); 116 117 final boolean hasWrite = addContentUriIfUidHasPermission(contentUri, callerUid, 118 Intent.FLAG_GRANT_WRITE_URI_PERMISSION, callerInfo.mWritableContentUris); 119 120 if (!hasRead && !hasWrite) { 121 callerInfo.mInaccessibleContentUris.add(convertToGrantUri(contentUri, 122 /* modeFlags */ 0, callerUid)); 123 } 124 } 125 } 126 checkContentUriPermission(IBinder callerToken, GrantUri grantUri, int modeFlags)127 boolean checkContentUriPermission(IBinder callerToken, GrantUri grantUri, int modeFlags) { 128 if (!Intent.isAccessUriMode(modeFlags)) { 129 throw new IllegalArgumentException("Mode flags are not access URI mode flags: " 130 + modeFlags); 131 } 132 133 final CallerInfo callerInfo = mCallerTokenInfoMap.getOrDefault(callerToken, null); 134 if (callerInfo == null) { 135 Slog.e(TAG, "Caller not found for checkContentUriPermission of: " 136 + grantUri.uri.toSafeString()); 137 return false; 138 } 139 140 if (callerInfo.mInaccessibleContentUris.contains(grantUri)) { 141 return false; 142 } 143 144 final boolean readMet = callerInfo.mReadableContentUris.contains(grantUri); 145 final boolean writeMet = callerInfo.mWritableContentUris.contains(grantUri); 146 147 if (!readMet && !writeMet) { 148 throw new IllegalArgumentException("The supplied URI wasn't passed at launch in" 149 + " #getData, #EXTRA_STREAM, nor #getClipData: " + grantUri.uri.toSafeString()); 150 } 151 152 final boolean checkRead = (modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0; 153 if (checkRead && !readMet) { 154 return false; 155 } 156 157 final boolean checkWrite = (modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0; 158 if (checkWrite && !writeMet) { 159 return false; 160 } 161 162 return true; 163 } 164 addContentUriIfUidHasPermission(Uri contentUri, int uid, int modeFlags, ArraySet<GrantUri> grantUris)165 private boolean addContentUriIfUidHasPermission(Uri contentUri, int uid, int modeFlags, 166 ArraySet<GrantUri> grantUris) { 167 final GrantUri grantUri = convertToGrantUri(contentUri, modeFlags, uid); 168 if (mAtmService.mUgmInternal.checkUriPermission(grantUri, uid, 169 modeFlags, /* isFullAccessForContentUri */ true)) { 170 grantUris.add(grantUri); 171 return true; 172 } 173 return false; 174 } 175 convertToGrantUri(Uri contentUri, int modeFlags, int uid)176 private static GrantUri convertToGrantUri(Uri contentUri, int modeFlags, int uid) { 177 return new GrantUri(ContentProvider.getUserIdFromUri(contentUri, 178 UserHandle.getUserId(uid)), ContentProvider.getUriWithoutUserId(contentUri), 179 modeFlags); 180 } 181 getContentUrisFromIntent(Intent intent)182 private static ArraySet<Uri> getContentUrisFromIntent(Intent intent) { 183 final ArraySet<Uri> uris = new ArraySet<>(); 184 if (intent == null) return uris; 185 186 // getData 187 addUriIfContentUri(intent.getData(), uris); 188 189 // EXTRA_STREAM 190 if (intent.hasExtra(Intent.EXTRA_STREAM)) { 191 final ArrayList<Uri> streams = tryToUnparcelArrayListExtraStreamsUri(intent); 192 if (streams == null) { 193 addUriIfContentUri(tryToUnparcelExtraStreamUri(intent), uris); 194 } else { 195 for (int i = streams.size() - 1; i >= 0; i--) { 196 addUriIfContentUri(streams.get(i), uris); 197 } 198 } 199 } 200 201 final ClipData clipData = intent.getClipData(); 202 if (clipData == null) return uris; 203 204 for (int i = 0; i < clipData.getItemCount(); i++) { 205 final ClipData.Item item = clipData.getItemAt(i); 206 207 // getUri 208 addUriIfContentUri(item.getUri(), uris); 209 210 // getIntent 211 uris.addAll(getContentUrisFromIntent(item.getIntent())); 212 } 213 return uris; 214 } 215 tryToUnparcelExtraStreamUri(Intent intent)216 private static Uri tryToUnparcelExtraStreamUri(Intent intent) { 217 try { 218 return intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri.class); 219 } catch (BadParcelableException e) { 220 // Even though the system "defuses" all the parsed Bundles, i.e. suppresses and logs 221 // instances of {@link BadParcelableException}, we still want to be on the safer side 222 // and catch the exception to ensure no breakages happen. If the unparcel fails, the 223 // item is still preserved with the underlying parcel. 224 Slog.w(TAG, "Failed to unparcel an URI in EXTRA_STREAM, returning null: " + e); 225 return null; 226 } 227 } 228 tryToUnparcelArrayListExtraStreamsUri(Intent intent)229 private static ArrayList<Uri> tryToUnparcelArrayListExtraStreamsUri(Intent intent) { 230 try { 231 return intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri.class); 232 } catch (BadParcelableException e) { 233 // Even though the system "defuses" all the parsed Bundles, i.e. suppresses and logs 234 // instances of {@link BadParcelableException}, we still want to be on the safer side 235 // and catch the exception to ensure no breakages happen. If the unparcel fails, the 236 // item is still preserved with the underlying parcel. 237 Slog.w(TAG, "Failed to unparcel an ArrayList of URIs in EXTRA_STREAM, returning null: " 238 + e); 239 return null; 240 } 241 } 242 addUriIfContentUri(Uri uri, ArraySet<Uri> uris)243 private static void addUriIfContentUri(Uri uri, ArraySet<Uri> uris) { 244 if (uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { 245 uris.add(uri); 246 } 247 } 248 249 public static final class CallerInfo { 250 final int mUid; 251 final String mPackageName; 252 final boolean mIsShareIdentityEnabled; 253 final ArraySet<GrantUri> mReadableContentUris = new ArraySet<>(); 254 final ArraySet<GrantUri> mWritableContentUris = new ArraySet<>(); 255 final ArraySet<GrantUri> mInaccessibleContentUris = new ArraySet<>(); 256 CallerInfo(int uid, String packageName, boolean isShareIdentityEnabled)257 CallerInfo(int uid, String packageName, boolean isShareIdentityEnabled) { 258 mUid = uid; 259 mPackageName = packageName; 260 mIsShareIdentityEnabled = isShareIdentityEnabled; 261 } 262 saveToXml(TypedXmlSerializer out)263 public void saveToXml(TypedXmlSerializer out) 264 throws IOException, XmlPullParserException { 265 out.attributeInt(null, ATTR_CALLER_UID, mUid); 266 if (mPackageName != null) { 267 out.attribute(null, ATTR_CALLER_PACKAGE, mPackageName); 268 } 269 out.attributeBoolean(null, ATTR_CALLER_IS_SHARE_ENABLED, mIsShareIdentityEnabled); 270 for (int i = mReadableContentUris.size() - 1; i >= 0; i--) { 271 saveGrantUriToXml(out, mReadableContentUris.valueAt(i), TAG_READABLE_CONTENT_URI); 272 } 273 274 for (int i = mWritableContentUris.size() - 1; i >= 0; i--) { 275 saveGrantUriToXml(out, mWritableContentUris.valueAt(i), TAG_WRITABLE_CONTENT_URI); 276 } 277 278 for (int i = mInaccessibleContentUris.size() - 1; i >= 0; i--) { 279 saveGrantUriToXml(out, mInaccessibleContentUris.valueAt(i), 280 TAG_INACCESSIBLE_CONTENT_URI); 281 } 282 } 283 restoreFromXml(TypedXmlPullParser in)284 public static CallerInfo restoreFromXml(TypedXmlPullParser in) 285 throws IOException, XmlPullParserException { 286 int uid = in.getAttributeInt(null, ATTR_CALLER_UID, 0); 287 String packageName = in.getAttributeValue(null, ATTR_CALLER_PACKAGE); 288 boolean isShareIdentityEnabled = in.getAttributeBoolean(null, 289 ATTR_CALLER_IS_SHARE_ENABLED, false); 290 291 CallerInfo callerInfo = new CallerInfo(uid, packageName, isShareIdentityEnabled); 292 final int outerDepth = in.getDepth(); 293 int event; 294 while (((event = in.next()) != END_DOCUMENT) 295 && (event != END_TAG || in.getDepth() >= outerDepth)) { 296 if (event == START_TAG) { 297 final String name = in.getName(); 298 if (TAG_READABLE_CONTENT_URI.equals(name)) { 299 callerInfo.mReadableContentUris.add(restoreGrantUriFromXml(in)); 300 } else if (TAG_WRITABLE_CONTENT_URI.equals(name)) { 301 callerInfo.mWritableContentUris.add(restoreGrantUriFromXml(in)); 302 } else if (TAG_INACCESSIBLE_CONTENT_URI.equals(name)) { 303 callerInfo.mInaccessibleContentUris.add(restoreGrantUriFromXml(in)); 304 } else { 305 Slog.w(TAG, "restoreActivity: unexpected name=" + name); 306 XmlUtils.skipCurrentTag(in); 307 } 308 } 309 } 310 return callerInfo; 311 } 312 saveGrantUriToXml(TypedXmlSerializer out, GrantUri grantUri, String tag)313 private void saveGrantUriToXml(TypedXmlSerializer out, GrantUri grantUri, String tag) 314 throws IOException, XmlPullParserException { 315 out.startTag(null, tag); 316 out.attributeInt(null, ATTR_SOURCE_USER_ID, grantUri.sourceUserId); 317 out.attribute(null, ATTR_URI, String.valueOf(grantUri.uri)); 318 out.attributeBoolean(null, ATTR_PREFIX, grantUri.prefix); 319 out.endTag(null, tag); 320 } 321 restoreGrantUriFromXml(TypedXmlPullParser in)322 private static GrantUri restoreGrantUriFromXml(TypedXmlPullParser in) 323 throws IOException, XmlPullParserException { 324 int sourceUserId = in.getAttributeInt(null, ATTR_SOURCE_USER_ID, 0); 325 Uri uri = Uri.parse(in.getAttributeValue(null, ATTR_URI)); 326 boolean prefix = in.getAttributeBoolean(null, ATTR_PREFIX, false); 327 return new GrantUri(sourceUserId, uri, 328 prefix ? Intent.FLAG_GRANT_PREFIX_URI_PERMISSION : 0); 329 } 330 } 331 } 332