• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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