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