PersistentDataBlockService.java revision bfe1b04f8ba9cd5486e26ca4cc30242b3b2f2c7f
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 enforceOemUnlockPermission() {
130        mContext.enforceCallingOrSelfPermission(
131                Manifest.permission.OEM_UNLOCK_STATE,
132                "Can't access OEM unlock state");
133    }
134
135    private void enforceUid(int callingUid) {
136        if (callingUid != mAllowedUid) {
137            throw new SecurityException("uid " + callingUid + " not allowed to access PST");
138        }
139    }
140
141    private void enforceIsAdmin() {
142        final int userId = UserHandle.getCallingUserId();
143        final boolean isAdmin = UserManager.get(mContext).isUserAdmin(userId);
144        if (!isAdmin) {
145            throw new SecurityException(
146                    "Only the Admin user is allowed to change OEM unlock state");
147        }
148    }
149    private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
150        // skip over checksum
151        inputStream.skipBytes(DIGEST_SIZE_BYTES);
152
153        int totalDataSize;
154        int blockId = inputStream.readInt();
155        if (blockId == PARTITION_TYPE_MARKER) {
156            totalDataSize = inputStream.readInt();
157        } else {
158            totalDataSize = 0;
159        }
160        return totalDataSize;
161    }
162
163    private long getBlockDeviceSize() {
164        synchronized (mLock) {
165            if (mBlockDeviceSize == -1) {
166                mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
167            }
168        }
169
170        return mBlockDeviceSize;
171    }
172
173    private boolean enforceChecksumValidity() {
174        byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
175
176        synchronized (mLock) {
177            byte[] digest = computeDigestLocked(storedDigest);
178            if (digest == null || !Arrays.equals(storedDigest, digest)) {
179                Slog.i(TAG, "Formatting FRP partition...");
180                formatPartitionLocked(false);
181                return false;
182            }
183        }
184
185        return true;
186    }
187
188    private boolean computeAndWriteDigestLocked() {
189        byte[] digest = computeDigestLocked(null);
190        if (digest != null) {
191            DataOutputStream outputStream;
192            try {
193                outputStream = new DataOutputStream(
194                        new FileOutputStream(new File(mDataBlockFile)));
195            } catch (FileNotFoundException e) {
196                Slog.e(TAG, "partition not available?", e);
197                return false;
198            }
199
200            try {
201                outputStream.write(digest, 0, DIGEST_SIZE_BYTES);
202                outputStream.flush();
203            } catch (IOException e) {
204                Slog.e(TAG, "failed to write block checksum", e);
205                return false;
206            } finally {
207                IoUtils.closeQuietly(outputStream);
208            }
209            return true;
210        } else {
211            return false;
212        }
213    }
214
215    private byte[] computeDigestLocked(byte[] storedDigest) {
216        DataInputStream inputStream;
217        try {
218            inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
219        } catch (FileNotFoundException e) {
220            Slog.e(TAG, "partition not available?", e);
221            return null;
222        }
223
224        MessageDigest md;
225        try {
226            md = MessageDigest.getInstance("SHA-256");
227        } catch (NoSuchAlgorithmException e) {
228            // won't ever happen -- every implementation is required to support SHA-256
229            Slog.e(TAG, "SHA-256 not supported?", e);
230            IoUtils.closeQuietly(inputStream);
231            return null;
232        }
233
234        try {
235            if (storedDigest != null && storedDigest.length == DIGEST_SIZE_BYTES) {
236                inputStream.read(storedDigest);
237            } else {
238                inputStream.skipBytes(DIGEST_SIZE_BYTES);
239            }
240
241            int read;
242            byte[] data = new byte[1024];
243            md.update(data, 0, DIGEST_SIZE_BYTES); // include 0 checksum in digest
244            while ((read = inputStream.read(data)) != -1) {
245                md.update(data, 0, read);
246            }
247        } catch (IOException e) {
248            Slog.e(TAG, "failed to read partition", e);
249            return null;
250        } finally {
251            IoUtils.closeQuietly(inputStream);
252        }
253
254        return md.digest();
255    }
256
257    private void formatPartitionLocked(boolean setOemUnlockEnabled) {
258        DataOutputStream outputStream;
259        try {
260            outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
261        } catch (FileNotFoundException e) {
262            Slog.e(TAG, "partition not available?", e);
263            return;
264        }
265
266        byte[] data = new byte[DIGEST_SIZE_BYTES];
267        try {
268            outputStream.write(data, 0, DIGEST_SIZE_BYTES);
269            outputStream.writeInt(PARTITION_TYPE_MARKER);
270            outputStream.writeInt(0); // data size
271            outputStream.flush();
272        } catch (IOException e) {
273            Slog.e(TAG, "failed to format block", e);
274            return;
275        } finally {
276            IoUtils.closeQuietly(outputStream);
277        }
278
279        doSetOemUnlockEnabledLocked(setOemUnlockEnabled);
280        computeAndWriteDigestLocked();
281    }
282
283    private void doSetOemUnlockEnabledLocked(boolean enabled) {
284        FileOutputStream outputStream;
285        try {
286            outputStream = new FileOutputStream(new File(mDataBlockFile));
287        } catch (FileNotFoundException e) {
288            Slog.e(TAG, "partition not available", e);
289            return;
290        }
291
292        try {
293            FileChannel channel = outputStream.getChannel();
294
295            channel.position(getBlockDeviceSize() - 1);
296
297            ByteBuffer data = ByteBuffer.allocate(1);
298            data.put(enabled ? (byte) 1 : (byte) 0);
299            data.flip();
300            channel.write(data);
301            outputStream.flush();
302        } catch (IOException e) {
303            Slog.e(TAG, "unable to access persistent partition", e);
304            return;
305        } finally {
306            SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
307            IoUtils.closeQuietly(outputStream);
308        }
309    }
310
311    private boolean doGetOemUnlockEnabled() {
312        DataInputStream inputStream;
313        try {
314            inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
315        } catch (FileNotFoundException e) {
316            Slog.e(TAG, "partition not available");
317            return false;
318        }
319
320        try {
321            synchronized (mLock) {
322                inputStream.skip(getBlockDeviceSize() - 1);
323                return inputStream.readByte() != 0;
324            }
325        } catch (IOException e) {
326            Slog.e(TAG, "unable to access persistent partition", e);
327            return false;
328        } finally {
329            IoUtils.closeQuietly(inputStream);
330        }
331    }
332
333    private native long nativeGetBlockDeviceSize(String path);
334    private native int nativeWipe(String path);
335
336    private final IBinder mService = new IPersistentDataBlockService.Stub() {
337        @Override
338        public int write(byte[] data) throws RemoteException {
339            enforceUid(Binder.getCallingUid());
340
341            // Need to ensure we don't write over the last byte
342            long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
343            if (data.length > maxBlockSize) {
344                // partition is ~500k so shouldn't be a problem to downcast
345                return (int) -maxBlockSize;
346            }
347
348            DataOutputStream outputStream;
349            try {
350                outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
351            } catch (FileNotFoundException e) {
352                Slog.e(TAG, "partition not available?", e);
353                return -1;
354            }
355
356            ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
357            headerAndData.putInt(PARTITION_TYPE_MARKER);
358            headerAndData.putInt(data.length);
359            headerAndData.put(data);
360
361            synchronized (mLock) {
362                try {
363                    byte[] checksum = new byte[DIGEST_SIZE_BYTES];
364                    outputStream.write(checksum, 0, DIGEST_SIZE_BYTES);
365                    outputStream.write(headerAndData.array());
366                    outputStream.flush();
367                } catch (IOException e) {
368                    Slog.e(TAG, "failed writing to the persistent data block", e);
369                    return -1;
370                } finally {
371                    IoUtils.closeQuietly(outputStream);
372                }
373
374                if (computeAndWriteDigestLocked()) {
375                    return data.length;
376                } else {
377                    return -1;
378                }
379            }
380        }
381
382        @Override
383        public byte[] read() {
384            enforceUid(Binder.getCallingUid());
385            if (!enforceChecksumValidity()) {
386                return new byte[0];
387            }
388
389            DataInputStream inputStream;
390            try {
391                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
392            } catch (FileNotFoundException e) {
393                Slog.e(TAG, "partition not available?", e);
394                return null;
395            }
396
397            try {
398                synchronized (mLock) {
399                    int totalDataSize = getTotalDataSizeLocked(inputStream);
400
401                    if (totalDataSize == 0) {
402                        return new byte[0];
403                    }
404
405                    byte[] data = new byte[totalDataSize];
406                    int read = inputStream.read(data, 0, totalDataSize);
407                    if (read < totalDataSize) {
408                        // something went wrong, not returning potentially corrupt data
409                        Slog.e(TAG, "failed to read entire data block. bytes read: " +
410                                read + "/" + totalDataSize);
411                        return null;
412                    }
413                    return data;
414                }
415            } catch (IOException e) {
416                Slog.e(TAG, "failed to read data", e);
417                return null;
418            } finally {
419                try {
420                    inputStream.close();
421                } catch (IOException e) {
422                    Slog.e(TAG, "failed to close OutputStream");
423                }
424            }
425        }
426
427        @Override
428        public void wipe() {
429            enforceOemUnlockPermission();
430
431            synchronized (mLock) {
432                int ret = nativeWipe(mDataBlockFile);
433
434                if (ret < 0) {
435                    Slog.e(TAG, "failed to wipe persistent partition");
436                }
437            }
438        }
439
440        @Override
441        public void setOemUnlockEnabled(boolean enabled) throws SecurityException {
442            // do not allow monkey to flip the flag
443            if (ActivityManager.isUserAMonkey()) {
444                return;
445            }
446
447            enforceOemUnlockPermission();
448            enforceIsAdmin();
449
450            // Do not allow oem unlock modification if it has been disallowed.
451            if (Settings.Global.getInt(getContext().getContentResolver(),
452                    Settings.Global.OEM_UNLOCK_DISALLOWED, 0) == 1) {
453                throw new SecurityException("OEM unlock has been disallowed.");
454            }
455
456            synchronized (mLock) {
457                doSetOemUnlockEnabledLocked(enabled);
458                computeAndWriteDigestLocked();
459            }
460        }
461
462        @Override
463        public boolean getOemUnlockEnabled() {
464            enforceOemUnlockPermission();
465            return doGetOemUnlockEnabled();
466        }
467
468        @Override
469        public int getFlashLockState() {
470            enforceOemUnlockPermission();
471            String locked = SystemProperties.get(FLASH_LOCK_PROP);
472            switch (locked) {
473                case FLASH_LOCK_LOCKED:
474                    return PersistentDataBlockManager.FLASH_LOCK_LOCKED;
475                case FLASH_LOCK_UNLOCKED:
476                    return PersistentDataBlockManager.FLASH_LOCK_UNLOCKED;
477                default:
478                    return PersistentDataBlockManager.FLASH_LOCK_UNKNOWN;
479            }
480        }
481
482        @Override
483        public int getDataBlockSize() {
484            enforcePersistentDataBlockAccess();
485
486            DataInputStream inputStream;
487            try {
488                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
489            } catch (FileNotFoundException e) {
490                Slog.e(TAG, "partition not available");
491                return 0;
492            }
493
494            try {
495                synchronized (mLock) {
496                    return getTotalDataSizeLocked(inputStream);
497                }
498            } catch (IOException e) {
499                Slog.e(TAG, "error reading data block size");
500                return 0;
501            } finally {
502                IoUtils.closeQuietly(inputStream);
503            }
504        }
505
506        private void enforcePersistentDataBlockAccess() {
507            if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
508                    != PackageManager.PERMISSION_GRANTED) {
509                enforceUid(Binder.getCallingUid());
510            }
511        }
512
513        @Override
514        public long getMaximumDataBlockSize() {
515            long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
516            return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
517        }
518    };
519}
520