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