platform_system_vold/Volume.cpp
San Mehat fcf24fe62f vold: Add a versioned superblock to secure containers.
Add an un-encrypted superblock to the end of a secure container to
help identify the crypto cipher used. This is required in order to
provide some semblence of versioning, in case we have cause to
change the crypto type on an update - say for example switching
from software to hardware encryption, or from 128 -> a zillion
bit keys.

NOTE: This format is incompatible with previous secure containers.
After this change they will no longer be mountable.

Signed-off-by: San Mehat <san@google.com>
2010-03-03 12:40:34 -08:00

578 lines
16 KiB
C++

/*
* Copyright (C) 2008 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.
*/
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <linux/kdev_t.h>
#include <cutils/properties.h>
#include "diskmbr.h"
#define LOG_TAG "Vold"
#include <cutils/log.h>
#include "Volume.h"
#include "VolumeManager.h"
#include "ResponseCode.h"
#include "Fat.h"
#include "Process.h"
extern "C" void dos_partition_dec(void const *pp, struct dos_partition *d);
extern "C" void dos_partition_enc(void *pp, struct dos_partition *d);
/*
* Secure directory - stuff that only root can see
*/
const char *Volume::SECDIR = "/mnt/secure";
/*
* Secure staging directory - where media is mounted for preparation
*/
const char *Volume::SEC_STGDIR = "/mnt/secure/staging";
/*
* Path to the directory on the media which contains publicly accessable
* asec imagefiles. This path will be obscured before the mount is
* exposed to non priviledged users.
*/
const char *Volume::SEC_STG_SECIMGDIR = "/mnt/secure/staging/.android_secure";
/*
* Path to where *only* root can access asec imagefiles
*/
const char *Volume::SEC_ASECDIR = "/mnt/secure/asec";
/*
* Path to where secure containers are mounted
*/
const char *Volume::ASECDIR = "/mnt/asec";
static const char *stateToStr(int state) {
if (state == Volume::State_Init)
return "Initializing";
else if (state == Volume::State_NoMedia)
return "No-Media";
else if (state == Volume::State_Idle)
return "Idle-Unmounted";
else if (state == Volume::State_Pending)
return "Pending";
else if (state == Volume::State_Mounted)
return "Mounted";
else if (state == Volume::State_Unmounting)
return "Unmounting";
else if (state == Volume::State_Checking)
return "Checking";
else if (state == Volume::State_Formatting)
return "Formatting";
else if (state == Volume::State_Shared)
return "Shared-Unmounted";
else if (state == Volume::State_SharedMnt)
return "Shared-Mounted";
else
return "Unknown-Error";
}
Volume::Volume(VolumeManager *vm, const char *label, const char *mount_point) {
mVm = vm;
mLabel = strdup(label);
mMountpoint = strdup(mount_point);
mState = Volume::State_Init;
mCurrentlyMountedKdev = -1;
}
Volume::~Volume() {
free(mLabel);
free(mMountpoint);
}
dev_t Volume::getDiskDevice() {
return MKDEV(0, 0);
};
void Volume::handleVolumeShared() {
}
void Volume::handleVolumeUnshared() {
}
int Volume::handleBlockEvent(NetlinkEvent *evt) {
errno = ENOSYS;
return -1;
}
void Volume::setState(int state) {
char msg[255];
int oldState = mState;
if (oldState == state) {
LOGW("Duplicate state (%d)\n", state);
return;
}
mState = state;
LOGD("Volume %s state changing %d (%s) -> %d (%s)", mLabel,
oldState, stateToStr(oldState), mState, stateToStr(mState));
snprintf(msg, sizeof(msg),
"Volume %s %s state changed from %d (%s) to %d (%s)", getLabel(),
getMountpoint(), oldState, stateToStr(oldState), mState,
stateToStr(mState));
mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeStateChange,
msg, false);
}
int Volume::createDeviceNode(const char *path, int major, int minor) {
mode_t mode = 0660 | S_IFBLK;
dev_t dev = (major << 8) | minor;
if (mknod(path, mode, dev) < 0) {
if (errno != EEXIST) {
return -1;
}
}
return 0;
}
int Volume::formatVol() {
if (getState() == Volume::State_NoMedia) {
errno = ENODEV;
return -1;
} else if (getState() != Volume::State_Idle) {
errno = EBUSY;
return -1;
}
if (isMountpointMounted(getMountpoint())) {
LOGW("Volume is idle but appears to be mounted - fixing");
setState(Volume::State_Mounted);
// mCurrentlyMountedKdev = XXX
errno = EBUSY;
return -1;
}
char devicePath[255];
dev_t diskNode = getDiskDevice();
dev_t partNode = MKDEV(MAJOR(diskNode), 1); // XXX: Hmmm
sprintf(devicePath, "/dev/block/vold/%d:%d",
MAJOR(diskNode), MINOR(diskNode));
LOGI("Formatting volume %s (%s)", getLabel(), devicePath);
if (initializeMbr(devicePath)) {
LOGE("Failed to initialize MBR (%s)", strerror(errno));
goto err;
}
sprintf(devicePath, "/dev/block/vold/%d:%d",
MAJOR(partNode), MINOR(partNode));
if (Fat::format(devicePath, 0)) {
LOGE("Failed to format (%s)", strerror(errno));
goto err;
}
return 0;
err:
return -1;
}
bool Volume::isMountpointMounted(const char *path) {
char device[256];
char mount_path[256];
char rest[256];
FILE *fp;
char line[1024];
if (!(fp = fopen("/proc/mounts", "r"))) {
LOGE("Error opening /proc/mounts (%s)", strerror(errno));
return false;
}
while(fgets(line, sizeof(line), fp)) {
line[strlen(line)-1] = '\0';
sscanf(line, "%255s %255s %255s\n", device, mount_path, rest);
if (!strcmp(mount_path, path)) {
fclose(fp);
return true;
}
}
fclose(fp);
return false;
}
int Volume::mountVol() {
dev_t deviceNodes[4];
int n, i, rc = 0;
char errmsg[255];
if (getState() == Volume::State_NoMedia) {
snprintf(errmsg, sizeof(errmsg),
"Volume %s %s mount failed - no media",
getLabel(), getMountpoint());
mVm->getBroadcaster()->sendBroadcast(
ResponseCode::VolumeMountFailedNoMedia,
errmsg, false);
errno = ENODEV;
return -1;
} else if (getState() != Volume::State_Idle) {
errno = EBUSY;
return -1;
}
if (isMountpointMounted(getMountpoint())) {
LOGW("Volume is idle but appears to be mounted - fixing");
setState(Volume::State_Mounted);
// mCurrentlyMountedKdev = XXX
return 0;
}
n = getDeviceNodes((dev_t *) &deviceNodes, 4);
if (!n) {
LOGE("Failed to get device nodes (%s)\n", strerror(errno));
return -1;
}
for (i = 0; i < n; i++) {
char devicePath[255];
sprintf(devicePath, "/dev/block/vold/%d:%d", MAJOR(deviceNodes[i]),
MINOR(deviceNodes[i]));
LOGI("%s being considered for volume %s\n", devicePath, getLabel());
errno = 0;
setState(Volume::State_Checking);
if (Fat::check(devicePath)) {
if (errno == ENODATA) {
LOGW("%s does not contain a FAT filesystem\n", devicePath);
continue;
}
errno = EIO;
/* Badness - abort the mount */
LOGE("%s failed FS checks (%s)", devicePath, strerror(errno));
setState(Volume::State_Idle);
return -1;
}
/*
* Mount the device on our internal staging mountpoint so we can
* muck with it before exposing it to non priviledged users.
*/
errno = 0;
if (Fat::doMount(devicePath, "/mnt/secure/staging", false, false, 1000, 1015, 0702, true)) {
LOGE("%s failed to mount via VFAT (%s)\n", devicePath, strerror(errno));
continue;
}
LOGI("Device %s, target %s mounted @ /mnt/secure/staging", devicePath, getMountpoint());
if (createBindMounts()) {
LOGE("Failed to create bindmounts (%s)", strerror(errno));
umount("/mnt/secure/staging");
setState(Volume::State_Idle);
return -1;
}
/*
* Now that the bindmount trickery is done, atomically move the
* whole subtree to expose it to non priviledged users.
*/
if (doMoveMount("/mnt/secure/staging", getMountpoint(), false)) {
LOGE("Failed to move mount (%s)", strerror(errno));
umount("/mnt/secure/staging");
setState(Volume::State_Idle);
return -1;
}
setState(Volume::State_Mounted);
mCurrentlyMountedKdev = deviceNodes[i];
return 0;
}
LOGE("Volume %s found no suitable devices for mounting :(\n", getLabel());
setState(Volume::State_Idle);
return -1;
}
int Volume::createBindMounts() {
unsigned long flags;
/*
* Rename old /android_secure -> /.android_secure
*/
if (!access("/mnt/secure/staging/android_secure", R_OK | X_OK) &&
access(SEC_STG_SECIMGDIR, R_OK | X_OK)) {
if (rename("/mnt/secure/staging/android_secure", SEC_STG_SECIMGDIR)) {
LOGE("Failed to rename legacy asec dir (%s)", strerror(errno));
}
}
/*
* Ensure that /android_secure exists and is a directory
*/
if (access(SEC_STG_SECIMGDIR, R_OK | X_OK)) {
if (errno == ENOENT) {
if (mkdir(SEC_STG_SECIMGDIR, 0777)) {
LOGE("Failed to create %s (%s)", SEC_STG_SECIMGDIR, strerror(errno));
return -1;
}
} else {
LOGE("Failed to access %s (%s)", SEC_STG_SECIMGDIR, strerror(errno));
return -1;
}
} else {
struct stat sbuf;
if (stat(SEC_STG_SECIMGDIR, &sbuf)) {
LOGE("Failed to stat %s (%s)", SEC_STG_SECIMGDIR, strerror(errno));
return -1;
}
if (!S_ISDIR(sbuf.st_mode)) {
LOGE("%s is not a directory", SEC_STG_SECIMGDIR);
errno = ENOTDIR;
return -1;
}
}
/*
* Bind mount /mnt/secure/staging/android_secure -> /mnt/secure/asec so we'll
* have a root only accessable mountpoint for it.
*/
if (mount(SEC_STG_SECIMGDIR, SEC_ASECDIR, "", MS_BIND, NULL)) {
LOGE("Failed to bind mount points %s -> %s (%s)",
SEC_STG_SECIMGDIR, SEC_ASECDIR, strerror(errno));
return -1;
}
/*
* Mount a read-only, zero-sized tmpfs on <mountpoint>/android_secure to
* obscure the underlying directory from everybody - sneaky eh? ;)
*/
if (mount("tmpfs", SEC_STG_SECIMGDIR, "tmpfs", MS_RDONLY, "size=0,mode=000,uid=0,gid=0")) {
LOGE("Failed to obscure %s (%s)", SEC_STG_SECIMGDIR, strerror(errno));
umount("/mnt/asec_secure");
return -1;
}
return 0;
}
int Volume::doMoveMount(const char *src, const char *dst, bool force) {
unsigned int flags = MS_MOVE;
int retries = 5;
while(retries--) {
if (!mount(src, dst, "", flags, NULL)) {
LOGD("Moved mount %s -> %s sucessfully", src, dst);
return 0;
} else if (errno != EBUSY) {
LOGE("Failed to move mount %s -> %s (%s)", src, dst, strerror(errno));
return -1;
}
int action = 0;
if (force) {
if (retries == 1) {
action = 2; // SIGKILL
} else if (retries == 2) {
action = 1; // SIGHUP
}
}
LOGW("Failed to move %s -> %s (%s, retries %d, action %d)",
src, dst, strerror(errno), retries, action);
Process::killProcessesWithOpenFiles(src, action);
usleep(1000*250);
}
errno = EBUSY;
LOGE("Giving up on move %s -> %s (%s)", src, dst, strerror(errno));
return -1;
}
int Volume::doUnmount(const char *path, bool force) {
int retries = 10;
while (retries--) {
if (!umount(path) || errno == EINVAL || errno == ENOENT) {
LOGI("%s sucessfully unmounted", path);
return 0;
}
int action = 0;
if (force) {
if (retries == 1) {
action = 2; // SIGKILL
} else if (retries == 2) {
action = 1; // SIGHUP
}
}
LOGW("Failed to unmount %s (%s, retries %d, action %d)",
path, strerror(errno), retries, action);
Process::killProcessesWithOpenFiles(path, action);
usleep(1000*1000);
}
errno = EBUSY;
LOGE("Giving up on unmount %s (%s)", path, strerror(errno));
return -1;
}
int Volume::unmountVol(bool force) {
int i, rc;
if (getState() != Volume::State_Mounted) {
LOGE("Volume %s unmount request when not mounted", getLabel());
errno = EINVAL;
return -1;
}
setState(Volume::State_Unmounting);
usleep(1000 * 1000); // Give the framework some time to react
/*
* First move the mountpoint back to our internal staging point
* so nobody else can muck with it while we work.
*/
if (doMoveMount(getMountpoint(), SEC_STGDIR, force)) {
LOGE("Failed to move mount %s => %s (%s)", getMountpoint(), SEC_STGDIR, strerror(errno));
setState(Volume::State_Mounted);
return -1;
}
/*
* Unmount the tmpfs which was obscuring the asec image directory
* from non root users
*/
if (doUnmount(Volume::SEC_STG_SECIMGDIR, force)) {
LOGE("Failed to unmount tmpfs on %s (%s)", SEC_STG_SECIMGDIR, strerror(errno));
goto fail_republish;
}
/*
* Remove the bindmount we were using to keep a reference to
* the previously obscured directory.
*/
if (doUnmount(Volume::SEC_ASECDIR, force)) {
LOGE("Failed to remove bindmount on %s (%s)", SEC_ASECDIR, strerror(errno));
goto fail_remount_tmpfs;
}
/*
* Finally, unmount the actual block device from the staging dir
*/
if (doUnmount(Volume::SEC_STGDIR, force)) {
LOGE("Failed to unmount %s (%s)", SEC_STGDIR, strerror(errno));
goto fail_recreate_bindmount;
}
LOGI("%s unmounted sucessfully", getMountpoint());
setState(Volume::State_Idle);
mCurrentlyMountedKdev = -1;
return 0;
/*
* Failure handling - try to restore everything back the way it was
*/
fail_recreate_bindmount:
if (mount(SEC_STG_SECIMGDIR, SEC_ASECDIR, "", MS_BIND, NULL)) {
LOGE("Failed to restore bindmount after failure! - Storage will appear offline!");
goto out_nomedia;
}
fail_remount_tmpfs:
if (mount("tmpfs", SEC_STG_SECIMGDIR, "tmpfs", MS_RDONLY, "size=0,mode=0,uid=0,gid=0")) {
LOGE("Failed to restore tmpfs after failure! - Storage will appear offline!");
goto out_nomedia;
}
fail_republish:
if (doMoveMount(SEC_STGDIR, getMountpoint(), force)) {
LOGE("Failed to republish mount after failure! - Storage will appear offline!");
goto out_nomedia;
}
setState(Volume::State_Mounted);
return -1;
out_nomedia:
setState(Volume::State_NoMedia);
return -1;
}
int Volume::initializeMbr(const char *deviceNode) {
int fd, rc;
unsigned char block[512];
struct dos_partition part;
unsigned int nr_sec;
if ((fd = open(deviceNode, O_RDWR)) < 0) {
LOGE("Error opening disk file (%s)", strerror(errno));
return -1;
}
if (ioctl(fd, BLKGETSIZE, &nr_sec)) {
LOGE("Unable to get device size (%s)", strerror(errno));
close(fd);
return -1;
}
memset(&part, 0, sizeof(part));
part.dp_flag = 0x80;
part.dp_typ = 0xc;
part.dp_start = ((1024 * 64) / 512) + 1;
part.dp_size = nr_sec - part.dp_start;
memset(block, 0, sizeof(block));
block[0x1fe] = 0x55;
block[0x1ff] = 0xaa;
dos_partition_enc(block + DOSPARTOFF, &part);
if (write(fd, block, sizeof(block)) < 0) {
LOGE("Error writing MBR (%s)", strerror(errno));
close(fd);
return -1;
}
if (ioctl(fd, BLKRRPART, NULL) < 0) {
LOGE("Error re-reading partition table (%s)", strerror(errno));
close(fd);
return -1;
}
close(fd);
return 0;
}