blob: ccfd4ada8a46bba30ca1a471bd05a81bec9c5755 [file] [log] [blame]
/*
* Copyright (C) 2018 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.
*/
#define LOG_TAG "apexd"
#include "apexd.h"
#include "apex_database.h"
#include "apex_file.h"
#include "apex_manifest.h"
#include "status_or.h"
#include "string_log.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/macros.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <libavb/libavb.h>
#include <libdm/dm.h>
#include <libdm/dm_table.h>
#include <libdm/dm_target.h>
#include <dirent.h>
#include <fcntl.h>
#include <linux/loop.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <array>
#include <fstream>
#include <iomanip>
#include <memory>
#include <string>
using android::base::Basename;
using android::base::EndsWith;
using android::base::ReadFullyAtOffset;
using android::base::StringPrintf;
using android::base::unique_fd;
using android::dm::DeviceMapper;
using android::dm::DmTable;
using android::dm::DmTargetVerity;
namespace android {
namespace apex {
using MountedApexData = MountedApexDatabase::MountedApexData;
namespace {
static constexpr const char* kApexPackageSuffix = ".apex";
static constexpr const char* kApexLoopIdPrefix = "apex:";
static constexpr const char* kApexKeyDirectory = "/system/etc/security/apex/";
static constexpr const char* kApexKeyProp = "apex.key";
// 128 kB read-ahead, which we currently use for /system as well
static constexpr const char* kReadAheadKb = "128";
// These should be in-sync with system/sepolicy/public/property_contexts
static constexpr const char* kApexStatusSysprop = "apexd.status";
static constexpr const char* kApexStatusStarting = "starting";
static constexpr const char* kApexStatusReady = "ready";
static constexpr int kVbMetaMaxSize = 64 * 1024;
static constexpr int kMkdirMode = 0755;
MountedApexDatabase gMountedApexes;
std::string GetPackageMountPoint(const ApexManifest& manifest) {
return StringPrintf("%s/%s", kApexRoot, manifest.GetPackageId().c_str());
}
std::string GetActiveMountPoint(const ApexManifest& manifest) {
return StringPrintf("%s/%s", kApexRoot, manifest.GetName().c_str());
}
struct LoopbackDeviceUniqueFd {
unique_fd device_fd;
std::string name;
LoopbackDeviceUniqueFd() {}
LoopbackDeviceUniqueFd(unique_fd&& fd, const std::string& name)
: device_fd(std::move(fd)), name(name) {}
LoopbackDeviceUniqueFd(LoopbackDeviceUniqueFd&& fd) noexcept
: device_fd(std::move(fd.device_fd)), name(fd.name) {}
LoopbackDeviceUniqueFd& operator=(LoopbackDeviceUniqueFd&& other) noexcept {
MaybeCloseBad();
device_fd = std::move(other.device_fd);
name = std::move(other.name);
return *this;
}
~LoopbackDeviceUniqueFd() { MaybeCloseBad(); }
void MaybeCloseBad() {
if (device_fd.get() != -1) {
// Disassociate any files.
if (ioctl(device_fd.get(), LOOP_CLR_FD) == -1) {
PLOG(ERROR) << "Unable to clear fd for loopback device";
}
}
}
void CloseGood() { device_fd.reset(-1); }
int get() { return device_fd.get(); }
};
StatusOr<LoopbackDeviceUniqueFd> createLoopDevice(const std::string& target,
const int32_t imageOffset,
const size_t imageSize) {
using Failed = StatusOr<LoopbackDeviceUniqueFd>;
unique_fd ctl_fd(open("/dev/loop-control", O_RDWR | O_CLOEXEC));
if (ctl_fd.get() == -1) {
return Failed::MakeError(PStringLog() << "Failed to open loop-control");
}
int num = ioctl(ctl_fd.get(), LOOP_CTL_GET_FREE);
if (num == -1) {
return Failed::MakeError(PStringLog() << "Failed LOOP_CTL_GET_FREE");
}
std::string device = StringPrintf("/dev/block/loop%d", num);
unique_fd target_fd(open(target.c_str(), O_RDONLY | O_CLOEXEC));
if (target_fd.get() == -1) {
return Failed::MakeError(PStringLog() << "Failed to open " << target);
}
LoopbackDeviceUniqueFd device_fd(
unique_fd(open(device.c_str(), O_RDWR | O_CLOEXEC)), device);
if (device_fd.get() == -1) {
return Failed::MakeError(PStringLog() << "Failed to open " << device);
}
if (ioctl(device_fd.get(), LOOP_SET_FD, target_fd.get()) == -1) {
return Failed::MakeError(PStringLog() << "Failed to LOOP_SET_FD");
}
struct loop_info64 li;
memset(&li, 0, sizeof(li));
strlcpy((char*)li.lo_crypt_name, kApexLoopIdPrefix, LO_NAME_SIZE);
li.lo_offset = imageOffset;
li.lo_sizelimit = imageSize;
if (ioctl(device_fd.get(), LOOP_SET_STATUS64, &li) == -1) {
return Failed::MakeError(PStringLog() << "Failed to LOOP_SET_STATUS64");
}
// Direct-IO requires the loop device to have the same block size as the
// underlying filesystem.
if (ioctl(device_fd.get(), LOOP_SET_BLOCK_SIZE, 4096) == -1) {
PLOG(WARNING) << "Failed to LOOP_SET_BLOCK_SIZE";
} else {
if (ioctl(device_fd.get(), LOOP_SET_DIRECT_IO, 1) == -1) {
PLOG(WARNING) << "Failed to LOOP_SET_DIRECT_IO";
// TODO Eventually we'll want to fail on this; right now we can't because
// not all devices have the necessary kernel patches.
}
}
return StatusOr<LoopbackDeviceUniqueFd>(std::move(device_fd));
}
void destroyAllLoopDevices() {
std::string root = "/dev/block/";
auto dirp =
std::unique_ptr<DIR, int (*)(DIR*)>(opendir(root.c_str()), closedir);
if (!dirp) {
PLOG(ERROR) << "Failed to open /dev/block/, can't destroy loop devices.";
return;
}
// Poke through all devices looking for loop devices.
struct dirent* de;
while ((de = readdir(dirp.get()))) {
auto test = std::string(de->d_name);
if (!android::base::StartsWith(test, "loop")) continue;
auto path = root + de->d_name;
unique_fd fd(open(path.c_str(), O_RDWR | O_CLOEXEC));
if (fd.get() == -1) {
if (errno != ENOENT) {
PLOG(WARNING) << "Failed to open " << path;
}
continue;
}
struct loop_info64 li;
if (ioctl(fd.get(), LOOP_GET_STATUS64, &li) < 0) {
PLOG(WARNING) << "Failed to LOOP_GET_STATUS64 " << path;
continue;
}
auto id = std::string((char*)li.lo_crypt_name);
if (android::base::StartsWith(id, kApexLoopIdPrefix)) {
LOG(DEBUG) << "Tearing down stale loop device at " << path << " named "
<< id;
if (ioctl(fd.get(), LOOP_CLR_FD, 0) < 0) {
PLOG(WARNING) << "Failed to LOOP_CLR_FD " << path;
}
} else {
LOG(VERBOSE) << "Found unmanaged loop device at " << path << " named "
<< id;
}
}
}
static constexpr size_t kLoopDeviceSetupAttempts = 3u;
std::string bytes_to_hex(const uint8_t* bytes, size_t bytes_len) {
std::ostringstream s;
s << std::hex << std::setfill('0');
for (size_t i = 0; i < bytes_len; i++) {
s << std::setw(2) << static_cast<int>(bytes[i]);
}
return s.str();
}
std::string getSalt(const AvbHashtreeDescriptor& desc,
const uint8_t* trailingData) {
const uint8_t* desc_salt = trailingData + desc.partition_name_len;
return bytes_to_hex(desc_salt, desc.salt_len);
}
std::string getDigest(const AvbHashtreeDescriptor& desc,
const uint8_t* trailingData) {
const uint8_t* desc_digest =
trailingData + desc.partition_name_len + desc.salt_len;
return bytes_to_hex(desc_digest, desc.root_digest_len);
}
// Data needed to construct a valid VerityTable
struct ApexVerityData {
std::unique_ptr<AvbHashtreeDescriptor> desc;
std::string salt;
std::string root_digest;
};
std::unique_ptr<DmTable> createVerityTable(const ApexVerityData& verity_data,
const std::string& loop) {
AvbHashtreeDescriptor* desc = verity_data.desc.get();
auto table = std::make_unique<DmTable>();
std::ostringstream hash_algorithm;
hash_algorithm << desc->hash_algorithm;
auto target = std::make_unique<DmTargetVerity>(
0, desc->image_size / 512, desc->dm_verity_version, loop, loop,
desc->data_block_size, desc->hash_block_size,
desc->image_size / desc->data_block_size,
desc->tree_offset / desc->hash_block_size, hash_algorithm.str(),
verity_data.root_digest, verity_data.salt);
target->IgnoreZeroBlocks();
table->AddTarget(std::move(target));
table->set_readonly(true);
return table;
}
StatusOr<std::unique_ptr<AvbFooter>> getAvbFooter(const ApexFile& apex,
const unique_fd& fd) {
std::array<uint8_t, AVB_FOOTER_SIZE> footer_data;
auto footer = std::make_unique<AvbFooter>();
// The AVB footer is located in the last part of the image
off_t offset = apex.GetImageSize() + apex.GetImageOffset() - AVB_FOOTER_SIZE;
int ret = lseek(fd, offset, SEEK_SET);
if (ret == -1) {
return StatusOr<std::unique_ptr<AvbFooter>>::MakeError(
PStringLog() << "Couldn't seek to AVB footer.");
}
ret = read(fd, footer_data.data(), AVB_FOOTER_SIZE);
if (ret != AVB_FOOTER_SIZE) {
return StatusOr<std::unique_ptr<AvbFooter>>::MakeError(
PStringLog() << "Couldn't read AVB footer.");
}
if (!avb_footer_validate_and_byteswap((const AvbFooter*)footer_data.data(),
footer.get())) {
return StatusOr<std::unique_ptr<AvbFooter>>::MakeError(
StringLog() << "AVB footer verification failed.");
}
LOG(VERBOSE) << "AVB footer verification successful.";
return StatusOr<std::unique_ptr<AvbFooter>>(std::move(footer));
}
// TODO We'll want to cache the verified key to avoid having to read it every
// time.
Status verifyPublicKey(const uint8_t* key, size_t length,
std::string acceptedKeyFile) {
std::ifstream pubkeyFile(acceptedKeyFile, std::ios::binary | std::ios::ate);
if (pubkeyFile.bad()) {
return Status::Fail(StringLog() << "Can't open " << acceptedKeyFile);
}
std::streamsize size = pubkeyFile.tellg();
if (size < 0) {
return Status::Fail(StringLog()
<< "Could not get public key length position");
}
if (static_cast<size_t>(size) != length) {
return Status::Fail(StringLog()
<< "Public key length (" << std::to_string(size) << ")"
<< " doesn't equal APEX public key length ("
<< std::to_string(length) << ")");
}
pubkeyFile.seekg(0, std::ios::beg);
std::string verifiedKey(size, 0);
pubkeyFile.read(&verifiedKey[0], size);
if (pubkeyFile.bad()) {
return Status::Fail(StringLog() << "Can't read from " << acceptedKeyFile);
}
if (memcmp(&verifiedKey[0], key, length) != 0) {
return Status::Fail("Failed to compare verified key with key");
}
return Status::Success();
}
StatusOr<std::string> getPublicKeyFilePath(const ApexFile& apex,
const uint8_t* data, size_t length) {
size_t keyNameLen;
const char* keyName = avb_property_lookup(data, length, kApexKeyProp,
strlen(kApexKeyProp), &keyNameLen);
if (keyName == nullptr || keyNameLen == 0) {
return StatusOr<std::string>::MakeError(
StringLog() << "Cannot find prop \"" << kApexKeyProp << "\" from "
<< apex.GetPath());
}
std::string keyFilePath(kApexKeyDirectory);
keyFilePath.append(keyName, keyNameLen);
std::string canonicalKeyFilePath;
if (!android::base::Realpath(keyFilePath, &canonicalKeyFilePath)) {
return StatusOr<std::string>::MakeError(
PStringLog() << "Failed to get realpath of " << keyFilePath);
}
if (!android::base::StartsWith(canonicalKeyFilePath, kApexKeyDirectory)) {
return StatusOr<std::string>::MakeError(
StringLog() << "Key file " << canonicalKeyFilePath << " is not under "
<< kApexKeyDirectory);
}
return StatusOr<std::string>(canonicalKeyFilePath);
}
Status verifyVbMetaSignature(const ApexFile& apex, const uint8_t* data,
size_t length) {
const uint8_t* pk;
size_t pk_len;
AvbVBMetaVerifyResult res;
res = avb_vbmeta_image_verify(data, length, &pk, &pk_len);
switch (res) {
case AVB_VBMETA_VERIFY_RESULT_OK:
break;
case AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED:
case AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH:
case AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH:
return Status::Fail(StringLog()
<< "Error verifying " << apex.GetPath() << ": "
<< avb_vbmeta_verify_result_to_string(res));
case AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER:
return Status::Fail(StringLog()
<< "Error verifying " << apex.GetPath() << ": "
<< "invalid vbmeta header");
case AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION:
return Status::Fail(StringLog()
<< "Error verifying " << apex.GetPath() << ": "
<< "unsupported version");
default:
return Status::Fail("Unknown vmbeta_image_verify return value");
}
StatusOr<std::string> keyFilePath = getPublicKeyFilePath(apex, data, length);
if (!keyFilePath.Ok()) {
return keyFilePath.ErrorStatus();
}
// TODO(b/115718846)
// We need to decide whether we need rollback protection, and whether
// we can use the rollback protection provided by libavb.
Status st = verifyPublicKey(pk, pk_len, *keyFilePath);
if (st.Ok()) {
LOG(VERBOSE) << apex.GetPath() << ": public key matches.";
return st;
}
return Status::Fail(StringLog()
<< "Error verifying " << apex.GetPath() << ": "
<< "couldn't verify public key: " << st.ErrorMessage());
}
StatusOr<std::unique_ptr<uint8_t[]>> verifyVbMeta(const ApexFile& apex,
const unique_fd& fd,
const AvbFooter& footer) {
if (footer.vbmeta_size > kVbMetaMaxSize) {
return StatusOr<std::unique_ptr<uint8_t[]>>::MakeError(
"VbMeta size in footer exceeds kVbMetaMaxSize.");
}
off_t offset = apex.GetImageOffset() + footer.vbmeta_offset;
std::unique_ptr<uint8_t[]> vbmeta_buf(new uint8_t[footer.vbmeta_size]);
if (!ReadFullyAtOffset(fd, vbmeta_buf.get(), footer.vbmeta_size, offset)) {
return StatusOr<std::unique_ptr<uint8_t[]>>::MakeError(
PStringLog() << "Couldn't read AVB meta-data.");
}
Status st = verifyVbMetaSignature(apex, vbmeta_buf.get(), footer.vbmeta_size);
if (!st.Ok()) {
return StatusOr<std::unique_ptr<uint8_t[]>>::MakeError(st.ErrorMessage());
}
return StatusOr<std::unique_ptr<uint8_t[]>>(std::move(vbmeta_buf));
}
StatusOr<const AvbHashtreeDescriptor*> findDescriptor(uint8_t* vbmeta_data,
size_t vbmeta_size) {
const AvbDescriptor** descriptors;
size_t num_descriptors;
descriptors =
avb_descriptor_get_all(vbmeta_data, vbmeta_size, &num_descriptors);
for (size_t i = 0; i < num_descriptors; i++) {
AvbDescriptor desc;
if (!avb_descriptor_validate_and_byteswap(descriptors[i], &desc)) {
return StatusOr<const AvbHashtreeDescriptor*>::MakeError(
"Couldn't validate AvbDescriptor.");
}
if (desc.tag != AVB_DESCRIPTOR_TAG_HASHTREE) {
// Ignore other descriptors
continue;
}
return StatusOr<const AvbHashtreeDescriptor*>(
(const AvbHashtreeDescriptor*)descriptors[i]);
}
return StatusOr<const AvbHashtreeDescriptor*>::MakeError(
"Couldn't find any AVB hashtree descriptors.");
}
StatusOr<std::unique_ptr<AvbHashtreeDescriptor>> verifyDescriptor(
const AvbHashtreeDescriptor* desc) {
auto verifiedDesc = std::make_unique<AvbHashtreeDescriptor>();
if (!avb_hashtree_descriptor_validate_and_byteswap(desc,
verifiedDesc.get())) {
StatusOr<std::unique_ptr<AvbHashtreeDescriptor>>::MakeError(
"Couldn't validate AvbDescriptor.");
}
return StatusOr<std::unique_ptr<AvbHashtreeDescriptor>>(
std::move(verifiedDesc));
}
class DmVerityDevice {
public:
DmVerityDevice() : cleared_(true) {}
DmVerityDevice(const std::string& name) : name_(name), cleared_(false) {}
DmVerityDevice(const std::string& name, const std::string& dev_path)
: name_(name), dev_path_(dev_path), cleared_(false) {}
DmVerityDevice(DmVerityDevice&& other)
: name_(other.name_),
dev_path_(other.dev_path_),
cleared_(other.cleared_) {
other.cleared_ = true;
}
DmVerityDevice& operator=(DmVerityDevice&& other) {
name_ = other.name_;
dev_path_ = other.dev_path_;
cleared_ = other.cleared_;
other.cleared_ = true;
return *this;
}
~DmVerityDevice() {
if (!cleared_) {
DeviceMapper& dm = DeviceMapper::Instance();
dm.DeleteDevice(name_);
}
}
const std::string& GetName() const { return name_; }
const std::string& GetDevPath() const { return dev_path_; }
void SetDevPath(const std::string& dev_path) { dev_path_ = dev_path; }
void Release() { cleared_ = true; }
private:
std::string name_;
std::string dev_path_;
bool cleared_;
};
StatusOr<DmVerityDevice> createVerityDevice(const std::string& name,
const DmTable& table) {
DeviceMapper& dm = DeviceMapper::Instance();
dm.DeleteDevice(name);
if (!dm.CreateDevice(name, table)) {
return StatusOr<DmVerityDevice>::MakeError(
"Couldn't create verity device.");
}
DmVerityDevice dev(name);
std::string dev_path;
if (!dm.GetDmDevicePathByName(name, &dev_path)) {
return StatusOr<DmVerityDevice>::MakeError(
"Couldn't get verity device path!");
}
dev.SetDevPath(dev_path);
return StatusOr<DmVerityDevice>(std::move(dev));
}
// What this function verifies
// 1. The apex file has an AVB footer and that it's valid
// 2. The apex file has a vb metadata structure that is valid
// 3. The vb metadata structure is signed with the correct key
// 4. The vb metadata contains a valid AvbHashTreeDescriptor
//
// If all these steps pass, this function returns an ApexVerityTable
// struct with all the data necessary to create a dm-verity device for this
// APEX.
StatusOr<std::unique_ptr<ApexVerityData>> verifyApexVerity(
const ApexFile& apex) {
auto verityData = std::make_unique<ApexVerityData>();
unique_fd fd(open(apex.GetPath().c_str(), O_RDONLY | O_CLOEXEC));
if (fd.get() == -1) {
return StatusOr<std::unique_ptr<ApexVerityData>>::MakeError(
PStringLog() << "Failed to open " << apex.GetPath());
}
StatusOr<std::unique_ptr<AvbFooter>> footer = getAvbFooter(apex, fd);
if (!footer.Ok()) {
return StatusOr<std::unique_ptr<ApexVerityData>>::MakeError(
footer.ErrorMessage());
}
StatusOr<std::unique_ptr<uint8_t[]>> vbmeta_data =
verifyVbMeta(apex, fd, **footer);
if (!vbmeta_data.Ok()) {
return StatusOr<std::unique_ptr<ApexVerityData>>::MakeError(
vbmeta_data.ErrorMessage());
}
StatusOr<const AvbHashtreeDescriptor*> descriptor =
findDescriptor(vbmeta_data->get(), (*footer)->vbmeta_size);
if (!descriptor.Ok()) {
return StatusOr<std::unique_ptr<ApexVerityData>>::MakeError(
descriptor.ErrorMessage());
}
StatusOr<std::unique_ptr<AvbHashtreeDescriptor>> verifiedDescriptor =
verifyDescriptor(*descriptor);
if (!verifiedDescriptor.Ok()) {
return StatusOr<std::unique_ptr<ApexVerityData>>::MakeError(
verifiedDescriptor.ErrorMessage());
}
verityData->desc = std::move(*verifiedDescriptor);
// This area is now safe to access, because we just verified it
const uint8_t* trailingData =
(const uint8_t*)*descriptor + sizeof(AvbHashtreeDescriptor);
verityData->salt = getSalt(*(verityData->desc), trailingData);
verityData->root_digest = getDigest(*(verityData->desc), trailingData);
return StatusOr<std::unique_ptr<ApexVerityData>>(std::move(verityData));
}
Status updateLatest(const std::string& latest_path,
const std::string& mount_point) {
LOG(VERBOSE) << "Creating bind-mount for " << latest_path << " with target "
<< mount_point;
// Ensure the directory exists, try to unmount.
{
bool exists;
bool is_dir;
{
struct stat buf;
if (stat(latest_path.c_str(), &buf) != 0) {
if (errno == ENOENT) {
exists = false;
is_dir = false;
} else {
PLOG(ERROR) << "Could not stat target directory " << latest_path;
// Still attempt to bind-mount.
exists = true;
is_dir = true;
}
} else {
exists = true;
is_dir = S_ISDIR(buf.st_mode);
}
}
// Ensure that it is a folder.
if (exists && !is_dir) {
LOG(WARNING) << latest_path << " is not a directory, attempting to fix";
if (unlink(latest_path.c_str()) != 0) {
PLOG(ERROR) << "Failed to unlink " << latest_path;
// Try mkdir, anyways.
}
exists = false;
}
// And create it if necessary.
if (!exists) {
LOG(VERBOSE) << "Creating mountpoint " << latest_path;
if (mkdir(latest_path.c_str(), kMkdirMode) != 0) {
return Status::Fail(PStringLog()
<< "Could not create mountpoint " << latest_path);
}
};
// Unmount any active bind-mount.
if (exists) {
int rc = umount2(latest_path.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH);
if (rc != 0 && errno != EINVAL) {
// Log error but ignore.
PLOG(ERROR) << "Could not unmount " << latest_path;
}
}
}
LOG(VERBOSE) << "Bind-mounting " << mount_point << " to " << latest_path;
if (mount(mount_point.c_str(), latest_path.c_str(), nullptr, MS_BIND,
nullptr) == 0) {
return Status::Success();
}
return Status::Fail(PStringLog() << "Could not bind-mount " << mount_point
<< " to " << latest_path);
}
StatusOr<std::vector<std::string>> getApexRootSubFolders() {
// This code would be much shorter if C++17's std::filesystem were available,
// which is not at the time of writing this.
auto d = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(kApexRoot), closedir);
if (!d) {
return StatusOr<std::vector<std::string>>::MakeError(
PStringLog() << "Can't open " << kApexRoot << " for reading.");
}
std::vector<std::string> ret;
struct dirent* dp;
while ((dp = readdir(d.get())) != NULL) {
if (dp->d_type != DT_DIR || (strcmp(dp->d_name, ".") == 0) ||
(strcmp(dp->d_name, "..") == 0)) {
continue;
}
ret.push_back(dp->d_name);
}
return StatusOr<std::vector<std::string>>(std::move(ret));
}
Status configureReadAhead(const std::string& device_path) {
auto pos = device_path.find("/dev/block/");
if (pos != 0) {
return Status::Fail(StringLog()
<< "Device path does not start with /dev/block.");
}
pos = device_path.find_last_of("/");
std::string device_name = device_path.substr(pos + 1, std::string::npos);
std::string sysfs_device =
StringPrintf("/sys/block/%s/queue/read_ahead_kb", device_name.c_str());
unique_fd sysfs_fd(open(sysfs_device.c_str(), O_RDWR | O_CLOEXEC));
if (sysfs_fd.get() == -1) {
return Status::Fail(PStringLog() << "Failed to open " << sysfs_device);
}
int ret = TEMP_FAILURE_RETRY(
write(sysfs_fd.get(), kReadAheadKb, strlen(kReadAheadKb) + 1));
if (ret < 0) {
return Status::Fail(PStringLog() << "Failed to write to " << sysfs_device);
}
return Status::Success();
}
using ApexFileAndManifest =
std::pair<std::unique_ptr<ApexFile>, std::unique_ptr<ApexManifest>>;
StatusOr<ApexFileAndManifest> openFileAndManifest(
const std::string& full_path) {
StatusOr<std::unique_ptr<ApexFile>> apexFileRes = ApexFile::Open(full_path);
if (!apexFileRes.Ok()) {
return StatusOr<ApexFileAndManifest>::MakeError(apexFileRes.ErrorMessage());
}
StatusOr<std::unique_ptr<ApexManifest>> manifestRes =
ApexManifest::Open((*apexFileRes)->GetManifest());
if (!manifestRes.Ok()) {
return StatusOr<ApexFileAndManifest>::MakeError(manifestRes.ErrorMessage());
}
return StatusOr<ApexFileAndManifest>(std::move(*apexFileRes),
std::move(*manifestRes));
}
Status activateNonFlattened(const ApexFile& apex, const ApexManifest& manifest,
const std::string& mountPoint,
MountedApexData* apex_data) {
const std::string& full_path = apex.GetPath();
const std::string& packageId = manifest.GetPackageId();
LoopbackDeviceUniqueFd loopbackDevice;
for (size_t attempts = 1;; ++attempts) {
StatusOr<LoopbackDeviceUniqueFd> ret =
createLoopDevice(full_path, apex.GetImageOffset(), apex.GetImageSize());
if (ret.Ok()) {
loopbackDevice = std::move(*ret);
break;
}
if (attempts >= kLoopDeviceSetupAttempts) {
return Status::Fail(StringLog()
<< "Could not create loop device for " << full_path
<< ": " << ret.ErrorMessage());
}
}
LOG(VERBOSE) << "Loopback device created: " << loopbackDevice.name;
auto verityData = verifyApexVerity(apex);
if (!verityData.Ok()) {
return Status(StringLog()
<< "Failed to verify Apex Verity data for " << full_path
<< ": " << verityData.ErrorMessage());
}
std::string blockDevice = loopbackDevice.name;
apex_data->loop_name = loopbackDevice.name;
// for APEXes in system partition, we don't need to mount them on dm-verity
// because they are already in the dm-verity protected partition; system.
// However, note that we don't skip verification to ensure that APEXes are
// correctly signed.
const bool mountOnVerity =
!android::base::StartsWith(full_path, kApexPackageSystemDir);
DmVerityDevice verityDev;
if (mountOnVerity) {
auto verityTable = createVerityTable(**verityData, loopbackDevice.name);
StatusOr<DmVerityDevice> verityDevRes =
createVerityDevice(packageId, *verityTable);
if (!verityDevRes.Ok()) {
return Status(StringLog()
<< "Failed to create Apex Verity device " << full_path
<< ": " << verityDevRes.ErrorMessage());
}
verityDev = std::move(*verityDevRes);
blockDevice = verityDev.GetDevPath();
Status readAheadStatus = configureReadAhead(verityDev.GetDevPath());
if (!readAheadStatus.Ok()) {
return readAheadStatus.ErrorMessage();
}
}
if (mount(blockDevice.c_str(), mountPoint.c_str(), "ext4",
MS_NOATIME | MS_NODEV | MS_DIRSYNC | MS_RDONLY, NULL) == 0) {
LOG(INFO) << "Successfully mounted package " << full_path << " on "
<< mountPoint;
// Time to accept the temporaries as good.
if (mountOnVerity) {
verityDev.Release();
}
loopbackDevice.CloseGood();
return Status::Success();
}
return Status::Fail(PStringLog()
<< "Mounting failed for package " << full_path);
}
Status activateFlattened(const ApexFile& apex,
const ApexManifest& manifest ATTRIBUTE_UNUSED,
const std::string& mountPoint,
MountedApexData* apex_data) {
if (!android::base::StartsWith(apex.GetPath(), kApexPackageSystemDir)) {
return Status::Fail(StringLog()
<< "Cannot activate flattened APEX " << apex.GetPath());
}
if (mount(apex.GetPath().c_str(), mountPoint.c_str(), nullptr, MS_BIND,
nullptr) == 0) {
LOG(INFO) << "Successfully bind-mounted flattened package "
<< apex.GetPath() << " on " << mountPoint;
apex_data->loop_name = ""; // No loop device.
return Status::Success();
}
return Status::Fail(PStringLog() << "Mounting failed for flattened package "
<< apex.GetPath());
}
Status deactivatePackageImpl(const ApexFile& apex,
const ApexManifest& manifest) {
// TODO: It's not clear what the right thing to do is for umount failures.
// Unmount "latest" bind-mount.
// TODO: What if bind-mount isn't latest?
{
std::string mount_point = GetActiveMountPoint(manifest);
LOG(VERBOSE) << "Unmounting and deleting " << mount_point;
if (umount2(mount_point.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) != 0) {
return Status::Fail(PStringLog() << "Failed to unmount " << mount_point);
}
if (rmdir(mount_point.c_str()) != 0) {
PLOG(ERROR) << "Could not rmdir " << mount_point;
// Continue here.
}
}
std::string mount_point = GetPackageMountPoint(manifest);
LOG(VERBOSE) << "Unmounting and deleting " << mount_point;
if (umount2(mount_point.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) != 0) {
return Status::Fail(PStringLog() << "Failed to unmount " << mount_point);
}
std::string error_msg;
if (rmdir(mount_point.c_str()) != 0) {
// If we cannot delete the directory, we're in a bad state (e.g., getting
// active packages depends on directory existence right now).
// TODO: consider additional delayed cleanups, and rewrite once we have
// a package database.
error_msg = PStringLog() << "Failed to rmdir " << mount_point;
}
// TODO: Find the loop device connected with the mount. For now, just run the
// destroy-all and rely on EBUSY.
if (!apex.IsFlattened()) {
destroyAllLoopDevices();
}
if (error_msg.empty()) {
return Status::Success();
} else {
return Status::Fail(error_msg);
}
}
} // namespace
Status activatePackage(const std::string& full_path) {
LOG(INFO) << "Trying to activate " << full_path;
StatusOr<ApexFileAndManifest> apexFileAndManifest =
openFileAndManifest(full_path);
if (!apexFileAndManifest.Ok()) {
return apexFileAndManifest.ErrorStatus();
}
const std::unique_ptr<ApexFile>& apex = apexFileAndManifest->first;
const std::unique_ptr<ApexManifest>& manifest = apexFileAndManifest->second;
// See whether we think it's active, and do not allow to activate the same
// version. Also detect whether this is the highest version.
// We roll this into a single check.
bool is_newest_version = true;
bool found_other_version = false;
{
uint64_t new_version = manifest->GetVersion();
bool version_found = false;
gMountedApexes.ForallMountedApexes(
manifest->GetName(),
[&](const MountedApexData& data, bool latest ATTRIBUTE_UNUSED) {
StatusOr<ApexFileAndManifest> otherFileAndManifest =
openFileAndManifest(data.full_path);
if (!otherFileAndManifest.Ok()) {
return;
}
found_other_version = true;
if (otherFileAndManifest->second->GetVersion() == new_version) {
version_found = true;
}
if (otherFileAndManifest->second->GetVersion() > new_version) {
is_newest_version = false;
}
});
if (version_found) {
return Status::Fail("Package is already active.");
}
}
std::string mountPoint = GetPackageMountPoint(*manifest);
LOG(VERBOSE) << "Creating mount point: " << mountPoint;
if (mkdir(mountPoint.c_str(), kMkdirMode) != 0) {
return Status::Fail(PStringLog()
<< "Could not create mount point " << mountPoint);
}
MountedApexData apex_data("", full_path);
Status st =
apex->IsFlattened()
? activateFlattened(*apex, *manifest, mountPoint, &apex_data)
: activateNonFlattened(*apex, *manifest, mountPoint, &apex_data);
if (st.Ok()) {
bool mounted_latest = false;
if (is_newest_version) {
Status update_st =
updateLatest(GetActiveMountPoint(*manifest), mountPoint);
mounted_latest = update_st.Ok();
if (!update_st.Ok()) {
// TODO: Fail?
LOG(ERROR) << st.ErrorMessage();
}
}
if (found_other_version && mounted_latest) {
gMountedApexes.UnsetLatestForall(manifest->GetName());
}
gMountedApexes.AddMountedApex(manifest->GetName(), mounted_latest,
std::move(apex_data));
} else {
rmdir(mountPoint.c_str());
}
return st;
}
Status deactivatePackage(const std::string& full_path) {
LOG(INFO) << "Trying to deactivate " << full_path;
StatusOr<ApexFileAndManifest> apexFileAndManifest =
openFileAndManifest(full_path);
if (!apexFileAndManifest.Ok()) {
return apexFileAndManifest.ErrorStatus();
}
const std::unique_ptr<ApexFile>& apex = apexFileAndManifest->first;
const std::unique_ptr<ApexManifest>& manifest = apexFileAndManifest->second;
Status st = deactivatePackageImpl(*apex, *manifest);
if (st.Ok()) {
gMountedApexes.RemoveMountedApex(manifest->GetName(), full_path);
}
return st;
}
std::vector<std::pair<std::string, uint64_t>> getActivePackages() {
std::vector<std::pair<std::string, uint64_t>> ret;
gMountedApexes.ForallMountedApexes([&](const std::string& package,
const MountedApexData& data,
bool latest) {
if (!latest) {
return;
}
StatusOr<ApexFileAndManifest> apexFileAndManifest =
openFileAndManifest(data.full_path);
if (!apexFileAndManifest.Ok()) {
// TODO: Fail?
return;
}
const std::unique_ptr<ApexManifest>& manifest = apexFileAndManifest->second;
ret.emplace_back(package, manifest->GetVersion());
});
return ret;
}
void unmountAndDetachExistingImages() {
// TODO: this procedure should probably not be needed anymore when apexd
// becomes an actual daemon. Remove if that's the case.
LOG(INFO) << "Scanning " << kApexRoot
<< " looking for packages already mounted.";
StatusOr<std::vector<std::string>> folders_status = getApexRootSubFolders();
if (!folders_status.Ok()) {
LOG(ERROR) << folders_status.ErrorMessage();
return;
}
// Sort the folders. This way, the "latest" folder will appear before any
// versioned folder, so we'll unmount the bind-mount first.
std::vector<std::string>& folders = *folders_status;
std::sort(folders.begin(), folders.end());
for (const std::string& folder : folders) {
std::string full_path = std::string(kApexRoot).append("/").append(folder);
LOG(INFO) << "Unmounting " << full_path;
// Lazily try to umount whatever is mounted.
if (umount2(full_path.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) != 0 &&
errno != EINVAL && errno != ENOENT) {
PLOG(ERROR) << "Failed to unmount directory " << full_path;
}
// Attempt to delete the folder. If the folder is retained, other
// data may be incorrect.
// TODO: Fix this.
if (rmdir(full_path.c_str()) != 0) {
PLOG(ERROR) << "Failed to rmdir directory " << full_path;
}
}
destroyAllLoopDevices();
}
void scanPackagesDirAndActivate(const char* apex_package_dir) {
LOG(INFO) << "Scanning " << apex_package_dir << " looking for APEX packages.";
auto d =
std::unique_ptr<DIR, int (*)(DIR*)>(opendir(apex_package_dir), closedir);
if (!d) {
PLOG(WARNING) << "Package directory " << apex_package_dir
<< " not found, nothing to do.";
return;
}
const bool scanSystemApexes =
android::base::StartsWith(apex_package_dir, kApexPackageSystemDir);
struct dirent* dp;
while ((dp = readdir(d.get())) != NULL) {
const std::string name(dp->d_name);
if (name == "." || name == "..") {
continue;
}
const bool isApexFile =
dp->d_type == DT_REG && EndsWith(name, kApexPackageSuffix);
if (isApexFile || (dp->d_type == DT_DIR && scanSystemApexes)) {
LOG(INFO) << "Found " << name;
Status res = activatePackage(std::string(apex_package_dir) + "/" + name);
if (!res.Ok()) {
LOG(ERROR) << res.ErrorMessage();
}
}
}
}
Status stagePackage(const std::string& packageTmpPath) {
LOG(DEBUG) << "stagePackage() for " << packageTmpPath;
StatusOr<ApexFileAndManifest> apexFileAndManifest =
openFileAndManifest(packageTmpPath);
if (!apexFileAndManifest.Ok()) {
return apexFileAndManifest.ErrorStatus();
}
const std::unique_ptr<ApexManifest>& manifest = apexFileAndManifest->second;
std::string packageId =
manifest->GetName() + "@" + std::to_string(manifest->GetVersion());
std::string destPath = StringPrintf("%s/%s%s", kApexPackageDataDir,
packageId.c_str(), kApexPackageSuffix);
if (rename(packageTmpPath.c_str(), destPath.c_str()) != 0) {
// TODO: Get correct binder error status.
return Status::Fail(PStringLog() << "Unable to rename " << packageTmpPath
<< " to " << destPath);
}
LOG(DEBUG) << "Success renaming " << packageTmpPath << " to " << destPath;
return Status::Success();
}
void onStart() {
if (!android::base::SetProperty(kApexStatusSysprop, kApexStatusStarting)) {
PLOG(ERROR) << "Failed to set " << kApexStatusSysprop << " to "
<< kApexStatusStarting;
}
}
void onAllPackagesReady() {
// Set a system property to let other components to know that APEXs are
// correctly mounted and ready to be used. Before using any file from APEXs,
// they can query this system property to ensure that they are okay to
// access. Or they may have a on-property trigger to delay a task until
// APEXs become ready.
if (!android::base::SetProperty(kApexStatusSysprop, kApexStatusReady)) {
PLOG(ERROR) << "Failed to set " << kApexStatusSysprop << " to "
<< kApexStatusReady;
}
}
} // namespace apex
} // namespace android