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