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