static bool emergency_reboot() { util::vibrate(100, 150); util::vibrate(100, 150); util::vibrate(100, 150); util::vibrate(100, 150); util::vibrate(100, 150); LOGW("--- EMERGENCY REBOOT FROM MBTOOL ---"); // Some devices don't have /proc/last_kmsg, so we'll attempt to save the // kernel log to /data/media/0/MultiBoot/kernel.log if (!util::is_mounted("/data")) { LOGW("/data is not mounted. Attempting to mount /data"); struct stat sb; // Try mounting /data in case we couldn't get through the fstab mounting // steps. (This is an ugly brute force method...) for (const char **ptr = data_block_devs; *ptr; ++ptr) { const char *block_dev = *ptr; if (stat(block_dev, &sb) < 0) { continue; } if (mount(block_dev, "/data", "ext4", 0, "") == 0 || mount(block_dev, "/data", "f2fs", 0, "") == 0) { LOGW("Mounted %s at /data", block_dev); break; } } } LOGW("Dumping kernel log to %s", MULTIBOOT_LOG_KERNEL); // Remove old log remove(MULTIBOOT_LOG_KERNEL); // Write new log util::mkdir_parent(MULTIBOOT_LOG_KERNEL, 0775); dump_kernel_log(MULTIBOOT_LOG_KERNEL); // Set file attributes on log file fix_multiboot_permissions(); sync(); umount("/data"); // Does not return if successful reboot_directly("recovery"); return false; }
void RecoveryInstaller::on_cleanup(Installer::ProceedState ret) { if (ret == ProceedState::Fail) { if (!util::copy_file("/tmp/recovery.log", MULTIBOOT_LOG_INSTALLER, 0)) { LOGE("Failed to copy log file: %s", strerror(errno)); } fix_multiboot_permissions(); display_msg("The log file was saved as MultiBoot.log on the " "internal storage."); } }
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; }
/*! * \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; }
int rom_installer_main(int argc, char *argv[]) { if (unshare(CLONE_NEWNS) < 0) { fprintf(stderr, "unshare() failed: %s\n", strerror(errno)); return EXIT_FAILURE; } if (mount("", "/", "", MS_PRIVATE | MS_REC, "") < 0) { fprintf(stderr, "Failed to set private mount propagation: %s\n", strerror(errno)); return false; } // Make stdout unbuffered setvbuf(stdout, nullptr, _IONBF, 0); std::string rom_id; std::string zip_file; int opt; static struct option long_options[] = { {"romid", required_argument, 0, 'r'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; int long_index = 0; while ((opt = getopt_long(argc, argv, "r:h", long_options, &long_index)) != -1) { switch (opt) { case 'r': rom_id = optarg; break; case 'h': rom_installer_usage(false); return EXIT_SUCCESS; default: rom_installer_usage(true); return EXIT_FAILURE; } } if (argc - optind != 1) { rom_installer_usage(true); return EXIT_FAILURE; } zip_file = argv[optind]; if (rom_id.empty()) { fprintf(stderr, "-r/--romid must be specified\n"); return EXIT_FAILURE; } if (zip_file.empty()) { fprintf(stderr, "Invalid zip file path\n"); return EXIT_FAILURE; } // Make sure install type is valid if (!Roms::is_valid(rom_id)) { fprintf(stderr, "Invalid ROM ID: %s\n", rom_id.c_str()); return EXIT_FAILURE; } auto rom = Roms::get_current_rom(); if (!rom) { fprintf(stderr, "Could not determine current ROM\n"); return EXIT_FAILURE; } if (rom->id == rom_id) { fprintf(stderr, "Can't install over current ROM (%s)\n", rom_id.c_str()); return EXIT_FAILURE; } if (geteuid() != 0) { fprintf(stderr, "rom-installer must be run as root\n"); return EXIT_FAILURE; } if (mount("", "/", "", MS_REMOUNT, "") < 0) { fprintf(stderr, "Failed to remount / as writable\n"); return EXIT_FAILURE; } // We do not need to patch the SELinux policy or switch to mb_exec because // the daemon will guarantee that we run in that context. We'll just warn if // this happens to not be the case (eg. debugging via command line). std::string context; if (util::selinux_get_process_attr( 0, util::SELinuxAttr::CURRENT, &context) && context != MB_EXEC_CONTEXT) { fprintf(stderr, "WARNING: Not running under %s context\n", MB_EXEC_CONTEXT); } autoclose::file fp(autoclose::fopen(MULTIBOOT_LOG_INSTALLER, "wb")); if (!fp) { fprintf(stderr, "Failed to open %s: %s\n", MULTIBOOT_LOG_INSTALLER, strerror(errno)); return EXIT_FAILURE; } fix_multiboot_permissions(); // Close stdin #if !DEBUG_LEAVE_STDIN_OPEN int fd = open("/dev/null", O_RDONLY); if (fd >= 0) { dup2(fd, STDIN_FILENO); close(fd); } #endif // mbtool logging log::log_set_logger(std::make_shared<log::StdioLogger>(fp.get(), false)); // Start installing! RomInstaller ri(zip_file, rom_id, fp.get()); return ri.start_installation() ? EXIT_SUCCESS : EXIT_FAILURE; }
int rom_installer_main(int argc, char *argv[]) { // Make stdout unbuffered setvbuf(stdout, nullptr, _IONBF, 0); std::string rom_id; std::string zip_file; int opt; static struct option long_options[] = { {"romid", required_argument, 0, 'r'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; int long_index = 0; while ((opt = getopt_long(argc, argv, "r:h", long_options, &long_index)) != -1) { switch (opt) { case 'r': rom_id = optarg; break; case 'h': rom_installer_usage(false); return EXIT_SUCCESS; default: rom_installer_usage(true); return EXIT_FAILURE; } } if (argc - optind != 1) { rom_installer_usage(true); return EXIT_FAILURE; } zip_file = argv[optind]; if (rom_id.empty()) { fprintf(stderr, "-r/--romid must be specified\n"); return EXIT_FAILURE; } if (zip_file.empty()) { fprintf(stderr, "Invalid zip file path\n"); return EXIT_FAILURE; } // Translate paths char *emu_source_path = getenv("EMULATED_STORAGE_SOURCE"); char *emu_target_path = getenv("EMULATED_STORAGE_TARGET"); if (emu_source_path && emu_target_path) { if (util::starts_with(zip_file, emu_target_path)) { printf("Zip path uses EMULATED_STORAGE_TARGET\n"); zip_file.erase(0, strlen(emu_target_path)); zip_file.insert(0, emu_source_path); } } // Make sure install type is valid if (!Roms::is_valid(rom_id)) { fprintf(stderr, "Invalid ROM ID: %s\n", rom_id.c_str()); return EXIT_FAILURE; } auto rom = Roms::get_current_rom(); if (!rom) { fprintf(stderr, "Could not determine current ROM\n"); return EXIT_FAILURE; } if (rom->id == rom_id) { fprintf(stderr, "Can't install over current ROM (%s)\n", rom_id.c_str()); return EXIT_FAILURE; } if (geteuid() != 0) { fprintf(stderr, "rom-installer must be run as root\n"); return EXIT_FAILURE; } // Since many stock ROMs, most notably TouchWiz, don't allow setting SELinux // to be globally permissive, we'll do the next best thing: modify the // policy to make every type permissive. bool selinux_supported = true; bool backup_successful = true; struct stat sb; if (stat("/sys/fs/selinux", &sb) < 0) { printf("SELinux not supported. No need to modify policy\n"); selinux_supported = false; } if (selinux_supported) { backup_successful = backup_sepolicy(sepolicy_bak_path); printf("Patching SELinux policy to make all types permissive\n"); if (!patch_sepolicy()) { fprintf(stderr, "Failed to patch current SELinux policy\n"); } } auto restore_selinux = util::finally([&] { if (selinux_supported && backup_successful) { printf("Restoring backup SELinux policy\n"); if (!restore_sepolicy(sepolicy_bak_path)) { fprintf(stderr, "Failed to restore SELinux policy\n"); } } }); autoclose::file fp(autoclose::fopen(MULTIBOOT_LOG_INSTALLER, "wb")); if (!fp) { fprintf(stderr, "Failed to open %s: %s\n", MULTIBOOT_LOG_INSTALLER, strerror(errno)); return EXIT_FAILURE; } fix_multiboot_permissions(); // mbtool logging util::log_set_logger(std::make_shared<util::StdioLogger>(fp.get(), false)); // libmbp logging mbp::setLogCallback(mbp_log_cb); // Start installing! RomInstaller ri(zip_file, rom_id, fp.get()); return ri.start_installation() ? EXIT_SUCCESS : EXIT_FAILURE; }
int daemon_main(int argc, char *argv[]) { int opt; bool fork_flag = false; bool replace_flag = false; static struct option long_options[] = { {"daemonize", no_argument, 0, 'd'}, {"replace", no_argument, 0, 'r'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; int long_index = 0; while ((opt = getopt_long(argc, argv, "drh", long_options, &long_index)) != -1) { switch (opt) { case 'd': fork_flag = true; break; case 'r': replace_flag = true; break; case 'h': daemon_usage(0); return EXIT_SUCCESS; default: daemon_usage(1); return EXIT_FAILURE; } } // There should be no other arguments if (argc - optind != 0) { daemon_usage(1); return EXIT_FAILURE; } // Patch SELinux policy to make init permissive patch_loaded_sepolicy(); // Allow untrusted_app to connect to our daemon patch_sepolicy_daemon(); // Set version property if we're the system mbtool (i.e. launched by init) // Possible to override this with another program by double forking, letting // 2nd child reparent to init, and then calling execve("/mbtool", ...), but // meh ... if (getppid() == 1) { if (!util::set_property("ro.multiboot.version", get_mbtool_version())) { std::printf("Failed to set 'ro.multiboot.version' to '%s'\n", get_mbtool_version()); } } if (replace_flag) { PROCTAB *proc = openproc(PROC_FILLCOM | PROC_FILLSTAT); if (proc) { pid_t curpid = getpid(); while (proc_t *info = readproc(proc, nullptr)) { if (strcmp(info->cmd, "mbtool") == 0 // This is mbtool && info->cmdline // And we can see the command line && info->cmdline[1] // And argc > 1 && strstr(info->cmdline[1], "daemon") // And it's a daemon process && info->tid != curpid) { // And we're not killing ourself // Kill the daemon process std::printf("Killing PID %d\n", info->tid); kill(info->tid, SIGTERM); } freeproc(info); } closeproc(proc); } // Give processes a chance to exit usleep(500000); } // Set up logging if (!util::mkdir_parent(MULTIBOOT_LOG_DAEMON, 0775) && errno != EEXIST) { fprintf(stderr, "Failed to create parent directory of %s: %s\n", MULTIBOOT_LOG_DAEMON, strerror(errno)); return EXIT_FAILURE; } autoclose::file fp(autoclose::fopen(MULTIBOOT_LOG_DAEMON, "w")); if (!fp) { fprintf(stderr, "Failed to open log file %s: %s\n", MULTIBOOT_LOG_DAEMON, strerror(errno)); return EXIT_FAILURE; } fix_multiboot_permissions(); // mbtool logging log::log_set_logger(std::make_shared<log::StdioLogger>(fp.get(), true)); if (fork_flag) { run_daemon_fork(); } else { return run_daemon() ? EXIT_SUCCESS : EXIT_FAILURE; } }