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