PersistentDataBlockService.java revision a31c23da300b5d1f4b1fc261bb0dcb1fee9b61f1
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;
44
45/**
46 * Service for reading and writing blocks to a persistent partition.
47 * This data will live across factory resets not initiated via the Settings UI.
48 * When a device is factory reset through Settings this data is wiped.
49 *
50 * Allows writing one block at a time. Namely, each time
51 * {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
52 * is called, it will overwite the data that was previously written on the block.
53 *
54 * Clients can query the size of the currently written block via
55 * {@link android.service.persistentdata.IPersistentDataBlockService}.getTotalDataSize().
56 *
57 * Clients can any number of bytes from the currently written block up to its total size by invoking
58 * {@link android.service.persistentdata.IPersistentDataBlockService}.read(byte[] data)
59 */
60public class PersistentDataBlockService extends SystemService {
61    private static final String TAG = PersistentDataBlockService.class.getSimpleName();
62
63    private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
64    private static final int HEADER_SIZE = 8;
65    // Magic number to mark block device as adhering to the format consumed by this service
66    private static final int PARTITION_TYPE_MARKER = 0x1990;
67    // Limit to 100k as blocks larger than this might cause strain on Binder.
68    // TODO(anmorales): Consider splitting up too-large blocks in PersistentDataBlockManager
69    private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
70
71    private final Context mContext;
72    private final String mDataBlockFile;
73    private final Object mLock = new Object();
74
75    private int mAllowedUid = -1;
76    /*
77     * Separate lock for OEM unlock related operations as they can happen in parallel with regular
78     * block operations.
79     */
80    private final Object mOemLock = new Object();
81
82    private long mBlockDeviceSize;
83
84    public PersistentDataBlockService(Context context) {
85        super(context);
86        mContext = context;
87        mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
88        mBlockDeviceSize = -1; // Load lazily
89        mAllowedUid = getAllowedUid(UserHandle.USER_OWNER);
90    }
91
92
93    private int getAllowedUid(int userHandle) {
94        String allowedPackage = mContext.getResources()
95                .getString(R.string.config_persistentDataPackageName);
96        PackageManager pm = mContext.getPackageManager();
97        int allowedUid = -1;
98        try {
99            allowedUid = pm.getPackageUid(allowedPackage, userHandle);
100        } catch (PackageManager.NameNotFoundException e) {
101            // not expected
102            Slog.e(TAG, "not able to find package " + allowedPackage, e);
103        }
104        return allowedUid;
105    }
106
107    @Override
108    public void onStart() {
109        publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
110    }
111
112    private void enforceOemUnlockPermission() {
113        mContext.enforceCallingOrSelfPermission(
114                Manifest.permission.OEM_UNLOCK_STATE,
115                "Can't access OEM unlock state");
116    }
117
118    private void enforceUid(int callingUid) {
119        if (callingUid != mAllowedUid) {
120            throw new SecurityException("uid " + callingUid + " not allowed to access PST");
121        }
122    }
123
124    private void enforceIsOwner() {
125        if (!Binder.getCallingUserHandle().isOwner()) {
126            throw new SecurityException("Only the Owner is allowed to change OEM unlock state");
127        }
128    }
129
130    private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
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 native long nativeGetBlockDeviceSize(String path);
152    private native int nativeWipe(String path);
153
154    private final IBinder mService = new IPersistentDataBlockService.Stub() {
155        @Override
156        public int write(byte[] data) throws RemoteException {
157            enforceUid(Binder.getCallingUid());
158
159            // Need to ensure we don't write over the last byte
160            long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
161            if (data.length > maxBlockSize) {
162                // partition is ~500k so shouldn't be a problem to downcast
163                return (int) -maxBlockSize;
164            }
165
166            DataOutputStream outputStream;
167            try {
168                outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
169            } catch (FileNotFoundException e) {
170                Slog.e(TAG, "partition not available?", e);
171                return -1;
172            }
173
174            ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
175            headerAndData.putInt(PARTITION_TYPE_MARKER);
176            headerAndData.putInt(data.length);
177            headerAndData.put(data);
178
179            try {
180                synchronized (mLock) {
181                    outputStream.write(headerAndData.array());
182                    return data.length;
183                }
184            } catch (IOException e) {
185                Slog.e(TAG, "failed writing to the persistent data block", e);
186                return -1;
187            } finally {
188                try {
189                    outputStream.close();
190                } catch (IOException e) {
191                    Slog.e(TAG, "failed closing output stream", e);
192                }
193            }
194        }
195
196        @Override
197        public byte[] read() {
198            enforceUid(Binder.getCallingUid());
199
200            DataInputStream inputStream;
201            try {
202                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
203            } catch (FileNotFoundException e) {
204                Slog.e(TAG, "partition not available?", e);
205                return null;
206            }
207
208            try {
209                synchronized (mLock) {
210                    int totalDataSize = getTotalDataSizeLocked(inputStream);
211
212                    if (totalDataSize == 0) {
213                        return new byte[0];
214                    }
215
216                    byte[] data = new byte[totalDataSize];
217                    int read = inputStream.read(data, 0, totalDataSize);
218                    if (read < totalDataSize) {
219                        // something went wrong, not returning potentially corrupt data
220                        Slog.e(TAG, "failed to read entire data block. bytes read: " +
221                                read + "/" + totalDataSize);
222                        return null;
223                    }
224                    return data;
225                }
226            } catch (IOException e) {
227                Slog.e(TAG, "failed to read data", e);
228                return null;
229            } finally {
230                try {
231                    inputStream.close();
232                } catch (IOException e) {
233                    Slog.e(TAG, "failed to close OutputStream");
234                }
235            }
236        }
237
238        @Override
239        public void wipe() {
240            enforceOemUnlockPermission();
241
242            synchronized (mLock) {
243                int ret = nativeWipe(mDataBlockFile);
244
245                if (ret < 0) {
246                    Slog.e(TAG, "failed to wipe persistent partition");
247                }
248            }
249        }
250
251        @Override
252        public void setOemUnlockEnabled(boolean enabled) {
253            // do not allow monkey to flip the flag
254            if (ActivityManager.isUserAMonkey()) {
255                return;
256            }
257            enforceOemUnlockPermission();
258            enforceIsOwner();
259            FileOutputStream outputStream;
260            try {
261                outputStream = new FileOutputStream(new File(mDataBlockFile));
262            } catch (FileNotFoundException e) {
263                Slog.e(TAG, "parition not available", e);
264                return;
265            }
266
267            try {
268                FileChannel channel = outputStream.getChannel();
269
270                channel.position(getBlockDeviceSize() - 1);
271
272                ByteBuffer data = ByteBuffer.allocate(1);
273                data.put(enabled ? (byte) 1 : (byte) 0);
274                data.flip();
275
276                synchronized (mOemLock) {
277                    channel.write(data);
278                }
279            } catch (IOException e) {
280                Slog.e(TAG, "unable to access persistent partition", e);
281            } finally {
282                IoUtils.closeQuietly(outputStream);
283            }
284        }
285
286        @Override
287        public boolean getOemUnlockEnabled() {
288            enforceOemUnlockPermission();
289            DataInputStream inputStream;
290            try {
291                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
292            } catch (FileNotFoundException e) {
293                Slog.e(TAG, "partition not available");
294                return false;
295            }
296
297            try {
298                inputStream.skip(getBlockDeviceSize() - 1);
299                synchronized (mOemLock) {
300                    return inputStream.readByte() != 0;
301                }
302            } catch (IOException e) {
303                Slog.e(TAG, "unable to access persistent partition", e);
304                return false;
305            } finally {
306                IoUtils.closeQuietly(inputStream);
307            }
308        }
309
310        @Override
311        public int getDataBlockSize() {
312            enforceUid(Binder.getCallingUid());
313
314            DataInputStream inputStream;
315            try {
316                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
317            } catch (FileNotFoundException e) {
318                Slog.e(TAG, "partition not available");
319                return 0;
320            }
321
322            try {
323                synchronized (mLock) {
324                    return getTotalDataSizeLocked(inputStream);
325                }
326            } catch (IOException e) {
327                Slog.e(TAG, "error reading data block size");
328                return 0;
329            } finally {
330                IoUtils.closeQuietly(inputStream);
331            }
332        }
333
334        @Override
335        public long getMaximumDataBlockSize() {
336            long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
337            return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
338        }
339
340    };
341}
342