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