• 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 android.Manifest;
20 import android.app.ActivityManager;
21 import android.content.Context;
22 import android.content.pm.PackageManager;
23 import android.os.Binder;
24 import android.os.IBinder;
25 import android.os.RemoteException;
26 import android.os.SystemProperties;
27 import android.os.UserHandle;
28 import android.os.UserManager;
29 import android.service.persistentdata.IPersistentDataBlockService;
30 import android.service.persistentdata.PersistentDataBlockManager;
31 import android.util.Slog;
32 
33 import com.android.internal.R;
34 import com.android.internal.annotations.GuardedBy;
35 
36 import libcore.io.IoUtils;
37 
38 import java.io.DataInputStream;
39 import java.io.DataOutputStream;
40 import java.io.File;
41 import java.io.FileInputStream;
42 import java.io.FileNotFoundException;
43 import java.io.FileOutputStream;
44 import java.io.IOException;
45 import java.nio.ByteBuffer;
46 import java.nio.channels.FileChannel;
47 import java.security.MessageDigest;
48 import java.security.NoSuchAlgorithmException;
49 import java.util.Arrays;
50 import java.util.concurrent.CountDownLatch;
51 import java.util.concurrent.TimeUnit;
52 
53 /**
54  * Service for reading and writing blocks to a persistent partition.
55  * This data will live across factory resets not initiated via the Settings UI.
56  * When a device is factory reset through Settings this data is wiped.
57  *
58  * Allows writing one block at a time. Namely, each time {@link IPersistentDataBlockService#write}
59  * is called, it will overwrite the data that was previously written on the block.
60  *
61  * Clients can query the size of the currently written block via
62  * {@link IPersistentDataBlockService#getDataBlockSize}
63  *
64  * Clients can read any number of bytes from the currently written block up to its total size by
65  * invoking {@link IPersistentDataBlockService#read}
66  */
67 public class PersistentDataBlockService extends SystemService {
68     private static final String TAG = PersistentDataBlockService.class.getSimpleName();
69 
70     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
71     private static final int HEADER_SIZE = 8;
72     // Magic number to mark block device as adhering to the format consumed by this service
73     private static final int PARTITION_TYPE_MARKER = 0x19901873;
74     // Limit to 100k as blocks larger than this might cause strain on Binder.
75     private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
76     public static final int DIGEST_SIZE_BYTES = 32;
77     private static final String OEM_UNLOCK_PROP = "sys.oem_unlock_allowed";
78     private static final String FLASH_LOCK_PROP = "ro.boot.flash.locked";
79     private static final String FLASH_LOCK_LOCKED = "1";
80     private static final String FLASH_LOCK_UNLOCKED = "0";
81 
82     private final Context mContext;
83     private final String mDataBlockFile;
84     private final Object mLock = new Object();
85     private final CountDownLatch mInitDoneSignal = new CountDownLatch(1);
86 
87     private int mAllowedUid = -1;
88     private long mBlockDeviceSize;
89 
90     @GuardedBy("mLock")
91     private boolean mIsWritable = true;
92 
PersistentDataBlockService(Context context)93     public PersistentDataBlockService(Context context) {
94         super(context);
95         mContext = context;
96         mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
97         mBlockDeviceSize = -1; // Load lazily
98     }
99 
getAllowedUid(int userHandle)100     private int getAllowedUid(int userHandle) {
101         String allowedPackage = mContext.getResources()
102                 .getString(R.string.config_persistentDataPackageName);
103         PackageManager pm = mContext.getPackageManager();
104         int allowedUid = -1;
105         try {
106             allowedUid = pm.getPackageUidAsUser(allowedPackage,
107                     PackageManager.MATCH_SYSTEM_ONLY, userHandle);
108         } catch (PackageManager.NameNotFoundException e) {
109             // not expected
110             Slog.e(TAG, "not able to find package " + allowedPackage, e);
111         }
112         return allowedUid;
113     }
114 
115     @Override
onStart()116     public void onStart() {
117         // Do init on a separate thread, will join in PHASE_ACTIVITY_MANAGER_READY
118         SystemServerInitThreadPool.get().submit(() -> {
119             mAllowedUid = getAllowedUid(UserHandle.USER_SYSTEM);
120             enforceChecksumValidity();
121             formatIfOemUnlockEnabled();
122             publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
123             mInitDoneSignal.countDown();
124         }, TAG + ".onStart");
125     }
126 
127     @Override
onBootPhase(int phase)128     public void onBootPhase(int phase) {
129         // Wait for initialization in onStart to finish
130         if (phase == PHASE_SYSTEM_SERVICES_READY) {
131             try {
132                 if (!mInitDoneSignal.await(10, TimeUnit.SECONDS)) {
133                     throw new IllegalStateException("Service " + TAG + " init timeout");
134                 }
135             } catch (InterruptedException e) {
136                 Thread.currentThread().interrupt();
137                 throw new IllegalStateException("Service " + TAG + " init interrupted", e);
138             }
139         }
140         super.onBootPhase(phase);
141     }
142 
formatIfOemUnlockEnabled()143     private void formatIfOemUnlockEnabled() {
144         boolean enabled = doGetOemUnlockEnabled();
145         if (enabled) {
146             synchronized (mLock) {
147                 formatPartitionLocked(true);
148             }
149         }
150 
151         SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
152     }
153 
enforceOemUnlockReadPermission()154     private void enforceOemUnlockReadPermission() {
155         if (mContext.checkCallingOrSelfPermission(Manifest.permission.READ_OEM_UNLOCK_STATE)
156                 == PackageManager.PERMISSION_DENIED
157                 && mContext.checkCallingOrSelfPermission(Manifest.permission.OEM_UNLOCK_STATE)
158                 == PackageManager.PERMISSION_DENIED) {
159             throw new SecurityException("Can't access OEM unlock state. Requires "
160                     + "READ_OEM_UNLOCK_STATE or OEM_UNLOCK_STATE permission.");
161         }
162     }
163 
enforceOemUnlockWritePermission()164     private void enforceOemUnlockWritePermission() {
165         mContext.enforceCallingOrSelfPermission(
166                 Manifest.permission.OEM_UNLOCK_STATE,
167                 "Can't modify OEM unlock state");
168     }
169 
enforceUid(int callingUid)170     private void enforceUid(int callingUid) {
171         if (callingUid != mAllowedUid) {
172             throw new SecurityException("uid " + callingUid + " not allowed to access PST");
173         }
174     }
175 
enforceIsAdmin()176     private void enforceIsAdmin() {
177         final int userId = UserHandle.getCallingUserId();
178         final boolean isAdmin = UserManager.get(mContext).isUserAdmin(userId);
179         if (!isAdmin) {
180             throw new SecurityException(
181                     "Only the Admin user is allowed to change OEM unlock state");
182         }
183     }
184 
enforceUserRestriction(String userRestriction)185     private void enforceUserRestriction(String userRestriction) {
186         if (UserManager.get(mContext).hasUserRestriction(userRestriction)) {
187             throw new SecurityException(
188                     "OEM unlock is disallowed by user restriction: " + userRestriction);
189         }
190     }
191 
getTotalDataSizeLocked(DataInputStream inputStream)192     private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
193         // skip over checksum
194         inputStream.skipBytes(DIGEST_SIZE_BYTES);
195 
196         int totalDataSize;
197         int blockId = inputStream.readInt();
198         if (blockId == PARTITION_TYPE_MARKER) {
199             totalDataSize = inputStream.readInt();
200         } else {
201             totalDataSize = 0;
202         }
203         return totalDataSize;
204     }
205 
getBlockDeviceSize()206     private long getBlockDeviceSize() {
207         synchronized (mLock) {
208             if (mBlockDeviceSize == -1) {
209                 mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
210             }
211         }
212 
213         return mBlockDeviceSize;
214     }
215 
enforceChecksumValidity()216     private boolean enforceChecksumValidity() {
217         byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
218 
219         synchronized (mLock) {
220             byte[] digest = computeDigestLocked(storedDigest);
221             if (digest == null || !Arrays.equals(storedDigest, digest)) {
222                 Slog.i(TAG, "Formatting FRP partition...");
223                 formatPartitionLocked(false);
224                 return false;
225             }
226         }
227 
228         return true;
229     }
230 
computeAndWriteDigestLocked()231     private boolean computeAndWriteDigestLocked() {
232         byte[] digest = computeDigestLocked(null);
233         if (digest != null) {
234             DataOutputStream outputStream;
235             try {
236                 outputStream = new DataOutputStream(
237                         new FileOutputStream(new File(mDataBlockFile)));
238             } catch (FileNotFoundException e) {
239                 Slog.e(TAG, "partition not available?", e);
240                 return false;
241             }
242 
243             try {
244                 outputStream.write(digest, 0, DIGEST_SIZE_BYTES);
245                 outputStream.flush();
246             } catch (IOException e) {
247                 Slog.e(TAG, "failed to write block checksum", e);
248                 return false;
249             } finally {
250                 IoUtils.closeQuietly(outputStream);
251             }
252             return true;
253         } else {
254             return false;
255         }
256     }
257 
computeDigestLocked(byte[] storedDigest)258     private byte[] computeDigestLocked(byte[] storedDigest) {
259         DataInputStream inputStream;
260         try {
261             inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
262         } catch (FileNotFoundException e) {
263             Slog.e(TAG, "partition not available?", e);
264             return null;
265         }
266 
267         MessageDigest md;
268         try {
269             md = MessageDigest.getInstance("SHA-256");
270         } catch (NoSuchAlgorithmException e) {
271             // won't ever happen -- every implementation is required to support SHA-256
272             Slog.e(TAG, "SHA-256 not supported?", e);
273             IoUtils.closeQuietly(inputStream);
274             return null;
275         }
276 
277         try {
278             if (storedDigest != null && storedDigest.length == DIGEST_SIZE_BYTES) {
279                 inputStream.read(storedDigest);
280             } else {
281                 inputStream.skipBytes(DIGEST_SIZE_BYTES);
282             }
283 
284             int read;
285             byte[] data = new byte[1024];
286             md.update(data, 0, DIGEST_SIZE_BYTES); // include 0 checksum in digest
287             while ((read = inputStream.read(data)) != -1) {
288                 md.update(data, 0, read);
289             }
290         } catch (IOException e) {
291             Slog.e(TAG, "failed to read partition", e);
292             return null;
293         } finally {
294             IoUtils.closeQuietly(inputStream);
295         }
296 
297         return md.digest();
298     }
299 
formatPartitionLocked(boolean setOemUnlockEnabled)300     private void formatPartitionLocked(boolean setOemUnlockEnabled) {
301         DataOutputStream outputStream;
302         try {
303             outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
304         } catch (FileNotFoundException e) {
305             Slog.e(TAG, "partition not available?", e);
306             return;
307         }
308 
309         byte[] data = new byte[DIGEST_SIZE_BYTES];
310         try {
311             outputStream.write(data, 0, DIGEST_SIZE_BYTES);
312             outputStream.writeInt(PARTITION_TYPE_MARKER);
313             outputStream.writeInt(0); // data size
314             outputStream.flush();
315         } catch (IOException e) {
316             Slog.e(TAG, "failed to format block", e);
317             return;
318         } finally {
319             IoUtils.closeQuietly(outputStream);
320         }
321 
322         doSetOemUnlockEnabledLocked(setOemUnlockEnabled);
323         computeAndWriteDigestLocked();
324     }
325 
doSetOemUnlockEnabledLocked(boolean enabled)326     private void doSetOemUnlockEnabledLocked(boolean enabled) {
327         FileOutputStream outputStream;
328         try {
329             outputStream = new FileOutputStream(new File(mDataBlockFile));
330         } catch (FileNotFoundException e) {
331             Slog.e(TAG, "partition not available", e);
332             return;
333         }
334 
335         try {
336             FileChannel channel = outputStream.getChannel();
337 
338             channel.position(getBlockDeviceSize() - 1);
339 
340             ByteBuffer data = ByteBuffer.allocate(1);
341             data.put(enabled ? (byte) 1 : (byte) 0);
342             data.flip();
343             channel.write(data);
344             outputStream.flush();
345         } catch (IOException e) {
346             Slog.e(TAG, "unable to access persistent partition", e);
347             return;
348         } finally {
349             SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
350             IoUtils.closeQuietly(outputStream);
351         }
352     }
353 
doGetOemUnlockEnabled()354     private boolean doGetOemUnlockEnabled() {
355         DataInputStream inputStream;
356         try {
357             inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
358         } catch (FileNotFoundException e) {
359             Slog.e(TAG, "partition not available");
360             return false;
361         }
362 
363         try {
364             synchronized (mLock) {
365                 inputStream.skip(getBlockDeviceSize() - 1);
366                 return inputStream.readByte() != 0;
367             }
368         } catch (IOException e) {
369             Slog.e(TAG, "unable to access persistent partition", e);
370             return false;
371         } finally {
372             IoUtils.closeQuietly(inputStream);
373         }
374     }
375 
nativeGetBlockDeviceSize(String path)376     private native long nativeGetBlockDeviceSize(String path);
nativeWipe(String path)377     private native int nativeWipe(String path);
378 
379     private final IBinder mService = new IPersistentDataBlockService.Stub() {
380         @Override
381         public int write(byte[] data) throws RemoteException {
382             enforceUid(Binder.getCallingUid());
383 
384             // Need to ensure we don't write over the last byte
385             long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
386             if (data.length > maxBlockSize) {
387                 // partition is ~500k so shouldn't be a problem to downcast
388                 return (int) -maxBlockSize;
389             }
390 
391             DataOutputStream outputStream;
392             try {
393                 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
394             } catch (FileNotFoundException e) {
395                 Slog.e(TAG, "partition not available?", e);
396                 return -1;
397             }
398 
399             ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
400             headerAndData.putInt(PARTITION_TYPE_MARKER);
401             headerAndData.putInt(data.length);
402             headerAndData.put(data);
403 
404             synchronized (mLock) {
405                 if (!mIsWritable) {
406                     IoUtils.closeQuietly(outputStream);
407                     return -1;
408                 }
409 
410                 try {
411                     byte[] checksum = new byte[DIGEST_SIZE_BYTES];
412                     outputStream.write(checksum, 0, DIGEST_SIZE_BYTES);
413                     outputStream.write(headerAndData.array());
414                     outputStream.flush();
415                 } catch (IOException e) {
416                     Slog.e(TAG, "failed writing to the persistent data block", e);
417                     return -1;
418                 } finally {
419                     IoUtils.closeQuietly(outputStream);
420                 }
421 
422                 if (computeAndWriteDigestLocked()) {
423                     return data.length;
424                 } else {
425                     return -1;
426                 }
427             }
428         }
429 
430         @Override
431         public byte[] read() {
432             enforceUid(Binder.getCallingUid());
433             if (!enforceChecksumValidity()) {
434                 return new byte[0];
435             }
436 
437             DataInputStream inputStream;
438             try {
439                 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
440             } catch (FileNotFoundException e) {
441                 Slog.e(TAG, "partition not available?", e);
442                 return null;
443             }
444 
445             try {
446                 synchronized (mLock) {
447                     int totalDataSize = getTotalDataSizeLocked(inputStream);
448 
449                     if (totalDataSize == 0) {
450                         return new byte[0];
451                     }
452 
453                     byte[] data = new byte[totalDataSize];
454                     int read = inputStream.read(data, 0, totalDataSize);
455                     if (read < totalDataSize) {
456                         // something went wrong, not returning potentially corrupt data
457                         Slog.e(TAG, "failed to read entire data block. bytes read: " +
458                                 read + "/" + totalDataSize);
459                         return null;
460                     }
461                     return data;
462                 }
463             } catch (IOException e) {
464                 Slog.e(TAG, "failed to read data", e);
465                 return null;
466             } finally {
467                 try {
468                     inputStream.close();
469                 } catch (IOException e) {
470                     Slog.e(TAG, "failed to close OutputStream");
471                 }
472             }
473         }
474 
475         @Override
476         public void wipe() {
477             enforceOemUnlockWritePermission();
478 
479             synchronized (mLock) {
480                 int ret = nativeWipe(mDataBlockFile);
481 
482                 if (ret < 0) {
483                     Slog.e(TAG, "failed to wipe persistent partition");
484                 } else {
485                     mIsWritable = false;
486                     Slog.i(TAG, "persistent partition now wiped and unwritable");
487                 }
488             }
489         }
490 
491         @Override
492         public void setOemUnlockEnabled(boolean enabled) throws SecurityException {
493             // do not allow monkey to flip the flag
494             if (ActivityManager.isUserAMonkey()) {
495                 return;
496             }
497 
498             enforceOemUnlockWritePermission();
499             enforceIsAdmin();
500 
501             if (enabled) {
502                 // Do not allow oem unlock to be enabled if it's disallowed by a user restriction.
503                 enforceUserRestriction(UserManager.DISALLOW_OEM_UNLOCK);
504                 enforceUserRestriction(UserManager.DISALLOW_FACTORY_RESET);
505             }
506             synchronized (mLock) {
507                 doSetOemUnlockEnabledLocked(enabled);
508                 computeAndWriteDigestLocked();
509             }
510         }
511 
512         @Override
513         public boolean getOemUnlockEnabled() {
514             enforceOemUnlockReadPermission();
515             return doGetOemUnlockEnabled();
516         }
517 
518         @Override
519         public int getFlashLockState() {
520             enforceOemUnlockReadPermission();
521             String locked = SystemProperties.get(FLASH_LOCK_PROP);
522             switch (locked) {
523                 case FLASH_LOCK_LOCKED:
524                     return PersistentDataBlockManager.FLASH_LOCK_LOCKED;
525                 case FLASH_LOCK_UNLOCKED:
526                     return PersistentDataBlockManager.FLASH_LOCK_UNLOCKED;
527                 default:
528                     return PersistentDataBlockManager.FLASH_LOCK_UNKNOWN;
529             }
530         }
531 
532         @Override
533         public int getDataBlockSize() {
534             enforcePersistentDataBlockAccess();
535 
536             DataInputStream inputStream;
537             try {
538                 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
539             } catch (FileNotFoundException e) {
540                 Slog.e(TAG, "partition not available");
541                 return 0;
542             }
543 
544             try {
545                 synchronized (mLock) {
546                     return getTotalDataSizeLocked(inputStream);
547                 }
548             } catch (IOException e) {
549                 Slog.e(TAG, "error reading data block size");
550                 return 0;
551             } finally {
552                 IoUtils.closeQuietly(inputStream);
553             }
554         }
555 
556         private void enforcePersistentDataBlockAccess() {
557             if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
558                     != PackageManager.PERMISSION_GRANTED) {
559                 enforceUid(Binder.getCallingUid());
560             }
561         }
562 
563         @Override
564         public long getMaximumDataBlockSize() {
565             long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
566             return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
567         }
568     };
569 }
570