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 and PID can be trusted 158 enforceCallingUidAndPid(); 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 whether the 230 * attribution source is one for the calling app to prevent the caller to pass you a source from 231 * another app without including themselves in the attribution chain. 232 * 233 * @throws SecurityException if the attribution source cannot be trusted to be from the caller. 234 */ enforceCallingUidAndPid()235 private void enforceCallingUidAndPid() { 236 enforceCallingUid(); 237 enforceCallingPid(); 238 } 239 240 /** 241 * If you are handling an IPC and you don't trust the caller you need to validate 242 * whether the attribution source is one for the calling app to prevent the caller 243 * to pass you a source from another app without including themselves in the 244 * attribution chain. 245 * 246 * @throws SecurityException if the attribution source cannot be trusted to be from the caller. 247 */ enforceCallingUid()248 public void enforceCallingUid() { 249 if (!checkCallingUid()) { 250 throw new SecurityException("Calling uid: " + Binder.getCallingUid() 251 + " doesn't match source uid: " + mAttributionSourceState.uid); 252 } 253 // No need to check package as app ops manager does it already. 254 } 255 256 /** 257 * If you are handling an IPC and you don't trust the caller you need to validate 258 * whether the attribution source is one for the calling app to prevent the caller 259 * to pass you a source from another app without including themselves in the 260 * attribution chain. 261 *f 262 * @return if the attribution source cannot be trusted to be from the caller. 263 */ checkCallingUid()264 public boolean checkCallingUid() { 265 final int callingUid = Binder.getCallingUid(); 266 if (callingUid != Process.ROOT_UID 267 && callingUid != Process.SYSTEM_UID 268 && callingUid != mAttributionSourceState.uid) { 269 return false; 270 } 271 // No need to check package as app ops manager does it already. 272 return true; 273 } 274 275 /** 276 * Validate that the pid being claimed for the calling app is not spoofed 277 * 278 * @throws SecurityException if the attribution source cannot be trusted to be from the caller. 279 * @hide 280 */ 281 @TestApi enforceCallingPid()282 public void enforceCallingPid() { 283 if (!checkCallingPid()) { 284 throw new SecurityException("Calling pid: " + Binder.getCallingPid() 285 + " doesn't match source pid: " + mAttributionSourceState.pid); 286 } 287 } 288 289 /** 290 * Validate that the pid being claimed for the calling app is not spoofed 291 * 292 * @return if the attribution source cannot be trusted to be from the caller. 293 */ checkCallingPid()294 private boolean checkCallingPid() { 295 final int callingPid = Binder.getCallingPid(); 296 if (mAttributionSourceState.pid != -1 && callingPid != mAttributionSourceState.pid) { 297 return false; 298 } 299 return true; 300 } 301 302 @Override toString()303 public String toString() { 304 if (Build.IS_DEBUGGABLE) { 305 return "AttributionSource { " + 306 "uid = " + mAttributionSourceState.uid + ", " + 307 "packageName = " + mAttributionSourceState.packageName + ", " + 308 "attributionTag = " + mAttributionSourceState.attributionTag + ", " + 309 "token = " + mAttributionSourceState.token + ", " + 310 "next = " + (mAttributionSourceState.next != null 311 && mAttributionSourceState.next.length > 0 312 ? mAttributionSourceState.next[0] : null) + 313 " }"; 314 } 315 return super.toString(); 316 } 317 318 /** 319 * @return The next UID that would receive the permission protected data. 320 * 321 * @hide 322 */ getNextUid()323 public int getNextUid() { 324 if (mAttributionSourceState.next != null 325 && mAttributionSourceState.next.length > 0) { 326 return mAttributionSourceState.next[0].uid; 327 } 328 return Process.INVALID_UID; 329 } 330 331 /** 332 * @return The next package that would receive the permission protected data. 333 * 334 * @hide 335 */ getNextPackageName()336 public @Nullable String getNextPackageName() { 337 if (mAttributionSourceState.next != null 338 && mAttributionSourceState.next.length > 0) { 339 return mAttributionSourceState.next[0].packageName; 340 } 341 return null; 342 } 343 344 /** 345 * @return The next package's attribution tag that would receive 346 * the permission protected data. 347 * 348 * @hide 349 */ getNextAttributionTag()350 public @Nullable String getNextAttributionTag() { 351 if (mAttributionSourceState.next != null 352 && mAttributionSourceState.next.length > 0) { 353 return mAttributionSourceState.next[0].attributionTag; 354 } 355 return null; 356 } 357 358 /** 359 * @return The next package's token that would receive 360 * the permission protected data. 361 * 362 * @hide 363 */ getNextToken()364 public @Nullable IBinder getNextToken() { 365 if (mAttributionSourceState.next != null 366 && mAttributionSourceState.next.length > 0) { 367 return mAttributionSourceState.next[0].token; 368 } 369 return null; 370 } 371 372 /** 373 * Checks whether this attribution source can be trusted. That is whether 374 * the app it refers to created it and provided to the attribution chain. 375 * 376 * @param context Context handle. 377 * @return Whether this is a trusted source. 378 */ isTrusted(@onNull Context context)379 public boolean isTrusted(@NonNull Context context) { 380 return mAttributionSourceState.token != null 381 && context.getSystemService(PermissionManager.class) 382 .isRegisteredAttributionSource(this); 383 } 384 385 /** 386 * Permissions that should be considered revoked regardless if granted. 387 * 388 * @hide 389 */ 390 @SystemApi 391 @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) 392 @NonNull getRenouncedPermissions()393 public Set<String> getRenouncedPermissions() { 394 if (mRenouncedPermissionsCached == null) { 395 if (mAttributionSourceState.renouncedPermissions != null) { 396 mRenouncedPermissionsCached = new ArraySet<>( 397 mAttributionSourceState.renouncedPermissions); 398 } else { 399 mRenouncedPermissionsCached = Collections.emptySet(); 400 } 401 } 402 return mRenouncedPermissionsCached; 403 } 404 405 /** 406 * The UID that is accessing the permission protected data. 407 */ getUid()408 public int getUid() { 409 return mAttributionSourceState.uid; 410 } 411 412 /** 413 * The package that is accessing the permission protected data. 414 */ getPackageName()415 public @Nullable String getPackageName() { 416 return mAttributionSourceState.packageName; 417 } 418 419 /** 420 * The attribution tag of the app accessing the permission protected data. 421 */ getAttributionTag()422 public @Nullable String getAttributionTag() { 423 return mAttributionSourceState.attributionTag; 424 } 425 426 /** 427 * Unique token for that source. 428 * 429 * @hide 430 */ getToken()431 public @NonNull IBinder getToken() { 432 return mAttributionSourceState.token; 433 } 434 435 /** 436 * The next app to receive the permission protected data. 437 */ getNext()438 public @Nullable AttributionSource getNext() { 439 if (mNextCached == null && mAttributionSourceState.next != null 440 && mAttributionSourceState.next.length > 0) { 441 mNextCached = new AttributionSource(mAttributionSourceState.next[0]); 442 } 443 return mNextCached; 444 } 445 446 @Override equals(@ullable Object o)447 public boolean equals(@Nullable Object o) { 448 if (this == o) return true; 449 if (o == null || getClass() != o.getClass()) return false; 450 AttributionSource that = (AttributionSource) o; 451 return mAttributionSourceState.uid == that.mAttributionSourceState.uid 452 && Objects.equals(mAttributionSourceState.packageName, 453 that.mAttributionSourceState.packageName) 454 && Objects.equals(mAttributionSourceState.attributionTag, 455 that.mAttributionSourceState.attributionTag) 456 && Objects.equals(mAttributionSourceState.token, 457 that.mAttributionSourceState.token) 458 && Arrays.equals(mAttributionSourceState.renouncedPermissions, 459 that.mAttributionSourceState.renouncedPermissions) 460 && Objects.equals(getNext(), that.getNext()); 461 } 462 463 @Override hashCode()464 public int hashCode() { 465 int _hash = 1; 466 _hash = 31 * _hash + mAttributionSourceState.uid; 467 _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.packageName); 468 _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.attributionTag); 469 _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.token); 470 _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.renouncedPermissions); 471 _hash = 31 * _hash + Objects.hashCode(getNext()); 472 return _hash; 473 } 474 475 @Override writeToParcel(@onNull Parcel dest, int flags)476 public void writeToParcel(@NonNull Parcel dest, int flags) { 477 mAttributionSourceState.writeToParcel(dest, flags); 478 } 479 480 @Override describeContents()481 public int describeContents() { return 0; } 482 483 public static final @NonNull Parcelable.Creator<AttributionSource> CREATOR 484 = new Parcelable.Creator<AttributionSource>() { 485 @Override 486 public AttributionSource[] newArray(int size) { 487 return new AttributionSource[size]; 488 } 489 490 @Override 491 public AttributionSource createFromParcel(@NonNull Parcel in) { 492 return new AttributionSource(in); 493 } 494 }; 495 496 /** 497 * A builder for {@link AttributionSource} 498 */ 499 public static final class Builder { 500 private @NonNull final AttributionSourceState mAttributionSourceState = 501 new AttributionSourceState(); 502 503 private long mBuilderFieldsSet = 0L; 504 505 /** 506 * Creates a new Builder. 507 * 508 * @param uid 509 * The UID that is accessing the permission protected data. 510 */ Builder(int uid)511 public Builder(int uid) { 512 mAttributionSourceState.uid = uid; 513 } 514 515 /** 516 * The package that is accessing the permission protected data. 517 */ setPackageName(@ullable String value)518 public @NonNull Builder setPackageName(@Nullable String value) { 519 checkNotUsed(); 520 mBuilderFieldsSet |= 0x2; 521 mAttributionSourceState.packageName = value; 522 return this; 523 } 524 525 /** 526 * The attribution tag of the app accessing the permission protected data. 527 */ setAttributionTag(@ullable String value)528 public @NonNull Builder setAttributionTag(@Nullable String value) { 529 checkNotUsed(); 530 mBuilderFieldsSet |= 0x4; 531 mAttributionSourceState.attributionTag = value; 532 return this; 533 } 534 535 /** 536 * Sets permissions which have been voluntarily "renounced" by the 537 * calling app. 538 * <p> 539 * Interactions performed through services obtained from the created 540 * Context will ideally be treated as if these "renounced" permissions 541 * have not actually been granted to the app, regardless of their actual 542 * grant status. 543 * <p> 544 * This is designed for use by separate logical components within an app 545 * which have no intention of interacting with data or services that are 546 * protected by the renounced permissions. 547 * <p> 548 * Note that only {@link PermissionInfo#PROTECTION_DANGEROUS} 549 * permissions are supported by this mechanism. Additionally, this 550 * mechanism only applies to calls made through services obtained via 551 * {@link Context#getSystemService}; it has no effect on static or raw 552 * Binder calls. 553 * 554 * @param renouncedPermissions The set of permissions to treat as 555 * renounced, which is as if not granted. 556 * @return This builder. 557 * @hide 558 */ 559 @SystemApi 560 @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) setRenouncedPermissions(@ullable Set<String> value)561 public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) { 562 checkNotUsed(); 563 mBuilderFieldsSet |= 0x8; 564 mAttributionSourceState.renouncedPermissions = (value != null) 565 ? value.toArray(new String[0]) : null; 566 return this; 567 } 568 569 /** 570 * The next app to receive the permission protected data. 571 */ setNext(@ullable AttributionSource value)572 public @NonNull Builder setNext(@Nullable AttributionSource value) { 573 checkNotUsed(); 574 mBuilderFieldsSet |= 0x10; 575 mAttributionSourceState.next = (value != null) ? new AttributionSourceState[] 576 {value.mAttributionSourceState} : mAttributionSourceState.next; 577 return this; 578 } 579 580 /** Builds the instance. This builder should not be touched after calling this! */ build()581 public @NonNull AttributionSource build() { 582 checkNotUsed(); 583 mBuilderFieldsSet |= 0x40; // Mark builder used 584 585 if ((mBuilderFieldsSet & 0x2) == 0) { 586 mAttributionSourceState.packageName = null; 587 } 588 if ((mBuilderFieldsSet & 0x4) == 0) { 589 mAttributionSourceState.attributionTag = null; 590 } 591 if ((mBuilderFieldsSet & 0x8) == 0) { 592 mAttributionSourceState.renouncedPermissions = null; 593 } 594 if ((mBuilderFieldsSet & 0x10) == 0) { 595 mAttributionSourceState.next = null; 596 } 597 598 mAttributionSourceState.token = sDefaultToken; 599 600 if (mAttributionSourceState.next == null) { 601 // The NDK aidl backend doesn't support null parcelable arrays. 602 mAttributionSourceState.next = new AttributionSourceState[0]; 603 } 604 return new AttributionSource(mAttributionSourceState); 605 } 606 checkNotUsed()607 private void checkNotUsed() { 608 if ((mBuilderFieldsSet & 0x40) != 0) { 609 throw new IllegalStateException( 610 "This Builder should not be reused. Use a new Builder instance instead"); 611 } 612 } 613 } 614 } 615