vold: Stage the mounting of media to hide the ASEC imagefile directory

In order to protect the '/android_secure' directory on VFAT removable media
from being mucked with by 3rd party applications on the device, we hide the
directory with a read-only, zero-sized tmpfs mounted on-top. A reference to the
hidden directory is kept by a bind-mount which is mounted at a location which
only root can access.

Staging consists of:
  1. Mount checked media at a secure location (/mnt/secure/staging)
  2. Ensure /android_secure exists on the media, (creating if it doesnt)
  3. Bind-mount /mnt/secure/staging/android_secure -> /mnt/secure/asec
     (where only root can access it)
  4. Mount an RDONLY zero-sized tmpfs over /mnt/secure/staging/android_secure
  5. Atomically move /mnt/secure/staging to the publicly accessable storage
     directory (/mnt/sdcard)

Signed-off-by: San Mehat <san@google.com>
gugelfrei
San Mehat 15 years ago
parent 8f2875b297
commit 3bb6020e46

@ -191,7 +191,7 @@ int CommandListener::AsecCmd::runCommand(SocketClient *cli,
int rc = 0;
if (!strcmp(argv[1], "list")) {
DIR *d = opendir("/sdcard/android_secure");
DIR *d = opendir(Volume::SEC_ASECDIR);
if (!d) {
cli->sendMsg(ResponseCode::OperationFailed, "Failed to open asec dir", true);

@ -45,6 +45,34 @@
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";
@ -245,7 +273,7 @@ int Volume::mountVol() {
errno = 0;
setState(Volume::State_Checking);
if ((rc = Fat::check(devicePath))) {
if (Fat::check(devicePath)) {
if (errno == ENODATA) {
LOGW("%s does not contain a FAT filesystem\n", devicePath);
continue;
@ -257,16 +285,38 @@ int Volume::mountVol() {
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 (!(rc = Fat::doMount(devicePath, getMountpoint(), false, false,
1000, 1015, 0702, true))) {
LOGI("%s sucessfully mounted for volume %s\n", devicePath, getLabel());
setState(Volume::State_Mounted);
mCurrentlyMountedKdev = deviceNodes[i];
return 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;
}
LOGW("%s failed to mount via VFAT (%s)\n", devicePath, strerror(errno));
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());
@ -275,55 +325,202 @@ int Volume::mountVol() {
return -1;
}
int Volume::unmountVol(bool force) {
int i, rc;
int Volume::createBindMounts() {
unsigned long flags;
/*
* 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 (getState() != Volume::State_Mounted) {
LOGE("Volume %s unmount request when not mounted", getLabel());
errno = EINVAL;
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;
}
setState(Volume::State_Unmounting);
usleep(1000 * 1000); // Give the framework some time to react
for (i = 1; i <= 10; i++) {
rc = umount(getMountpoint());
if (!rc)
break;
if (rc && (errno == EINVAL || errno == ENOENT)) {
rc = 0;
break;
/*
* 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);
}
LOGW("Volume %s unmount attempt %d failed (%s)",
getLabel(), i, strerror(errno));
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 (i > 8) {
if (retries == 1) {
action = 2; // SIGKILL
} else if (i > 7) {
} else if (retries == 2) {
action = 1; // SIGHUP
}
}
Process::killProcessesWithOpenFiles(getMountpoint(), action);
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;
}
if (!rc) {
LOGI("Volume %s unmounted sucessfully", getLabel());
setState(Volume::State_Idle);
mCurrentlyMountedKdev = -1;
return 0;
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;
}
errno = EBUSY;
LOGE("Volume %s failed to unmount (%s)\n", getLabel(), strerror(errno));
setState(Volume::State_Mounted);
return -1;
out_nomedia:
setState(Volume::State_NoMedia);
return -1;
}
int Volume::initializeMbr(const char *deviceNode) {

@ -38,6 +38,12 @@ public:
static const int State_Shared = 7;
static const int State_SharedMnt = 8;
static const char *SECDIR;
static const char *SEC_STGDIR;
static const char *SEC_STG_SECIMGDIR;
static const char *SEC_ASECDIR;
static const char *ASECDIR;
protected:
char *mLabel;
char *mMountpoint;
@ -75,6 +81,9 @@ protected:
private:
int initializeMbr(const char *deviceNode);
bool isMountpointMounted(const char *path);
int createBindMounts();
int doUnmount(const char *path, bool force);
int doMoveMount(const char *src, const char *dst, bool force);
};
typedef android::List<Volume *> VolumeCollection;

@ -161,10 +161,8 @@ int VolumeManager::formatVolume(const char *label) {
}
int VolumeManager::getAsecMountPath(const char *id, char *buffer, int maxlen) {
char mountPoint[255];
snprintf(mountPoint, sizeof(mountPoint), "/asec/%s", id);
snprintf(buffer, maxlen, "/asec/%s", id);
snprintf(buffer, maxlen, "%s/%s", Volume::ASECDIR, id);
return 0;
}
@ -177,17 +175,14 @@ int VolumeManager::createAsec(const char *id, unsigned int numSectors,
return -1;
}
mkdir("/sdcard/android_secure", 0777);
if (lookupVolume(id)) {
LOGE("ASEC volume '%s' currently exists", id);
LOGE("ASEC id '%s' currently exists", id);
errno = EADDRINUSE;
return -1;
}
char asecFileName[255];
snprintf(asecFileName, sizeof(asecFileName),
"/sdcard/android_secure/%s.asec", id);
snprintf(asecFileName, sizeof(asecFileName), "%s/%s.asec", Volume::SEC_ASECDIR, id);
if (!access(asecFileName, F_OK)) {
LOGE("ASEC file '%s' currently exists - destroy it first! (%s)",
@ -236,7 +231,7 @@ int VolumeManager::createAsec(const char *id, unsigned int numSectors,
char mountPoint[255];
snprintf(mountPoint, sizeof(mountPoint), "/asec/%s", id);
snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id);
if (mkdir(mountPoint, 0777)) {
if (errno != EEXIST) {
LOGE("Mountpoint creation failed (%s)", strerror(errno));
@ -270,15 +265,14 @@ int VolumeManager::finalizeAsec(const char *id) {
char loopDevice[255];
char mountPoint[255];
snprintf(asecFileName, sizeof(asecFileName),
"/sdcard/android_secure/%s.asec", id);
snprintf(asecFileName, sizeof(asecFileName), "%s/%s.asec", Volume::SEC_ASECDIR, id);
if (Loop::lookupActive(asecFileName, loopDevice, sizeof(loopDevice))) {
LOGE("Unable to finalize %s (%s)", id, strerror(errno));
return -1;
}
snprintf(mountPoint, sizeof(mountPoint), "/asec/%s", id);
snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id);
// XXX:
if (Fat::doMount(loopDevice, mountPoint, true, true, 0, 0, 0227, false)) {
LOGE("ASEC finalize mount failed (%s)", strerror(errno));
@ -294,10 +288,10 @@ int VolumeManager::renameAsec(const char *id1, const char *id2) {
char *asecFilename2;
char mountPoint[255];
asprintf(&asecFilename1, "/sdcard/android_secure/%s.asec", id1);
asprintf(&asecFilename2, "/sdcard/android_secure/%s.asec", id2);
asprintf(&asecFilename1, "%s/%s.asec", Volume::SEC_ASECDIR, id1);
asprintf(&asecFilename2, "%s/%s.asec", Volume::SEC_ASECDIR, id2);
snprintf(mountPoint, sizeof(mountPoint), "/asec/%s", id1);
snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id1);
if (isMountpointMounted(mountPoint)) {
LOGW("Rename attempt when src mounted");
errno = EBUSY;
@ -330,9 +324,8 @@ int VolumeManager::unmountAsec(const char *id, bool force) {
char asecFileName[255];
char mountPoint[255];
snprintf(asecFileName, sizeof(asecFileName),
"/sdcard/android_secure/%s.asec", id);
snprintf(mountPoint, sizeof(mountPoint), "/asec/%s", id);
snprintf(asecFileName, sizeof(asecFileName), "%s/%s.asec", Volume::SEC_ASECDIR, id);
snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id);
if (!isMountpointMounted(mountPoint)) {
LOGE("Unmount request for ASEC %s when not mounted", id);
@ -403,9 +396,7 @@ int VolumeManager::destroyAsec(const char *id, bool force) {
char asecFileName[255];
char mountPoint[255];
snprintf(asecFileName, sizeof(asecFileName),
"/sdcard/android_secure/%s.asec", id);
snprintf(mountPoint, sizeof(mountPoint), "/asec/%s", id);
snprintf(asecFileName, sizeof(asecFileName), "%s/%s.asec", Volume::SEC_ASECDIR, id);
if (isMountpointMounted(mountPoint)) {
LOGD("Unmounting container before destroy");
@ -428,9 +419,8 @@ int VolumeManager::mountAsec(const char *id, const char *key, int ownerUid) {
char asecFileName[255];
char mountPoint[255];
snprintf(asecFileName, sizeof(asecFileName),
"/sdcard/android_secure/%s.asec", id);
snprintf(mountPoint, sizeof(mountPoint), "/asec/%s", id);
snprintf(asecFileName, sizeof(asecFileName), "%s/%s.asec", Volume::SEC_ASECDIR, id);
snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id);
if (isMountpointMounted(mountPoint)) {
LOGE("ASEC %s already mounted", id);

Loading…
Cancel
Save