1 /* 2 * Copyright (C) 2021 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 android.content; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.RequiresPermission; 22 import android.annotation.SystemApi; 23 import android.annotation.TestApi; 24 import android.app.ActivityThread; 25 import android.os.Binder; 26 import android.os.Build; 27 import android.os.IBinder; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 import android.os.Process; 31 import android.permission.PermissionManager; 32 import android.util.ArraySet; 33 34 import com.android.internal.annotations.Immutable; 35 36 import java.util.Arrays; 37 import java.util.Collections; 38 import java.util.Objects; 39 import java.util.Set; 40 41 /** 42 * This class represents a source to which access to permission protected data should be 43 * attributed. Attribution sources can be chained to represent cases where the protected 44 * data would flow through several applications. For example, app A may ask app B for 45 * contacts and in turn app B may ask app C for contacts. In this case, the attribution 46 * chain would be A -> B -> C and the data flow would be C -> B -> A. There are two 47 * main benefits of using the attribution source mechanism: avoid doing explicit permission 48 * checks on behalf of the calling app if you are accessing private data on their behalf 49 * to send back; avoid double data access blaming which happens as you check the calling 50 * app's permissions and when you access the data behind these permissions (for runtime 51 * permissions). Also if not explicitly blaming the caller the data access would be 52 * counted towards your app vs to the previous app where yours was just a proxy. 53 * <p> 54 * Every {@link Context} has an attribution source and you can get it via {@link 55 * Context#getAttributionSource()} representing itself, which is a chain of one. You 56 * can attribute work to another app, or more precisely to a chain of apps, through 57 * which the data you would be accessing would flow, via {@link Context#createContext( 58 * ContextParams)} plus specifying an attribution source for the next app to receive 59 * the protected data you are accessing via {@link AttributionSource.Builder#setNext( 60 * AttributionSource)}. Creating this attribution chain ensures that the datasource would 61 * check whether every app in the attribution chain has permission to access the data 62 * before releasing it. The datasource will also record appropriately that this data was 63 * accessed by the apps in the sequence if the data is behind a sensitive permission 64 * (e.g. dangerous). Again, this is useful if you are accessing the data on behalf of another 65 * app, for example a speech recognizer using the mic so it can provide recognition to 66 * a calling app. 67 * <p> 68 * You can create an attribution chain of you and any other app without any verification 69 * as this is something already available via the {@link android.app.AppOpsManager} APIs. 70 * This is supported to handle cases where you don't have access to the caller's attribution 71 * source and you can directly use the {@link AttributionSource.Builder} APIs. However, 72 * if the data flows through more than two apps (more than you access the data for the 73 * caller) you need to have a handle to the {@link AttributionSource} for the calling app's 74 * context in order to create an attribution context. This means you either need to have an 75 * API for the other app to send you its attribution source or use a platform API that pipes 76 * the callers attribution source. 77 * <p> 78 * You cannot forge an attribution chain without the participation of every app in the 79 * attribution chain (aside of the special case mentioned above). To create an attribution 80 * source that is trusted you need to create an attribution context that points to an 81 * attribution source that was explicitly created by the app that it refers to, recursively. 82 * <p> 83 * Since creating an attribution context leads to all permissions for apps in the attribution 84 * chain being checked, you need to expect getting a security exception when accessing 85 * permission protected APIs since some app in the chain may not have the permission. 86 */ 87 @Immutable 88 public final class AttributionSource implements Parcelable { 89 private static final String DESCRIPTOR = "android.content.AttributionSource"; 90 91 private static final Binder sDefaultToken = new Binder(DESCRIPTOR); 92 93 private final @NonNull AttributionSourceState mAttributionSourceState; 94 95 private @Nullable AttributionSource mNextCached; 96 private @Nullable Set<String> mRenouncedPermissionsCached; 97 98 /** @hide */ 99 @TestApi AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag)100 public AttributionSource(int uid, @Nullable String packageName, 101 @Nullable String attributionTag) { 102 this(uid, packageName, attributionTag, sDefaultToken); 103 } 104 105 /** @hide */ 106 @TestApi AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, @NonNull IBinder token)107 public AttributionSource(int uid, @Nullable String packageName, 108 @Nullable String attributionTag, @NonNull IBinder token) { 109 this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null, 110 /*next*/ null); 111 } 112 113 /** @hide */ AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, @NonNull IBinder token, @Nullable AttributionSource next)114 public AttributionSource(int uid, @Nullable String packageName, 115 @Nullable String attributionTag, @NonNull IBinder token, 116 @Nullable AttributionSource next) { 117 this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null, next); 118 } 119 120 /** @hide */ 121 @TestApi AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable Set<String> renouncedPermissions, @Nullable AttributionSource next)122 public AttributionSource(int uid, @Nullable String packageName, 123 @Nullable String attributionTag, @Nullable Set<String> renouncedPermissions, 124 @Nullable AttributionSource next) { 125 this(uid, packageName, attributionTag, (renouncedPermissions != null) 126 ? renouncedPermissions.toArray(new String[0]) : null, next); 127 } 128 129 /** @hide */ AttributionSource(@onNull AttributionSource current, @Nullable AttributionSource next)130 public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) { 131 this(current.getUid(), current.getPackageName(), current.getAttributionTag(), 132 current.getToken(), current.mAttributionSourceState.renouncedPermissions, next); 133 } 134 AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String[] renouncedPermissions, @Nullable AttributionSource next)135 AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, 136 @Nullable String[] renouncedPermissions, @Nullable AttributionSource next) { 137 this(uid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next); 138 } 139 AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, @NonNull IBinder token, @Nullable String[] renouncedPermissions, @Nullable AttributionSource next)140 AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag, 141 @NonNull IBinder token, @Nullable String[] renouncedPermissions, 142 @Nullable AttributionSource next) { 143 mAttributionSourceState = new AttributionSourceState(); 144 mAttributionSourceState.uid = uid; 145 mAttributionSourceState.token = token; 146 mAttributionSourceState.packageName = packageName; 147 mAttributionSourceState.attributionTag = attributionTag; 148 mAttributionSourceState.renouncedPermissions = renouncedPermissions; 149 mAttributionSourceState.next = (next != null) ? new AttributionSourceState[] 150 {next.mAttributionSourceState} : new AttributionSourceState[0]; 151 } 152 AttributionSource(@onNull Parcel in)153 AttributionSource(@NonNull Parcel in) { 154 this(AttributionSourceState.CREATOR.createFromParcel(in)); 155 156 // Since we just unpacked this object as part of it transiting a Binder 157 // call, this is the perfect time to enforce that its UID can be trusted 158 enforceCallingUid(); 159 } 160 161 /** @hide */ AttributionSource(@onNull AttributionSourceState attributionSourceState)162 public AttributionSource(@NonNull AttributionSourceState attributionSourceState) { 163 mAttributionSourceState = attributionSourceState; 164 } 165 166 /** @hide */ withNextAttributionSource(@ullable AttributionSource next)167 public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) { 168 return new AttributionSource(getUid(), getPackageName(), getAttributionTag(), 169 mAttributionSourceState.renouncedPermissions, next); 170 } 171 172 /** @hide */ withPackageName(@ullable String packageName)173 public AttributionSource withPackageName(@Nullable String packageName) { 174 return new AttributionSource(getUid(), packageName, getAttributionTag(), 175 mAttributionSourceState.renouncedPermissions, getNext()); 176 } 177 178 /** @hide */ withToken(@onNull Binder token)179 public AttributionSource withToken(@NonNull Binder token) { 180 return new AttributionSource(getUid(), getPackageName(), getAttributionTag(), 181 token, mAttributionSourceState.renouncedPermissions, getNext()); 182 } 183 184 /** @hide */ asState()185 public @NonNull AttributionSourceState asState() { 186 return mAttributionSourceState; 187 } 188 189 /** @hide */ asScopedParcelState()190 public @NonNull ScopedParcelState asScopedParcelState() { 191 return new ScopedParcelState(this); 192 } 193 194 /** @hide */ myAttributionSource()195 public static AttributionSource myAttributionSource() { 196 return new AttributionSource(Process.myUid(), ActivityThread.currentOpPackageName(), 197 /*attributionTag*/ null, (String[]) /*renouncedPermissions*/ null, /*next*/ null); 198 } 199 200 /** 201 * This is a scoped object that exposes the content of an attribution source 202 * as a parcel. This is useful when passing one to native and avoid custom 203 * conversion logic from Java to native state that needs to be kept in sync 204 * as attribution source evolves. This way we use the same logic for passing 205 * to native as the ones for passing in an IPC - in both cases this is the 206 * same auto generated code. 207 * 208 * @hide 209 */ 210 public static class ScopedParcelState implements AutoCloseable { 211 private final Parcel mParcel; 212 getParcel()213 public @NonNull Parcel getParcel() { 214 return mParcel; 215 } 216 ScopedParcelState(AttributionSource attributionSource)217 public ScopedParcelState(AttributionSource attributionSource) { 218 mParcel = Parcel.obtain(); 219 attributionSource.writeToParcel(mParcel, 0); 220 mParcel.setDataPosition(0); 221 } 222 close()223 public void close() { 224 mParcel.recycle(); 225 } 226 } 227 228 /** 229 * If you are handling an IPC and you don't trust the caller you need to validate 230 * whether the attribution source is one for the calling app to prevent the caller 231 * to pass you a source from another app without including themselves in the 232 * attribution chain. 233 * 234 * @throws SecurityException if the attribution source cannot be trusted to be 235 * from the caller. 236 */ enforceCallingUid()237 public void enforceCallingUid() { 238 if (!checkCallingUid()) { 239 throw new SecurityException("Calling uid: " + Binder.getCallingUid() 240 + " doesn't match source uid: " + mAttributionSourceState.uid); 241 } 242 // No need to check package as app ops manager does it already. 243 } 244 245 /** 246 * If you are handling an IPC and you don't trust the caller you need to validate 247 * whether the attribution source is one for the calling app to prevent the caller 248 * to pass you a source from another app without including themselves in the 249 * attribution chain. 250 *f 251 * @return if the attribution source cannot be trusted to be from the caller. 252 */ checkCallingUid()253 public boolean checkCallingUid() { 254 final int callingUid = Binder.getCallingUid(); 255 if (callingUid != Process.ROOT_UID 256 && callingUid != Process.SYSTEM_UID 257 && callingUid != mAttributionSourceState.uid) { 258 return false; 259 } 260 // No need to check package as app ops manager does it already. 261 return true; 262 } 263 264 @Override toString()265 public String toString() { 266 if (Build.IS_DEBUGGABLE) { 267 return "AttributionSource { " + 268 "uid = " + mAttributionSourceState.uid + ", " + 269 "packageName = " + mAttributionSourceState.packageName + ", " + 270 "attributionTag = " + mAttributionSourceState.attributionTag + ", " + 271 "token = " + mAttributionSourceState.token + ", " + 272 "next = " + (mAttributionSourceState.next != null 273 && mAttributionSourceState.next.length > 0 274 ? mAttributionSourceState.next[0] : null) + 275 " }"; 276 } 277 return super.toString(); 278 } 279 280 /** 281 * @return The next UID that would receive the permission protected data. 282 * 283 * @hide 284 */ getNextUid()285 public int getNextUid() { 286 if (mAttributionSourceState.next != null 287 && mAttributionSourceState.next.length > 0) { 288 return mAttributionSourceState.next[0].uid; 289 } 290 return Process.INVALID_UID; 291 } 292 293 /** 294 * @return The next package that would receive the permission protected data. 295 * 296 * @hide 297 */ getNextPackageName()298 public @Nullable String getNextPackageName() { 299 if (mAttributionSourceState.next != null 300 && mAttributionSourceState.next.length > 0) { 301 return mAttributionSourceState.next[0].packageName; 302 } 303 return null; 304 } 305 306 /** 307 * @return The next package's attribution tag that would receive 308 * the permission protected data. 309 * 310 * @hide 311 */ getNextAttributionTag()312 public @Nullable String getNextAttributionTag() { 313 if (mAttributionSourceState.next != null 314 && mAttributionSourceState.next.length > 0) { 315 return mAttributionSourceState.next[0].attributionTag; 316 } 317 return null; 318 } 319 320 /** 321 * @return The next package's token that would receive 322 * the permission protected data. 323 * 324 * @hide 325 */ getNextToken()326 public @Nullable IBinder getNextToken() { 327 if (mAttributionSourceState.next != null 328 && mAttributionSourceState.next.length > 0) { 329 return mAttributionSourceState.next[0].token; 330 } 331 return null; 332 } 333 334 /** 335 * Checks whether this attribution source can be trusted. That is whether 336 * the app it refers to created it and provided to the attribution chain. 337 * 338 * @param context Context handle. 339 * @return Whether this is a trusted source. 340 */ isTrusted(@onNull Context context)341 public boolean isTrusted(@NonNull Context context) { 342 return mAttributionSourceState.token != null 343 && context.getSystemService(PermissionManager.class) 344 .isRegisteredAttributionSource(this); 345 } 346 347 /** 348 * Permissions that should be considered revoked regardless if granted. 349 * 350 * @hide 351 */ 352 @SystemApi 353 @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) 354 @NonNull getRenouncedPermissions()355 public Set<String> getRenouncedPermissions() { 356 if (mRenouncedPermissionsCached == null) { 357 if (mAttributionSourceState.renouncedPermissions != null) { 358 mRenouncedPermissionsCached = new ArraySet<>( 359 mAttributionSourceState.renouncedPermissions); 360 } else { 361 mRenouncedPermissionsCached = Collections.emptySet(); 362 } 363 } 364 return mRenouncedPermissionsCached; 365 } 366 367 /** 368 * The UID that is accessing the permission protected data. 369 */ getUid()370 public int getUid() { 371 return mAttributionSourceState.uid; 372 } 373 374 /** 375 * The package that is accessing the permission protected data. 376 */ getPackageName()377 public @Nullable String getPackageName() { 378 return mAttributionSourceState.packageName; 379 } 380 381 /** 382 * The attribution tag of the app accessing the permission protected data. 383 */ getAttributionTag()384 public @Nullable String getAttributionTag() { 385 return mAttributionSourceState.attributionTag; 386 } 387 388 /** 389 * Unique token for that source. 390 * 391 * @hide 392 */ getToken()393 public @NonNull IBinder getToken() { 394 return mAttributionSourceState.token; 395 } 396 397 /** 398 * The next app to receive the permission protected data. 399 */ getNext()400 public @Nullable AttributionSource getNext() { 401 if (mNextCached == null && mAttributionSourceState.next != null 402 && mAttributionSourceState.next.length > 0) { 403 mNextCached = new AttributionSource(mAttributionSourceState.next[0]); 404 } 405 return mNextCached; 406 } 407 408 @Override equals(@ullable Object o)409 public boolean equals(@Nullable Object o) { 410 if (this == o) return true; 411 if (o == null || getClass() != o.getClass()) return false; 412 AttributionSource that = (AttributionSource) o; 413 return mAttributionSourceState.uid == that.mAttributionSourceState.uid 414 && Objects.equals(mAttributionSourceState.packageName, 415 that.mAttributionSourceState.packageName) 416 && Objects.equals(mAttributionSourceState.attributionTag, 417 that.mAttributionSourceState.attributionTag) 418 && Objects.equals(mAttributionSourceState.token, 419 that.mAttributionSourceState.token) 420 && Arrays.equals(mAttributionSourceState.renouncedPermissions, 421 that.mAttributionSourceState.renouncedPermissions) 422 && Objects.equals(getNext(), that.getNext()); 423 } 424 425 @Override hashCode()426 public int hashCode() { 427 int _hash = 1; 428 _hash = 31 * _hash + mAttributionSourceState.uid; 429 _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.packageName); 430 _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.attributionTag); 431 _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.token); 432 _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.renouncedPermissions); 433 _hash = 31 * _hash + Objects.hashCode(getNext()); 434 return _hash; 435 } 436 437 @Override writeToParcel(@onNull Parcel dest, int flags)438 public void writeToParcel(@NonNull Parcel dest, int flags) { 439 mAttributionSourceState.writeToParcel(dest, flags); 440 } 441 442 @Override describeContents()443 public int describeContents() { return 0; } 444 445 public static final @NonNull Parcelable.Creator<AttributionSource> CREATOR 446 = new Parcelable.Creator<AttributionSource>() { 447 @Override 448 public AttributionSource[] newArray(int size) { 449 return new AttributionSource[size]; 450 } 451 452 @Override 453 public AttributionSource createFromParcel(@NonNull Parcel in) { 454 return new AttributionSource(in); 455 } 456 }; 457 458 /** 459 * A builder for {@link AttributionSource} 460 */ 461 public static final class Builder { 462 private @NonNull final AttributionSourceState mAttributionSourceState = 463 new AttributionSourceState(); 464 465 private long mBuilderFieldsSet = 0L; 466 467 /** 468 * Creates a new Builder. 469 * 470 * @param uid 471 * The UID that is accessing the permission protected data. 472 */ Builder(int uid)473 public Builder(int uid) { 474 mAttributionSourceState.uid = uid; 475 } 476 477 /** 478 * The package that is accessing the permission protected data. 479 */ setPackageName(@ullable String value)480 public @NonNull Builder setPackageName(@Nullable String value) { 481 checkNotUsed(); 482 mBuilderFieldsSet |= 0x2; 483 mAttributionSourceState.packageName = value; 484 return this; 485 } 486 487 /** 488 * The attribution tag of the app accessing the permission protected data. 489 */ setAttributionTag(@ullable String value)490 public @NonNull Builder setAttributionTag(@Nullable String value) { 491 checkNotUsed(); 492 mBuilderFieldsSet |= 0x4; 493 mAttributionSourceState.attributionTag = value; 494 return this; 495 } 496 497 /** 498 * Sets permissions which have been voluntarily "renounced" by the 499 * calling app. 500 * <p> 501 * Interactions performed through services obtained from the created 502 * Context will ideally be treated as if these "renounced" permissions 503 * have not actually been granted to the app, regardless of their actual 504 * grant status. 505 * <p> 506 * This is designed for use by separate logical components within an app 507 * which have no intention of interacting with data or services that are 508 * protected by the renounced permissions. 509 * <p> 510 * Note that only {@link PermissionInfo#PROTECTION_DANGEROUS} 511 * permissions are supported by this mechanism. Additionally, this 512 * mechanism only applies to calls made through services obtained via 513 * {@link Context#getSystemService}; it has no effect on static or raw 514 * Binder calls. 515 * 516 * @param renouncedPermissions The set of permissions to treat as 517 * renounced, which is as if not granted. 518 * @return This builder. 519 * @hide 520 */ 521 @SystemApi 522 @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) setRenouncedPermissions(@ullable Set<String> value)523 public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) { 524 checkNotUsed(); 525 mBuilderFieldsSet |= 0x8; 526 mAttributionSourceState.renouncedPermissions = (value != null) 527 ? value.toArray(new String[0]) : null; 528 return this; 529 } 530 531 /** 532 * The next app to receive the permission protected data. 533 */ setNext(@ullable AttributionSource value)534 public @NonNull Builder setNext(@Nullable AttributionSource value) { 535 checkNotUsed(); 536 mBuilderFieldsSet |= 0x10; 537 mAttributionSourceState.next = (value != null) ? new AttributionSourceState[] 538 {value.mAttributionSourceState} : mAttributionSourceState.next; 539 return this; 540 } 541 542 /** Builds the instance. This builder should not be touched after calling this! */ build()543 public @NonNull AttributionSource build() { 544 checkNotUsed(); 545 mBuilderFieldsSet |= 0x40; // Mark builder used 546 547 if ((mBuilderFieldsSet & 0x2) == 0) { 548 mAttributionSourceState.packageName = null; 549 } 550 if ((mBuilderFieldsSet & 0x4) == 0) { 551 mAttributionSourceState.attributionTag = null; 552 } 553 if ((mBuilderFieldsSet & 0x8) == 0) { 554 mAttributionSourceState.renouncedPermissions = null; 555 } 556 if ((mBuilderFieldsSet & 0x10) == 0) { 557 mAttributionSourceState.next = null; 558 } 559 560 mAttributionSourceState.token = sDefaultToken; 561 562 if (mAttributionSourceState.next == null) { 563 // The NDK aidl backend doesn't support null parcelable arrays. 564 mAttributionSourceState.next = new AttributionSourceState[0]; 565 } 566 return new AttributionSource(mAttributionSourceState); 567 } 568 checkNotUsed()569 private void checkNotUsed() { 570 if ((mBuilderFieldsSet & 0x40) != 0) { 571 throw new IllegalStateException( 572 "This Builder should not be reused. Use a new Builder instance instead"); 573 } 574 } 575 } 576 } 577