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