static retvalue checkpoolfile(const char *fullfilename, const struct checksums *expected, bool *improveable) { struct checksums *actual; retvalue r; bool improves; r = checksums_read(fullfilename, &actual); if (RET_IS_OK(r)) { if (!checksums_check(expected, actual, &improves)) { fprintf(stderr, "WRONG CHECKSUMS of '%s':\n", fullfilename); checksums_printdifferences(stderr, expected, actual); r = RET_ERROR_WRONG_MD5; } else if (improves) *improveable = true; checksums_free(actual); } return r; }
retvalue files_detect(const char *filekey) { struct checksums *checksums; char *fullfilename; retvalue r; fullfilename = files_calcfullfilename(filekey); if (FAILEDTOALLOC(fullfilename)) return RET_ERROR_OOM; r = checksums_read(fullfilename, &checksums); if (r == RET_NOTHING) { fprintf(stderr, "Error opening '%s'!\n", fullfilename); r = RET_ERROR_MISSING; } if (RET_WAS_ERROR(r)) { free(fullfilename); return r; } free(fullfilename); r = files_add_checksums(filekey, checksums); checksums_free(checksums); return r; }
/*! * \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; }
static retvalue checkimproveorinclude(const char *sourcedir, const char *basefilename, const char *filekey, struct checksums **checksums_p, bool *improving) { retvalue r; struct checksums *checksums = NULL; bool improves, copied = false; char *fullfilename = files_calcfullfilename(filekey); if (FAILEDTOALLOC(fullfilename)) return RET_ERROR_OOM; if (checksums_iscomplete(*checksums_p)) { r = checksums_cheaptest(fullfilename, *checksums_p, true); if (r != RET_NOTHING) { free(fullfilename); return r; } } else { r = checksums_read(fullfilename, &checksums); if (RET_WAS_ERROR(r)) { free(fullfilename); return r; } } if (r == RET_NOTHING) { char *sourcefilename = calc_dirconcat(sourcedir, basefilename); if (FAILEDTOALLOC(sourcefilename)) { free(fullfilename); return RET_ERROR_OOM; } fprintf(stderr, "WARNING: file %s was lost!\n" "(i.e. found in the database, but not in the pool)\n" "trying to compensate...\n", filekey); (void)dirs_make_parent(fullfilename); r = checksums_copyfile(fullfilename, sourcefilename, false, &checksums); if (r == RET_ERROR_EXIST) { fprintf(stderr, "File '%s' seems to be missing and existing at the same time!\n" "To confused to continue...\n", fullfilename); } if (r == RET_NOTHING) { fprintf(stderr, "Could not open '%s'!\n", sourcefilename); r = RET_ERROR_MISSING; } free(sourcefilename); if (RET_WAS_ERROR(r)) { free(fullfilename); return r; } copied = true; } assert (checksums != NULL); if (!checksums_check(*checksums_p, checksums, &improves)) { if (copied) { deletefile(fullfilename); fprintf(stderr, "ERROR: Unexpected content of file '%s/%s'!\n", sourcedir, basefilename); } else // TODO: if the database only listed some of the currently supported checksums, // and the caller of checkincludefile supplied some (which none yet does), but // not all (which needs at least three checksums, i.e. not applicaple before // sha256 get added), then this might also be called if the file in the pool // just has the same checksums as previously recorded (e.g. a md5sum collision) // but the new file was listed with another secondary hash than the original. // In that situation it might be a bit misleading... fprintf(stderr, "ERROR: file %s is damaged!\n" "(i.e. found in the database, but with different checksums in the pool)\n", filekey); checksums_printdifferences(stderr, *checksums_p, checksums); r = RET_ERROR_WRONG_MD5; } if (improves) { r = checksums_combine(checksums_p, checksums, NULL); if (RET_IS_OK(r)) *improving = true; } checksums_free(checksums); free(fullfilename); return r; }
/* Include a yet unknown file into the pool */ retvalue files_preinclude(const char *sourcefilename, const char *filekey, struct checksums **checksums_p) { retvalue r; struct checksums *checksums, *realchecksums; bool improves; char *fullfilename; r = files_get_checksums(filekey, &checksums); if (RET_WAS_ERROR(r)) return r; if (RET_IS_OK(r)) { r = checksums_read(sourcefilename, &realchecksums); if (r == RET_NOTHING) r = RET_ERROR_MISSING; if (RET_WAS_ERROR(r)) { checksums_free(checksums); return r; } if (!checksums_check(checksums, realchecksums, &improves)) { fprintf(stderr, "ERROR: '%s' cannot be included as '%s'.\n" "Already existing files can only be included again, if they are the same, but:\n", sourcefilename, filekey); checksums_printdifferences(stderr, checksums, realchecksums); checksums_free(checksums); checksums_free(realchecksums); return RET_ERROR_WRONG_MD5; } if (improves) { r = checksums_combine(&checksums, realchecksums, NULL); if (RET_WAS_ERROR(r)) { checksums_free(realchecksums); checksums_free(checksums); return r; } r = files_replace_checksums(filekey, checksums); if (RET_WAS_ERROR(r)) { checksums_free(realchecksums); checksums_free(checksums); return r; } } checksums_free(realchecksums); // args, this breaks retvalue semantics! if (checksums_p != NULL) *checksums_p = checksums; else checksums_free(checksums); return RET_NOTHING; } assert (sourcefilename != NULL); fullfilename = files_calcfullfilename(filekey); if (FAILEDTOALLOC(fullfilename)) return RET_ERROR_OOM; (void)dirs_make_parent(fullfilename); r = checksums_copyfile(fullfilename, sourcefilename, true, &checksums); if (r == RET_ERROR_EXIST) { // TODO: deal with already existing files! fprintf(stderr, "File '%s' does already exist!\n", fullfilename); } if (r == RET_NOTHING) { fprintf(stderr, "Could not open '%s'!\n", sourcefilename); r = RET_ERROR_MISSING; } if (RET_WAS_ERROR(r)) { free(fullfilename); return r; } free(fullfilename); r = files_add_checksums(filekey, checksums); if (RET_WAS_ERROR(r)) { checksums_free(checksums); return r; } if (checksums_p != NULL) *checksums_p = checksums; else checksums_free(checksums); return RET_OK; }