• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
18 
19 import com.android.internal.annotations.VisibleForTesting;
20 
21 import android.content.ContentValues;
22 import android.content.Context;
23 import android.content.pm.UserInfo;
24 import android.database.Cursor;
25 import android.database.sqlite.SQLiteDatabase;
26 import android.database.sqlite.SQLiteOpenHelper;
27 import android.os.Environment;
28 import android.os.UserManager;
29 import android.util.ArrayMap;
30 import android.util.Log;
31 import android.util.Slog;
32 import android.util.SparseArray;
33 
34 import java.io.File;
35 import java.io.IOException;
36 import java.io.RandomAccessFile;
37 
38 import static android.content.Context.USER_SERVICE;
39 
40 /**
41  * Storage for the lock settings service.
42  */
43 class LockSettingsStorage {
44 
45     private static final String TAG = "LockSettingsStorage";
46     private static final String TABLE = "locksettings";
47     private static final boolean DEBUG = false;
48 
49     private static final String COLUMN_KEY = "name";
50     private static final String COLUMN_USERID = "user";
51     private static final String COLUMN_VALUE = "value";
52 
53     private static final String[] COLUMNS_FOR_QUERY = {
54             COLUMN_VALUE
55     };
56     private static final String[] COLUMNS_FOR_PREFETCH = {
57             COLUMN_KEY, COLUMN_VALUE
58     };
59 
60     private static final String SYSTEM_DIRECTORY = "/system/";
61     private static final String LOCK_PATTERN_FILE = "gatekeeper.pattern.key";
62     private static final String BASE_ZERO_LOCK_PATTERN_FILE = "gatekeeper.gesture.key";
63     private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key";
64     private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
65     private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key";
66     private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
67 
68     private static final Object DEFAULT = new Object();
69 
70     private final DatabaseHelper mOpenHelper;
71     private final Context mContext;
72     private final Cache mCache = new Cache();
73     private final Object mFileWriteLock = new Object();
74 
75     private SparseArray<Integer> mStoredCredentialType;
76 
77     static class CredentialHash {
78         static final int TYPE_NONE = -1;
79         static final int TYPE_PATTERN = 1;
80         static final int TYPE_PASSWORD = 2;
81 
82         static final int VERSION_LEGACY = 0;
83         static final int VERSION_GATEKEEPER = 1;
84 
CredentialHash(byte[] hash, int version)85         CredentialHash(byte[] hash, int version) {
86             this.hash = hash;
87             this.version = version;
88             this.isBaseZeroPattern = false;
89         }
90 
CredentialHash(byte[] hash, boolean isBaseZeroPattern)91         CredentialHash(byte[] hash, boolean isBaseZeroPattern) {
92             this.hash = hash;
93             this.version = VERSION_GATEKEEPER;
94             this.isBaseZeroPattern = isBaseZeroPattern;
95         }
96 
97         byte[] hash;
98         int version;
99         boolean isBaseZeroPattern;
100     }
101 
LockSettingsStorage(Context context, Callback callback)102     public LockSettingsStorage(Context context, Callback callback) {
103         mContext = context;
104         mOpenHelper = new DatabaseHelper(context, callback);
105         mStoredCredentialType = new SparseArray<Integer>();
106     }
107 
writeKeyValue(String key, String value, int userId)108     public void writeKeyValue(String key, String value, int userId) {
109         writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
110     }
111 
writeKeyValue(SQLiteDatabase db, String key, String value, int userId)112     public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
113         ContentValues cv = new ContentValues();
114         cv.put(COLUMN_KEY, key);
115         cv.put(COLUMN_USERID, userId);
116         cv.put(COLUMN_VALUE, value);
117 
118         db.beginTransaction();
119         try {
120             db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
121                     new String[] {key, Integer.toString(userId)});
122             db.insert(TABLE, null, cv);
123             db.setTransactionSuccessful();
124             mCache.putKeyValue(key, value, userId);
125         } finally {
126             db.endTransaction();
127         }
128 
129     }
130 
readKeyValue(String key, String defaultValue, int userId)131     public String readKeyValue(String key, String defaultValue, int userId) {
132         int version;
133         synchronized (mCache) {
134             if (mCache.hasKeyValue(key, userId)) {
135                 return mCache.peekKeyValue(key, defaultValue, userId);
136             }
137             version = mCache.getVersion();
138         }
139 
140         Cursor cursor;
141         Object result = DEFAULT;
142         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
143         if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
144                 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
145                 new String[] { Integer.toString(userId), key },
146                 null, null, null)) != null) {
147             if (cursor.moveToFirst()) {
148                 result = cursor.getString(0);
149             }
150             cursor.close();
151         }
152         mCache.putKeyValueIfUnchanged(key, result, userId, version);
153         return result == DEFAULT ? defaultValue : (String) result;
154     }
155 
prefetchUser(int userId)156     public void prefetchUser(int userId) {
157         int version;
158         synchronized (mCache) {
159             if (mCache.isFetched(userId)) {
160                 return;
161             }
162             mCache.setFetched(userId);
163             version = mCache.getVersion();
164         }
165 
166         Cursor cursor;
167         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
168         if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH,
169                 COLUMN_USERID + "=?",
170                 new String[] { Integer.toString(userId) },
171                 null, null, null)) != null) {
172             while (cursor.moveToNext()) {
173                 String key = cursor.getString(0);
174                 String value = cursor.getString(1);
175                 mCache.putKeyValueIfUnchanged(key, value, userId, version);
176             }
177             cursor.close();
178         }
179 
180         // Populate cache by reading the password and pattern files.
181         readPasswordHash(userId);
182         readPatternHash(userId);
183     }
184 
getStoredCredentialType(int userId)185     public int getStoredCredentialType(int userId) {
186         final Integer cachedStoredCredentialType = mStoredCredentialType.get(userId);
187         if (cachedStoredCredentialType != null) {
188             return cachedStoredCredentialType.intValue();
189         }
190 
191         int storedCredentialType;
192         CredentialHash pattern = readPatternHash(userId);
193         if (pattern == null) {
194             if (readPasswordHash(userId) != null) {
195                 storedCredentialType = CredentialHash.TYPE_PASSWORD;
196             } else {
197                 storedCredentialType = CredentialHash.TYPE_NONE;
198             }
199         } else {
200             CredentialHash password = readPasswordHash(userId);
201             if (password != null) {
202                 // Both will never be GateKeeper
203                 if (password.version == CredentialHash.VERSION_GATEKEEPER) {
204                     storedCredentialType = CredentialHash.TYPE_PASSWORD;
205                 } else {
206                     storedCredentialType = CredentialHash.TYPE_PATTERN;
207                 }
208             } else {
209                 storedCredentialType = CredentialHash.TYPE_PATTERN;
210             }
211         }
212         mStoredCredentialType.put(userId, storedCredentialType);
213         return storedCredentialType;
214     }
215 
216 
readPasswordHash(int userId)217     public CredentialHash readPasswordHash(int userId) {
218         byte[] stored = readFile(getLockPasswordFilename(userId));
219         if (stored != null && stored.length > 0) {
220             return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER);
221         }
222 
223         stored = readFile(getLegacyLockPasswordFilename(userId));
224         if (stored != null && stored.length > 0) {
225             return new CredentialHash(stored, CredentialHash.VERSION_LEGACY);
226         }
227 
228         return null;
229     }
230 
readPatternHash(int userId)231     public CredentialHash readPatternHash(int userId) {
232         byte[] stored = readFile(getLockPatternFilename(userId));
233         if (stored != null && stored.length > 0) {
234             return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER);
235         }
236 
237         stored = readFile(getBaseZeroLockPatternFilename(userId));
238         if (stored != null && stored.length > 0) {
239             return new CredentialHash(stored, true);
240         }
241 
242         stored = readFile(getLegacyLockPatternFilename(userId));
243         if (stored != null && stored.length > 0) {
244             return new CredentialHash(stored, CredentialHash.VERSION_LEGACY);
245         }
246 
247         return null;
248     }
249 
removeChildProfileLock(int userId)250     public void removeChildProfileLock(int userId) {
251         if (DEBUG)
252             Slog.e(TAG, "Remove child profile lock for user: " + userId);
253         try {
254             deleteFile(getChildProfileLockFile(userId));
255         } catch (Exception e) {
256             e.printStackTrace();
257         }
258     }
259 
writeChildProfileLock(int userId, byte[] lock)260     public void writeChildProfileLock(int userId, byte[] lock) {
261         writeFile(getChildProfileLockFile(userId), lock);
262     }
263 
readChildProfileLock(int userId)264     public byte[] readChildProfileLock(int userId) {
265         return readFile(getChildProfileLockFile(userId));
266     }
267 
hasChildProfileLock(int userId)268     public boolean hasChildProfileLock(int userId) {
269         return hasFile(getChildProfileLockFile(userId));
270     }
271 
hasPassword(int userId)272     public boolean hasPassword(int userId) {
273         return hasFile(getLockPasswordFilename(userId)) ||
274             hasFile(getLegacyLockPasswordFilename(userId));
275     }
276 
hasPattern(int userId)277     public boolean hasPattern(int userId) {
278         return hasFile(getLockPatternFilename(userId)) ||
279             hasFile(getBaseZeroLockPatternFilename(userId)) ||
280             hasFile(getLegacyLockPatternFilename(userId));
281     }
282 
hasFile(String name)283     private boolean hasFile(String name) {
284         byte[] contents = readFile(name);
285         return contents != null && contents.length > 0;
286     }
287 
readFile(String name)288     private byte[] readFile(String name) {
289         int version;
290         synchronized (mCache) {
291             if (mCache.hasFile(name)) {
292                 return mCache.peekFile(name);
293             }
294             version = mCache.getVersion();
295         }
296 
297         RandomAccessFile raf = null;
298         byte[] stored = null;
299         try {
300             raf = new RandomAccessFile(name, "r");
301             stored = new byte[(int) raf.length()];
302             raf.readFully(stored, 0, stored.length);
303             raf.close();
304         } catch (IOException e) {
305             Slog.e(TAG, "Cannot read file " + e);
306         } finally {
307             if (raf != null) {
308                 try {
309                     raf.close();
310                 } catch (IOException e) {
311                     Slog.e(TAG, "Error closing file " + e);
312                 }
313             }
314         }
315         mCache.putFileIfUnchanged(name, stored, version);
316         return stored;
317     }
318 
writeFile(String name, byte[] hash)319     private void writeFile(String name, byte[] hash) {
320         synchronized (mFileWriteLock) {
321             RandomAccessFile raf = null;
322             try {
323                 // Write the hash to file
324                 raf = new RandomAccessFile(name, "rw");
325                 // Truncate the file if pattern is null, to clear the lock
326                 if (hash == null || hash.length == 0) {
327                     raf.setLength(0);
328                 } else {
329                     raf.write(hash, 0, hash.length);
330                 }
331                 raf.close();
332             } catch (IOException e) {
333                 Slog.e(TAG, "Error writing to file " + e);
334             } finally {
335                 if (raf != null) {
336                     try {
337                         raf.close();
338                     } catch (IOException e) {
339                         Slog.e(TAG, "Error closing file " + e);
340                     }
341                 }
342             }
343             mCache.putFile(name, hash);
344         }
345     }
346 
deleteFile(String name)347     private void deleteFile(String name) {
348         if (DEBUG) Slog.e(TAG, "Delete file " + name);
349         synchronized (mFileWriteLock) {
350             File file = new File(name);
351             if (file.exists()) {
352                 file.delete();
353                 mCache.putFile(name, null);
354             }
355         }
356     }
357 
writePatternHash(byte[] hash, int userId)358     public void writePatternHash(byte[] hash, int userId) {
359         mStoredCredentialType.put(userId, hash == null ? CredentialHash.TYPE_NONE
360                 : CredentialHash.TYPE_PATTERN);
361         writeFile(getLockPatternFilename(userId), hash);
362         clearPasswordHash(userId);
363     }
364 
clearPatternHash(int userId)365     private void clearPatternHash(int userId) {
366         writeFile(getLockPatternFilename(userId), null);
367     }
368 
writePasswordHash(byte[] hash, int userId)369     public void writePasswordHash(byte[] hash, int userId) {
370         mStoredCredentialType.put(userId, hash == null ? CredentialHash.TYPE_NONE
371                 : CredentialHash.TYPE_PASSWORD);
372         writeFile(getLockPasswordFilename(userId), hash);
373         clearPatternHash(userId);
374     }
375 
clearPasswordHash(int userId)376     private void clearPasswordHash(int userId) {
377         writeFile(getLockPasswordFilename(userId), null);
378     }
379 
380     @VisibleForTesting
getLockPatternFilename(int userId)381     String getLockPatternFilename(int userId) {
382         return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE);
383     }
384 
385     @VisibleForTesting
getLockPasswordFilename(int userId)386     String getLockPasswordFilename(int userId) {
387         return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
388     }
389 
390     @VisibleForTesting
getLegacyLockPatternFilename(int userId)391     String getLegacyLockPatternFilename(int userId) {
392         return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PATTERN_FILE);
393     }
394 
395     @VisibleForTesting
getLegacyLockPasswordFilename(int userId)396     String getLegacyLockPasswordFilename(int userId) {
397         return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PASSWORD_FILE);
398     }
399 
getBaseZeroLockPatternFilename(int userId)400     private String getBaseZeroLockPatternFilename(int userId) {
401         return getLockCredentialFilePathForUser(userId, BASE_ZERO_LOCK_PATTERN_FILE);
402     }
403 
404     @VisibleForTesting
getChildProfileLockFile(int userId)405     String getChildProfileLockFile(int userId) {
406         return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
407     }
408 
getLockCredentialFilePathForUser(int userId, String basename)409     private String getLockCredentialFilePathForUser(int userId, String basename) {
410         String dataSystemDirectory =
411                 android.os.Environment.getDataDirectory().getAbsolutePath() +
412                         SYSTEM_DIRECTORY;
413         if (userId == 0) {
414             // Leave it in the same place for user 0
415             return dataSystemDirectory + basename;
416         } else {
417             return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath();
418         }
419     }
420 
removeUser(int userId)421     public void removeUser(int userId) {
422         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
423 
424         final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
425         final UserInfo parentInfo = um.getProfileParent(userId);
426 
427         if (parentInfo == null) {
428             // This user owns its lock settings files - safe to delete them
429             synchronized (mFileWriteLock) {
430                 String name = getLockPasswordFilename(userId);
431                 File file = new File(name);
432                 if (file.exists()) {
433                     file.delete();
434                     mCache.putFile(name, null);
435                 }
436                 name = getLockPatternFilename(userId);
437                 file = new File(name);
438                 if (file.exists()) {
439                     file.delete();
440                     mCache.putFile(name, null);
441                 }
442             }
443         } else {
444             // Manged profile
445             removeChildProfileLock(userId);
446         }
447 
448         try {
449             db.beginTransaction();
450             db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
451             db.setTransactionSuccessful();
452             mCache.removeUser(userId);
453         } finally {
454             db.endTransaction();
455         }
456     }
457 
458     @VisibleForTesting
closeDatabase()459     void closeDatabase() {
460         mOpenHelper.close();
461     }
462 
463     @VisibleForTesting
clearCache()464     void clearCache() {
465         mCache.clear();
466     }
467 
468     public interface Callback {
initialize(SQLiteDatabase db)469         void initialize(SQLiteDatabase db);
470     }
471 
472     class DatabaseHelper extends SQLiteOpenHelper {
473         private static final String TAG = "LockSettingsDB";
474         private static final String DATABASE_NAME = "locksettings.db";
475 
476         private static final int DATABASE_VERSION = 2;
477 
478         private final Callback mCallback;
479 
DatabaseHelper(Context context, Callback callback)480         public DatabaseHelper(Context context, Callback callback) {
481             super(context, DATABASE_NAME, null, DATABASE_VERSION);
482             setWriteAheadLoggingEnabled(true);
483             mCallback = callback;
484         }
485 
createTable(SQLiteDatabase db)486         private void createTable(SQLiteDatabase db) {
487             db.execSQL("CREATE TABLE " + TABLE + " (" +
488                     "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
489                     COLUMN_KEY + " TEXT," +
490                     COLUMN_USERID + " INTEGER," +
491                     COLUMN_VALUE + " TEXT" +
492                     ");");
493         }
494 
495         @Override
onCreate(SQLiteDatabase db)496         public void onCreate(SQLiteDatabase db) {
497             createTable(db);
498             mCallback.initialize(db);
499         }
500 
501         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion)502         public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
503             int upgradeVersion = oldVersion;
504             if (upgradeVersion == 1) {
505                 // Previously migrated lock screen widget settings. Now defunct.
506                 upgradeVersion = 2;
507             }
508 
509             if (upgradeVersion != DATABASE_VERSION) {
510                 Log.w(TAG, "Failed to upgrade database!");
511             }
512         }
513     }
514 
515     /**
516      * Cache consistency model:
517      * - Writes to storage write directly to the cache, but this MUST happen within the atomic
518      *   section either provided by the database transaction or mWriteLock, such that writes to the
519      *   cache and writes to the backing storage are guaranteed to occur in the same order
520      *
521      * - Reads can populate the cache, but because they are no strong ordering guarantees with
522      *   respect to writes this precaution is taken:
523      *   - The cache is assigned a version number that increases every time the cache is modified.
524      *     Reads from backing storage can only populate the cache if the backing storage
525      *     has not changed since the load operation has begun.
526      *     This guarantees that no read operation can shadow a write to the cache that happens
527      *     after it had begun.
528      */
529     private static class Cache {
530         private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>();
531         private final CacheKey mCacheKey = new CacheKey();
532         private int mVersion = 0;
533 
peekKeyValue(String key, String defaultValue, int userId)534         String peekKeyValue(String key, String defaultValue, int userId) {
535             Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId);
536             return cached == DEFAULT ? defaultValue : (String) cached;
537         }
538 
hasKeyValue(String key, int userId)539         boolean hasKeyValue(String key, int userId) {
540             return contains(CacheKey.TYPE_KEY_VALUE, key, userId);
541         }
542 
putKeyValue(String key, String value, int userId)543         void putKeyValue(String key, String value, int userId) {
544             put(CacheKey.TYPE_KEY_VALUE, key, value, userId);
545         }
546 
putKeyValueIfUnchanged(String key, Object value, int userId, int version)547         void putKeyValueIfUnchanged(String key, Object value, int userId, int version) {
548             putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version);
549         }
550 
peekFile(String fileName)551         byte[] peekFile(String fileName) {
552             return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
553         }
554 
hasFile(String fileName)555         boolean hasFile(String fileName) {
556             return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
557         }
558 
putFile(String key, byte[] value)559         void putFile(String key, byte[] value) {
560             put(CacheKey.TYPE_FILE, key, value, -1 /* userId */);
561         }
562 
putFileIfUnchanged(String key, byte[] value, int version)563         void putFileIfUnchanged(String key, byte[] value, int version) {
564             putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version);
565         }
566 
setFetched(int userId)567         void setFetched(int userId) {
568             put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId);
569         }
570 
isFetched(int userId)571         boolean isFetched(int userId) {
572             return contains(CacheKey.TYPE_FETCHED, "", userId);
573         }
574 
575 
put(int type, String key, Object value, int userId)576         private synchronized void put(int type, String key, Object value, int userId) {
577             // Create a new CachKey here because it may be saved in the map if the key is absent.
578             mCache.put(new CacheKey().set(type, key, userId), value);
579             mVersion++;
580         }
581 
putIfUnchanged(int type, String key, Object value, int userId, int version)582         private synchronized void putIfUnchanged(int type, String key, Object value, int userId,
583                 int version) {
584             if (!contains(type, key, userId) && mVersion == version) {
585                 put(type, key, value, userId);
586             }
587         }
588 
contains(int type, String key, int userId)589         private synchronized boolean contains(int type, String key, int userId) {
590             return mCache.containsKey(mCacheKey.set(type, key, userId));
591         }
592 
peek(int type, String key, int userId)593         private synchronized Object peek(int type, String key, int userId) {
594             return mCache.get(mCacheKey.set(type, key, userId));
595         }
596 
getVersion()597         private synchronized int getVersion() {
598             return mVersion;
599         }
600 
removeUser(int userId)601         synchronized void removeUser(int userId) {
602             for (int i = mCache.size() - 1; i >= 0; i--) {
603                 if (mCache.keyAt(i).userId == userId) {
604                     mCache.removeAt(i);
605                 }
606             }
607 
608             // Make sure in-flight loads can't write to cache.
609             mVersion++;
610         }
611 
clear()612         synchronized void clear() {
613             mCache.clear();
614             mVersion++;
615         }
616 
617         private static final class CacheKey {
618             static final int TYPE_KEY_VALUE = 0;
619             static final int TYPE_FILE = 1;
620             static final int TYPE_FETCHED = 2;
621 
622             String key;
623             int userId;
624             int type;
625 
set(int type, String key, int userId)626             public CacheKey set(int type, String key, int userId) {
627                 this.type = type;
628                 this.key = key;
629                 this.userId = userId;
630                 return this;
631             }
632 
633             @Override
equals(Object obj)634             public boolean equals(Object obj) {
635                 if (!(obj instanceof CacheKey))
636                     return false;
637                 CacheKey o = (CacheKey) obj;
638                 return userId == o.userId && type == o.type && key.equals(o.key);
639             }
640 
641             @Override
hashCode()642             public int hashCode() {
643                 return key.hashCode() ^ userId ^ type;
644             }
645         }
646     }
647 }
648