1 /* 2 * Copyright (C) 2014 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 com.android.server.locksettings; 18 19 import static android.content.Context.USER_SERVICE; 20 21 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; 22 import static com.android.internal.widget.LockPatternUtils.isSpecialUserId; 23 24 import android.annotation.Nullable; 25 import android.app.admin.DevicePolicyManager; 26 import android.app.backup.BackupManager; 27 import android.content.ContentValues; 28 import android.content.Context; 29 import android.content.pm.UserInfo; 30 import android.database.Cursor; 31 import android.database.sqlite.SQLiteDatabase; 32 import android.database.sqlite.SQLiteOpenHelper; 33 import android.os.Environment; 34 import android.os.UserHandle; 35 import android.os.UserManager; 36 import android.provider.Settings; 37 import android.text.TextUtils; 38 import android.util.ArrayMap; 39 import android.util.AtomicFile; 40 import android.util.Slog; 41 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.internal.util.ArrayUtils; 44 import com.android.internal.util.IndentingPrintWriter; 45 import com.android.internal.util.Preconditions; 46 import com.android.internal.widget.LockPatternUtils; 47 import com.android.server.LocalServices; 48 import com.android.server.PersistentDataBlockManagerInternal; 49 50 import java.io.ByteArrayInputStream; 51 import java.io.ByteArrayOutputStream; 52 import java.io.DataInputStream; 53 import java.io.DataOutputStream; 54 import java.io.File; 55 import java.io.FileNotFoundException; 56 import java.io.FileOutputStream; 57 import java.io.IOException; 58 import java.io.RandomAccessFile; 59 import java.nio.channels.FileChannel; 60 import java.nio.file.StandardOpenOption; 61 import java.util.ArrayList; 62 import java.util.Arrays; 63 import java.util.List; 64 import java.util.Map; 65 import java.util.Objects; 66 67 /** 68 * Storage for the lock settings service. 69 */ 70 class LockSettingsStorage { 71 72 private static final String TAG = "LockSettingsStorage"; 73 private static final String TABLE = "locksettings"; 74 75 private static final String COLUMN_KEY = "name"; 76 private static final String COLUMN_USERID = "user"; 77 private static final String COLUMN_VALUE = "value"; 78 79 private static final String[] COLUMNS_FOR_QUERY = { 80 COLUMN_VALUE 81 }; 82 private static final String[] COLUMNS_FOR_PREFETCH = { 83 COLUMN_KEY, COLUMN_VALUE 84 }; 85 86 private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key"; 87 88 private static final String REBOOT_ESCROW_FILE = "reboot.escrow.key"; 89 private static final String REBOOT_ESCROW_SERVER_BLOB_FILE = "reboot.escrow.server.blob.key"; 90 91 private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/"; 92 93 private static final String REPAIR_MODE_DIRECTORY = "repair-mode/"; 94 private static final String REPAIR_MODE_PERSISTENT_FILE = "pst"; 95 96 private static final Object DEFAULT = new Object(); 97 98 private static final String[] SETTINGS_TO_BACKUP = new String[] { 99 Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 100 Settings.Secure.LOCK_SCREEN_OWNER_INFO, 101 Settings.Secure.LOCK_PATTERN_VISIBLE, 102 LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS 103 }; 104 105 private final DatabaseHelper mOpenHelper; 106 private final Context mContext; 107 private final Cache mCache = new Cache(); 108 private final Object mFileWriteLock = new Object(); 109 110 private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal; 111 LockSettingsStorage(Context context)112 public LockSettingsStorage(Context context) { 113 mContext = context; 114 mOpenHelper = new DatabaseHelper(context); 115 } 116 setDatabaseOnCreateCallback(Callback callback)117 public void setDatabaseOnCreateCallback(Callback callback) { 118 mOpenHelper.setCallback(callback); 119 } 120 121 @VisibleForTesting(visibility = PACKAGE) writeKeyValue(String key, String value, int userId)122 public void writeKeyValue(String key, String value, int userId) { 123 writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId); 124 } 125 126 @VisibleForTesting isAutoPinConfirmSettingEnabled(int userId)127 public boolean isAutoPinConfirmSettingEnabled(int userId) { 128 return getBoolean(LockPatternUtils.AUTO_PIN_CONFIRM, false, userId); 129 } 130 131 @VisibleForTesting writeKeyValue(SQLiteDatabase db, String key, String value, int userId)132 public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) { 133 ContentValues cv = new ContentValues(); 134 cv.put(COLUMN_KEY, key); 135 cv.put(COLUMN_USERID, userId); 136 cv.put(COLUMN_VALUE, value); 137 138 db.beginTransaction(); 139 try { 140 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", 141 new String[] {key, Integer.toString(userId)}); 142 db.insert(TABLE, null, cv); 143 db.setTransactionSuccessful(); 144 mCache.putKeyValue(key, value, userId); 145 } finally { 146 db.endTransaction(); 147 } 148 } 149 150 @VisibleForTesting readKeyValue(String key, String defaultValue, int userId)151 public String readKeyValue(String key, String defaultValue, int userId) { 152 int version; 153 synchronized (mCache) { 154 if (mCache.hasKeyValue(key, userId)) { 155 return mCache.peekKeyValue(key, defaultValue, userId); 156 } 157 version = mCache.getVersion(); 158 } 159 160 Cursor cursor; 161 Object result = DEFAULT; 162 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 163 if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY, 164 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?", 165 new String[] { Integer.toString(userId), key }, 166 null, null, null)) != null) { 167 if (cursor.moveToFirst()) { 168 result = cursor.getString(0); 169 } 170 cursor.close(); 171 } 172 mCache.putKeyValueIfUnchanged(key, result, userId, version); 173 return result == DEFAULT ? defaultValue : (String) result; 174 } 175 176 @VisibleForTesting isKeyValueCached(String key, int userId)177 boolean isKeyValueCached(String key, int userId) { 178 return mCache.hasKeyValue(key, userId); 179 } 180 181 @VisibleForTesting isUserPrefetched(int userId)182 boolean isUserPrefetched(int userId) { 183 return mCache.isFetched(userId); 184 } 185 186 @VisibleForTesting removeKey(String key, int userId)187 public void removeKey(String key, int userId) { 188 removeKey(mOpenHelper.getWritableDatabase(), key, userId); 189 } 190 removeKey(SQLiteDatabase db, String key, int userId)191 private void removeKey(SQLiteDatabase db, String key, int userId) { 192 ContentValues cv = new ContentValues(); 193 cv.put(COLUMN_KEY, key); 194 cv.put(COLUMN_USERID, userId); 195 196 db.beginTransaction(); 197 try { 198 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", 199 new String[] {key, Integer.toString(userId)}); 200 db.setTransactionSuccessful(); 201 mCache.removeKey(key, userId); 202 } finally { 203 db.endTransaction(); 204 } 205 } 206 prefetchUser(int userId)207 public void prefetchUser(int userId) { 208 int version; 209 synchronized (mCache) { 210 if (mCache.isFetched(userId)) { 211 return; 212 } 213 mCache.setFetched(userId); 214 version = mCache.getVersion(); 215 } 216 217 Cursor cursor; 218 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 219 if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH, 220 COLUMN_USERID + "=?", 221 new String[] { Integer.toString(userId) }, 222 null, null, null)) != null) { 223 while (cursor.moveToNext()) { 224 String key = cursor.getString(0); 225 String value = cursor.getString(1); 226 mCache.putKeyValueIfUnchanged(key, value, userId, version); 227 } 228 cursor.close(); 229 } 230 } 231 removeChildProfileLock(int userId)232 public void removeChildProfileLock(int userId) { 233 deleteFile(getChildProfileLockFile(userId)); 234 } 235 writeChildProfileLock(int userId, byte[] lock)236 public void writeChildProfileLock(int userId, byte[] lock) { 237 writeFile(getChildProfileLockFile(userId), lock); 238 } 239 readChildProfileLock(int userId)240 public byte[] readChildProfileLock(int userId) { 241 return readFile(getChildProfileLockFile(userId)); 242 } 243 hasChildProfileLock(int userId)244 public boolean hasChildProfileLock(int userId) { 245 return hasFile(getChildProfileLockFile(userId)); 246 } 247 writeRebootEscrow(int userId, byte[] rebootEscrow)248 public void writeRebootEscrow(int userId, byte[] rebootEscrow) { 249 writeFile(getRebootEscrowFile(userId), rebootEscrow); 250 } 251 readRebootEscrow(int userId)252 public byte[] readRebootEscrow(int userId) { 253 return readFile(getRebootEscrowFile(userId)); 254 } 255 hasRebootEscrow(int userId)256 public boolean hasRebootEscrow(int userId) { 257 return hasFile(getRebootEscrowFile(userId)); 258 } 259 removeRebootEscrow(int userId)260 public void removeRebootEscrow(int userId) { 261 deleteFile(getRebootEscrowFile(userId)); 262 } 263 writeRebootEscrowServerBlob(byte[] serverBlob)264 public void writeRebootEscrowServerBlob(byte[] serverBlob) { 265 writeFile(getRebootEscrowServerBlobFile(), serverBlob); 266 } 267 readRebootEscrowServerBlob()268 public byte[] readRebootEscrowServerBlob() { 269 return readFile(getRebootEscrowServerBlobFile()); 270 } 271 hasRebootEscrowServerBlob()272 public boolean hasRebootEscrowServerBlob() { 273 return hasFile(getRebootEscrowServerBlobFile()); 274 } 275 removeRebootEscrowServerBlob()276 public void removeRebootEscrowServerBlob() { 277 deleteFile(getRebootEscrowServerBlobFile()); 278 } 279 hasFile(File path)280 private boolean hasFile(File path) { 281 byte[] contents = readFile(path); 282 return contents != null && contents.length > 0; 283 } 284 readFile(File path)285 private byte[] readFile(File path) { 286 int version; 287 synchronized (mCache) { 288 if (mCache.hasFile(path)) { 289 return mCache.peekFile(path); 290 } 291 version = mCache.getVersion(); 292 } 293 294 byte[] data = null; 295 try (RandomAccessFile raf = new RandomAccessFile(path, "r")) { 296 data = new byte[(int) raf.length()]; 297 raf.readFully(data, 0, data.length); 298 raf.close(); 299 } catch (FileNotFoundException suppressed) { 300 // readFile() is also called by hasFile() to check the existence of files, in this 301 // case FileNotFoundException is expected. 302 } catch (IOException e) { 303 Slog.e(TAG, "Cannot read file " + e); 304 } 305 mCache.putFileIfUnchanged(path, data, version); 306 return data; 307 } 308 fsyncDirectory(File directory)309 private void fsyncDirectory(File directory) { 310 try { 311 try (FileChannel file = FileChannel.open(directory.toPath(), 312 StandardOpenOption.READ)) { 313 file.force(true); 314 } 315 } catch (IOException e) { 316 Slog.e(TAG, "Error syncing directory: " + directory, e); 317 } 318 } 319 writeFile(File path, byte[] data)320 private void writeFile(File path, byte[] data) { 321 writeFile(path, data, /* syncParentDir= */ true); 322 } 323 writeFile(File path, byte[] data, boolean syncParentDir)324 private void writeFile(File path, byte[] data, boolean syncParentDir) { 325 synchronized (mFileWriteLock) { 326 // Use AtomicFile to guarantee atomicity of the file write, including when an existing 327 // file is replaced with a new one. This method is usually used to create new files, 328 // but there are some edge cases in which it is used to replace an existing file. 329 AtomicFile file = new AtomicFile(path); 330 FileOutputStream out = null; 331 try { 332 out = file.startWrite(); 333 out.write(data); 334 file.finishWrite(out); 335 out = null; 336 } catch (IOException e) { 337 Slog.e(TAG, "Error writing file " + path, e); 338 } finally { 339 file.failWrite(out); 340 } 341 // For performance reasons, AtomicFile only syncs the file itself, not also the parent 342 // directory. The latter must be done explicitly when requested here, as some callers 343 // need a guarantee that the file really exists on-disk when this returns. 344 if (syncParentDir) { 345 fsyncDirectory(path.getParentFile()); 346 } 347 mCache.putFile(path, data); 348 } 349 } 350 deleteFile(File path)351 private void deleteFile(File path) { 352 synchronized (mFileWriteLock) { 353 // Zeroize the file to try to make its contents unrecoverable. This is *not* guaranteed 354 // to be effective, and in fact it usually isn't, but it doesn't hurt. We also don't 355 // bother zeroizing |path|.new, which may exist from an interrupted AtomicFile write. 356 if (path.exists()) { 357 try (RandomAccessFile raf = new RandomAccessFile(path, "rws")) { 358 final int fileSize = (int) raf.length(); 359 raf.write(new byte[fileSize]); 360 } catch (Exception e) { 361 Slog.w(TAG, "Failed to zeroize " + path, e); 362 } 363 } 364 // To ensure that |path|.new is deleted if it exists, use AtomicFile.delete() here. 365 new AtomicFile(path).delete(); 366 mCache.putFile(path, null); 367 } 368 } 369 370 @VisibleForTesting getChildProfileLockFile(int userId)371 File getChildProfileLockFile(int userId) { 372 return getLockCredentialFileForUser(userId, CHILD_PROFILE_LOCK_FILE); 373 } 374 375 @VisibleForTesting getRebootEscrowFile(int userId)376 File getRebootEscrowFile(int userId) { 377 return getLockCredentialFileForUser(userId, REBOOT_ESCROW_FILE); 378 } 379 380 @VisibleForTesting getRebootEscrowServerBlobFile()381 File getRebootEscrowServerBlobFile() { 382 // There is a single copy of server blob for all users. 383 return getLockCredentialFileForUser(UserHandle.USER_SYSTEM, REBOOT_ESCROW_SERVER_BLOB_FILE); 384 } 385 getLockCredentialFileForUser(int userId, String fileName)386 private File getLockCredentialFileForUser(int userId, String fileName) { 387 if (userId == 0) { 388 // The files for user 0 are stored directly in /data/system, since this is where they 389 // originally were, and they haven't been moved yet. 390 return new File(Environment.getDataSystemDirectory(), fileName); 391 } else { 392 return new File(Environment.getUserSystemDirectory(userId), fileName); 393 } 394 } 395 396 @VisibleForTesting getRepairModePersistentDataFile()397 File getRepairModePersistentDataFile() { 398 final File directory = new File(Environment.getMetadataDirectory(), REPAIR_MODE_DIRECTORY); 399 return new File(directory, REPAIR_MODE_PERSISTENT_FILE); 400 } 401 readRepairModePersistentData()402 public PersistentData readRepairModePersistentData() { 403 final byte[] data = readFile(getRepairModePersistentDataFile()); 404 if (data == null) { 405 return PersistentData.NONE; 406 } 407 return PersistentData.fromBytes(data); 408 } 409 writeRepairModePersistentData(int persistentType, int userId, byte[] payload)410 public void writeRepairModePersistentData(int persistentType, int userId, byte[] payload) { 411 writeFile(getRepairModePersistentDataFile(), 412 PersistentData.toBytes(persistentType, userId, /* qualityForUi= */0, payload)); 413 } 414 deleteRepairModePersistentData()415 public void deleteRepairModePersistentData() { 416 deleteFile(getRepairModePersistentDataFile()); 417 } 418 419 /** 420 * Writes the synthetic password state file for the given user ID, protector ID, and state name. 421 * If the file already exists, then it is atomically replaced. 422 * <p> 423 * This doesn't sync the parent directory, and a result the new state file may be lost if the 424 * system crashes. The caller must call {@link syncSyntheticPasswordState()} afterwards to sync 425 * the parent directory if needed, preferably after batching up other state file creations for 426 * the same user. We do it this way because directory syncs are expensive on some filesystems. 427 */ writeSyntheticPasswordState(int userId, long protectorId, String name, byte[] data)428 public void writeSyntheticPasswordState(int userId, long protectorId, String name, 429 byte[] data) { 430 ensureSyntheticPasswordDirectoryForUser(userId); 431 writeFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name), data, 432 /* syncParentDir= */ false); 433 } 434 readSyntheticPasswordState(int userId, long protectorId, String name)435 public byte[] readSyntheticPasswordState(int userId, long protectorId, String name) { 436 return readFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name)); 437 } 438 deleteSyntheticPasswordState(int userId, long protectorId, String name)439 public void deleteSyntheticPasswordState(int userId, long protectorId, String name) { 440 deleteFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name)); 441 } 442 443 /** 444 * Ensures that all synthetic password state files for the user have really been saved to disk. 445 */ syncSyntheticPasswordState(int userId)446 public void syncSyntheticPasswordState(int userId) { 447 fsyncDirectory(getSyntheticPasswordDirectoryForUser(userId)); 448 } 449 listSyntheticPasswordProtectorsForAllUsers(String stateName)450 public Map<Integer, List<Long>> listSyntheticPasswordProtectorsForAllUsers(String stateName) { 451 Map<Integer, List<Long>> result = new ArrayMap<>(); 452 final UserManager um = UserManager.get(mContext); 453 for (UserInfo user : um.getUsers()) { 454 result.put(user.id, listSyntheticPasswordProtectorsForUser(stateName, user.id)); 455 } 456 return result; 457 } 458 listSyntheticPasswordProtectorsForUser(String stateName, int userId)459 public List<Long> listSyntheticPasswordProtectorsForUser(String stateName, int userId) { 460 File baseDir = getSyntheticPasswordDirectoryForUser(userId); 461 List<Long> result = new ArrayList<>(); 462 File[] files = baseDir.listFiles(); 463 if (files == null) { 464 return result; 465 } 466 for (File file : files) { 467 String[] parts = file.getName().split("\\."); 468 if (parts.length == 2 && parts[1].equals(stateName)) { 469 try { 470 result.add(Long.parseUnsignedLong(parts[0], 16)); 471 } catch (NumberFormatException e) { 472 Slog.e(TAG, "Failed to parse protector ID " + parts[0]); 473 } 474 } 475 } 476 return result; 477 } 478 479 @VisibleForTesting getSyntheticPasswordDirectoryForUser(int userId)480 protected File getSyntheticPasswordDirectoryForUser(int userId) { 481 return new File(Environment.getDataSystemDeDirectory(userId), SYNTHETIC_PASSWORD_DIRECTORY); 482 } 483 484 /** Ensure per-user directory for synthetic password state exists */ ensureSyntheticPasswordDirectoryForUser(int userId)485 private void ensureSyntheticPasswordDirectoryForUser(int userId) { 486 File baseDir = getSyntheticPasswordDirectoryForUser(userId); 487 if (!baseDir.exists()) { 488 baseDir.mkdir(); 489 } 490 } 491 getSyntheticPasswordStateFileForUser(int userId, long protectorId, String name)492 private File getSyntheticPasswordStateFileForUser(int userId, long protectorId, String name) { 493 String fileName = TextUtils.formatSimple("%016x.%s", protectorId, name); 494 return new File(getSyntheticPasswordDirectoryForUser(userId), fileName); 495 } 496 removeUser(int userId)497 public void removeUser(int userId) { 498 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 499 500 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); 501 final UserInfo parentInfo = um.getProfileParent(userId); 502 503 if (parentInfo == null) { 504 // This user owns its lock settings files - safe to delete them 505 deleteFile(getRebootEscrowFile(userId)); 506 } else { 507 // Managed profile 508 removeChildProfileLock(userId); 509 } 510 511 File spStateDir = getSyntheticPasswordDirectoryForUser(userId); 512 try { 513 db.beginTransaction(); 514 db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null); 515 db.setTransactionSuccessful(); 516 mCache.removeUser(userId); 517 // The directory itself will be deleted as part of user deletion operation by the 518 // framework, so only need to purge cache here. 519 //TODO: (b/34600579) invoke secdiscardable 520 mCache.purgePath(spStateDir); 521 } finally { 522 db.endTransaction(); 523 } 524 } 525 setBoolean(String key, boolean value, int userId)526 public void setBoolean(String key, boolean value, int userId) { 527 setString(key, value ? "1" : "0", userId); 528 } 529 setLong(String key, long value, int userId)530 public void setLong(String key, long value, int userId) { 531 setString(key, Long.toString(value), userId); 532 } 533 setInt(String key, int value, int userId)534 public void setInt(String key, int value, int userId) { 535 setString(key, Integer.toString(value), userId); 536 } 537 setString(String key, String value, int userId)538 public void setString(String key, String value, int userId) { 539 Preconditions.checkArgument(!isSpecialUserId(userId), 540 "cannot store lock settings for special user: %d", userId); 541 542 writeKeyValue(key, value, userId); 543 if (ArrayUtils.contains(SETTINGS_TO_BACKUP, key)) { 544 BackupManager.dataChanged("com.android.providers.settings"); 545 } 546 } 547 getBoolean(String key, boolean defaultValue, int userId)548 public boolean getBoolean(String key, boolean defaultValue, int userId) { 549 String value = getString(key, null, userId); 550 return TextUtils.isEmpty(value) 551 ? defaultValue : (value.equals("1") || value.equals("true")); 552 } 553 getLong(String key, long defaultValue, int userId)554 public long getLong(String key, long defaultValue, int userId) { 555 String value = getString(key, null, userId); 556 return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value); 557 } 558 getInt(String key, int defaultValue, int userId)559 public int getInt(String key, int defaultValue, int userId) { 560 String value = getString(key, null, userId); 561 return TextUtils.isEmpty(value) ? defaultValue : Integer.parseInt(value); 562 } 563 getString(String key, String defaultValue, int userId)564 public String getString(String key, String defaultValue, int userId) { 565 if (isSpecialUserId(userId)) { 566 return null; 567 } 568 return readKeyValue(key, defaultValue, userId); 569 } 570 571 @VisibleForTesting closeDatabase()572 void closeDatabase() { 573 mOpenHelper.close(); 574 } 575 576 @VisibleForTesting clearCache()577 void clearCache() { 578 mCache.clear(); 579 } 580 581 @Nullable getPersistentDataBlockManager()582 PersistentDataBlockManagerInternal getPersistentDataBlockManager() { 583 if (mPersistentDataBlockManagerInternal == null) { 584 mPersistentDataBlockManagerInternal = 585 LocalServices.getService(PersistentDataBlockManagerInternal.class); 586 } 587 return mPersistentDataBlockManagerInternal; 588 } 589 writePersistentDataBlock(int persistentType, int userId, int qualityForUi, byte[] payload)590 public void writePersistentDataBlock(int persistentType, int userId, int qualityForUi, 591 byte[] payload) { 592 PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager(); 593 if (persistentDataBlock == null) { 594 return; 595 } 596 persistentDataBlock.setFrpCredentialHandle(PersistentData.toBytes( 597 persistentType, userId, qualityForUi, payload)); 598 } 599 readPersistentDataBlock()600 public PersistentData readPersistentDataBlock() { 601 PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager(); 602 if (persistentDataBlock == null) { 603 return PersistentData.NONE; 604 } 605 try { 606 return PersistentData.fromBytes(persistentDataBlock.getFrpCredentialHandle()); 607 } catch (IllegalStateException e) { 608 Slog.e(TAG, "Error reading persistent data block", e); 609 return PersistentData.NONE; 610 } 611 } 612 613 /** 614 * Provides a concrete data structure to represent the minimal information from 615 * a user's LSKF-based SP protector that is needed to verify the user's LSKF, 616 * in combination with the corresponding Gatekeeper enrollment or Weaver slot. 617 * It can be stored in {@link com.android.server.PersistentDataBlockService} for 618 * FRP to live across factory resets not initiated via the Settings UI. 619 * Written to {@link #REPAIR_MODE_PERSISTENT_FILE} to support verification for 620 * exiting repair mode, since the device runs with an empty data partition in 621 * repair mode and the same credential be provided to exit repair mode is 622 * required. 623 */ 624 public static class PersistentData { 625 static final byte VERSION_1 = 1; 626 static final int VERSION_1_HEADER_SIZE = 1 + 1 + 4 + 4; 627 628 public static final int TYPE_NONE = 0; 629 public static final int TYPE_SP_GATEKEEPER = 1; 630 public static final int TYPE_SP_WEAVER = 2; 631 632 public static final PersistentData NONE = new PersistentData(TYPE_NONE, 633 UserHandle.USER_NULL, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, null); 634 635 final int type; 636 final int userId; 637 final int qualityForUi; 638 final byte[] payload; 639 PersistentData(int type, int userId, int qualityForUi, byte[] payload)640 private PersistentData(int type, int userId, int qualityForUi, byte[] payload) { 641 this.type = type; 642 this.userId = userId; 643 this.qualityForUi = qualityForUi; 644 this.payload = payload; 645 } 646 isBadFormatFromAndroid14Beta()647 public boolean isBadFormatFromAndroid14Beta() { 648 return (this.type == TYPE_SP_GATEKEEPER || this.type == TYPE_SP_WEAVER) 649 && SyntheticPasswordManager.PasswordData.isBadFormatFromAndroid14Beta(this.payload); 650 } 651 fromBytes(byte[] frpData)652 public static PersistentData fromBytes(byte[] frpData) { 653 if (frpData == null || frpData.length == 0) { 654 return NONE; 655 } 656 657 DataInputStream is = new DataInputStream(new ByteArrayInputStream(frpData)); 658 try { 659 byte version = is.readByte(); 660 if (version == PersistentData.VERSION_1) { 661 int type = is.readByte() & 0xFF; 662 int userId = is.readInt(); 663 int qualityForUi = is.readInt(); 664 byte[] payload = new byte[frpData.length - VERSION_1_HEADER_SIZE]; 665 System.arraycopy(frpData, VERSION_1_HEADER_SIZE, payload, 0, payload.length); 666 return new PersistentData(type, userId, qualityForUi, payload); 667 } else { 668 Slog.wtf(TAG, "Unknown PersistentData version code: " + version); 669 return NONE; 670 } 671 } catch (IOException e) { 672 Slog.wtf(TAG, "Could not parse PersistentData", e); 673 return NONE; 674 } 675 } 676 toBytes(int persistentType, int userId, int qualityForUi, byte[] payload)677 public static byte[] toBytes(int persistentType, int userId, int qualityForUi, 678 byte[] payload) { 679 if (persistentType == PersistentData.TYPE_NONE) { 680 Preconditions.checkArgument(payload == null, 681 "TYPE_NONE must have empty payload"); 682 return null; 683 } 684 Preconditions.checkArgument(payload != null && payload.length > 0, 685 "empty payload must only be used with TYPE_NONE"); 686 687 ByteArrayOutputStream os = new ByteArrayOutputStream( 688 VERSION_1_HEADER_SIZE + payload.length); 689 DataOutputStream dos = new DataOutputStream(os); 690 try { 691 dos.writeByte(PersistentData.VERSION_1); 692 dos.writeByte(persistentType); 693 dos.writeInt(userId); 694 dos.writeInt(qualityForUi); 695 dos.write(payload); 696 } catch (IOException e) { 697 throw new IllegalStateException("ByteArrayOutputStream cannot throw IOException"); 698 } 699 return os.toByteArray(); 700 } 701 } 702 703 public interface Callback { initialize(SQLiteDatabase db)704 void initialize(SQLiteDatabase db); 705 } 706 dump(IndentingPrintWriter pw)707 public void dump(IndentingPrintWriter pw) { 708 final UserManager um = UserManager.get(mContext); 709 for (UserInfo user : um.getUsers()) { 710 File userPath = getSyntheticPasswordDirectoryForUser(user.id); 711 pw.println(TextUtils.formatSimple("User %d [%s]:", user.id, userPath)); 712 pw.increaseIndent(); 713 File[] files = userPath.listFiles(); 714 if (files != null) { 715 Arrays.sort(files); 716 for (File file : files) { 717 pw.println(TextUtils.formatSimple("%6d %s %s", file.length(), 718 LockSettingsService.timestampToString(file.lastModified()), 719 file.getName())); 720 } 721 } else { 722 pw.println("[Not found]"); 723 } 724 pw.decreaseIndent(); 725 } 726 // Dump repair mode file states 727 final File repairModeFile = getRepairModePersistentDataFile(); 728 if (repairModeFile.exists()) { 729 pw.println(TextUtils.formatSimple("Repair Mode [%s]:", repairModeFile.getParent())); 730 pw.increaseIndent(); 731 pw.println(TextUtils.formatSimple("%6d %s %s", repairModeFile.length(), 732 LockSettingsService.timestampToString(repairModeFile.lastModified()), 733 repairModeFile.getName())); 734 final PersistentData data = readRepairModePersistentData(); 735 pw.println(TextUtils.formatSimple("type: %d, user id: %d, payload size: %d", 736 data.type, data.userId, data.payload != null ? data.payload.length : 0)); 737 pw.decreaseIndent(); 738 } 739 } 740 741 static class DatabaseHelper extends SQLiteOpenHelper { 742 private static final String TAG = "LockSettingsDB"; 743 private static final String DATABASE_NAME = "locksettings.db"; 744 745 private static final int DATABASE_VERSION = 2; 746 private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000; 747 748 private Callback mCallback; 749 DatabaseHelper(Context context)750 public DatabaseHelper(Context context) { 751 super(context, DATABASE_NAME, null, DATABASE_VERSION); 752 setWriteAheadLoggingEnabled(false); 753 // Memory optimization - close idle connections after 30s of inactivity 754 setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS); 755 } 756 setCallback(Callback callback)757 public void setCallback(Callback callback) { 758 mCallback = callback; 759 } 760 createTable(SQLiteDatabase db)761 private void createTable(SQLiteDatabase db) { 762 db.execSQL("CREATE TABLE " + TABLE + " (" + 763 "_id INTEGER PRIMARY KEY AUTOINCREMENT," + 764 COLUMN_KEY + " TEXT," + 765 COLUMN_USERID + " INTEGER," + 766 COLUMN_VALUE + " TEXT" + 767 ");"); 768 } 769 770 @Override onCreate(SQLiteDatabase db)771 public void onCreate(SQLiteDatabase db) { 772 createTable(db); 773 if (mCallback != null) { 774 mCallback.initialize(db); 775 } 776 } 777 778 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion)779 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { 780 int upgradeVersion = oldVersion; 781 if (upgradeVersion == 1) { 782 // Previously migrated lock screen widget settings. Now defunct. 783 upgradeVersion = 2; 784 } 785 786 if (upgradeVersion != DATABASE_VERSION) { 787 Slog.w(TAG, "Failed to upgrade database!"); 788 } 789 } 790 } 791 792 /* 793 * A cache for the following types of data: 794 * 795 * - Key-value entries from the locksettings database, where the key is the combination of a 796 * userId and a string key, and the value is a string. 797 * - File paths to file contents. 798 * - The per-user "prefetched" flag. 799 * 800 * Cache consistency model: 801 * - Writes to storage write directly to the cache, but this MUST happen within an atomic 802 * section either provided by the database transaction or mFileWriteLock, such that writes to 803 * the cache and writes to the backing storage are guaranteed to occur in the same order. 804 * - Reads can populate the cache, but because there are no strong ordering guarantees with 805 * respect to writes the following precaution is taken: The cache is assigned a version 806 * number that increases every time the backing storage is modified. Reads from backing 807 * storage can only populate the cache if the backing storage has not changed since the load 808 * operation has begun. This guarantees that a read operation can't clobber a different value 809 * that was written to the cache by a concurrent write operation. 810 */ 811 private static class Cache { 812 private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>(); 813 private final CacheKey mCacheKey = new CacheKey(); 814 private int mVersion = 0; 815 peekKeyValue(String key, String defaultValue, int userId)816 String peekKeyValue(String key, String defaultValue, int userId) { 817 Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId); 818 return cached == DEFAULT ? defaultValue : (String) cached; 819 } 820 hasKeyValue(String key, int userId)821 boolean hasKeyValue(String key, int userId) { 822 return contains(CacheKey.TYPE_KEY_VALUE, key, userId); 823 } 824 putKeyValue(String key, String value, int userId)825 void putKeyValue(String key, String value, int userId) { 826 put(CacheKey.TYPE_KEY_VALUE, key, value, userId); 827 } 828 putKeyValueIfUnchanged(String key, Object value, int userId, int version)829 void putKeyValueIfUnchanged(String key, Object value, int userId, int version) { 830 putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version); 831 } 832 removeKey(String key, int userId)833 void removeKey(String key, int userId) { 834 remove(CacheKey.TYPE_KEY_VALUE, key, userId); 835 } 836 peekFile(File path)837 byte[] peekFile(File path) { 838 return copyOf((byte[]) peek(CacheKey.TYPE_FILE, path.toString(), -1 /* userId */)); 839 } 840 hasFile(File path)841 boolean hasFile(File path) { 842 return contains(CacheKey.TYPE_FILE, path.toString(), -1 /* userId */); 843 } 844 putFile(File path, byte[] data)845 void putFile(File path, byte[] data) { 846 put(CacheKey.TYPE_FILE, path.toString(), copyOf(data), -1 /* userId */); 847 } 848 putFileIfUnchanged(File path, byte[] data, int version)849 void putFileIfUnchanged(File path, byte[] data, int version) { 850 putIfUnchanged(CacheKey.TYPE_FILE, path.toString(), copyOf(data), -1 /* userId */, 851 version); 852 } 853 setFetched(int userId)854 void setFetched(int userId) { 855 put(CacheKey.TYPE_FETCHED, "", "true", userId); 856 } 857 isFetched(int userId)858 boolean isFetched(int userId) { 859 return contains(CacheKey.TYPE_FETCHED, "", userId); 860 } 861 remove(int type, String key, int userId)862 private synchronized void remove(int type, String key, int userId) { 863 mCache.remove(mCacheKey.set(type, key, userId)); 864 mVersion++; 865 } 866 put(int type, String key, Object value, int userId)867 private synchronized void put(int type, String key, Object value, int userId) { 868 // Create a new CacheKey here because it may be saved in the map if the key is absent. 869 mCache.put(new CacheKey().set(type, key, userId), value); 870 mVersion++; 871 } 872 putIfUnchanged(int type, String key, Object value, int userId, int version)873 private synchronized void putIfUnchanged(int type, String key, Object value, int userId, 874 int version) { 875 if (!contains(type, key, userId) && mVersion == version) { 876 mCache.put(new CacheKey().set(type, key, userId), value); 877 // Don't increment mVersion, as this method should only be called in cases where the 878 // backing storage isn't being modified. 879 } 880 } 881 contains(int type, String key, int userId)882 private synchronized boolean contains(int type, String key, int userId) { 883 return mCache.containsKey(mCacheKey.set(type, key, userId)); 884 } 885 peek(int type, String key, int userId)886 private synchronized Object peek(int type, String key, int userId) { 887 return mCache.get(mCacheKey.set(type, key, userId)); 888 } 889 getVersion()890 private synchronized int getVersion() { 891 return mVersion; 892 } 893 removeUser(int userId)894 synchronized void removeUser(int userId) { 895 for (int i = mCache.size() - 1; i >= 0; i--) { 896 if (mCache.keyAt(i).userId == userId) { 897 mCache.removeAt(i); 898 } 899 } 900 901 // Make sure in-flight loads can't write to cache. 902 mVersion++; 903 } 904 copyOf(byte[] data)905 private byte[] copyOf(byte[] data) { 906 return data != null ? Arrays.copyOf(data, data.length) : null; 907 } 908 purgePath(File path)909 synchronized void purgePath(File path) { 910 final String pathStr = path.toString(); 911 for (int i = mCache.size() - 1; i >= 0; i--) { 912 CacheKey entry = mCache.keyAt(i); 913 if (entry.type == CacheKey.TYPE_FILE && entry.key.startsWith(pathStr)) { 914 mCache.removeAt(i); 915 } 916 } 917 mVersion++; 918 } 919 clear()920 synchronized void clear() { 921 mCache.clear(); 922 mVersion++; 923 } 924 925 private static final class CacheKey { 926 static final int TYPE_KEY_VALUE = 0; 927 static final int TYPE_FILE = 1; 928 static final int TYPE_FETCHED = 2; 929 930 String key; 931 int userId; 932 int type; 933 set(int type, String key, int userId)934 public CacheKey set(int type, String key, int userId) { 935 this.type = type; 936 this.key = key; 937 this.userId = userId; 938 return this; 939 } 940 941 @Override equals(Object obj)942 public boolean equals(Object obj) { 943 if (!(obj instanceof CacheKey)) 944 return false; 945 CacheKey o = (CacheKey) obj; 946 return userId == o.userId && type == o.type && Objects.equals(key, o.key); 947 } 948 949 @Override hashCode()950 public int hashCode() { 951 int hashCode = Objects.hashCode(key); 952 hashCode = 31 * hashCode + userId; 953 hashCode = 31 * hashCode + type; 954 return hashCode; 955 } 956 } 957 } 958 959 } 960