From 344ca10856f3d3087a3288ce8f91ad83665d93fb Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Tue, 3 Apr 2012 17:23:01 -0700 Subject: [PATCH] Add in ext4 support for ASEC containers Now forward locked applications will be in ASEC containers both internal to the system and externally. This change adds support for putting applications in ext4-based ASECs. Change-Id: I8d6765b72dd2606e429c067b47a2dbcaa8bef37d --- Android.mk | 1 + Asec.h | 1 + CommandListener.cpp | 81 +++++++--- CommandListener.h | 2 + Ext4.cpp | 90 +++++++++++ Ext4.h | 29 ++++ Loop.cpp | 44 ++++++ Loop.h | 1 + Volume.cpp | 18 ++- Volume.h | 3 +- VolumeManager.cpp | 355 ++++++++++++++++++++++++++++++++++++-------- VolumeManager.h | 17 ++- 12 files changed, 552 insertions(+), 90 deletions(-) create mode 100644 Ext4.cpp create mode 100644 Ext4.h diff --git a/Android.mk b/Android.mk index 54abb23..e4256ae 100644 --- a/Android.mk +++ b/Android.mk @@ -10,6 +10,7 @@ common_src_files := \ DirectVolume.cpp \ logwrapper.c \ Process.cpp \ + Ext4.cpp \ Fat.cpp \ Loop.cpp \ Devmapper.cpp \ diff --git a/Asec.h b/Asec.h index 136ad3b..dd64fd0 100644 --- a/Asec.h +++ b/Asec.h @@ -33,6 +33,7 @@ struct asec_superblock { unsigned char c_chain; #define ASEC_SB_C_OPTS_NONE 0 +#define ASEC_SB_C_OPTS_EXT4 1 unsigned char c_opts; #define ASEC_SB_C_MODE_NONE 0 diff --git a/CommandListener.cpp b/CommandListener.cpp index 33af9e5..d75d76c 100644 --- a/CommandListener.cpp +++ b/CommandListener.cpp @@ -261,6 +261,44 @@ CommandListener::AsecCmd::AsecCmd() : VoldCommand("asec") { } +void CommandListener::AsecCmd::listAsecsInDirectory(SocketClient *cli, const char *directory) { + DIR *d = opendir(directory); + + if (!d) { + cli->sendMsg(ResponseCode::OperationFailed, "Failed to open asec dir", true); + return; + } + + size_t dirent_len = offsetof(struct dirent, d_name) + + pathconf(directory, _PC_NAME_MAX) + 1; + + struct dirent *dent = (struct dirent *) malloc(dirent_len); + if (dent == NULL) { + cli->sendMsg(ResponseCode::OperationFailed, "Failed to allocate memory", true); + return; + } + + struct dirent *result; + + while (!readdir_r(d, dent, &result) && result != NULL) { + if (dent->d_name[0] == '.') + continue; + if (dent->d_type != DT_REG) + continue; + size_t name_len = strlen(dent->d_name); + if (name_len > 5 && name_len < 260 && + !strcmp(&dent->d_name[name_len - 5], ".asec")) { + char id[255]; + memset(id, 0, sizeof(id)); + strlcpy(id, dent->d_name, name_len - 5); + cli->sendMsg(ResponseCode::AsecListResult, id, false); + } + } + closedir(d); + + free(dent); +} + int CommandListener::AsecCmd::runCommand(SocketClient *cli, int argc, char **argv) { if (argc < 2) { @@ -273,35 +311,21 @@ int CommandListener::AsecCmd::runCommand(SocketClient *cli, if (!strcmp(argv[1], "list")) { dumpArgs(argc, argv, -1); - DIR *d = opendir(Volume::SEC_ASECDIR); - if (!d) { - cli->sendMsg(ResponseCode::OperationFailed, "Failed to open asec dir", true); - return 0; - } - - struct dirent *dent; - while ((dent = readdir(d))) { - if (dent->d_name[0] == '.') - continue; - if (!strcmp(&dent->d_name[strlen(dent->d_name)-5], ".asec")) { - char id[255]; - memset(id, 0, sizeof(id)); - strncpy(id, dent->d_name, strlen(dent->d_name) -5); - cli->sendMsg(ResponseCode::AsecListResult, id, false); - } - } - closedir(d); + listAsecsInDirectory(cli, Volume::SEC_ASECDIR_EXT); + listAsecsInDirectory(cli, Volume::SEC_ASECDIR_INT); } else if (!strcmp(argv[1], "create")) { dumpArgs(argc, argv, 5); - if (argc != 7) { + if (argc != 8) { cli->sendMsg(ResponseCode::CommandSyntaxError, - "Usage: asec create ", false); + "Usage: asec create " + "", false); return 0; } unsigned int numSectors = (atoi(argv[3]) * (1024 * 1024)) / 512; - rc = vm->createAsec(argv[2], numSectors, argv[4], argv[5], atoi(argv[6])); + const bool isExternal = (atoi(argv[7]) == 1); + rc = vm->createAsec(argv[2], numSectors, argv[4], argv[5], atoi(argv[6]), isExternal); } else if (!strcmp(argv[1], "finalize")) { dumpArgs(argc, argv, -1); if (argc != 3) { @@ -309,6 +333,21 @@ int CommandListener::AsecCmd::runCommand(SocketClient *cli, return 0; } rc = vm->finalizeAsec(argv[2]); + } else if (!strcmp(argv[1], "fixperms")) { + dumpArgs(argc, argv, -1); + if (argc != 5) { + cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: asec fixperms ", false); + return 0; + } + + char *endptr; + gid_t gid = (gid_t) strtoul(argv[3], &endptr, 10); + if (*endptr != '\0') { + cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: asec fixperms ", false); + return 0; + } + + rc = vm->fixupAsecPermissions(argv[2], gid, argv[4]); } else if (!strcmp(argv[1], "destroy")) { dumpArgs(argc, argv, -1); if (argc < 3) { diff --git a/CommandListener.h b/CommandListener.h index baf7760..4ef4a0c 100644 --- a/CommandListener.h +++ b/CommandListener.h @@ -47,6 +47,8 @@ private: AsecCmd(); virtual ~AsecCmd() {} int runCommand(SocketClient *c, int argc, char ** argv); + private: + void listAsecsInDirectory(SocketClient *c, const char *directory); }; class ObbCmd : public VoldCommand { diff --git a/Ext4.cpp b/Ext4.cpp new file mode 100644 index 0000000..290489e --- /dev/null +++ b/Ext4.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2012 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#define LOG_TAG "Vold" + +#include +#include + +#include "Ext4.h" + +#define MKEXT4FS_PATH "/system/bin/make_ext4fs"; + +extern "C" int logwrap(int argc, const char **argv, int background); + + +int Ext4::doMount(const char *fsPath, const char *mountPoint, bool ro, bool remount, + bool executable) { + int rc; + unsigned long flags; + + flags = MS_NOATIME | MS_NODEV | MS_NOSUID | MS_DIRSYNC; + + flags |= (executable ? 0 : MS_NOEXEC); + flags |= (ro ? MS_RDONLY : 0); + flags |= (remount ? MS_REMOUNT : 0); + + rc = mount(fsPath, mountPoint, "ext4", flags, NULL); + + if (rc && errno == EROFS) { + SLOGE("%s appears to be a read only filesystem - retrying mount RO", fsPath); + flags |= MS_RDONLY; + rc = mount(fsPath, mountPoint, "ext4", flags, NULL); + } + + return rc; +} + +int Ext4::format(const char *fsPath) { + int fd; + const char *args[4]; + int rc; + + args[0] = MKEXT4FS_PATH; + args[1] = "-J"; + args[2] = fsPath; + args[3] = NULL; + rc = logwrap(3, args, 1); + + if (rc == 0) { + SLOGI("Filesystem (ext4) formatted OK"); + return 0; + } else { + SLOGE("Format (ext4) failed (unknown exit code %d)", rc); + errno = EIO; + return -1; + } + return 0; +} diff --git a/Ext4.h b/Ext4.h new file mode 100644 index 0000000..a09b576 --- /dev/null +++ b/Ext4.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef _EXT4_H +#define _EXT4_H + +#include + +class Ext4 { +public: + static int doMount(const char *fsPath, const char *mountPoint, bool ro, bool remount, + bool executable); + static int format(const char *fsPath); +}; + +#endif diff --git a/Loop.cpp b/Loop.cpp index dad2c3f..78df132 100644 --- a/Loop.cpp +++ b/Loop.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -33,6 +34,7 @@ #include #include "Loop.h" +#include "Asec.h" int Loop::dumpState(SocketClient *c) { int i; @@ -246,3 +248,45 @@ int Loop::createImageFile(const char *file, unsigned int numSectors) { close(fd); return 0; } + +int Loop::lookupInfo(const char *loopDevice, struct asec_superblock *sb, unsigned int *nr_sec) { + int fd; + struct asec_superblock buffer; + + if ((fd = open(loopDevice, O_RDONLY)) < 0) { + SLOGE("Failed to open loopdevice (%s)", strerror(errno)); + destroyByDevice(loopDevice); + return -1; + } + + if (ioctl(fd, BLKGETSIZE, nr_sec)) { + SLOGE("Failed to get loop size (%s)", strerror(errno)); + destroyByDevice(loopDevice); + close(fd); + return -1; + } + + /* + * Try to read superblock. + */ + memset(&buffer, 0, sizeof(struct asec_superblock)); + if (lseek(fd, ((*nr_sec - 1) * 512), SEEK_SET) < 0) { + SLOGE("lseek failed (%s)", strerror(errno)); + close(fd); + destroyByDevice(loopDevice); + return -1; + } + if (read(fd, &buffer, sizeof(struct asec_superblock)) != sizeof(struct asec_superblock)) { + SLOGE("superblock read failed (%s)", strerror(errno)); + close(fd); + destroyByDevice(loopDevice); + return -1; + } + close(fd); + + /* + * Superblock successfully read. Copy to caller's struct. + */ + memcpy(sb, &buffer, sizeof(struct asec_superblock)); + return 0; +} diff --git a/Loop.h b/Loop.h index e48536b..d717cf0 100644 --- a/Loop.h +++ b/Loop.h @@ -27,6 +27,7 @@ public: static const int LOOP_MAX = 4096; public: static int lookupActive(const char *id, char *buffer, size_t len); + static int lookupInfo(const char *loopDevice, struct asec_superblock *sb, unsigned int *nr_sec); static int create(const char *id, const char *loopFile, char *loopDeviceBuffer, size_t len); static int destroyByDevice(const char *loopDevice); static int destroyByFile(const char *loopFile); diff --git a/Volume.cpp b/Volume.cpp index e70a590..a71000e 100644 --- a/Volume.cpp +++ b/Volume.cpp @@ -69,10 +69,14 @@ const char *Volume::SEC_STGDIR = "/mnt/secure/staging"; const char *Volume::SEC_STG_SECIMGDIR = "/mnt/secure/staging/.android_secure"; /* - * Path to where *only* root can access asec imagefiles + * Path to external storage where *only* root can access ASEC image files */ -const char *Volume::SEC_ASECDIR = "/mnt/secure/asec"; +const char *Volume::SEC_ASECDIR_EXT = "/mnt/secure/asec"; +/* + * Path to internal storage where *only* root can access ASEC image files + */ +const char *Volume::SEC_ASECDIR_INT = "/data/app-asec"; /* * Path to where secure containers are mounted */ @@ -504,9 +508,9 @@ int Volume::createBindMounts() { * 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)) { + if (mount(SEC_STG_SECIMGDIR, SEC_ASECDIR_EXT, "", MS_BIND, NULL)) { SLOGE("Failed to bind mount points %s -> %s (%s)", - SEC_STG_SECIMGDIR, SEC_ASECDIR, strerror(errno)); + SEC_STG_SECIMGDIR, SEC_ASECDIR_EXT, strerror(errno)); return -1; } @@ -630,8 +634,8 @@ int Volume::unmountVol(bool force, bool revert) { * the previously obscured directory. */ - if (doUnmount(Volume::SEC_ASECDIR, force)) { - SLOGE("Failed to remove bindmount on %s (%s)", SEC_ASECDIR, strerror(errno)); + if (doUnmount(Volume::SEC_ASECDIR_EXT, force)) { + SLOGE("Failed to remove bindmount on %s (%s)", SEC_ASECDIR_EXT, strerror(errno)); goto fail_remount_tmpfs; } @@ -663,7 +667,7 @@ int Volume::unmountVol(bool force, bool revert) { * Failure handling - try to restore everything back the way it was */ fail_recreate_bindmount: - if (mount(SEC_STG_SECIMGDIR, SEC_ASECDIR, "", MS_BIND, NULL)) { + if (mount(SEC_STG_SECIMGDIR, SEC_ASECDIR_EXT, "", MS_BIND, NULL)) { SLOGE("Failed to restore bindmount after failure! - Storage will appear offline!"); goto out_nomedia; } diff --git a/Volume.h b/Volume.h index 274fb54..c717d4d 100644 --- a/Volume.h +++ b/Volume.h @@ -41,7 +41,8 @@ public: static const char *SECDIR; static const char *SEC_STGDIR; static const char *SEC_STG_SECIMGDIR; - static const char *SEC_ASECDIR; + static const char *SEC_ASECDIR_EXT; + static const char *SEC_ASECDIR_INT; static const char *ASECDIR; static const char *LOOPDIR; diff --git a/VolumeManager.cpp b/VolumeManager.cpp index 0eb72a1..3a63a19 100644 --- a/VolumeManager.cpp +++ b/VolumeManager.cpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include #include @@ -33,10 +35,13 @@ #include +#include + #include "VolumeManager.h" #include "DirectVolume.h" #include "ResponseCode.h" #include "Loop.h" +#include "Ext4.h" #include "Fat.h" #include "Devmapper.h" #include "Process.h" @@ -197,7 +202,11 @@ int VolumeManager::getObbMountPath(const char *sourceFile, char *mountPath, int int VolumeManager::getAsecMountPath(const char *id, char *buffer, int maxlen) { char asecFileName[255]; - snprintf(asecFileName, sizeof(asecFileName), "%s/%s.asec", Volume::SEC_ASECDIR, id); + + if (findAsec(id, asecFileName, sizeof(asecFileName))) { + SLOGE("Couldn't find ASEC %s", id); + return -1; + } memset(buffer, 0, maxlen); if (access(asecFileName, F_OK)) { @@ -211,7 +220,11 @@ int VolumeManager::getAsecMountPath(const char *id, char *buffer, int maxlen) { int VolumeManager::getAsecFilesystemPath(const char *id, char *buffer, int maxlen) { char asecFileName[255]; - snprintf(asecFileName, sizeof(asecFileName), "%s/%s.asec", Volume::SEC_ASECDIR, id); + + if (findAsec(id, asecFileName, sizeof(asecFileName))) { + SLOGE("Couldn't find ASEC %s", id); + return -1; + } memset(buffer, 0, maxlen); if (access(asecFileName, F_OK)) { @@ -223,11 +236,24 @@ int VolumeManager::getAsecFilesystemPath(const char *id, char *buffer, int maxle return 0; } -int VolumeManager::createAsec(const char *id, unsigned int numSectors, - const char *fstype, const char *key, int ownerUid) { +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)); + 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; @@ -244,11 +270,21 @@ int VolumeManager::createAsec(const char *id, unsigned int numSectors, } char asecFileName[255]; - snprintf(asecFileName, sizeof(asecFileName), "%s/%s.asec", Volume::SEC_ASECDIR, id); + + 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; + + snprintf(asecFileName, sizeof(asecFileName), "%s/%s.asec", asecDir, id); if (!access(asecFileName, F_OK)) { SLOGE("ASEC file '%s' currently exists - destroy it first! (%s)", - asecFileName, strerror(errno)); + asecFileName, strerror(errno)); errno = EADDRINUSE; return -1; } @@ -340,13 +376,16 @@ int VolumeManager::createAsec(const char *id, unsigned int numSectors, } close(sbfd); - if (strcmp(fstype, "none")) { - if (strcmp(fstype, "fat")) { - SLOGW("Unknown fstype '%s' specified for container", fstype); + if (wantFilesystem) { + int formatStatus; + if (usingExt4) { + formatStatus = Ext4::format(dmDevice); + } else { + formatStatus = Fat::format(dmDevice, numImgSectors); } - if (Fat::format(dmDevice, numImgSectors)) { - SLOGE("ASEC FAT format failed (%s)", strerror(errno)); + if (formatStatus < 0) { + SLOGE("ASEC fs format failed (%s)", strerror(errno)); if (cleanupDm) { Devmapper::destroy(idHash); } @@ -354,10 +393,11 @@ int VolumeManager::createAsec(const char *id, unsigned int numSectors, unlink(asecFileName); return -1; } + char mountPoint[255]; snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id); - if (mkdir(mountPoint, 0777)) { + if (mkdir(mountPoint, 0000)) { if (errno != EEXIST) { SLOGE("Mountpoint creation failed (%s)", strerror(errno)); if (cleanupDm) { @@ -369,8 +409,15 @@ int VolumeManager::createAsec(const char *id, unsigned int numSectors, } } - if (Fat::doMount(dmDevice, mountPoint, false, false, false, ownerUid, - 0, 0000, false)) { + int mountStatus; + if (usingExt4) { + mountStatus = Ext4::doMount(dmDevice, mountPoint, 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); @@ -379,6 +426,17 @@ int VolumeManager::createAsec(const char *id, unsigned int numSectors, 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); } @@ -392,7 +450,10 @@ int VolumeManager::finalizeAsec(const char *id) { char loopDevice[255]; char mountPoint[255]; - snprintf(asecFileName, sizeof(asecFileName), "%s/%s.asec", Volume::SEC_ASECDIR, id); + if (findAsec(id, asecFileName, sizeof(asecFileName))) { + SLOGE("Couldn't find ASEC %s", id); + return -1; + } char idHash[33]; if (!asecHash(id, idHash, sizeof(idHash))) { @@ -405,9 +466,23 @@ int VolumeManager::finalizeAsec(const char *id) { return -1; } + unsigned int nr_sec = 0; + struct asec_superblock sb; + + if (Loop::lookupInfo(loopDevice, &sb, &nr_sec)) { + return -1; + } + snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id); - // XXX: - if (Fat::doMount(loopDevice, mountPoint, true, true, true, 0, 0, 0227, false)) { + + int result = 0; + if (sb.c_opts & ASEC_SB_C_OPTS_EXT4) { + result = Ext4::doMount(loopDevice, mountPoint, true, true, true); + } 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; } @@ -418,13 +493,131 @@ int VolumeManager::finalizeAsec(const char *id) { 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 (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; + } + + snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id); + + 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 */); + 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, 0711); + } else { + result |= fchmod(fd, privateFile ? 0640 : 0644); + } + 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 */); + + if (result) { + SLOGE("ASEC fix permissions failed (%s)", strerror(errno)); + return -1; + } + + if (mDebug) { + SLOGD("ASEC %s permissions fixed", id); + } + return 0; +} + int VolumeManager::renameAsec(const char *id1, const char *id2) { - char *asecFilename1; + char asecFilename1[255]; char *asecFilename2; char mountPoint[255]; - asprintf(&asecFilename1, "%s/%s.asec", Volume::SEC_ASECDIR, id1); - asprintf(&asecFilename2, "%s/%s.asec", Volume::SEC_ASECDIR, id2); + 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); snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id1); if (isMountpointMounted(mountPoint)) { @@ -451,12 +644,10 @@ int VolumeManager::renameAsec(const char *id1, const char *id2) { goto out_err; } - free(asecFilename1); free(asecFilename2); return 0; out_err: - free(asecFilename1); free(asecFilename2); return -1; } @@ -467,7 +658,11 @@ int VolumeManager::unmountAsec(const char *id, bool force) { char asecFileName[255]; char mountPoint[255]; - snprintf(asecFileName, sizeof(asecFileName), "%s/%s.asec", Volume::SEC_ASECDIR, id); + 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]; @@ -579,7 +774,11 @@ int VolumeManager::destroyAsec(const char *id, bool force) { char asecFileName[255]; char mountPoint[255]; - snprintf(asecFileName, sizeof(asecFileName), "%s/%s.asec", Volume::SEC_ASECDIR, id); + if (findAsec(id, asecFileName, sizeof(asecFileName))) { + SLOGE("Couldn't find ASEC %s", id); + return -1; + } + snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id); if (isMountpointMounted(mountPoint)) { @@ -603,11 +802,70 @@ int VolumeManager::destroyAsec(const char *id, bool force) { return 0; } +bool VolumeManager::isAsecInDirectory(const char *dir, const char *asecName) const { + int dirfd = open(dir, O_DIRECTORY); + if (dirfd < 0) { + SLOGE("Couldn't open internal ASEC dir (%s)", strerror(errno)); + return -1; + } + + bool ret = false; + + if (!faccessat(dirfd, asecName, F_OK, AT_SYMLINK_NOFOLLOW)) { + ret = true; + } + + close(dirfd); + + return ret; +} + +int VolumeManager::findAsec(const char *id, char *asecPath, size_t asecPathLen, + const char **directory) const { + int dirfd, fd; + const int idLen = strlen(id); + char *asecName; + + if (asprintf(&asecName, "%s.asec", id) < 0) { + SLOGE("Couldn't allocate string to write ASEC name"); + return -1; + } + + const char *dir; + if (isAsecInDirectory(Volume::SEC_ASECDIR_INT, asecName)) { + dir = Volume::SEC_ASECDIR_INT; + } else if (isAsecInDirectory(Volume::SEC_ASECDIR_EXT, asecName)) { + dir = Volume::SEC_ASECDIR_EXT; + } else { + free(asecName); + return -1; + } + + if (directory != NULL) { + *directory = dir; + } + + if (asecPath != NULL) { + int written = snprintf(asecPath, asecPathLen, "%s/%s", dir, asecName); + if (written < 0 || static_cast(written) >= asecPathLen) { + free(asecName); + return -1; + } + } + + free(asecName); + return 0; +} + int VolumeManager::mountAsec(const char *id, const char *key, int ownerUid) { char asecFileName[255]; char mountPoint[255]; - snprintf(asecFileName, sizeof(asecFileName), "%s/%s.asec", Volume::SEC_ASECDIR, id); + if (findAsec(id, asecFileName, sizeof(asecFileName))) { + SLOGE("Couldn't find ASEC %s", id); + return -1; + } + snprintf(mountPoint, sizeof(mountPoint), "%s/%s", Volume::ASECDIR, id); if (isMountpointMounted(mountPoint)) { @@ -641,39 +899,11 @@ int VolumeManager::mountAsec(const char *id, const char *key, int ownerUid) { bool cleanupDm = false; int fd; unsigned int nr_sec = 0; - - if ((fd = open(loopDevice, O_RDWR)) < 0) { - SLOGE("Failed to open loopdevice (%s)", strerror(errno)); - Loop::destroyByDevice(loopDevice); - return -1; - } - - if (ioctl(fd, BLKGETSIZE, &nr_sec)) { - SLOGE("Failed to get loop size (%s)", strerror(errno)); - Loop::destroyByDevice(loopDevice); - close(fd); - return -1; - } - - /* - * Validate superblock - */ struct asec_superblock sb; - memset(&sb, 0, sizeof(sb)); - if (lseek(fd, ((nr_sec-1) * 512), SEEK_SET) < 0) { - SLOGE("lseek failed (%s)", strerror(errno)); - close(fd); - Loop::destroyByDevice(loopDevice); - return -1; - } - if (read(fd, &sb, sizeof(sb)) != sizeof(sb)) { - SLOGE("superblock read failed (%s)", strerror(errno)); - close(fd); - Loop::destroyByDevice(loopDevice); - return -1; - } - close(fd); + if (Loop::lookupInfo(loopDevice, &sb, &nr_sec)) { + return -1; + } if (mDebug) { SLOGD("Container sb magic/ver (%.8x/%.2x)", sb.magic, sb.ver); @@ -707,7 +937,7 @@ int VolumeManager::mountAsec(const char *id, const char *key, int ownerUid) { strcpy(dmDevice, loopDevice); } - if (mkdir(mountPoint, 0777)) { + if (mkdir(mountPoint, 0000)) { if (errno != EEXIST) { SLOGE("Mountpoint creation failed (%s)", strerror(errno)); if (cleanupDm) { @@ -718,9 +948,14 @@ int VolumeManager::mountAsec(const char *id, const char *key, int ownerUid) { } } - if (Fat::doMount(dmDevice, mountPoint, true, false, true, ownerUid, 0, - 0222, false)) { -// 0227, false)) { + int result; + if (sb.c_opts & ASEC_SB_C_OPTS_EXT4) { + result = Ext4::doMount(dmDevice, mountPoint, true, false, true); + } 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); diff --git a/VolumeManager.h b/VolumeManager.h index a000556..3802503 100644 --- a/VolumeManager.h +++ b/VolumeManager.h @@ -87,9 +87,23 @@ public: void disableVolumeManager(void) { mVolManagerDisabled = 1; } /* ASEC */ + int findAsec(const char *id, char *asecPath = NULL, size_t asecPathLen = 0, + const char **directory = NULL) const; int createAsec(const char *id, unsigned numSectors, const char *fstype, - const char *key, int ownerUid); + const char *key, const int ownerUid, bool isExternal); int finalizeAsec(const char *id); + + /** + * Fixes ASEC permissions on a filesystem that has owners and permissions. + * This currently means EXT4-based ASEC containers. + * + * There is a single file that can be marked as "private" and will not have + * world-readable permission. The group for that file will be set to the gid + * supplied. + * + * Returns 0 on success. + */ + int fixupAsecPermissions(const char *id, gid_t gid, const char* privateFilename); int destroyAsec(const char *id, bool force); int mountAsec(const char *id, const char *key, int ownerUid); int unmountAsec(const char *id, bool force); @@ -127,6 +141,7 @@ private: VolumeManager(); void readInitialState(); bool isMountpointMounted(const char *mp); + bool isAsecInDirectory(const char *dir, const char *asec) const; }; extern "C" {