1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.server.slice; 16 17 import android.content.ContentProvider; 18 import android.content.Context; 19 import android.net.Uri; 20 import android.os.Environment; 21 import android.os.Handler; 22 import android.os.Looper; 23 import android.os.Message; 24 import android.text.format.DateUtils; 25 import android.util.ArrayMap; 26 import android.util.ArraySet; 27 import android.util.AtomicFile; 28 import android.util.Log; 29 import android.util.Slog; 30 import android.util.Xml.Encoding; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.internal.util.XmlUtils; 34 import com.android.server.slice.SliceProviderPermissions.SliceAuthority; 35 36 import org.xmlpull.v1.XmlPullParser; 37 import org.xmlpull.v1.XmlPullParserException; 38 import org.xmlpull.v1.XmlPullParserFactory; 39 import org.xmlpull.v1.XmlSerializer; 40 41 import java.io.File; 42 import java.io.FileNotFoundException; 43 import java.io.FileOutputStream; 44 import java.io.IOException; 45 import java.io.InputStream; 46 import java.util.Objects; 47 48 public class SlicePermissionManager implements DirtyTracker { 49 50 private static final String TAG = "SlicePermissionManager"; 51 52 /** 53 * The amount of time we'll cache a SliceProviderPermissions or SliceClientPermissions 54 * in case they are used again. 55 */ 56 private static final long PERMISSION_CACHE_PERIOD = 5 * DateUtils.MINUTE_IN_MILLIS; 57 58 /** 59 * The amount of time we delay flushing out permission changes to disk because they usually 60 * come in short bursts. 61 */ 62 private static final long WRITE_GRACE_PERIOD = 500; 63 64 private static final String SLICE_DIR = "slice"; 65 66 // If/when this bumps again we'll need to write it out in the disk somewhere. 67 // Currently we don't have a central file for this in version 2 and there is no 68 // reason to add one until we actually have incompatible version bumps. 69 // This does however block us from reading backups from P-DP1 which may contain 70 // a very different XML format for perms. 71 static final int DB_VERSION = 2; 72 73 private static final String TAG_LIST = "slice-access-list"; 74 private final String ATT_VERSION = "version"; 75 76 private final File mSliceDir; 77 private final Context mContext; 78 private final Handler mHandler; 79 private final ArrayMap<PkgUser, SliceProviderPermissions> mCachedProviders = new ArrayMap<>(); 80 private final ArrayMap<PkgUser, SliceClientPermissions> mCachedClients = new ArrayMap<>(); 81 private final ArraySet<Persistable> mDirty = new ArraySet<>(); 82 83 @VisibleForTesting SlicePermissionManager(Context context, Looper looper, File sliceDir)84 SlicePermissionManager(Context context, Looper looper, File sliceDir) { 85 mContext = context; 86 mHandler = new H(looper); 87 mSliceDir = sliceDir; 88 } 89 SlicePermissionManager(Context context, Looper looper)90 public SlicePermissionManager(Context context, Looper looper) { 91 this(context, looper, new File(Environment.getDataDirectory(), "system/" + SLICE_DIR)); 92 } 93 grantFullAccess(String pkg, int userId)94 public void grantFullAccess(String pkg, int userId) { 95 PkgUser pkgUser = new PkgUser(pkg, userId); 96 SliceClientPermissions client = getClient(pkgUser); 97 client.setHasFullAccess(true); 98 } 99 grantSliceAccess(String pkg, int userId, String providerPkg, int providerUser, Uri uri)100 public void grantSliceAccess(String pkg, int userId, String providerPkg, int providerUser, 101 Uri uri) { 102 PkgUser pkgUser = new PkgUser(pkg, userId); 103 PkgUser providerPkgUser = new PkgUser(providerPkg, providerUser); 104 105 SliceClientPermissions client = getClient(pkgUser); 106 client.grantUri(uri, providerPkgUser); 107 108 SliceProviderPermissions provider = getProvider(providerPkgUser); 109 provider.getOrCreateAuthority(ContentProvider.getUriWithoutUserId(uri).getAuthority()) 110 .addPkg(pkgUser); 111 } 112 revokeSliceAccess(String pkg, int userId, String providerPkg, int providerUser, Uri uri)113 public void revokeSliceAccess(String pkg, int userId, String providerPkg, int providerUser, 114 Uri uri) { 115 PkgUser pkgUser = new PkgUser(pkg, userId); 116 PkgUser providerPkgUser = new PkgUser(providerPkg, providerUser); 117 118 SliceClientPermissions client = getClient(pkgUser); 119 client.revokeUri(uri, providerPkgUser); 120 } 121 removePkg(String pkg, int userId)122 public void removePkg(String pkg, int userId) { 123 PkgUser pkgUser = new PkgUser(pkg, userId); 124 SliceProviderPermissions provider = getProvider(pkgUser); 125 126 for (SliceAuthority authority : provider.getAuthorities()) { 127 for (PkgUser p : authority.getPkgs()) { 128 getClient(p).removeAuthority(authority.getAuthority(), userId); 129 } 130 } 131 SliceClientPermissions client = getClient(pkgUser); 132 client.clear(); 133 mHandler.obtainMessage(H.MSG_REMOVE, pkgUser).sendToTarget(); 134 } 135 getAllPackagesGranted(String pkg)136 public String[] getAllPackagesGranted(String pkg) { 137 ArraySet<String> ret = new ArraySet<>(); 138 for (SliceAuthority authority : getProvider(new PkgUser(pkg, 0)).getAuthorities()) { 139 for (PkgUser pkgUser : authority.getPkgs()) { 140 ret.add(pkgUser.mPkg); 141 } 142 } 143 return ret.toArray(new String[ret.size()]); 144 } 145 hasFullAccess(String pkg, int userId)146 public boolean hasFullAccess(String pkg, int userId) { 147 PkgUser pkgUser = new PkgUser(pkg, userId); 148 return getClient(pkgUser).hasFullAccess(); 149 } 150 hasPermission(String pkg, int userId, Uri uri)151 public boolean hasPermission(String pkg, int userId, Uri uri) { 152 PkgUser pkgUser = new PkgUser(pkg, userId); 153 SliceClientPermissions client = getClient(pkgUser); 154 int providerUserId = ContentProvider.getUserIdFromUri(uri, userId); 155 return client.hasFullAccess() 156 || client.hasPermission(ContentProvider.getUriWithoutUserId(uri), providerUserId); 157 } 158 159 @Override onPersistableDirty(Persistable obj)160 public void onPersistableDirty(Persistable obj) { 161 mHandler.removeMessages(H.MSG_PERSIST); 162 mHandler.obtainMessage(H.MSG_ADD_DIRTY, obj).sendToTarget(); 163 mHandler.sendEmptyMessageDelayed(H.MSG_PERSIST, WRITE_GRACE_PERIOD); 164 } 165 writeBackup(XmlSerializer out)166 public void writeBackup(XmlSerializer out) throws IOException, XmlPullParserException { 167 synchronized (this) { 168 out.startTag(null, TAG_LIST); 169 out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION)); 170 171 // Don't do anything with changes from the backup, because there shouldn't be any. 172 DirtyTracker tracker = obj -> { }; 173 if (mHandler.hasMessages(H.MSG_PERSIST)) { 174 mHandler.removeMessages(H.MSG_PERSIST); 175 handlePersist(); 176 } 177 for (String file : new File(mSliceDir.getAbsolutePath()).list()) { 178 try (ParserHolder parser = getParser(file)) { 179 Persistable p = null; 180 while (parser.parser.getEventType() != XmlPullParser.END_DOCUMENT) { 181 if (parser.parser.getEventType() == XmlPullParser.START_TAG) { 182 if (SliceClientPermissions.TAG_CLIENT.equals(parser.parser.getName())) { 183 p = SliceClientPermissions.createFrom(parser.parser, tracker); 184 } else { 185 p = SliceProviderPermissions.createFrom(parser.parser, tracker); 186 } 187 break; 188 } 189 parser.parser.next(); 190 } 191 if (p != null) { 192 p.writeTo(out); 193 } else { 194 Slog.w(TAG, "Invalid or empty slice permissions file: " + file); 195 } 196 } 197 } 198 199 out.endTag(null, TAG_LIST); 200 } 201 } 202 readRestore(XmlPullParser parser)203 public void readRestore(XmlPullParser parser) throws IOException, XmlPullParserException { 204 synchronized (this) { 205 while ((parser.getEventType() != XmlPullParser.START_TAG 206 || !TAG_LIST.equals(parser.getName())) 207 && parser.getEventType() != XmlPullParser.END_DOCUMENT) { 208 parser.next(); 209 } 210 int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0); 211 if (xmlVersion < DB_VERSION) { 212 // No conversion support right now. 213 return; 214 } 215 while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { 216 if (parser.getEventType() == XmlPullParser.START_TAG) { 217 if (SliceClientPermissions.TAG_CLIENT.equals(parser.getName())) { 218 SliceClientPermissions client = SliceClientPermissions.createFrom(parser, 219 this); 220 synchronized (mCachedClients) { 221 mCachedClients.put(client.getPkg(), client); 222 } 223 onPersistableDirty(client); 224 mHandler.sendMessageDelayed( 225 mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, client.getPkg()), 226 PERMISSION_CACHE_PERIOD); 227 } else if (SliceProviderPermissions.TAG_PROVIDER.equals(parser.getName())) { 228 SliceProviderPermissions provider = SliceProviderPermissions.createFrom( 229 parser, this); 230 synchronized (mCachedProviders) { 231 mCachedProviders.put(provider.getPkg(), provider); 232 } 233 onPersistableDirty(provider); 234 mHandler.sendMessageDelayed( 235 mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, provider.getPkg()), 236 PERMISSION_CACHE_PERIOD); 237 } else { 238 parser.next(); 239 } 240 } else { 241 parser.next(); 242 } 243 } 244 } 245 } 246 getClient(PkgUser pkgUser)247 private SliceClientPermissions getClient(PkgUser pkgUser) { 248 SliceClientPermissions client; 249 synchronized (mCachedClients) { 250 client = mCachedClients.get(pkgUser); 251 } 252 if (client == null) { 253 try (ParserHolder parser = getParser(SliceClientPermissions.getFileName(pkgUser))) { 254 client = SliceClientPermissions.createFrom(parser.parser, this); 255 synchronized (mCachedClients) { 256 mCachedClients.put(pkgUser, client); 257 } 258 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, pkgUser), 259 PERMISSION_CACHE_PERIOD); 260 return client; 261 } catch (FileNotFoundException e) { 262 // No client exists yet. 263 } catch (IOException e) { 264 Log.e(TAG, "Can't read client", e); 265 } catch (XmlPullParserException e) { 266 Log.e(TAG, "Can't read client", e); 267 } 268 // Can't read or no permissions exist, create a clean object. 269 client = new SliceClientPermissions(pkgUser, this); 270 synchronized (mCachedClients) { 271 mCachedClients.put(pkgUser, client); 272 } 273 } 274 return client; 275 } 276 getProvider(PkgUser pkgUser)277 private SliceProviderPermissions getProvider(PkgUser pkgUser) { 278 SliceProviderPermissions provider; 279 synchronized (mCachedProviders) { 280 provider = mCachedProviders.get(pkgUser); 281 } 282 if (provider == null) { 283 try (ParserHolder parser = getParser(SliceProviderPermissions.getFileName(pkgUser))) { 284 provider = SliceProviderPermissions.createFrom(parser.parser, this); 285 synchronized (mCachedProviders) { 286 mCachedProviders.put(pkgUser, provider); 287 } 288 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, pkgUser), 289 PERMISSION_CACHE_PERIOD); 290 return provider; 291 } catch (FileNotFoundException e) { 292 // No provider exists yet. 293 } catch (IOException e) { 294 Log.e(TAG, "Can't read provider", e); 295 } catch (XmlPullParserException e) { 296 Log.e(TAG, "Can't read provider", e); 297 } 298 // Can't read or no permissions exist, create a clean object. 299 provider = new SliceProviderPermissions(pkgUser, this); 300 synchronized (mCachedProviders) { 301 mCachedProviders.put(pkgUser, provider); 302 } 303 } 304 return provider; 305 } 306 getParser(String fileName)307 private ParserHolder getParser(String fileName) 308 throws FileNotFoundException, XmlPullParserException { 309 AtomicFile file = getFile(fileName); 310 ParserHolder holder = new ParserHolder(); 311 holder.input = file.openRead(); 312 holder.parser = XmlPullParserFactory.newInstance().newPullParser(); 313 holder.parser.setInput(holder.input, Encoding.UTF_8.name()); 314 return holder; 315 } 316 getFile(String fileName)317 private AtomicFile getFile(String fileName) { 318 if (!mSliceDir.exists()) { 319 mSliceDir.mkdir(); 320 } 321 return new AtomicFile(new File(mSliceDir, fileName)); 322 } 323 324 @VisibleForTesting handlePersist()325 void handlePersist() { 326 synchronized (this) { 327 for (Persistable persistable : mDirty) { 328 AtomicFile file = getFile(persistable.getFileName()); 329 final FileOutputStream stream; 330 try { 331 stream = file.startWrite(); 332 } catch (IOException e) { 333 Slog.w(TAG, "Failed to save access file", e); 334 return; 335 } 336 337 try { 338 XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer(); 339 out.setOutput(stream, Encoding.UTF_8.name()); 340 341 persistable.writeTo(out); 342 343 out.flush(); 344 file.finishWrite(stream); 345 } catch (IOException | XmlPullParserException | RuntimeException e) { 346 Slog.w(TAG, "Failed to save access file, restoring backup", e); 347 file.failWrite(stream); 348 } 349 } 350 mDirty.clear(); 351 } 352 } 353 354 // use addPersistableDirty(); this is just for tests 355 @VisibleForTesting addDirtyImmediate(Persistable obj)356 void addDirtyImmediate(Persistable obj) { 357 mDirty.add(obj); 358 } 359 handleRemove(PkgUser pkgUser)360 private void handleRemove(PkgUser pkgUser) { 361 getFile(SliceClientPermissions.getFileName(pkgUser)).delete(); 362 getFile(SliceProviderPermissions.getFileName(pkgUser)).delete(); 363 mDirty.remove(mCachedClients.remove(pkgUser)); 364 mDirty.remove(mCachedProviders.remove(pkgUser)); 365 } 366 367 private final class H extends Handler { 368 private static final int MSG_ADD_DIRTY = 1; 369 private static final int MSG_PERSIST = 2; 370 private static final int MSG_REMOVE = 3; 371 private static final int MSG_CLEAR_CLIENT = 4; 372 private static final int MSG_CLEAR_PROVIDER = 5; 373 H(Looper looper)374 public H(Looper looper) { 375 super(looper); 376 } 377 378 @Override handleMessage(Message msg)379 public void handleMessage(Message msg) { 380 switch (msg.what) { 381 case MSG_ADD_DIRTY: 382 mDirty.add((Persistable) msg.obj); 383 break; 384 case MSG_PERSIST: 385 handlePersist(); 386 break; 387 case MSG_REMOVE: 388 handleRemove((PkgUser) msg.obj); 389 break; 390 case MSG_CLEAR_CLIENT: 391 synchronized (mCachedClients) { 392 mCachedClients.remove(msg.obj); 393 } 394 break; 395 case MSG_CLEAR_PROVIDER: 396 synchronized (mCachedProviders) { 397 mCachedProviders.remove(msg.obj); 398 } 399 break; 400 } 401 } 402 } 403 404 public static class PkgUser { 405 private static final String SEPARATOR = "@"; 406 private static final String FORMAT = "%s" + SEPARATOR + "%d"; 407 private final String mPkg; 408 private final int mUserId; 409 PkgUser(String pkg, int userId)410 public PkgUser(String pkg, int userId) { 411 mPkg = pkg; 412 mUserId = userId; 413 } 414 PkgUser(String pkgUserStr)415 public PkgUser(String pkgUserStr) throws IllegalArgumentException { 416 try { 417 String[] vals = pkgUserStr.split(SEPARATOR, 2); 418 mPkg = vals[0]; 419 mUserId = Integer.parseInt(vals[1]); 420 } catch (Exception e) { 421 throw new IllegalArgumentException(e); 422 } 423 } 424 getPkg()425 public String getPkg() { 426 return mPkg; 427 } 428 getUserId()429 public int getUserId() { 430 return mUserId; 431 } 432 433 @Override hashCode()434 public int hashCode() { 435 return mPkg.hashCode() + mUserId; 436 } 437 438 @Override equals(Object obj)439 public boolean equals(Object obj) { 440 if (!getClass().equals(obj != null ? obj.getClass() : null)) return false; 441 PkgUser other = (PkgUser) obj; 442 return Objects.equals(other.mPkg, mPkg) && other.mUserId == mUserId; 443 } 444 445 @Override toString()446 public String toString() { 447 return String.format(FORMAT, mPkg, mUserId); 448 } 449 } 450 451 private class ParserHolder implements AutoCloseable { 452 453 private InputStream input; 454 private XmlPullParser parser; 455 456 @Override close()457 public void close() throws IOException { 458 input.close(); 459 } 460 } 461 } 462