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"