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