int VolumeManager::unmountAsec(const char *id, bool force) { char asecFileName[255]; char mountPoint[255]; if (!isLegalAsecId(id)) { SLOGE("unmountAsec: Invalid asec id \"%s\"", id); errno = EINVAL; return -1; } if (findAsec(id, asecFileName, sizeof(asecFileName))) { SLOGE("Couldn't find ASEC %s", id); return -1; } int written = snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id); if ((written < 0) || (size_t(written) >= sizeof(mountPoint))) { SLOGE("ASEC unmount failed for %s: couldn't construct mountpoint", id); return -1; } char idHash[33]; if (!asecHash(id, idHash, sizeof(idHash))) { SLOGE("Hash of '%s' failed (%s)", id, strerror(errno)); return -1; } return unmountLoopImage(id, idHash, asecFileName, mountPoint, force); }
int VolumeManager::getAsecFilesystemPath(const char *id, char *buffer, int maxlen) { char asecFileName[255]; if (!isLegalAsecId(id)) { SLOGE("getAsecFilesystemPath: Invalid asec id \"%s\"", id); errno = EINVAL; return -1; } if (findAsec(id, asecFileName, sizeof(asecFileName))) { SLOGE("Couldn't find ASEC %s", id); return -1; } memset(buffer, 0, maxlen); if (access(asecFileName, F_OK)) { errno = ENOENT; return -1; } int written = snprintf(buffer, maxlen, "%s", asecFileName); if ((written < 0) || (written >= maxlen)) { errno = EINVAL; return -1; } return 0; }
int VolumeManager::destroyAsec(const char *id, bool force) { char asecFileName[255]; char mountPoint[255]; if (findAsec(id, asecFileName, sizeof(asecFileName))) { SLOGE("Couldn't find ASEC %s", id); return -1; } int written = snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id); if ((written < 0) || (size_t(written) >= sizeof(mountPoint))) { SLOGE("ASEC destroy failed for %s: couldn't construct mountpoint", id); return -1; } if (isMountpointMounted(mountPoint)) { if (mDebug) { SLOGD("Unmounting container before destroy"); } if (unmountAsec(id, force)) { SLOGE("Failed to unmount asec %s for destroy (%s)", id, strerror(errno)); return -1; } } if (unlink(asecFileName)) { SLOGE("Failed to unlink asec '%s' (%s)", asecFileName, strerror(errno)); return -1; } if (mDebug) { SLOGD("ASEC %s destroyed", id); } return 0; }
int VolumeManager::cleanupAsec(Volume *v, bool force) { // Continue for the primary storage (VOL_PROVIDES_ASEC) and for the // external apps volume (VOL_EXTERNAL_APPS) if app moving is enabled if ((v->getFlags() & VOL_PROVIDES_ASEC) == 0 && ((v->getFlags() & VOL_EXTERNAL_APPS) == 0 || !v->isExternalAppsEnabled())) { return 0; } int rc = 0; char asecFileName[255]; AsecIdCollection removeAsec; AsecIdCollection removeObb; for (AsecIdCollection::iterator it = mActiveContainers->begin(); it != mActiveContainers->end(); ++it) { ContainerData* cd = *it; if (cd->type == ASEC) { if (findAsec(cd->id, asecFileName, sizeof(asecFileName))) { SLOGE("Couldn't find ASEC %s; cleaning up", cd->id); removeAsec.push_back(cd); } else { SLOGD("Found ASEC at path %s", asecFileName); if (!strncmp(asecFileName, Volume::SEC_ASECDIR_EXT, strlen(Volume::SEC_ASECDIR_EXT))) { removeAsec.push_back(cd); } } } else if (cd->type == OBB) { if (v == getVolumeForFile(cd->id)) { removeObb.push_back(cd); } } else { SLOGE("Unknown container type %d!", cd->type); } } for (AsecIdCollection::iterator it = removeAsec.begin(); it != removeAsec.end(); ++it) { ContainerData *cd = *it; SLOGI("Unmounting ASEC %s (dependent on %s)", cd->id, v->getLabel()); if (unmountAsec(cd->id, force)) { SLOGE("Failed to unmount ASEC %s (%s)", cd->id, strerror(errno)); rc = -1; } } for (AsecIdCollection::iterator it = removeObb.begin(); it != removeObb.end(); ++it) { ContainerData *cd = *it; SLOGI("Unmounting OBB %s (dependent on %s)", cd->id, v->getLabel()); if (unmountObb(cd->id, force)) { SLOGE("Failed to unmount OBB %s (%s)", cd->id, strerror(errno)); rc = -1; } } return rc; }
int VolumeManager::finalizeAsec(const char *id) { char asecFileName[255]; char loopDevice[255]; char mountPoint[255]; if (!isLegalAsecId(id)) { SLOGE("finalizeAsec: Invalid asec id \"%s\"", id); errno = EINVAL; return -1; } if (findAsec(id, asecFileName, sizeof(asecFileName))) { SLOGE("Couldn't find ASEC %s", id); return -1; } char idHash[33]; if (!asecHash(id, idHash, sizeof(idHash))) { SLOGE("Hash of '%s' failed (%s)", id, strerror(errno)); return -1; } if (Loop::lookupActive(idHash, loopDevice, sizeof(loopDevice))) { SLOGE("Unable to finalize %s (%s)", id, strerror(errno)); return -1; } unsigned int nr_sec = 0; struct asec_superblock sb; if (Loop::lookupInfo(loopDevice, &sb, &nr_sec)) { return -1; } int written = snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id); if ((written < 0) || (size_t(written) >= sizeof(mountPoint))) { SLOGE("ASEC finalize failed: couldn't construct mountPoint"); return -1; } int result = 0; if (sb.c_opts & ASEC_SB_C_OPTS_EXT4) { result = Ext4::doMount(loopDevice, mountPoint, true, true, true, false); } else { result = Fat::doMount(loopDevice, mountPoint, true, true, true, 0, 0, 0227, false); } if (result) { SLOGE("ASEC finalize mount failed (%s)", strerror(errno)); return -1; } if (mDebug) { SLOGD("ASEC %s finalized", id); } return 0; }
int VolumeManager::renameAsec(const char *id1, const char *id2) { char asecFilename1[255]; char *asecFilename2; char mountPoint[255]; const char *dir; if (findAsec(id1, asecFilename1, sizeof(asecFilename1), &dir)) { SLOGE("Couldn't find ASEC %s", id1); return -1; } asprintf(&asecFilename2, "%s/%s.asec", dir, id2); int written = snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id1); if ((written < 0) || (size_t(written) >= sizeof(mountPoint))) { SLOGE("Rename failed: couldn't construct mountpoint"); goto out_err; } if (isMountpointMounted(mountPoint)) { SLOGW("Rename attempt when src mounted"); errno = EBUSY; goto out_err; } written = snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id2); if ((written < 0) || (size_t(written) >= sizeof(mountPoint))) { SLOGE("Rename failed: couldn't construct mountpoint2"); goto out_err; } if (isMountpointMounted(mountPoint)) { SLOGW("Rename attempt when dst mounted"); errno = EBUSY; goto out_err; } if (!access(asecFilename2, F_OK)) { SLOGE("Rename attempt when dst exists"); errno = EADDRINUSE; goto out_err; } if (rename(asecFilename1, asecFilename2)) { SLOGE("Rename of '%s' to '%s' failed (%s)", asecFilename1, asecFilename2, strerror(errno)); goto out_err; } free(asecFilename2); return 0; out_err: free(asecFilename2); return -1; }
int VolumeManager::getAsecFilesystemPath(const char *id, char *buffer, int maxlen) { char asecFileName[255]; if (findAsec(id, asecFileName, sizeof(asecFileName))) { SLOGE("Couldn't find ASEC %s", id); return -1; } memset(buffer, 0, maxlen); if (access(asecFileName, F_OK)) { errno = ENOENT; return -1; } snprintf(buffer, maxlen, "%s", asecFileName); return 0; }
int VolumeManager::unmountAsec(const char *id, bool force) { char asecFileName[255]; char mountPoint[255]; if (findAsec(id, asecFileName, sizeof(asecFileName))) { SLOGE("Couldn't find ASEC %s", id); return -1; } snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id); char idHash[33]; if (!asecHash(id, idHash, sizeof(idHash))) { SLOGE("Hash of '%s' failed (%s)", id, strerror(errno)); return -1; } return unmountLoopImage(id, idHash, asecFileName, mountPoint, force); }
int VolumeManager::getAsecMountPath(const char *id, char *buffer, int maxlen) { char asecFileName[255]; if (findAsec(id, asecFileName, sizeof(asecFileName))) { SLOGE("Couldn't find ASEC %s", id); return -1; } memset(buffer, 0, maxlen); if (access(asecFileName, F_OK)) { errno = ENOENT; return -1; } int written = snprintf(buffer, maxlen, "%s/%s", Volume::ASECDIR, id); if ((written < 0) || (written >= maxlen)) { SLOGE("getAsecMountPath failed for %s: couldn't construct path in buffer", id); errno = EINVAL; return -1; } return 0; }
int VolumeManager::fixupAsecPermissions(const char *id, gid_t gid, const char* filename) { char asecFileName[255]; char loopDevice[255]; char mountPoint[255]; if (gid < AID_APP) { SLOGE("Group ID is not in application range"); return -1; } if (!isLegalAsecId(id)) { SLOGE("fixupAsecPermissions: Invalid asec id \"%s\"", id); errno = EINVAL; return -1; } if (findAsec(id, asecFileName, sizeof(asecFileName))) { SLOGE("Couldn't find ASEC %s", id); return -1; } char idHash[33]; if (!asecHash(id, idHash, sizeof(idHash))) { SLOGE("Hash of '%s' failed (%s)", id, strerror(errno)); return -1; } if (Loop::lookupActive(idHash, loopDevice, sizeof(loopDevice))) { SLOGE("Unable fix permissions during lookup on %s (%s)", id, strerror(errno)); return -1; } unsigned int nr_sec = 0; struct asec_superblock sb; if (Loop::lookupInfo(loopDevice, &sb, &nr_sec)) { return -1; } int written = snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id); if ((written < 0) || (size_t(written) >= sizeof(mountPoint))) { SLOGE("Unable remount to fix permissions for %s: couldn't construct mountpoint", id); return -1; } int result = 0; if ((sb.c_opts & ASEC_SB_C_OPTS_EXT4) == 0) { return 0; } int ret = Ext4::doMount(loopDevice, mountPoint, false /* read-only */, true /* remount */, false /* executable */, false /* sdcard */); if (ret) { SLOGE("Unable remount to fix permissions for %s (%s)", id, strerror(errno)); return -1; } char *paths[] = { mountPoint, NULL }; FTS *fts = fts_open(paths, FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV, NULL); if (fts) { // Traverse the entire hierarchy and chown to system UID. for (FTSENT *ftsent = fts_read(fts); ftsent != NULL; ftsent = fts_read(fts)) { // We don't care about the lost+found directory. if (!strcmp(ftsent->fts_name, "lost+found")) { continue; } /* * There can only be one file marked as private right now. * This should be more robust, but it satisfies the requirements * we have for right now. */ const bool privateFile = !strcmp(ftsent->fts_name, filename); int fd = open(ftsent->fts_accpath, O_NOFOLLOW); if (fd < 0) { SLOGE("Couldn't open file %s: %s", ftsent->fts_accpath, strerror(errno)); result = -1; continue; } result |= fchown(fd, AID_SYSTEM, privateFile? gid : AID_SYSTEM); if (ftsent->fts_info & FTS_D) { result |= fchmod(fd, 0755); } else if (ftsent->fts_info & FTS_F) { result |= fchmod(fd, privateFile ? 0640 : 0644); } if (selinux_android_restorecon(ftsent->fts_path, 0) < 0) { SLOGE("restorecon failed for %s: %s\n", ftsent->fts_path, strerror(errno)); result |= -1; } close(fd); } fts_close(fts); // Finally make the directory readable by everyone. int dirfd = open(mountPoint, O_DIRECTORY); if (dirfd < 0 || fchmod(dirfd, 0755)) { SLOGE("Couldn't change owner of existing directory %s: %s", mountPoint, strerror(errno)); result |= -1; } close(dirfd); } else { result |= -1; } result |= Ext4::doMount(loopDevice, mountPoint, true /* read-only */, true /* remount */, true /* execute */, false /* sdcard */); if (result) { SLOGE("ASEC fix permissions failed (%s)", strerror(errno)); return -1; } if (mDebug) { SLOGD("ASEC %s permissions fixed", id); } return 0; }
int VolumeManager::createAsec(const char *id, unsigned int numSectors, const char *fstype, const char *key, const int ownerUid, bool isExternal) { struct asec_superblock sb; memset(&sb, 0, sizeof(sb)); if (!isLegalAsecId(id)) { SLOGE("createAsec: Invalid asec id \"%s\"", id); errno = EINVAL; return -1; } const bool wantFilesystem = strcmp(fstype, "none"); bool usingExt4 = false; if (wantFilesystem) { usingExt4 = !strcmp(fstype, "ext4"); if (usingExt4) { sb.c_opts |= ASEC_SB_C_OPTS_EXT4; } else if (strcmp(fstype, "fat")) { SLOGE("Invalid filesystem type %s", fstype); errno = EINVAL; return -1; } } sb.magic = ASEC_SB_MAGIC; sb.ver = ASEC_SB_VER; if (numSectors < ((1024*1024)/512)) { SLOGE("Invalid container size specified (%d sectors)", numSectors); errno = EINVAL; return -1; } if (lookupVolume(id)) { SLOGE("ASEC id '%s' currently exists", id); errno = EADDRINUSE; return -1; } char asecFileName[255]; if (!findAsec(id, asecFileName, sizeof(asecFileName))) { SLOGE("ASEC file '%s' currently exists - destroy it first! (%s)", asecFileName, strerror(errno)); errno = EADDRINUSE; return -1; } const char *asecDir = isExternal ? Volume::SEC_ASECDIR_EXT : Volume::SEC_ASECDIR_INT; int written = snprintf(asecFileName, sizeof(asecFileName), "%s/%s.asec", asecDir, id); if ((written < 0) || (size_t(written) >= sizeof(asecFileName))) { errno = EINVAL; return -1; } if (!access(asecFileName, F_OK)) { SLOGE("ASEC file '%s' currently exists - destroy it first! (%s)", asecFileName, strerror(errno)); errno = EADDRINUSE; return -1; } /* * Add some headroom */ unsigned fatSize = (((numSectors * 4) / 512) + 1) * 2; unsigned numImgSectors = numSectors + fatSize + 2; if (numImgSectors % 63) { numImgSectors += (63 - (numImgSectors % 63)); } // Add +1 for our superblock which is at the end if (Loop::createImageFile(asecFileName, numImgSectors + 1)) { SLOGE("ASEC image file creation failed (%s)", strerror(errno)); return -1; } char idHash[33]; if (!asecHash(id, idHash, sizeof(idHash))) { SLOGE("Hash of '%s' failed (%s)", id, strerror(errno)); unlink(asecFileName); return -1; } char loopDevice[255]; if (Loop::create(idHash, asecFileName, loopDevice, sizeof(loopDevice))) { SLOGE("ASEC loop device creation failed (%s)", strerror(errno)); unlink(asecFileName); return -1; } char dmDevice[255]; bool cleanupDm = false; if (strcmp(key, "none")) { // XXX: This is all we support for now sb.c_cipher = ASEC_SB_C_CIPHER_TWOFISH; if (Devmapper::create(idHash, loopDevice, key, numImgSectors, dmDevice, sizeof(dmDevice))) { SLOGE("ASEC device mapping failed (%s)", strerror(errno)); Loop::destroyByDevice(loopDevice); unlink(asecFileName); return -1; } cleanupDm = true; } else { sb.c_cipher = ASEC_SB_C_CIPHER_NONE; strcpy(dmDevice, loopDevice); } /* * Drop down the superblock at the end of the file */ int sbfd = open(loopDevice, O_RDWR); if (sbfd < 0) { SLOGE("Failed to open new DM device for superblock write (%s)", strerror(errno)); if (cleanupDm) { Devmapper::destroy(idHash); } Loop::destroyByDevice(loopDevice); unlink(asecFileName); return -1; } if (lseek(sbfd, (numImgSectors * 512), SEEK_SET) < 0) { close(sbfd); SLOGE("Failed to lseek for superblock (%s)", strerror(errno)); if (cleanupDm) { Devmapper::destroy(idHash); } Loop::destroyByDevice(loopDevice); unlink(asecFileName); return -1; } if (write(sbfd, &sb, sizeof(sb)) != sizeof(sb)) { close(sbfd); SLOGE("Failed to write superblock (%s)", strerror(errno)); if (cleanupDm) { Devmapper::destroy(idHash); } Loop::destroyByDevice(loopDevice); unlink(asecFileName); return -1; } close(sbfd); if (wantFilesystem) { int formatStatus; char mountPoint[255]; int written = snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id); if ((written < 0) || (size_t(written) >= sizeof(mountPoint))) { SLOGE("ASEC fs format failed: couldn't construct mountPoint"); if (cleanupDm) { Devmapper::destroy(idHash); } Loop::destroyByDevice(loopDevice); unlink(asecFileName); return -1; } if (usingExt4) { formatStatus = Ext4::format(dmDevice, mountPoint); } else { formatStatus = Fat::format(dmDevice, numImgSectors, 0); } if (formatStatus < 0) { SLOGE("ASEC fs format failed (%s)", strerror(errno)); if (cleanupDm) { Devmapper::destroy(idHash); } Loop::destroyByDevice(loopDevice); unlink(asecFileName); return -1; } if (mkdir(mountPoint, 0000)) { if (errno != EEXIST) { SLOGE("Mountpoint creation failed (%s)", strerror(errno)); if (cleanupDm) { Devmapper::destroy(idHash); } Loop::destroyByDevice(loopDevice); unlink(asecFileName); return -1; } } int mountStatus; if (usingExt4) { mountStatus = Ext4::doMount(dmDevice, mountPoint, false, false, false, false); } else { mountStatus = Fat::doMount(dmDevice, mountPoint, false, false, false, ownerUid, 0, 0000, false); } if (mountStatus) { SLOGE("ASEC FAT mount failed (%s)", strerror(errno)); if (cleanupDm) { Devmapper::destroy(idHash); } Loop::destroyByDevice(loopDevice); unlink(asecFileName); return -1; } if (usingExt4) { int dirfd = open(mountPoint, O_DIRECTORY); if (dirfd >= 0) { if (fchown(dirfd, ownerUid, AID_SYSTEM) || fchmod(dirfd, S_IRUSR | S_IWUSR | S_IXUSR | S_ISGID | S_IRGRP | S_IXGRP)) { SLOGI("Cannot chown/chmod new ASEC mount point %s", mountPoint); } close(dirfd); } } } else { SLOGI("Created raw secure container %s (no filesystem)", id); } mActiveContainers->push_back(new ContainerData(strdup(id), ASEC)); return 0; }
int VolumeManager::mountAsec(const char *id, const char *key, int ownerUid) { char asecFileName[255]; char mountPoint[255]; if (!isLegalAsecId(id)) { SLOGE("mountAsec: Invalid asec id \"%s\"", id); errno = EINVAL; return -1; } if (findAsec(id, asecFileName, sizeof(asecFileName))) { SLOGE("Couldn't find ASEC %s", id); return -1; } int written = snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id); if ((written < 0) || (size_t(written) >= sizeof(mountPoint))) { SLOGE("ASEC mount failed: couldn't construct mountpoint %s", id); return -1; } if (isMountpointMounted(mountPoint)) { SLOGE("ASEC %s already mounted", id); errno = EBUSY; return -1; } char idHash[33]; if (!asecHash(id, idHash, sizeof(idHash))) { SLOGE("Hash of '%s' failed (%s)", id, strerror(errno)); return -1; } char loopDevice[255]; if (Loop::lookupActive(idHash, loopDevice, sizeof(loopDevice))) { if (Loop::create(idHash, asecFileName, loopDevice, sizeof(loopDevice))) { SLOGE("ASEC loop device creation failed (%s)", strerror(errno)); return -1; } if (mDebug) { SLOGD("New loop device created at %s", loopDevice); } } else { if (mDebug) { SLOGD("Found active loopback for %s at %s", asecFileName, loopDevice); } } char dmDevice[255]; bool cleanupDm = false; unsigned int nr_sec = 0; struct asec_superblock sb; if (Loop::lookupInfo(loopDevice, &sb, &nr_sec)) { return -1; } if (mDebug) { SLOGD("Container sb magic/ver (%.8x/%.2x)", sb.magic, sb.ver); } if (sb.magic != ASEC_SB_MAGIC || sb.ver != ASEC_SB_VER) { SLOGE("Bad container magic/version (%.8x/%.2x)", sb.magic, sb.ver); Loop::destroyByDevice(loopDevice); errno = EMEDIUMTYPE; return -1; } nr_sec--; // We don't want the devmapping to extend onto our superblock if (strcmp(key, "none")) { if (Devmapper::lookupActive(idHash, dmDevice, sizeof(dmDevice))) { if (Devmapper::create(idHash, loopDevice, key, nr_sec, dmDevice, sizeof(dmDevice))) { SLOGE("ASEC device mapping failed (%s)", strerror(errno)); Loop::destroyByDevice(loopDevice); return -1; } if (mDebug) { SLOGD("New devmapper instance created at %s", dmDevice); } } else { if (mDebug) { SLOGD("Found active devmapper for %s at %s", asecFileName, dmDevice); } } cleanupDm = true; } else { strcpy(dmDevice, loopDevice); } if (mkdir(mountPoint, 0000)) { if (errno != EEXIST) { SLOGE("Mountpoint creation failed (%s)", strerror(errno)); if (cleanupDm) { Devmapper::destroy(idHash); } Loop::destroyByDevice(loopDevice); return -1; } } /* * The device mapper node needs to be created. Sometimes it takes a * while. Wait for up to 1 second. We could also inspect incoming uevents, * but that would take more effort. */ int tries = 25; while (tries--) { if (!access(dmDevice, F_OK) || errno != ENOENT) { break; } usleep(40 * 1000); } int result; if (sb.c_opts & ASEC_SB_C_OPTS_EXT4) { result = Ext4::doMount(dmDevice, mountPoint, true, false, true, false); } else { result = Fat::doMount(dmDevice, mountPoint, true, false, true, ownerUid, 0, 0222, false); } if (result) { SLOGE("ASEC mount failed (%s)", strerror(errno)); if (cleanupDm) { Devmapper::destroy(idHash); } Loop::destroyByDevice(loopDevice); return -1; } mActiveContainers->push_back(new ContainerData(strdup(id), ASEC)); if (mDebug) { SLOGD("ASEC %s mounted", id); } return 0; }