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