Re: veritysetup forward error correction failure

Tom Eccles <[email protected]>
Newsgroups gmane.linux.kernel.device-mapper.dm-crypt
Message-ID <[email protected]>
On 17/07/2019 8:49 pm, Milan Broz wrote:
> If you can still reproduce it, please send version of the utility and
> kernel (and --debug output as suggested in another mail) and if you have some
> data/hash/fec images that can be used to reproduce it, let me know where I can find it.

Attached is a bash script gen_image.sh which should reproduce the issue.
find_and_corrupt.c should be in the working directory when gen_image.sh is
run. find_and_corrupt.c corrupts the disk image by simply searching for a
known string and introducing an error (see the diff outputted by gen_image.sh).

See in particular the error and dmesg sample when reading the corrupted file
(gen_image.sh:77) and the success when running veritysetup verify
(gen_image.sh:86).

I have created an issue at https://gitlab.com/cryptsetup/cryptsetup/issues/462

Thanks,
Tom

_______________________________________________
dm-crypt mailing list
[email protected]
https://www.saout.de/mailman/listinfo/dm-crypt
find_and_corrupt.c (text/x-csrc, 3.8 KB)
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

// n <-> o is 0x6e <-> 0x6f so it is a single bit change
#define SEARCH_TEXT  "Can you spot this error?"
#define REPLACE_TEXT "Cao you spot this error?"
#define READ_SIZE    4096

// command line args
enum {
	PROG_NAME,
	PATH,
	MAX_ARGS
};

// so that we don't spend all our time in syscall overhead
struct read_buf {
	char *buffer;
	ssize_t len;
	ssize_t read_ptr;
	int fd;
};

static struct read_buf *read_buf_init(ssize_t size, int fd) {
	if (fd < 0) {
		return NULL;
	}

	struct read_buf *buf = malloc(sizeof(*buf));
	if (buf == NULL) {
		return NULL;
	}

	buf->buffer = malloc(size);
	if (buf->buffer == NULL) {
		free(buf);
		return NULL;
	}

	buf->len = size;
	buf->read_ptr = -1;
	buf->fd = fd;
	return buf;
}

static void read_buf_free(struct read_buf *buf) {
	free(buf->buffer);
	free(buf);
	// doesn't close fd
}

static inline int read_buf_getc(struct read_buf *buf, char *out) {
	// if we need to do another read
	if (buf->read_ptr == -1 || buf->read_ptr == buf->len) {
		// TODO partial read
		ssize_t count = read(buf->fd, buf->buffer, buf->len);
		if (count != buf->len) {
			fprintf(stderr, "Read %lli, wanted %lli: %s\n",
				(long long) count, (long long) buf->len,
				strerror(errno));
			return EXIT_FAILURE;
		}
		buf->read_ptr = 0;
	}

	*out = buf->buffer[buf->read_ptr++];
	return EXIT_SUCCESS;
}

// seek through fd until we find text.
// Position the file pointer at the start of the text
static int find_text(const int fd, const char *text)
{
	int ret = EXIT_FAILURE;
	const size_t len = strlen(text);
	size_t n_matching = 0;

	off_t offset = lseek(fd, 0, SEEK_CUR);;
	if (offset == -1) {
		fprintf(stderr, "Failed to get current offset: %s\n",
			strerror(errno));
		return EXIT_FAILURE;
	}

	struct read_buf *buf = read_buf_init(READ_SIZE, fd);
	if (buf == NULL) {
		fprintf(stderr, "Couldn't allocate read_buf\n");
		return EXIT_FAILURE;
	}

	printf("Searching for \"%s\"\n", text);

	while (n_matching < len) {
		char readc;

		if (read_buf_getc(buf, &readc) != EXIT_SUCCESS) {
			fprintf(stderr, "read_buf_getc failed\n");
			goto free_buffer;
		}
		++offset;

		if (readc == text[n_matching]) {
			++n_matching;
			if (n_matching == len) {
				break;
			}
		} else {
			// not a match
			if (n_matching) {
				n_matching = 0;
			}
		}
	}

	// go back to the beginning of the text
	off_t err = lseek(fd, offset - len, SEEK_SET);
	if (err == -1) {
		fprintf(stderr, "Lseek error: %s\n", strerror(errno));
		goto free_buffer;
	}

	printf("Found at %lli!\n", (long long) offset);
	ret = EXIT_SUCCESS;

free_buffer:
	read_buf_free(buf);
	return ret;
}

static int find_and_replace(const int fd, const char *search_text,
		     const char *replace_text)
{
	if (find_text(fd, search_text) != EXIT_SUCCESS) {
		return EXIT_FAILURE;
	}

	const ssize_t len = strlen(replace_text);

	printf("Overwriting with \"%s\"\n", replace_text);
	ssize_t count = write(fd, replace_text, len);
	// TODO partial write
	if (count != len) {
		fprintf(stderr, "write error: %s\n", strerror(errno));
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

static void print_help(const char *name)
{
	fprintf(stderr, "Usage: %s FILE\n", name);
}

int main(int argc, char **argv)
{
	int ret = EXIT_FAILURE;

	if (argc != MAX_ARGS) {
		print_help(argv[PROG_NAME]);
		return EXIT_FAILURE;
	}

	if (strcmp("--help", argv[1]) == 0 || strcmp("-h", argv[1]) == 0) {
		print_help(argv[PROG_NAME]);
		return EXIT_SUCCESS;
	}
	const char *path = argv[PATH];

	int fd = open(path, O_RDWR);
	if (fd == -1) {
		int err = errno;
		fprintf(stderr, "Can't open %s: %i: %s\n", path, err, strerror(err));
		return EXIT_FAILURE;
	}

	ret = find_and_replace(fd, SEARCH_TEXT, REPLACE_TEXT);

	close(fd);

	return ret;
}
gen_image.sh (application/x-shellscript, 2.1 KB)
#!/bin/bash

readonly IMG_SIZE="8M"
readonly IMG_PATH="data$IMG_SIZE.img"
readonly CORRUPT_IMG_PATH="$IMG_PATH.corrupt"

readonly MOUNTPOINT="mnt"
readonly TEST_STR="Can you spot this error?"
readonly TEST_FILE="$MOUNTPOINT/test_file.txt"

readonly FEC_IMG="fec-img"
readonly HASH_IMG="hash-img"

readonly VERITYSETUP_ARGS="--verbose --debug --fec-device \"$FEC_IMG\" --fec-roots 24"
readonly VERITYSETUP_OUT="veritysetup_format.out"
readonly DM_NAME="verity"

readonly FIND_AND_CORRUPT="find_and_corrupt"

pr_err()
{
	echo "ERROR: $*" 1>&2
	exit 1
}

assert_noerr()
{
	echo "$*"
	if ! eval "$*"; then
		pr_err "$*"
	fi
}

# set up dm-verity volume

assert_noerr sudo dd if=/dev/zero of="$IMG_PATH" bs="$IMG_SIZE" count=1

assert_noerr sudo mkfs.ext4 -b 4096 -L verity-data "$IMG_PATH"

if ! [ -d "$MOUNTPOINT" ]; then
	assert_noerr mkdir "$MOUNTPOINT"
fi

assert_noerr sudo mount -t ext4 "$IMG_PATH" "$MOUNTPOINT"

assert_noerr "echo \"$TEST_STR\" | sudo tee \"$TEST_FILE\""

assert_noerr sudo umount "$IMG_PATH"

assert_noerr sudo "veritysetup $VERITYSETUP_ARGS format \"$IMG_PATH\" \"$HASH_IMG\" > \"$VERITYSETUP_OUT\""

# create a corrupted version of the data image

if ! [ -x "$FIND_AND_CORRUPT" ]; then
	assert_noerr cc -Wall -Wextra -Wpedantic -Werror -O2 find_and_corrupt.c -o "$FIND_AND_CORRUPT"
fi

assert_noerr cp "$IMG_PATH" "$CORRUPT_IMG_PATH"

assert_noerr "./$FIND_AND_CORRUPT" "$CORRUPT_IMG_PATH"

# "Can" -> "Cao" is a 1-bit change: 0x63 -> 0x6f
diff -u <(xxd "$IMG_PATH") <(xxd "$CORRUPT_IMG_PATH")

# Access via verity open

hash=$(grep "Root hash:" "$VERITYSETUP_OUT" | cut -f 2)
if [ $? != 0 ]; then
	pr_err "Failed to get hash"
fi

assert_noerr sudo veritysetup "$VERITYSETUP_ARGS" open "$CORRUPT_IMG_PATH" "$DM_NAME" "$HASH_IMG" "$hash"

assert_noerr sudo mount -t ext4 -o ro "/dev/mapper/$DM_NAME" "$MOUNTPOINT"

# bug here:
cat "$TEST_FILE"
sudo dmesg | tail

assert_noerr sudo umount "$MOUNTPOINT"

assert_noerr sudo veritysetup close "$DM_NAME"

# demonstrate that the error is correctable by veritysetup verify

assert_noerr sudo veritysetup "$VERITYSETUP_ARGS" verify "$CORRUPT_IMG_PATH" "$HASH_IMG" "$hash"

echo "Success"
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.