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