Let's reinvent storage, yet again!

Now that we're treating storage as a runtime permission, we need to
grant read/write access without killing the app.  This is really
tricky, since we had been using GIDs for access control, and they're
set in stone once Zygote drops privileges.

The only thing left that can change dynamically is the filesystem
itself, so let's do that.  This means changing the FUSE daemon to
present itself as three different views:

/mnt/runtime_default/foo - view for apps with no access
/mnt/runtime_read/foo - view for apps with read access
/mnt/runtime_write/foo - view for apps with write access

There is still a single location for all the backing files, and
filesystem permissions are derived the same way for each view, but
the file modes are masked off differently for each mountpoint.

During Zygote fork, it wires up the appropriate storage access into
an isolated mount namespace based on the current app permissions.  When
the app is granted permissions dynamically at runtime, the system
asks vold to jump into the existing mount namespace and bind mount
the newly granted access model into place.

Bug: 21858077
Change-Id: Iade538e4bc7af979fe20095f74416e8a0f165a4a
This commit is contained in:
Jeff Sharkey 2015-06-24 11:49:24 -07:00
parent 5d268fdac4
commit 66270a21df
10 changed files with 200 additions and 53 deletions

View file

@ -263,6 +263,12 @@ int CommandListener::VolumeCmd::runCommand(SocketClient *cli,
// forget_partition [partGuid] // forget_partition [partGuid]
std::string partGuid(argv[2]); std::string partGuid(argv[2]);
return sendGenericOkFail(cli, vm->forgetPartition(partGuid)); return sendGenericOkFail(cli, vm->forgetPartition(partGuid));
} else if (cmd == "remount_uid" && argc > 3) {
// remount_uid [uid] [none|default|read|write]
uid_t uid = atoi(argv[2]);
std::string mode(argv[3]);
return sendGenericOkFail(cli, vm->remountUid(uid, mode));
} }
return cli->sendMsg(ResponseCode::CommandSyntaxError, nullptr, false); return cli->sendMsg(ResponseCode::CommandSyntaxError, nullptr, false);

View file

@ -39,37 +39,45 @@ static const char* kFusePath = "/system/bin/sdcard";
EmulatedVolume::EmulatedVolume(const std::string& rawPath) : EmulatedVolume::EmulatedVolume(const std::string& rawPath) :
VolumeBase(Type::kEmulated), mFusePid(0) { VolumeBase(Type::kEmulated), mFusePid(0) {
setId("emulated"); setId("emulated");
mFusePath = "/storage/emulated";
mRawPath = rawPath; mRawPath = rawPath;
mLabel = "emulated";
} }
EmulatedVolume::EmulatedVolume(const std::string& rawPath, dev_t device, EmulatedVolume::EmulatedVolume(const std::string& rawPath, dev_t device,
const std::string& fsUuid) : VolumeBase(Type::kEmulated), mFusePid(0) { const std::string& fsUuid) : VolumeBase(Type::kEmulated), mFusePid(0) {
setId(StringPrintf("emulated:%u,%u", major(device), minor(device))); setId(StringPrintf("emulated:%u,%u", major(device), minor(device)));
mFusePath = StringPrintf("/storage/%s", fsUuid.c_str());
mRawPath = rawPath; mRawPath = rawPath;
mLabel = fsUuid;
} }
EmulatedVolume::~EmulatedVolume() { EmulatedVolume::~EmulatedVolume() {
} }
status_t EmulatedVolume::doMount() { status_t EmulatedVolume::doMount() {
if (fs_prepare_dir(mFusePath.c_str(), 0700, AID_ROOT, AID_ROOT)) { mFuseDefault = StringPrintf("/mnt/runtime_default/%s", mLabel.c_str());
PLOG(ERROR) << getId() << " failed to create mount point " << mFusePath; mFuseRead = StringPrintf("/mnt/runtime_read/%s", mLabel.c_str());
mFuseWrite = StringPrintf("/mnt/runtime_write/%s", mLabel.c_str());
setInternalPath(mRawPath);
setPath(StringPrintf("/storage/%s", mLabel.c_str()));
if (fs_prepare_dir(mFuseDefault.c_str(), 0700, AID_ROOT, AID_ROOT) ||
fs_prepare_dir(mFuseRead.c_str(), 0700, AID_ROOT, AID_ROOT) ||
fs_prepare_dir(mFuseWrite.c_str(), 0700, AID_ROOT, AID_ROOT)) {
PLOG(ERROR) << getId() << " failed to create mount points";
return -errno; return -errno;
} }
setInternalPath(mRawPath); dev_t before = GetDevice(mFuseWrite);
setPath(mFusePath);
if (!(mFusePid = fork())) { if (!(mFusePid = fork())) {
// TODO: protect when not mounted as visible
if (execl(kFusePath, kFusePath, if (execl(kFusePath, kFusePath,
"-u", "1023", // AID_MEDIA_RW "-u", "1023", // AID_MEDIA_RW
"-g", "1023", // AID_MEDIA_RW "-g", "1023", // AID_MEDIA_RW
"-l", "-m",
"-w",
mRawPath.c_str(), mRawPath.c_str(),
mFusePath.c_str(), mLabel.c_str(),
NULL)) { NULL)) {
PLOG(ERROR) << "Failed to exec"; PLOG(ERROR) << "Failed to exec";
} }
@ -83,6 +91,11 @@ status_t EmulatedVolume::doMount() {
return -errno; return -errno;
} }
while (before == GetDevice(mFuseWrite)) {
LOG(VERBOSE) << "Waiting for FUSE to spin up...";
usleep(50000); // 50ms
}
return OK; return OK;
} }
@ -93,13 +106,17 @@ status_t EmulatedVolume::doUnmount() {
mFusePid = 0; mFusePid = 0;
} }
ForceUnmount(mFusePath); ForceUnmount(mFuseDefault);
ForceUnmount(mRawPath); ForceUnmount(mFuseRead);
ForceUnmount(mFuseWrite);
if (TEMP_FAILURE_RETRY(rmdir(mFusePath.c_str()))) { rmdir(mFuseDefault.c_str());
PLOG(ERROR) << getId() << " failed to rmdir mount point " << mFusePath; rmdir(mFuseRead.c_str());
return -errno; rmdir(mFuseWrite.c_str());
}
mFuseDefault.clear();
mFuseRead.clear();
mFuseWrite.clear();
return OK; return OK;
} }

View file

@ -46,10 +46,13 @@ protected:
status_t doUnmount() override; status_t doUnmount() override;
private: private:
/* Mount point of raw storage */
std::string mRawPath; std::string mRawPath;
/* Mount point of visible storage */ std::string mLabel;
std::string mFusePath;
std::string mFuseDefault;
std::string mFuseRead;
std::string mFuseWrite;
/* PID of FUSE wrapper */ /* PID of FUSE wrapper */
pid_t mFusePid; pid_t mFusePid;

View file

@ -111,16 +111,19 @@ status_t PublicVolume::doMount() {
} }
mRawPath = StringPrintf("/mnt/media_rw/%s", stableName.c_str()); mRawPath = StringPrintf("/mnt/media_rw/%s", stableName.c_str());
mFusePath = StringPrintf("/storage/%s", stableName.c_str());
setInternalPath(mRawPath);
setPath(mFusePath);
if (fs_prepare_dir(mRawPath.c_str(), 0700, AID_ROOT, AID_ROOT)) { mFuseDefault = StringPrintf("/mnt/runtime_default/%s", stableName.c_str());
PLOG(ERROR) << getId() << " failed to create mount point " << mRawPath; mFuseRead = StringPrintf("/mnt/runtime_read/%s", stableName.c_str());
return -errno; mFuseWrite = StringPrintf("/mnt/runtime_write/%s", stableName.c_str());
}
if (fs_prepare_dir(mFusePath.c_str(), 0700, AID_ROOT, AID_ROOT)) { setInternalPath(mRawPath);
PLOG(ERROR) << getId() << " failed to create mount point " << mFusePath; setPath(StringPrintf("/storage/%s", stableName.c_str()));
if (fs_prepare_dir(mRawPath.c_str(), 0700, AID_ROOT, AID_ROOT) ||
fs_prepare_dir(mFuseDefault.c_str(), 0700, AID_ROOT, AID_ROOT) ||
fs_prepare_dir(mFuseRead.c_str(), 0700, AID_ROOT, AID_ROOT) ||
fs_prepare_dir(mFuseWrite.c_str(), 0700, AID_ROOT, AID_ROOT)) {
PLOG(ERROR) << getId() << " failed to create mount points";
return -errno; return -errno;
} }
@ -134,25 +137,18 @@ status_t PublicVolume::doMount() {
initAsecStage(); initAsecStage();
} }
// TODO: teach FUSE daemon to protect itself with user-specific GID dev_t before = GetDevice(mFuseWrite);
if (!(mFusePid = fork())) { if (!(mFusePid = fork())) {
if (!(getMountFlags() & MountFlags::kVisible)) { if (!(getMountFlags() & MountFlags::kVisible)) {
// TODO: mount so that only system apps can access // TODO: do we need to wrap this device?
if (execl(kFusePath, kFusePath,
"-u", "1023", // AID_MEDIA_RW
"-g", "1023", // AID_MEDIA_RW
mRawPath.c_str(),
mFusePath.c_str(),
NULL)) {
PLOG(ERROR) << "Failed to exec";
}
} else if (getMountFlags() & MountFlags::kPrimary) { } else if (getMountFlags() & MountFlags::kPrimary) {
if (execl(kFusePath, kFusePath, if (execl(kFusePath, kFusePath,
"-u", "1023", // AID_MEDIA_RW "-u", "1023", // AID_MEDIA_RW
"-g", "1023", // AID_MEDIA_RW "-g", "1023", // AID_MEDIA_RW
"-d", "-w",
mRawPath.c_str(), mRawPath.c_str(),
mFusePath.c_str(), stableName.c_str(),
NULL)) { NULL)) {
PLOG(ERROR) << "Failed to exec"; PLOG(ERROR) << "Failed to exec";
} }
@ -160,10 +156,8 @@ status_t PublicVolume::doMount() {
if (execl(kFusePath, kFusePath, if (execl(kFusePath, kFusePath,
"-u", "1023", // AID_MEDIA_RW "-u", "1023", // AID_MEDIA_RW
"-g", "1023", // AID_MEDIA_RW "-g", "1023", // AID_MEDIA_RW
"-w", "1023", // AID_MEDIA_RW
"-d",
mRawPath.c_str(), mRawPath.c_str(),
mFusePath.c_str(), stableName.c_str(),
NULL)) { NULL)) {
PLOG(ERROR) << "Failed to exec"; PLOG(ERROR) << "Failed to exec";
} }
@ -178,6 +172,11 @@ status_t PublicVolume::doMount() {
return -errno; return -errno;
} }
while (before == GetDevice(mFuseWrite)) {
LOG(VERBOSE) << "Waiting for FUSE to spin up...";
usleep(50000); // 50ms
}
return OK; return OK;
} }
@ -189,17 +188,20 @@ status_t PublicVolume::doUnmount() {
} }
ForceUnmount(kAsecPath); ForceUnmount(kAsecPath);
ForceUnmount(mFusePath);
ForceUnmount(mFuseDefault);
ForceUnmount(mFuseRead);
ForceUnmount(mFuseWrite);
ForceUnmount(mRawPath); ForceUnmount(mRawPath);
if (TEMP_FAILURE_RETRY(rmdir(mRawPath.c_str()))) { rmdir(mFuseDefault.c_str());
PLOG(ERROR) << getId() << " failed to rmdir mount point " << mRawPath; rmdir(mFuseRead.c_str());
} rmdir(mFuseWrite.c_str());
if (TEMP_FAILURE_RETRY(rmdir(mFusePath.c_str()))) { rmdir(mRawPath.c_str());
PLOG(ERROR) << getId() << " failed to rmdir mount point " << mFusePath;
}
mFusePath.clear(); mFuseDefault.clear();
mFuseRead.clear();
mFuseWrite.clear();
mRawPath.clear(); mRawPath.clear();
return OK; return OK;

View file

@ -59,8 +59,11 @@ private:
std::string mDevPath; std::string mDevPath;
/* Mount point of raw partition */ /* Mount point of raw partition */
std::string mRawPath; std::string mRawPath;
/* Mount point of FUSE wrapper */
std::string mFusePath; std::string mFuseDefault;
std::string mFuseRead;
std::string mFuseWrite;
/* PID of FUSE wrapper */ /* PID of FUSE wrapper */
pid_t mFusePid; pid_t mFusePid;

View file

@ -522,5 +522,15 @@ std::string BuildKeyPath(const std::string& partGuid) {
return StringPrintf("%s/expand_%s.key", kKeyPath, partGuid.c_str()); return StringPrintf("%s/expand_%s.key", kKeyPath, partGuid.c_str());
} }
dev_t GetDevice(const std::string& path) {
struct stat sb;
if (stat(path.c_str(), &sb)) {
PLOG(WARNING) << "Failed to stat " << path;
return 0;
} else {
return sb.st_dev;
}
}
} // namespace vold } // namespace vold
} // namespace android } // namespace android

View file

@ -90,6 +90,8 @@ status_t WipeBlockDevice(const std::string& path);
std::string BuildKeyPath(const std::string& partGuid); std::string BuildKeyPath(const std::string& partGuid);
dev_t GetDevice(const std::string& path);
} // namespace vold } // namespace vold
} // namespace android } // namespace android

View file

@ -219,7 +219,6 @@ status_t VolumeBase::unmount() {
} }
setState(State::kEjecting); setState(State::kEjecting);
for (auto vol : mVolumes) { for (auto vol : mVolumes) {
if (vol->destroy()) { if (vol->destroy()) {
LOG(WARNING) << getId() << " failed to destroy " << vol->getId() LOG(WARNING) << getId() << " failed to destroy " << vol->getId()

View file

@ -26,6 +26,7 @@
#include <sys/mount.h> #include <sys/mount.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include <linux/kdev_t.h> #include <linux/kdev_t.h>
@ -480,6 +481,108 @@ int VolumeManager::setPrimary(const std::shared_ptr<android::vold::VolumeBase>&
return 0; return 0;
} }
int VolumeManager::remountUid(uid_t uid, const std::string& mode) {
LOG(DEBUG) << "Remounting " << uid << " as mode " << mode;
DIR* dir;
struct dirent* de;
char rootName[PATH_MAX];
char pidName[PATH_MAX];
int pidFd;
int nsFd;
struct stat sb;
pid_t child;
if (!(dir = opendir("/proc"))) {
PLOG(ERROR) << "Failed to opendir";
return -1;
}
// Figure out root namespace to compare against below
if (readlinkat(dirfd(dir), "1/ns/mnt", rootName, PATH_MAX) == -1) {
PLOG(ERROR) << "Failed to readlink";
closedir(dir);
return -1;
}
// Poke through all running PIDs look for apps running as UID
while ((de = readdir(dir))) {
pidFd = -1;
nsFd = -1;
pidFd = openat(dirfd(dir), de->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (pidFd < 0) {
goto next;
}
if (fstat(pidFd, &sb) != 0) {
PLOG(WARNING) << "Failed to stat " << de->d_name;
goto next;
}
if (sb.st_uid != uid) {
goto next;
}
// Matches so far, but refuse to touch if in root namespace
LOG(DEBUG) << "Found matching PID " << de->d_name;
if (readlinkat(pidFd, "ns/mnt", pidName, PATH_MAX) == -1) {
PLOG(WARNING) << "Failed to read namespace for " << de->d_name;
goto next;
}
if (!strcmp(rootName, pidName)) {
LOG(WARNING) << "Skipping due to root namespace";
goto next;
}
// We purposefully leave the namespace open across the fork
nsFd = openat(pidFd, "ns/mnt", O_RDONLY);
if (nsFd < 0) {
PLOG(WARNING) << "Failed to open namespace";
goto next;
}
if (!(child = fork())) {
if (setns(nsFd, CLONE_NEWNS) != 0) {
PLOG(ERROR) << "Failed to setns";
_exit(1);
}
// Unmount current view and replace with requested view
umount2("/storage", MNT_FORCE);
std::string storageSource;
if (mode == "default") {
storageSource = "/mnt/runtime_default";
} else if (mode == "read") {
storageSource = "/mnt/runtime_read";
} else if (mode == "write") {
storageSource = "/mnt/runtime_write";
} else {
// Sane default of no storage visible
_exit(0);
}
if (TEMP_FAILURE_RETRY(mount(storageSource.c_str(), "/storage",
NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
PLOG(WARNING) << "Failed to mount " << storageSource;
return false;
}
_exit(0);
}
if (child == -1) {
PLOG(ERROR) << "Failed to fork";
goto next;
} else {
TEMP_FAILURE_RETRY(waitpid(child, nullptr, 0));
}
next:
close(nsFd);
close(pidFd);
}
closedir(dir);
return 0;
}
int VolumeManager::reset() { int VolumeManager::reset() {
// Tear down all existing disks/volumes and start from a blank slate so // Tear down all existing disks/volumes and start from a blank slate so
// newly connected framework hears all events. // newly connected framework hears all events.

View file

@ -129,6 +129,8 @@ public:
int setPrimary(const std::shared_ptr<android::vold::VolumeBase>& vol); int setPrimary(const std::shared_ptr<android::vold::VolumeBase>& vol);
int remountUid(uid_t uid, const std::string& mode);
/* Reset all internal state, typically during framework boot */ /* Reset all internal state, typically during framework boot */
int reset(); int reset();
/* Prepare for device shutdown, safely unmounting all devices */ /* Prepare for device shutdown, safely unmounting all devices */