1 /* 2 * Copyright (C) 2023 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.app.appsearch.aidl; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.RequiresApi; 22 import android.app.appsearch.safeparcel.AbstractSafeParcelable; 23 import android.app.appsearch.safeparcel.SafeParcelable; 24 import android.content.AttributionSource; 25 import android.content.Context; 26 import android.os.Binder; 27 import android.os.Build; 28 import android.os.Build.VERSION; 29 import android.os.Build.VERSION_CODES; 30 import android.os.Parcel; 31 import android.os.Parcelable; 32 import android.os.Process; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 36 import java.util.Objects; 37 38 /** 39 * Compatibility version of AttributionSource. 40 * 41 * <p>Refactor AttributionSource to work on older API levels. For Android S+, this class maintains 42 * the original implementation of AttributionSource methods. However, for Android R-, this class 43 * creates a new implementation. Replace calls to AttributionSource with AppSearchAttributionSource. 44 * For a given Context, replace calls to getAttributionSource with createAttributionSource. 45 * 46 * @hide 47 */ 48 @SafeParcelable.Class(creator = "AppSearchAttributionSourceCreator") 49 public final class AppSearchAttributionSource extends AbstractSafeParcelable { 50 @NonNull 51 public static final Parcelable.Creator<AppSearchAttributionSource> CREATOR = 52 new AppSearchAttributionSourceCreator(); 53 54 @NonNull private final Compat mCompat; 55 56 @Nullable 57 @Field(id = 1, getter = "getAttributionSource") 58 private final AttributionSource mAttributionSource; 59 60 @NonNull 61 @Field(id = 2, getter = "getPackageName") 62 private final String mCallingPackageName; 63 64 @Field(id = 3, getter = "getUid") 65 private final int mCallingUid; 66 67 @Field(id = 4, getter = "getPid") 68 private int mCallingPid; 69 70 private static final int INVALID_PID = -1; 71 72 /** 73 * Constructs an instance of AppSearchAttributionSource for AbstractSafeParcelable. 74 * 75 * @param attributionSource The attribution source that is accessing permission protected data. 76 * @param callingPackageName The package that is accessing the permission protected data. 77 * @param callingUid The UID that is accessing the permission protected data. 78 */ 79 @Constructor AppSearchAttributionSource( @aramid = 1) @ullable AttributionSource attributionSource, @Param(id = 2) @NonNull String callingPackageName, @Param(id = 3) int callingUid, @Param(id = 4) int callingPid)80 AppSearchAttributionSource( 81 @Param(id = 1) @Nullable AttributionSource attributionSource, 82 @Param(id = 2) @NonNull String callingPackageName, 83 @Param(id = 3) int callingUid, 84 @Param(id = 4) int callingPid) { 85 mAttributionSource = attributionSource; 86 mCallingPackageName = Objects.requireNonNull(callingPackageName); 87 mCallingUid = callingUid; 88 mCallingPid = callingPid; 89 if (VERSION.SDK_INT >= Build.VERSION_CODES.S && mAttributionSource != null) { 90 mCompat = new Api31Impl(mAttributionSource, mCallingPid); 91 } else { 92 // If this object is being constructed as part of a oneway Binder call, getCallingPid 93 // will return 0 instead of the true PID. In that case, invalidate the PID by setting it 94 // to INVALID_PID (-1). 95 final int callingPidFromBinder = Binder.getCallingPid(); 96 if (callingPidFromBinder == 0) { 97 mCallingPid = INVALID_PID; 98 } 99 Api19Impl impl = new Api19Impl(mCallingPackageName, mCallingUid, mCallingPid); 100 impl.enforceCallingUid(); 101 impl.enforceCallingPid(); 102 mCompat = impl; 103 } 104 } 105 106 /** 107 * Constructs an instance of AppSearchAttributionSource. 108 * 109 * @param compat The compat version that provides AttributionSource implementation on lower API 110 * levels. 111 */ AppSearchAttributionSource(@onNull Compat compat)112 private AppSearchAttributionSource(@NonNull Compat compat) { 113 mCompat = Objects.requireNonNull(compat); 114 mAttributionSource = mCompat.getAttributionSource(); 115 mCallingPackageName = mCompat.getPackageName(); 116 mCallingUid = mCompat.getUid(); 117 mCallingPid = mCompat.getPid(); 118 } 119 120 /** 121 * Constructs an instance of AppSearchAttributionSource for testing. 122 * 123 * @param callingPackageName The package that is accessing the permission protected data. 124 * @param callingUid The UID that is accessing the permission protected data. 125 */ 126 @VisibleForTesting AppSearchAttributionSource( @onNull String callingPackageName, int callingUid, int callingPid)127 public AppSearchAttributionSource( 128 @NonNull String callingPackageName, int callingUid, int callingPid) { 129 mCallingPackageName = Objects.requireNonNull(callingPackageName); 130 mCallingUid = callingUid; 131 mCallingPid = callingPid; 132 133 if (VERSION.SDK_INT >= Build.VERSION_CODES.S) { 134 // This constructor is only used in unit test, AttributionSource#setPid is only 135 // available on 34+. 136 AttributionSource.Builder attributionSourceBuilder = 137 new AttributionSource.Builder(mCallingUid).setPackageName(mCallingPackageName); 138 if (VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE) { 139 attributionSourceBuilder.setPid(callingPid); 140 } 141 mAttributionSource = attributionSourceBuilder.build(); 142 mCompat = new Api31Impl(mAttributionSource, mCallingPid); 143 } else { 144 mAttributionSource = null; 145 mCompat = new Api19Impl(mCallingPackageName, mCallingUid, mCallingPid); 146 } 147 } 148 149 /** 150 * Provides a backward-compatible wrapper for AttributionSource. 151 * 152 * <p>This method is not supported on devices running SDK <= 30(R) since the AttributionSource 153 * class will not be available. 154 * 155 * @param attributionSource AttributionSource class to wrap, must not be null 156 * @return wrapped class 157 */ 158 @RequiresApi(Build.VERSION_CODES.S) 159 @NonNull toAppSearchAttributionSource( @onNull AttributionSource attributionSource, int pid)160 private static AppSearchAttributionSource toAppSearchAttributionSource( 161 @NonNull AttributionSource attributionSource, int pid) { 162 return new AppSearchAttributionSource(new Api31Impl(attributionSource, pid)); 163 } 164 165 /** 166 * Provides a backward-compatible wrapper for AttributionSource. 167 * 168 * <p>This method is not supported on devices running SDK <= 19(H) since the AttributionSource 169 * class will not be available. 170 * 171 * @param packageName The package name to wrap, must not be null 172 * @param uid The uid to wrap 173 * @return wrapped class 174 */ toAppSearchAttributionSource( @onNull String packageName, int uid, int pid)175 private static AppSearchAttributionSource toAppSearchAttributionSource( 176 @NonNull String packageName, int uid, int pid) { 177 return new AppSearchAttributionSource(new Api19Impl(packageName, uid, pid)); 178 } 179 180 /** 181 * Create an instance of AppSearchAttributionSource. 182 * 183 * @param context Context the application is running on. 184 */ createAttributionSource( @onNull Context context, int callingPid)185 public static AppSearchAttributionSource createAttributionSource( 186 @NonNull Context context, int callingPid) { 187 if (VERSION.SDK_INT >= Build.VERSION_CODES.S) { 188 return toAppSearchAttributionSource(context.getAttributionSource(), callingPid); 189 } 190 191 return toAppSearchAttributionSource(context.getPackageName(), Process.myUid(), callingPid); 192 } 193 194 /** Return AttributionSource on Android S+ and return null on Android R-. */ 195 @Nullable getAttributionSource()196 public AttributionSource getAttributionSource() { 197 return mCompat.getAttributionSource(); 198 } 199 200 @NonNull getPackageName()201 public String getPackageName() { 202 return mCompat.getPackageName(); 203 } 204 getUid()205 public int getUid() { 206 return mCompat.getUid(); 207 } 208 getPid()209 public int getPid() { 210 return mCompat.getPid(); 211 } 212 213 @Override hashCode()214 public int hashCode() { 215 if (VERSION.SDK_INT >= Build.VERSION_CODES.S) { 216 AttributionSource attributionSource = 217 Objects.requireNonNull(mCompat.getAttributionSource()); 218 return attributionSource.hashCode(); 219 } 220 221 return Objects.hash(mCompat.getUid(), mCompat.getPackageName()); 222 } 223 224 @Override equals(@ullable Object o)225 public boolean equals(@Nullable Object o) { 226 if (o == null || !(o instanceof AppSearchAttributionSource)) { 227 return false; 228 } 229 230 AppSearchAttributionSource that = (AppSearchAttributionSource) o; 231 if (VERSION.SDK_INT >= Build.VERSION_CODES.S) { 232 AttributionSource thisAttributionSource = 233 Objects.requireNonNull(mCompat.getAttributionSource()); 234 AttributionSource thatAttributionSource = 235 Objects.requireNonNull(that.getAttributionSource()); 236 return thisAttributionSource.equals(thatAttributionSource) 237 && (that.getPid() == mCompat.getPid()); 238 } 239 240 return (Objects.equals(mCompat.getPackageName(), that.getPackageName()) 241 && (mCompat.getUid() == that.getUid()) 242 && mCompat.getPid() == that.getPid()); 243 } 244 245 @Override writeToParcel(@onNull Parcel dest, int flags)246 public void writeToParcel(@NonNull Parcel dest, int flags) { 247 AppSearchAttributionSourceCreator.writeToParcel(this, dest, flags); 248 } 249 250 /** Compat class for AttributionSource to provide implementation for lower API levels. */ 251 private interface Compat { 252 /** The package that is accessing the permission protected data. */ 253 @NonNull getPackageName()254 String getPackageName(); 255 256 /** The attribution source of the app accessing the permission protected data. */ 257 @Nullable getAttributionSource()258 AttributionSource getAttributionSource(); 259 260 /** The UID that is accessing the permission protected data. */ getUid()261 int getUid(); 262 263 /** The PID that is accessing the permission protected data. */ getPid()264 int getPid(); 265 } 266 267 @RequiresApi(VERSION_CODES.S) 268 private static final class Api31Impl implements Compat { 269 270 private final AttributionSource mAttributionSource; 271 private final int mPid; 272 273 /** 274 * Creates a new implementation for AppSearchAttributionSource's Compat for API levels 31+. 275 * 276 * @param attributionSource The attribution source that is accessing permission protected 277 * data. 278 */ Api31Impl(@onNull AttributionSource attributionSource, int pid)279 Api31Impl(@NonNull AttributionSource attributionSource, int pid) { 280 mAttributionSource = attributionSource; 281 mPid = pid; 282 } 283 284 @Override 285 @NonNull getPackageName()286 public String getPackageName() { 287 // The {@link AttributionSource} in the constructor is set using 288 // {@link Context#getAttributionSource} and not using the Builder. The 289 // packageName returned from {@link AttributionSource#getPackageName} can be null as 290 // AttributionSource can use either uid and package name to determine who has access 291 // to the data, so either one of them can be null but not both. It is a common practice 292 // to use {@link AttributionSource#getPackageName} without any known issues/bugs. If 293 // we ever receive a null here we will throw a NullPointerException. 294 return Objects.requireNonNull(mAttributionSource.getPackageName()); 295 } 296 297 @Nullable 298 @Override getAttributionSource()299 public AttributionSource getAttributionSource() { 300 return mAttributionSource; 301 } 302 303 @Override getUid()304 public int getUid() { 305 return mAttributionSource.getUid(); 306 } 307 308 @Override getPid()309 public int getPid() { 310 return mPid; 311 } 312 } 313 314 private static class Api19Impl implements Compat { 315 316 @NonNull private final String mPackageName; 317 private final int mUid; 318 private final int mPid; 319 320 /** 321 * Creates a new implementation for AppSearchAttributionSource's Compat for API levels 19+. 322 * 323 * @param packageName The package name that is accessing permission protected data. 324 * @param uid The uid that is accessing permission protected data. 325 */ Api19Impl(@onNull String packageName, int uid, int pid)326 Api19Impl(@NonNull String packageName, int uid, int pid) { 327 mPackageName = Objects.requireNonNull(packageName); 328 mUid = uid; 329 mPid = pid; 330 } 331 332 @Override 333 @NonNull getPackageName()334 public String getPackageName() { 335 return mPackageName; 336 } 337 338 @Nullable 339 @Override getAttributionSource()340 public AttributionSource getAttributionSource() { 341 // AttributionSource class was added in Api level 31 and hence it is unavailable on API 342 // levels lower than 31. This class is used in AppSearch to get package name, uid etc, 343 // this implementation has util methods for getPackageName, getUid etc which could 344 // be used instead. 345 return null; 346 } 347 348 @Override getUid()349 public int getUid() { 350 return mUid; 351 } 352 353 @Override getPid()354 public int getPid() { 355 return mPid; 356 } 357 358 /** 359 * If you are handling an IPC and you don't trust the caller you need to validate whether 360 * the attribution source is one for the calling app to prevent the caller to pass you a 361 * source from another app without including themselves in the attribution chain. 362 * 363 * @throws SecurityException if the attribution source cannot be trusted to be from the 364 * caller. 365 */ enforceCallingUid()366 private void enforceCallingUid() { 367 if (!checkCallingUid()) { 368 int callingUid = Binder.getCallingUid(); 369 throw new SecurityException( 370 "Calling uid: " + callingUid + " doesn't match source uid: " + mUid); 371 } 372 // The verification for calling package happens in the service during API call. 373 } 374 375 /** 376 * If you are handling an IPC and you don't trust the caller you need to validate whether 377 * the attribution source is one for the calling app to prevent the caller to pass you a 378 * source from another app without including themselves in the attribution chain. 379 * 380 * @return if the attribution source cannot be trusted to be from the caller. 381 */ checkCallingUid()382 private boolean checkCallingUid() { 383 final int callingUid = Binder.getCallingUid(); 384 if (callingUid != mUid) { 385 return false; 386 } 387 // The verification for calling package happens in the service during API call. 388 return true; 389 } 390 391 /** 392 * Validate that the pid being claimed for the calling app is not spoofed. 393 * 394 * <p>Note that the PID may be unavailable, for example if we're in a oneway Binder call. In 395 * this case, calling enforceCallingPid is guaranteed to fail. The caller should anticipate 396 * this. 397 * 398 * @throws SecurityException if the attribution source cannot be trusted to be from the 399 * caller. 400 */ enforceCallingPid()401 private void enforceCallingPid() { 402 if (!checkCallingPid()) { 403 if (Binder.getCallingPid() == 0) { 404 throw new SecurityException( 405 "Calling pid unavailable due to oneway Binder " + "call."); 406 } else { 407 throw new SecurityException( 408 "Calling pid: " 409 + Binder.getCallingPid() 410 + " doesn't match source pid: " 411 + mPid); 412 } 413 } 414 } 415 416 /** 417 * Validate that the pid being claimed for the calling app is not spoofed 418 * 419 * @return if the attribution source cannot be trusted to be from the caller. 420 */ checkCallingPid()421 private boolean checkCallingPid() { 422 final int callingPid = Binder.getCallingPid(); 423 if (mPid != INVALID_PID && mPid != callingPid) { 424 // Only call this on the binder thread. If a new thread is created to handle the 425 // client request, Binder.getCallingPid() will return the thread's own pid. 426 return false; 427 } 428 return true; 429 } 430 } 431 } 432