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