/*! * \brief Write checksums properties to \a /data/multiboot/checksums.prop * * \param props Properties map * * \return True if successfully written. Otherwise, false. */ bool checksums_write(const std::unordered_map<std::string, std::string> &props) { std::string checksums_path = get_raw_path(CHECKSUMS_PATH); if (remove(checksums_path.c_str()) < 0 && errno != ENOENT) { LOGW("%s: Failed to remove file: %s", checksums_path.c_str(), strerror(errno)); } util::mkdir_parent(checksums_path, 0755); util::create_empty_file(checksums_path); if (!util::chown(checksums_path, 0, 0, 0)) { LOGW("%s: Failed to chown file: %s", checksums_path.c_str(), strerror(errno)); } if (chmod(checksums_path.c_str(), 0700) < 0) { LOGW("%s: Failed to chmod file: %s", checksums_path.c_str(), strerror(errno)); } if (!util::file_write_properties(checksums_path, props)) { LOGW("%s: Failed to write new properties: %s", checksums_path.c_str(), strerror(errno)); return false; } return true; }
/*! * \brief Check a checksum property * * \param props Pointer to properties map * \param rom_id ROM ID * \param image Image filename (without directory) * \param sha512_out SHA512 hex digest output * * \return ChecksumsGetResult::FOUND if the hash was successfully retrieved, * ChecksumsGetResult::NOT_FOUND if the hash does not exist in the map, * ChecksumsGetResult::MALFORMED if the property has an invalid format */ ChecksumsGetResult checksums_get(std::unordered_map<std::string, std::string> *props, const std::string &rom_id, const std::string &image, std::string *sha512_out) { std::string checksums_path = get_raw_path(CHECKSUMS_PATH); std::string key(rom_id); key += "/"; key += image; if (props->find(key) == props->end()) { return ChecksumsGetResult::NOT_FOUND; } const std::string &value = (*props)[key]; std::size_t pos = value.find(":"); if (pos != std::string::npos) { std::string algo = value.substr(0, pos); std::string hash = value.substr(pos + 1); if (algo != "sha512") { LOGE("%s: Invalid hash algorithm: %s", checksums_path.c_str(), algo.c_str()); return ChecksumsGetResult::MALFORMED; } *sha512_out = hash; return ChecksumsGetResult::FOUND; } else { LOGE("%s: Invalid checksum property: %s=%s", checksums_path.c_str(), key.c_str(), value.c_str()); return ChecksumsGetResult::MALFORMED; } }
void Roms::add_data_roms() { std::string system = get_raw_path("/data/multiboot"); DIR *dp = opendir(system.c_str()); if (!dp ) { return; } auto close_dp = util::finally([&]{ closedir(dp); }); struct stat sb; struct dirent *ent; while ((ent = readdir(dp))) { if (strcmp(ent->d_name, "data-slot-") == 0 || !util::starts_with(ent->d_name, "data-slot-")) { continue; } std::string fullpath(system); fullpath += "/"; fullpath += ent->d_name; if (stat(fullpath.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) { roms.push_back(create_rom_data_slot(ent->d_name + 10)); } } }
/*! * \brief Read checksums properties from \a /data/multiboot/checksums.prop * * \param props Pointer to properties map * * \return True if the file was successfully read. Otherwise, false. */ bool checksums_read(std::unordered_map<std::string, std::string> *props) { std::string checksums_path = get_raw_path(CHECKSUMS_PATH); if (!util::file_get_all_properties(checksums_path, props)) { LOGE("%s: Failed to load properties", checksums_path.c_str()); return false; } return true; }
static bool daemon_init() { if (chdir("/") < 0) { LOGE("Failed to change cwd to /: %s", strerror(errno)); return false; } umask(0); if (!log_to_stdio && !redirect_stdio_to_dev_null()) { return false; } // Set up logging if (log_to_stdio) { // Default; do nothing } else if (log_to_kmsg) { log::log_set_logger(std::make_shared<log::KmsgLogger>(false)); } else { if (!util::mkdir_parent(MULTIBOOT_LOG_DAEMON, 0775) && errno != EEXIST) { LOGE("Failed to create parent directory of %s: %s", MULTIBOOT_LOG_DAEMON, strerror(errno)); return false; } log_fp = autoclose::fopen( get_raw_path(MULTIBOOT_LOG_DAEMON).c_str(), "w"); if (!log_fp) { LOGE("Failed to open log file %s: %s", MULTIBOOT_LOG_DAEMON, strerror(errno)); return false; } fix_multiboot_permissions(); // mbtool logging log::log_set_logger( std::make_shared<log::StdioLogger>(log_fp.get(), true)); } LOGD("Initialized daemon"); return true; }
void Roms::add_data_roms() { std::string system = get_raw_path("/data/multiboot"); DIR *dp = opendir(system.c_str()); if (!dp ) { return; } auto close_dp = util::finally([&]{ closedir(dp); }); struct stat sb; std::vector<std::shared_ptr<Rom>> temp_roms; struct dirent *ent; while ((ent = readdir(dp))) { if (strcmp(ent->d_name, "data-slot-") == 0 || !util::starts_with(ent->d_name, "data-slot-")) { continue; } std::string fullpath(system); fullpath += "/"; fullpath += ent->d_name; if (stat(fullpath.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) { temp_roms.push_back(create_rom_data_slot(ent->d_name + 10)); } } // Sort by ID std::sort(temp_roms.begin(), temp_roms.end(), &cmp_rom_id); std::move(temp_roms.begin(), temp_roms.end(), std::back_inserter(roms)); }
/*! * \brief Set the kernel for a ROM * * \note This will update the checksum for the image in * \a /data/multiboot/checksums.prop. * * \param id ROM ID to set the kernel for * \param boot_blockdev Block device path of the boot partition * * \return True if the kernel was successfully set. Otherwise, false. */ bool set_kernel(const char *id, const char *boot_blockdev) { LOGD("Attempting to set the kernel for %s", id); // Path for all of the images std::string multiboot_path(get_raw_path(MULTIBOOT_DIR)); multiboot_path += "/"; multiboot_path += id; std::string bootimg_path(multiboot_path); bootimg_path += "/boot.img"; // Verify ROM ID Roms roms; roms.add_installed(); auto r = roms.find_by_id(id); if (!r) { LOGE("Invalid ROM ID: %s", id); return false; } if (!util::mkdir_recursive(multiboot_path, 0775)) { LOGE("%s: Failed to create directory: %s", multiboot_path.c_str(), strerror(errno)); return false; } unsigned char *data; std::size_t size; if (!util::file_read_all(boot_blockdev, &data, &size)) { LOGE("%s: Failed to read block device: %s", boot_blockdev, strerror(errno)); return false; } auto free_data = util::finally([&]{ free(data); }); // Get actual sha512sum unsigned char digest[SHA512_DIGEST_LENGTH]; SHA512(data, size, digest); std::string hash = util::hex_string(digest, SHA512_DIGEST_LENGTH); // Add to checksums.prop std::unordered_map<std::string, std::string> props; checksums_read(&props); checksums_update(&props, id, "boot.img", hash); // NOTE: This function isn't responsible for updating the checksums for // any extra images. We don't want to mask any malicious changes. // Cast is okay. The data is just passed to fwrite (ie. no signed // extension issues) if (!util::file_write_data(bootimg_path, (char *) data, size)) { LOGE("%s: Failed to write image: %s", bootimg_path.c_str(), strerror(errno)); return false; } LOGD("Updating checksums file"); checksums_write(props); if (!fix_multiboot_permissions()) { //return false; } return true; }
/*! * \brief Switch to another ROM * * \note If the checksum is missing for some images to be flashed and invalid * for some other images to be flashed, this function will always return * SwitchRomResult::CHECKSUM_INVALID. * * \param id ROM ID to switch to * \param boot_blockdev Block device path of the boot partition * \param blockdev_base_dirs Search paths (non-recursive) for block devices * corresponding to extra flashable images in * /sdcard/MultiBoot/[ROM ID]/ *.img * * \return SwitchRomResult::SUCCEEDED if the switching succeeded, * SwitchRomResult::FAILED if the switching failed, * SwitchRomResult::CHECKSUM_NOT_FOUND if the checksum for some image is missing, * SwitchRomResult::CHECKSUM_INVALID if the checksum for some image is invalid * */ SwitchRomResult switch_rom(const char *id, const char *boot_blockdev, const char * const *blockdev_base_dirs, bool force_update_checksums) { LOGD("Attempting to switch to %s", id); LOGD("Force update checksums: %d", force_update_checksums); // Path for all of the images std::string multiboot_path(get_raw_path(MULTIBOOT_DIR)); multiboot_path += "/"; multiboot_path += id; std::string bootimg_path(multiboot_path); bootimg_path += "/boot.img"; // Verify ROM ID Roms roms; roms.add_installed(); auto r = roms.find_by_id(id); if (!r) { LOGE("Invalid ROM ID: %s", id); return SwitchRomResult::FAILED; } if (!util::mkdir_recursive(multiboot_path, 0775)) { LOGE("%s: Failed to create directory: %s", multiboot_path.c_str(), strerror(errno)); return SwitchRomResult::FAILED; } // We'll read the files we want to flash into memory so a malicious app // can't change the file between the hash verification step and flashing // step. std::vector<Flashable> flashables; auto free_flashables = util::finally([&]{ for (Flashable &f : flashables) { free(f.data); } }); flashables.emplace_back(); flashables.back().image = bootimg_path; flashables.back().block_dev = boot_blockdev; if (!add_extra_images(multiboot_path.c_str(), blockdev_base_dirs, &flashables)) { LOGW("Failed to find extra images"); } std::unordered_map<std::string, std::string> props; checksums_read(&props); for (Flashable &f : flashables) { // If memory becomes an issue, an alternative method is to create a // temporary directory in /data/multiboot/ that's only writable by root // and copy the images there. if (!util::file_read_all(f.image, &f.data, &f.size)) { LOGE("%s: Failed to read image: %s", f.image.c_str(), strerror(errno)); return SwitchRomResult::FAILED; } // Get actual sha512sum unsigned char digest[SHA512_DIGEST_LENGTH]; SHA512(f.data, f.size, digest); f.hash = util::hex_string(digest, SHA512_DIGEST_LENGTH); if (force_update_checksums) { checksums_update(&props, id, util::base_name(f.image), f.hash); } // Get expected sha512sum ChecksumsGetResult ret = checksums_get( &props, id, util::base_name(f.image), &f.expected_hash); if (ret == ChecksumsGetResult::MALFORMED) { return SwitchRomResult::CHECKSUM_INVALID; } // Verify hashes if we have an expected hash if (ret == ChecksumsGetResult::FOUND && f.expected_hash != f.hash) { LOGE("%s: Checksum (%s) does not match expected (%s)", f.image.c_str(), f.hash.c_str(), f.expected_hash.c_str()); return SwitchRomResult::CHECKSUM_INVALID; } } // Fail if we're missing expected hashes. We do this last to make sure // CHECKSUM_INVALID is returned if some checksums don't match (for the ones // that aren't missing). for (Flashable &f : flashables) { if (f.expected_hash.empty()) { LOGE("%s: Checksum does not exist", f.image.c_str()); return SwitchRomResult::CHECKSUM_NOT_FOUND; } } // Now we can flash the images for (Flashable &f : flashables) { // Cast is okay. The data is just passed to fwrite (ie. no signed // extension issues) if (!util::file_write_data(f.block_dev, (char *) f.data, f.size)) { LOGE("%s: Failed to write image: %s", f.block_dev.c_str(), strerror(errno)); return SwitchRomResult::FAILED; } } if (force_update_checksums) { LOGD("Updating checksums file"); checksums_write(props); } if (!fix_multiboot_permissions()) { //return SwitchRomResult::FAILED; } return SwitchRomResult::SUCCEEDED; }
std::string Rom::thumbnail_path() { return get_raw_path(util::format( MULTIBOOT_DIR "/%s/thumbnail.webp", id.c_str())); }
std::string Rom::config_path() { return get_raw_path(util::format( MULTIBOOT_DIR "/%s/config.json", id.c_str())); }
std::string Rom::boot_image_path() { return get_raw_path(util::format( MULTIBOOT_DIR "/%s/boot.img", id.c_str())); }