C++程序  |  308行  |  8.04 KB

// SPDX-License-Identifier: GPL-2.0+
/*
 * Elide and patch handling for 'fsverity setup'
 *
 * Copyright (C) 2018 Google LLC
 *
 * Written by Eric Biggers.
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "fsverity_uapi.h"
#include "fsveritysetup.h"

/* An elision or a patch */
struct fsverity_elide_patch {
	u64 offset;	/* byte offset within the original data */
	u64 length;	/* length in bytes */
	bool patch;	/* false if elision, true if patch */
	u8 data[];	/* replacement data (if patch=true) */
};

/* Maximum supported patch size, in bytes */
#define FS_VERITY_MAX_PATCH_SIZE	255

/* Parse an --elide=OFFSET,LENGTH option */
static struct fsverity_elide_patch *parse_elide_option(const char *optarg)
{
	struct fsverity_elide_patch *ext = NULL;
	char *sep, *end;
	unsigned long long offset;
	unsigned long long length;

	sep = strchr(optarg, ',');
	if (!sep || sep == optarg)
		goto invalid;
	errno = 0;
	*sep = '\0';
	offset = strtoull(optarg, &end, 10);
	*sep = ',';
	if (errno || end != sep)
		goto invalid;
	length = strtoull(sep + 1, &end, 10);
	if (errno || *end)
		goto invalid;
	if (length <= 0 || length > UINT64_MAX - offset) {
		error_msg("Invalid length in '--elide=%s'", optarg);
		return NULL;
	}
	ext = xzalloc(sizeof(*ext));
	ext->offset = offset;
	ext->length = length;
	ext->patch = false;
	return ext;

invalid:
	error_msg("Invalid --elide option: '%s'.  Must be formatted as OFFSET,LENGTH",
		  optarg);
	return NULL;
}

/* Parse a --patch=OFFSET,PATCHFILE option */
static struct fsverity_elide_patch *parse_patch_option(const char *optarg)
{
	struct fsverity_elide_patch *ext = NULL;
	struct filedes patchfile = { .fd = -1 };
	char *sep, *end;
	unsigned long long offset;
	u64 length;

	sep = strchr(optarg, ',');
	if (!sep || sep == optarg)
		goto invalid;
	errno = 0;
	*sep = '\0';
	offset = strtoull(optarg, &end, 10);
	*sep = ',';
	if (errno || end != sep)
		goto invalid;
	if (!open_file(&patchfile, sep + 1, O_RDONLY, 0))
		goto out;
	if (!get_file_size(&patchfile, &length))
		goto out;
	if (length <= 0) {
		error_msg("patch file '%s' is empty", patchfile.name);
		goto out;
	}
	if (length > FS_VERITY_MAX_PATCH_SIZE) {
		error_msg("Patch file '%s' is too long.  Max patch size is %d bytes.",
			  patchfile.name, FS_VERITY_MAX_PATCH_SIZE);
		goto out;
	}
	ext = xzalloc(sizeof(*ext) + length);
	ext->offset = offset;
	ext->length = length;
	ext->patch = true;
	if (!full_read(&patchfile, ext->data, length)) {
		free(ext);
		ext = NULL;
	}
out:
	filedes_close(&patchfile);
	return ext;

invalid:
	error_msg("Invalid --patch option: '%s'.  Must be formatted as OFFSET,PATCHFILE",
		  optarg);
	goto out;
}

/* Sort by increasing offset */
static int cmp_elide_patch_exts(const void *_p1, const void *_p2)
{
	const struct fsverity_elide_patch *ext1, *ext2;

	ext1 = *(const struct fsverity_elide_patch **)_p1;
	ext2 = *(const struct fsverity_elide_patch **)_p2;

	if (ext1->offset > ext2->offset)
		return 1;
	if (ext1->offset < ext2->offset)
		return -1;
	return 0;
}

/*
 * Given the lists of --elide and --patch options, validate and load the
 * elisions and patches into @params.
 */
bool load_elisions_and_patches(const struct string_list *elide_opts,
			       const struct string_list *patch_opts,
			       struct fsveritysetup_params *params)
{
	const size_t num_exts = elide_opts->length + patch_opts->length;
	struct fsverity_elide_patch **exts;
	size_t i, j;

	if (num_exts == 0)	/* Normal case: no elisions or patches */
		return true;
	params->num_elisions_and_patches = num_exts;
	exts = xzalloc(num_exts * sizeof(exts[0]));
	params->elisions_and_patches = exts;
	j = 0;

	/* Parse the --elide options */
	for (i = 0; i < elide_opts->length; i++) {
		exts[j] = parse_elide_option(elide_opts->strings[i]);
		if (!exts[j++])
			return false;
	}

	/* Parse the --patch options */
	for (i = 0; i < patch_opts->length; i++) {
		exts[j] = parse_patch_option(patch_opts->strings[i]);
		if (!exts[j++])
			return false;
	}

	/* Sort the elisions and patches by increasing offset */
	qsort(exts, num_exts, sizeof(exts[0]), cmp_elide_patch_exts);

	/* Verify that no elisions or patches overlap */
	for (j = 1; j < num_exts; j++) {
		if (exts[j]->offset <
		    exts[j - 1]->offset + exts[j - 1]->length) {
			error_msg("%s at [%"PRIu64", %"PRIu64") overlaps "
				  "%s at [%"PRIu64", %"PRIu64")",
				  exts[j - 1]->patch ? "Patch" : "Elision",
				  exts[j - 1]->offset,
				  exts[j - 1]->offset + exts[j - 1]->length,
				  exts[j]->patch ? "patch" : "elision",
				  exts[j]->offset,
				  exts[j]->offset + exts[j]->length);
			return false;
		}
	}
	return true;
}

void free_elisions_and_patches(struct fsveritysetup_params *params)
{
	size_t i;

	for (i = 0; i < params->num_elisions_and_patches; i++)
		free(params->elisions_and_patches[i]);
	free(params->elisions_and_patches);
}

/*
 * Given the original file @in of length @in_length bytes, create a temporary
 * file @out_ret and write to it the data with the elisions and patches applied,
 * with the end zero-padded to the next block boundary.  Returns in
 * @out_length_ret the length of the elided/patched file in bytes.
 */
bool apply_elisions_and_patches(const struct fsveritysetup_params *params,
				struct filedes *in, u64 in_length,
				struct filedes *out_ret, u64 *out_length_ret)
{
	struct fsverity_elide_patch **exts = params->elisions_and_patches;
	struct filedes *out = out_ret;
	size_t i;

	for (i = 0; i < params->num_elisions_and_patches; i++) {
		if (exts[i]->offset + exts[i]->length > in_length) {
			error_msg("%s at [%"PRIu64", %"PRIu64") extends beyond end of input file",
				  exts[i]->patch ? "Patch" : "Elision",
				  exts[i]->offset,
				  exts[i]->offset + exts[i]->length);
			return false;
		}
	}

	if (!filedes_seek(in, 0, SEEK_SET))
		return false;

	if (!open_tempfile(out))
		return false;

	for (i = 0; i < params->num_elisions_and_patches; i++) {
		printf("Applying %s: offset=%"PRIu64", length=%"PRIu64"\n",
		       exts[i]->patch ? "patch" : "elision",
		       exts[i]->offset, exts[i]->length);

		if (!copy_file_data(in, out, exts[i]->offset - in->pos))
			return false;

		if (exts[i]->patch &&
		    !full_write(out, exts[i]->data, exts[i]->length))
			return false;

		if (!filedes_seek(in, exts[i]->length, SEEK_CUR))
			return false;
	}
	if (!copy_file_data(in, out, in_length - in->pos))
		return false;
	if (!write_zeroes(out, ALIGN(out->pos, params->blocksize) - out->pos))
		return false;
	*out_length_ret = out->pos;
	return true;
}

/* Calculate the size the elisions and patches will take up when serialized */
size_t total_elide_patch_ext_length(const struct fsveritysetup_params *params)
{
	size_t total = 0;
	size_t i;

	for (i = 0; i < params->num_elisions_and_patches; i++) {
		const struct fsverity_elide_patch *ext =
			params->elisions_and_patches[i];
		size_t inner_len;

		if (ext->patch) {
			inner_len = sizeof(struct fsverity_extension_patch) +
				    ext->length;
		} else {
			inner_len = sizeof(struct fsverity_extension_elide);
		}
		total += FSVERITY_EXTLEN(inner_len);
	}
	return total;
}

/*
 * Append the elide and patch extensions (if any) to the given buffer.
 * The buffer must have enough space; call total_elide_patch_ext_length() first.
 */
void append_elide_patch_exts(void **buf_p,
			     const struct fsveritysetup_params *params)
{
	void *buf = *buf_p;
	size_t i;
	union {
		struct {
			struct fsverity_extension_patch hdr;
			u8 data[FS_VERITY_MAX_PATCH_SIZE];
		} patch;
		struct fsverity_extension_elide elide;
	} u;

	for (i = 0; i < params->num_elisions_and_patches; i++) {
		const struct fsverity_elide_patch *ext =
			params->elisions_and_patches[i];
		int type;
		size_t extlen;

		if (ext->patch) {
			type = FS_VERITY_EXT_PATCH;
			u.patch.hdr.offset = cpu_to_le64(ext->offset);
			ASSERT(ext->length <= sizeof(u.patch.data));
			memcpy(u.patch.data, ext->data, ext->length);
			extlen = sizeof(u.patch.hdr) + ext->length;
		} else {
			type = FS_VERITY_EXT_ELIDE;
			u.elide.offset = cpu_to_le64(ext->offset),
			u.elide.length = cpu_to_le64(ext->length);
			extlen = sizeof(u.elide);
		}
		fsverity_append_extension(&buf, type, &u, extlen);
	}

	*buf_p = buf;
}