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