Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 963295ea authored by Andres Morales's avatar Andres Morales
Browse files

Permit settings to "wipe" the persistent partition

One of the requirements is that when the user does a
factory reset through settings, all data on the
persistent partition should be cleared.

This adds one last API method that allows settings
to wipe the partition.

Bug: 14288780
Change-Id: Ib87ee741d1e5195814516ae1d66eb7c4cf754dcf
parent dcb743e2
Loading
Loading
Loading
Loading
+19 −1
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.service.persistentdata;

import android.os.ParcelFileDescriptor;
@@ -12,8 +28,10 @@ import android.os.ParcelFileDescriptor;
 */
interface IPersistentDataBlockService {
    int write(in byte[] data);
    int read(out byte[] data);
    byte[] read();
    void wipe();
    int getDataBlockSize();
    long getMaximumDataBlockSize();

    void setOemUnlockEnabled(boolean enabled);
    boolean getOemUnlockEnabled();
+61 −15
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.service.persistentdata;

import android.os.RemoteException;
@@ -7,14 +23,16 @@ import android.util.Slog;
 * Interface for reading and writing data blocks to a persistent partition.
 *
 * Allows writing one block at a time. Namely, each time
 * {@link android.service.persistentdata.PersistentDataBlockManager}.write(byte[] data)
 * {@link PersistentDataBlockManager#write(byte[])}
 * is called, it will overwite the data that was previously written on the block.
 *
 * Clients can query the size of the currently written block via
 * {@link android.service.persistentdata.PersistentDataBlockManager}.getTotalDataSize().
 * {@link PersistentDataBlockManager#getDataBlockSize()}.
 *
 * Clients can query the maximum size for a block via
 *
 * Clients can any number of bytes from the currently written block up to its total size by invoking
 * {@link android.service.persistentdata.PersistentDataBlockManager}.read(byte[] data).
 * Clients can read the currently written block by invoking
 * {@link PersistentDataBlockManager#read()}.
 *
 * @hide
 */
@@ -30,41 +48,69 @@ public class PersistentDataBlockManager {
     * Writes {@code data} to the persistent partition. Previously written data
     * will be overwritten. This data will persist across factory resets.
     *
     * Returns the number of bytes written or -1 on error. If the block is too big
     * to fit on the partition, returns -MAX_BLOCK_SIZE.
     *
     * @param data the data to write
     */
    public void write(byte[] data) {
    public int write(byte[] data) {
        try {
            sService.write(data);
            return sService.write(data);
        } catch (RemoteException e) {
            onError("writing data");
            return -1;
        }
    }

    /**
     * Tries to read {@code data.length} bytes into {@code data}. Call {@code getDataBlockSize()}
     * to determine the total size of the block currently residing in the persistent partition.
     *
     * @param data the buffer in which to read the data
     * @return the actual number of bytes read
     * Returns the data block stored on the persistent partition.
     */
    public int read(byte[] data) {
    public byte[] read() {
        try {
            return sService.read(data);
            return sService.read();
        } catch (RemoteException e) {
            onError("reading data");
            return -1;
            return null;
        }
    }

    /**
     * Retrieves the size of the block currently written to the persistent partition.
     *
     * Return -1 on error.
     */
    public int getDataBlockSize() {
        try {
            return sService.getDataBlockSize();
        } catch (RemoteException e) {
            onError("getting data block size");
            return 0;
            return -1;
        }
    }

    /**
     * Retrieves the maximum size allowed for a data block.
     *
     * Returns -1 on error.
     */
    public long getMaximumDataBlockSize() {
        try {
            return sService.getMaximumDataBlockSize();
        } catch (RemoteException e) {
            onError("getting maximum data block size");
            return -1;
        }
    }

    /**
     * Zeroes the previously written block in its entirety. Calling this method
     * will erase all data written to the persistent data partition.
     */
    public void wipe() {
        try {
            sService.wipe();
        } catch (RemoteException e) {
            onError("wiping persistent partition");
        }
    }

+118 −57
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server;

import android.Manifest;
@@ -8,8 +24,9 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.service.persistentdata.IPersistentDataBlockService;
import android.util.Log;
import android.util.Slog;
import com.android.internal.R;
import libcore.io.IoUtils;

import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -23,7 +40,8 @@ import java.nio.channels.FileChannel;

/**
 * Service for reading and writing blocks to a persistent partition.
 * This data will live across factory resets.
 * This data will live across factory resets not initiated via the Settings UI.
 * When a device is factory reset through Settings this data is wiped.
 *
 * Allows writing one block at a time. Namely, each time
 * {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
@@ -40,19 +58,29 @@ public class PersistentDataBlockService extends SystemService {

    private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
    private static final int HEADER_SIZE = 8;
    private static final int BLOCK_ID = 0x1990;
    // Magic number to mark block device as adhering to the format consumed by this service
    private static final int PARTITION_TYPE_MARKER = 0x1990;
    // Limit to 100k as blocks larger than this might cause strain on Binder.
    // TODO(anmorales): Consider splitting up too-large blocks in PersistentDataBlockManager
    private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;

    private final Context mContext;
    private final String mDataBlockFile;
    private long mBlockDeviceSize;

    private final int mAllowedUid;
    private final Object mLock = new Object();
    /*
     * Separate lock for OEM unlock related operations as they can happen in parallel with regular
     * block operations.
     */
    private final Object mOemLock = new Object();

    private long mBlockDeviceSize;

    public PersistentDataBlockService(Context context) {
        super(context);
        mContext = context;
        mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
        mBlockDeviceSize = 0; // Load lazily
        mBlockDeviceSize = -1; // Load lazily
        String allowedPackage = context.getResources()
                .getString(R.string.config_persistentDataPackageName);
        PackageManager pm = mContext.getPackageManager();
@@ -62,7 +90,7 @@ public class PersistentDataBlockService extends SystemService {
                    Binder.getCallingUserHandle().getIdentifier());
        } catch (PackageManager.NameNotFoundException e) {
            // not expected
            Log.e(TAG, "not able to find package " + allowedPackage, e);
            Slog.e(TAG, "not able to find package " + allowedPackage, e);
        }

        mAllowedUid = allowedUid;
@@ -85,10 +113,10 @@ public class PersistentDataBlockService extends SystemService {
        }
    }

    private int getTotalDataSize(DataInputStream inputStream) throws IOException {
    private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
        int totalDataSize;
        int blockId = inputStream.readInt();
        if (blockId == BLOCK_ID) {
        if (blockId == PARTITION_TYPE_MARKER) {
            totalDataSize = inputStream.readInt();
        } else {
            totalDataSize = 0;
@@ -96,17 +124,18 @@ public class PersistentDataBlockService extends SystemService {
        return totalDataSize;
    }

    private long maybeReadBlockDeviceSize() {
        synchronized (this) {
            if (mBlockDeviceSize == 0) {
                mBlockDeviceSize = getBlockDeviceSize(mDataBlockFile);
    private long getBlockDeviceSize() {
        synchronized (mLock) {
            if (mBlockDeviceSize == -1) {
                mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
            }
        }

        return mBlockDeviceSize;
    }

    private native long getBlockDeviceSize(String path);
    private native long nativeGetBlockDeviceSize(String path);
    private native int nativeWipe(String path);

    private final IBinder mService = new IPersistentDataBlockService.Stub() {
        @Override
@@ -114,62 +143,93 @@ public class PersistentDataBlockService extends SystemService {
            enforceUid(Binder.getCallingUid());

            // Need to ensure we don't write over the last byte
            if (data.length > maybeReadBlockDeviceSize() - HEADER_SIZE - 1) {
                return -1;
            long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
            if (data.length > maxBlockSize) {
                // partition is ~500k so shouldn't be a problem to downcast
                return (int) -maxBlockSize;
            }

            DataOutputStream outputStream;
            try {
                outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
            } catch (FileNotFoundException e) {
                Log.e(TAG, "partition not available?", e);
                Slog.e(TAG, "partition not available?", e);
                return -1;
            }

            ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
            headerAndData.putInt(BLOCK_ID);
            headerAndData.putInt(PARTITION_TYPE_MARKER);
            headerAndData.putInt(data.length);
            headerAndData.put(data);

            try {
                synchronized (mLock) {
                    outputStream.write(headerAndData.array());
                    return data.length;
                }
            } catch (IOException e) {
                Log.e(TAG, "failed writing to the persistent data block", e);
                Slog.e(TAG, "failed writing to the persistent data block", e);
                return -1;
            } finally {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    Log.e(TAG, "failed closing output stream", e);
                    Slog.e(TAG, "failed closing output stream", e);
                }
            }
        }

        @Override
        public int read(byte[] data) {
        public byte[] read() {
            enforceUid(Binder.getCallingUid());

            DataInputStream inputStream;
            try {
                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
            } catch (FileNotFoundException e) {
                Log.e(TAG, "partition not available?", e);
                return -1;
                Slog.e(TAG, "partition not available?", e);
                return null;
            }

            try {
                int totalDataSize = getTotalDataSize(inputStream);
                return inputStream.read(data, 0,
                        (data.length > totalDataSize) ? totalDataSize : data.length);
                synchronized (mLock) {
                    int totalDataSize = getTotalDataSizeLocked(inputStream);

                    if (totalDataSize == 0) {
                        return new byte[0];
                    }

                    byte[] data = new byte[totalDataSize];
                    int read = inputStream.read(data, 0, totalDataSize);
                    if (read < totalDataSize) {
                        // something went wrong, not returning potentially corrupt data
                        Slog.e(TAG, "failed to read entire data block. bytes read: " +
                                read + "/" + totalDataSize);
                        return null;
                    }
                    return data;
                }
            } catch (IOException e) {
                Log.e(TAG, "failed to read data", e);
                return -1;
                Slog.e(TAG, "failed to read data", e);
                return null;
            } finally {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    Log.e(TAG, "failed to close OutputStream");
                    Slog.e(TAG, "failed to close OutputStream");
                }
            }
        }

        @Override
        public void wipe() {
            enforceOemUnlockPermission();

            synchronized (mLock) {
                int ret = nativeWipe(mDataBlockFile);

                if (ret < 0) {
                    Slog.e(TAG, "failed to wipe persistent partition");
                }
            }
        }
@@ -181,28 +241,26 @@ public class PersistentDataBlockService extends SystemService {
            try {
                outputStream = new FileOutputStream(new File(mDataBlockFile));
            } catch (FileNotFoundException e) {
                Log.e(TAG, "parition not available", e);
                Slog.e(TAG, "parition not available", e);
                return;
            }

            try {
                FileChannel channel = outputStream.getChannel();

                channel.position(maybeReadBlockDeviceSize() - 1);
                channel.position(getBlockDeviceSize() - 1);

                ByteBuffer data = ByteBuffer.allocate(1);
                data.put(enabled ? (byte) 1 : (byte) 0);
                data.flip();

                synchronized (mOemLock) {
                    channel.write(data);
                }
            } catch (IOException e) {
                Log.e(TAG, "unable to access persistent partition", e);
                Slog.e(TAG, "unable to access persistent partition", e);
            } finally {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    Log.e(TAG, "failed to close OutputStream");
                }
                IoUtils.closeQuietly(outputStream);
            }
        }

@@ -213,22 +271,20 @@ public class PersistentDataBlockService extends SystemService {
            try {
                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
            } catch (FileNotFoundException e) {
                Log.e(TAG, "partition not available");
                Slog.e(TAG, "partition not available");
                return false;
            }

            try {
                inputStream.skip(maybeReadBlockDeviceSize() - 1);
                inputStream.skip(getBlockDeviceSize() - 1);
                synchronized (mOemLock) {
                    return inputStream.readByte() != 0;
                }
            } catch (IOException e) {
                Log.e(TAG, "unable to access persistent partition", e);
                Slog.e(TAG, "unable to access persistent partition", e);
                return false;
            } finally {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    Log.e(TAG, "failed to close OutputStream");
                }
                IoUtils.closeQuietly(inputStream);
            }
        }

@@ -240,22 +296,27 @@ public class PersistentDataBlockService extends SystemService {
            try {
                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
            } catch (FileNotFoundException e) {
                Log.e(TAG, "partition not available");
                Slog.e(TAG, "partition not available");
                return 0;
            }

            try {
                return getTotalDataSize(inputStream);
                synchronized (mLock) {
                    return getTotalDataSizeLocked(inputStream);
                }
            } catch (IOException e) {
                Log.e(TAG, "error reading data block size");
                Slog.e(TAG, "error reading data block size");
                return 0;
            } finally {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    Log.e(TAG, "failed to close OutputStream");
                IoUtils.closeQuietly(inputStream);
            }
        }

        @Override
        public long getMaximumDataBlockSize() {
            long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
            return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
        }

    };
}
+49 −2
Original line number Diff line number Diff line
@@ -21,9 +21,13 @@
#include <utils/misc.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <utils/Log.h>


#include <inttypes.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

namespace android {

@@ -40,7 +44,39 @@ namespace android {
        return size;
    }

    static jlong com_android_server_PeristentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath) {
    int wipe_block_device(int fd)
    {
        uint64_t range[2];
        int ret;
        uint64_t len = get_block_device_size(fd);

        range[0] = 0;
        range[1] = len;

        if (range[1] == 0)
            return 0;

        ret = ioctl(fd, BLKSECDISCARD, &range);
        if (ret < 0) {
            ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno));
            range[0] = 0;
            range[1] = len;
            ret = ioctl(fd, BLKDISCARD, &range);
            if (ret < 0) {
                ALOGE("Discard failed: %s\n", strerror(errno));
                return -1;
            } else {
                ALOGE("Wipe via secure discard failed, used non-secure discard instead\n");
                return 0;
            }

        }

        return ret;
    }

    static jlong com_android_server_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath)
    {
        const char *path = env->GetStringUTFChars(jpath, 0);
        int fd = open(path, O_RDONLY);

@@ -50,9 +86,20 @@ namespace android {
        return get_block_device_size(fd);
    }

    static int com_android_server_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) {
        const char *path = env->GetStringUTFChars(jpath, 0);
        int fd = open(path, O_WRONLY);

        if (fd < 0)
            return 0;

        return wipe_block_device(fd);
    }

    static JNINativeMethod sMethods[] = {
         /* name, signature, funcPtr */
        {"getBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_PeristentDataBlockService_getBlockDeviceSize},
        {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_PersistentDataBlockService_getBlockDeviceSize},
        {"nativeWipe", "(Ljava/lang/String;)I", (void*)com_android_server_PersistentDataBlockService_wipe},
    };

    int register_android_server_PersistentDataBlockService(JNIEnv* env)