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