std::shared_ptr<Rom> Roms::get_current_rom() { Roms roms; roms.add_installed(); // This is set if mbtool is handling the boot process std::string prop_id; util::get_property("ro.multiboot.romid", &prop_id, std::string()); if (!prop_id.empty()) { auto rom = roms.find_by_id(prop_id); if (rom) { return rom; } } // If /raw/ or /raw-system/ does not exist, then this is an unpatched // primary ROM struct stat sb; bool has_raw = stat("/raw", &sb) == 0; bool has_raw_system = stat("/raw-system", &sb) == 0; if (!has_raw && !has_raw_system) { // Cache the result util::set_property("ro.multiboot.romid", "primary"); return roms.find_by_id("primary"); } // Otherwise, iterate through the installed ROMs if (stat("/system/build.prop", &sb) == 0) { for (auto rom : roms.roms) { // We can't check roms that use images since they aren't mounted if (rom->system_is_image) { continue; } std::string path = rom->full_system_path(); if (path.empty()) { continue; } path += "/build.prop"; struct stat sb2; if (stat(path.c_str(), &sb2) == 0 && sb.st_dev == sb2.st_dev && sb.st_ino == sb2.st_ino) { // Cache the result util::set_property("ro.multiboot.romid", rom->id); return rom; } } } return std::shared_ptr<Rom>(); }
/*! * \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; }
bool mount_fstab(const std::string &fstab_path) { bool ret = true; std::vector<util::fstab_rec> fstab; std::vector<util::fstab_rec *> recs_system; std::vector<util::fstab_rec *> recs_cache; std::vector<util::fstab_rec *> recs_data; std::vector<util::fstab_rec *> flags_system; std::vector<util::fstab_rec *> flags_cache; std::vector<util::fstab_rec *> flags_data; std::string target_system; std::string target_cache; std::string target_data; std::string path_fstab_gen; std::string path_completed; std::string path_failed; std::string base_name; std::string dir_name; struct stat st; std::shared_ptr<Rom> rom; std::string rom_id; Roms roms; roms.add_builtin(); base_name = util::base_name(fstab_path); dir_name = util::dir_name(fstab_path); path_fstab_gen += dir_name; path_fstab_gen += "/."; path_fstab_gen += base_name; path_fstab_gen += ".gen"; path_completed += dir_name; path_completed += "/."; path_completed += base_name; path_completed += ".completed"; path_failed += dir_name; path_failed += "/."; path_failed += base_name; path_failed += ".failed"; auto on_finish = util::finally([&] { if (ret) { util::create_empty_file(path_completed); LOGI("Successfully mounted partitions"); } else { util::create_empty_file(path_failed); } }); // This is a oneshot operation if (stat(path_completed.c_str(), &st) == 0) { LOGV("Filesystems already successfully mounted"); return true; } if (stat(path_failed.c_str(), &st) == 0) { LOGE("Failed to mount partitions ealier. No further attempts will be made"); return false; } // Remount rootfs as read-write so a new fstab file can be written if (mount("", "/", "", MS_REMOUNT, "") < 0) { LOGE("Failed to remount rootfs as rw: {}", strerror(errno)); } // Read original fstab fstab = util::read_fstab(fstab_path); if (fstab.empty()) { LOGE("Failed to read {}", fstab_path); return false; } // Generate new fstab without /system, /cache, or /data entries file_ptr out(std::fopen(path_fstab_gen.c_str(), "wb"), std::fclose); if (!out) { LOGE("Failed to open {} for writing: {}", path_fstab_gen, strerror(errno)); return false; } for (util::fstab_rec &rec : fstab) { if (rec.mount_point == "/system") { recs_system.push_back(&rec); } else if (rec.mount_point == "/cache") { recs_cache.push_back(&rec); } else if (rec.mount_point == "/data") { recs_data.push_back(&rec); } else { std::fprintf(out.get(), "%s\n", rec.orig_line.c_str()); } } out.reset(); // /system and /data are always in the fstab. The patcher should create // an entry for /cache for the ROMs that mount it manually in one of the // init scripts if (recs_system.empty() || recs_cache.empty() || recs_data.empty()) { LOGE("fstab does not contain all of /system, /cache, and /data!"); return false; } // Mount raw partitions to /raw/* if (!util::kernel_cmdline_get_option("romid", &rom_id) && !util::file_first_line("/romid", &rom_id)) { LOGE("Failed to determine ROM ID"); return false; } if (Roms::is_named_rom(rom_id)) { rom = Roms::create_named_rom(rom_id); } else { rom = roms.find_by_id(rom_id); if (!rom) { LOGE("Unknown ROM ID: {}", rom_id); return false; } } LOGD("ROM ID is: {}", rom_id); // Set property for the Android app to use if (!util::set_property("ro.multiboot.romid", rom_id)) { LOGE("Failed to set 'ro.multiboot.romid' to '{}'", rom_id); } // Because of how Android deals with partitions, if, say, the source path // for the /system bind mount resides on /cache, then the cache partition // must be mounted with the system partition's flags. In this future, this // may be avoided by mounting every partition with some more liberal flags, // since the current setup does not allow two bind mounted locations to // reside on the same partition. if (util::starts_with(rom->system_path, "/cache")) { flags_system = recs_cache; } else { flags_system = recs_system; } if (util::starts_with(rom->cache_path, "/system")) { flags_cache = recs_system; } else { flags_cache = recs_cache; } flags_data = recs_data; if (mkdir("/raw", 0755) < 0) { LOGE("Failed to create /raw"); return false; } if (!create_dir_and_mount(recs_system, flags_system, "/raw/system")) { LOGE("Failed to mount /raw/system"); return false; } if (!create_dir_and_mount(recs_cache, flags_cache, "/raw/cache")) { LOGE("Failed to mount /raw/cache"); return false; } if (!create_dir_and_mount(recs_data, flags_data, "/raw/data")) { LOGE("Failed to mount /raw/data"); return false; } // Make paths use /raw/... if (rom->system_path.empty() || rom->cache_path.empty() || rom->data_path.empty()) { LOGE("Invalid or empty paths"); return false; } target_system += "/raw"; target_system += rom->system_path; target_cache += "/raw"; target_cache += rom->cache_path; target_data += "/raw"; target_data += rom->data_path; if (!util::bind_mount(target_system, 0771, "/system", 0771)) { return false; } if (!util::bind_mount(target_cache, 0771, "/cache", 0771)) { return false; } if (!util::bind_mount(target_data, 0771, "/data", 0771)) { return false; } // Bind mount internal SD directory if (!util::bind_mount("/raw/data/media", 0771, "/data/media", 0771)) { return false; } // Prevent installd from dying because it can't unmount /data/media for // multi-user migration. Since <= 4.2 devices aren't supported anyway, // we'll bypass this. file_ptr fp(std::fopen("/data/.layout_version", "wb"), std::fclose); if (fp) { const char *layout_version; if (get_api_version() >= 21) { layout_version = "3"; } else { layout_version = "2"; } fwrite(layout_version, 1, strlen(layout_version), fp.get()); fp.reset(); } else { LOGE("Failed to open /data/.layout_version to disable migration"); } static std::string context("u:object_r:install_data_file:s0"); if (lsetxattr("/data/.layout_version", "security.selinux", context.c_str(), context.size() + 1, 0) < 0) { LOGE("{}: Failed to set SELinux context: {}", "/data/.layout_version", strerror(errno)); } // Global app sharing std::string config_path("/data/media/0/MultiBoot/"); config_path += rom->id; config_path += "/config.json"; RomConfig config; if (config.load_file(config_path)) { if (config.indiv_app_sharing && (config.global_app_sharing || config.global_paid_app_sharing)) { LOGW("Both individual and global sharing are enabled"); LOGW("Global sharing settings will be ignored"); } else { if (config.global_app_sharing || config.global_paid_app_sharing) { if (!util::bind_mount("/raw/data/app-lib", 0771, "/data/app-lib", 0771)) { return false; } } if (config.global_app_sharing) { if (!util::bind_mount("/raw/data/app", 0771, "/data/app", 0771)) { return false; } } if (config.global_paid_app_sharing) { if (!util::bind_mount("/raw/data/app-asec", 0771, "/data/app-asec", 0771)) { return false; } } } } return true; }
std::shared_ptr<Rom> Roms::get_current_rom() { Roms roms; roms.add_installed(); // This is set if mbtool is handling the boot process char prop_id[PROP_VALUE_MAX]; util::property_get(PROP_MULTIBOOT_ROM_ID, prop_id, ""); // This is necessary for the daemon to get a correct result before Android // boots (eg. for the boot UI) if (!prop_id[0]) { std::string temp; util::file_get_property(DEFAULT_PROP_PATH, PROP_MULTIBOOT_ROM_ID, &temp, std::string()); strlcpy(prop_id, temp.c_str(), sizeof(prop_id)); } if (prop_id[0]) { auto rom = roms.find_by_id(prop_id); if (rom) { return rom; } } // If /raw/ or /raw-system/ does not exist, then this is an unpatched // primary ROM struct stat sb; bool has_raw = stat("/raw", &sb) == 0; bool has_raw_system = stat("/raw-system", &sb) == 0; if (!has_raw && !has_raw_system) { // Cache the result util::property_set(PROP_MULTIBOOT_ROM_ID, "primary"); return roms.find_by_id("primary"); } // Otherwise, iterate through the installed ROMs if (stat("/system/build.prop", &sb) == 0) { for (auto rom : roms.roms) { // We can't check roms that use images since they aren't mounted if (rom->system_is_image) { continue; } std::string path = rom->full_system_path(); if (path.empty()) { continue; } path += "/build.prop"; struct stat sb2; if (stat(path.c_str(), &sb2) == 0 && sb.st_dev == sb2.st_dev && sb.st_ino == sb2.st_ino) { // Cache the result util::property_set(PROP_MULTIBOOT_ROM_ID, rom->id.c_str()); return rom; } } } return std::shared_ptr<Rom>(); }