PersistentDataBlockService.java revision b2375947777b7cc04c3f6745244e82418a6d66e6
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
17package com.android.server;
18
19import android.Manifest;
20import android.app.ActivityManager;
21import android.content.Context;
22import android.content.pm.PackageManager;
23import android.os.Binder;
24import android.os.IBinder;
25import android.os.RemoteException;
26import android.os.SystemProperties;
27import android.os.UserHandle;
28import android.os.UserManager;
29import android.service.persistentdata.IPersistentDataBlockService;
30import android.service.persistentdata.PersistentDataBlockManager;
31import android.util.Log;
32import android.util.Slog;
33
34import com.android.internal.R;
35import com.android.internal.annotations.GuardedBy;
36import com.android.internal.util.Preconditions;
37
38import libcore.io.IoUtils;
39
40import java.io.DataInputStream;
41import java.io.DataOutputStream;
42import java.io.File;
43import java.io.FileInputStream;
44import java.io.FileNotFoundException;
45import java.io.FileOutputStream;
46import java.io.IOException;
47import java.nio.ByteBuffer;
48import java.nio.channels.FileChannel;
49import java.security.MessageDigest;
50import java.security.NoSuchAlgorithmException;
51import java.util.Arrays;
52import java.util.concurrent.CountDownLatch;
53import 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 */
69public 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
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
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
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
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
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
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
172    private void enforceOemUnlockWritePermission() {
173        mContext.enforceCallingOrSelfPermission(
174                Manifest.permission.OEM_UNLOCK_STATE,
175                "Can't modify OEM unlock state");
176    }
177
178    private void enforceUid(int callingUid) {
179        if (callingUid != mAllowedUid) {
180            throw new SecurityException("uid " + callingUid + " not allowed to access PST");
181        }
182    }
183
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
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
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
214    private long getBlockDeviceSize() {
215        synchronized (mLock) {
216            if (mBlockDeviceSize == -1) {
217                mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
218            }
219        }
220
221        return mBlockDeviceSize;
222    }
223
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
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
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
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
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
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
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
390    private native long nativeGetBlockDeviceSize(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