diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 08:06:26 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 08:06:26 +0000 |
commit | 1660d4b7a65d9ad2ce0deaa19d35579ca4084ac5 (patch) | |
tree | 6cf8220b628ebd2ccfc1375dd6516c6996e9abcc /lib/verity | |
parent | Initial commit. (diff) | |
download | cryptsetup-upstream.tar.xz cryptsetup-upstream.zip |
Adding upstream version 2:2.6.1.upstream/2%2.6.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/verity')
-rw-r--r-- | lib/verity/rs.h | 63 | ||||
-rw-r--r-- | lib/verity/rs_decode_char.c | 201 | ||||
-rw-r--r-- | lib/verity/rs_encode_char.c | 173 | ||||
-rw-r--r-- | lib/verity/verity.c | 416 | ||||
-rw-r--r-- | lib/verity/verity.h | 87 | ||||
-rw-r--r-- | lib/verity/verity_fec.c | 336 | ||||
-rw-r--r-- | lib/verity/verity_hash.c | 444 |
7 files changed, 1720 insertions, 0 deletions
diff --git a/lib/verity/rs.h b/lib/verity/rs.h new file mode 100644 index 0000000..7638924 --- /dev/null +++ b/lib/verity/rs.h @@ -0,0 +1,63 @@ +/* + * Reed-Solomon codecs, based on libfec + * + * Copyright (C) 2004 Phil Karn, KA9Q + * libcryptsetup modifications + * Copyright (C) 2017-2023 Red Hat, Inc. All rights reserved. + * + * This file is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _LIBFEC_RS_H +#define _LIBFEC_RS_H + +/* Special reserved value encoding zero in index form. */ +#define A0 (rs->nn) + +#define RS_MIN(a, b) ((a) < (b) ? (a) : (b)) + +typedef unsigned char data_t; + +/* Reed-Solomon codec control block */ +struct rs { + int mm; /* Bits per symbol */ + int nn; /* Symbols per block (= (1<<mm)-1) */ + data_t *alpha_to;/* log lookup table */ + data_t *index_of;/* Antilog lookup table */ + data_t *genpoly; /* Generator polynomial */ + int nroots; /* Number of generator roots = number of parity symbols */ + int fcr; /* First consecutive root, index form */ + int prim; /* Primitive element, index form */ + int iprim; /* prim-th root of 1, index form */ + int pad; /* Padding bytes in shortened block */ +}; + +static inline int modnn(struct rs *rs, int x) +{ + while (x >= rs->nn) { + x -= rs->nn; + x = (x >> rs->mm) + (x & rs->nn); + } + return x; +} + +struct rs *init_rs_char(int symsize, int gfpoly, int fcr, int prim, int nroots, int pad); +void free_rs_char(struct rs *rs); + +/* General purpose RS codec, 8-bit symbols */ +void encode_rs_char(struct rs *rs, data_t *data, data_t *parity); +int decode_rs_char(struct rs *rs, data_t *data); + +#endif diff --git a/lib/verity/rs_decode_char.c b/lib/verity/rs_decode_char.c new file mode 100644 index 0000000..4473202 --- /dev/null +++ b/lib/verity/rs_decode_char.c @@ -0,0 +1,201 @@ +/* + * Reed-Solomon decoder, based on libfec + * + * Copyright (C) 2002, Phil Karn, KA9Q + * libcryptsetup modifications + * Copyright (C) 2017-2023 Red Hat, Inc. All rights reserved. + * + * This file is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <string.h> +#include <stdlib.h> + +#include "rs.h" + +#define MAX_NR_BUF 256 + +int decode_rs_char(struct rs* rs, data_t* data) +{ + int deg_lambda, el, deg_omega, syn_error, count; + int i, j, r, k; + data_t q, tmp, num1, num2, den, discr_r; + data_t lambda[MAX_NR_BUF], s[MAX_NR_BUF]; /* Err+Eras Locator poly and syndrome poly */ + data_t b[MAX_NR_BUF], t[MAX_NR_BUF], omega[MAX_NR_BUF]; + data_t root[MAX_NR_BUF], reg[MAX_NR_BUF], loc[MAX_NR_BUF]; + + if (rs->nroots >= MAX_NR_BUF) + return -1; + + memset(s, 0, rs->nroots * sizeof(data_t)); + memset(b, 0, (rs->nroots + 1) * sizeof(data_t)); + + /* form the syndromes; i.e., evaluate data(x) at roots of g(x) */ + for (i = 0; i < rs->nroots; i++) + s[i] = data[0]; + + for (j = 1; j < rs->nn - rs->pad; j++) { + for (i = 0; i < rs->nroots; i++) { + if (s[i] == 0) { + s[i] = data[j]; + } else { + s[i] = data[j] ^ rs->alpha_to[modnn(rs, rs->index_of[s[i]] + (rs->fcr + i) * rs->prim)]; + } + } + } + + /* Convert syndromes to index form, checking for nonzero condition */ + syn_error = 0; + for (i = 0; i < rs->nroots; i++) { + syn_error |= s[i]; + s[i] = rs->index_of[s[i]]; + } + + /* + * if syndrome is zero, data[] is a codeword and there are no + * errors to correct. So return data[] unmodified + */ + if (!syn_error) + return 0; + + memset(&lambda[1], 0, rs->nroots * sizeof(lambda[0])); + lambda[0] = 1; + + for (i = 0; i < rs->nroots + 1; i++) + b[i] = rs->index_of[lambda[i]]; + + /* + * Begin Berlekamp-Massey algorithm to determine error+erasure + * locator polynomial + */ + r = 0; + el = 0; + while (++r <= rs->nroots) { /* r is the step number */ + /* Compute discrepancy at the r-th step in poly-form */ + discr_r = 0; + for (i = 0; i < r; i++) { + if ((lambda[i] != 0) && (s[r - i - 1] != A0)) { + discr_r ^= rs->alpha_to[modnn(rs, rs->index_of[lambda[i]] + s[r - i - 1])]; + } + } + discr_r = rs->index_of[discr_r]; /* Index form */ + if (discr_r == A0) { + /* 2 lines below: B(x) <-- x*B(x) */ + memmove(&b[1], b, rs->nroots * sizeof(b[0])); + b[0] = A0; + } else { + /* 7 lines below: T(x) <-- lambda(x) - discr_r*x*b(x) */ + t[0] = lambda[0]; + for (i = 0; i < rs->nroots; i++) { + if (b[i] != A0) + t[i + 1] = lambda[i + 1] ^ rs->alpha_to[modnn(rs, discr_r + b[i])]; + else + t[i + 1] = lambda[i + 1]; + } + if (2 * el <= r - 1) { + el = r - el; + /* + * 2 lines below: B(x) <-- inv(discr_r) * + * lambda(x) + */ + for (i = 0; i <= rs->nroots; i++) + b[i] = (lambda[i] == 0) ? A0 : modnn(rs, rs->index_of[lambda[i]] - discr_r + rs->nn); + } else { + /* 2 lines below: B(x) <-- x*B(x) */ + memmove(&b[1], b, rs->nroots * sizeof(b[0])); + b[0] = A0; + } + memcpy(lambda, t, (rs->nroots + 1) * sizeof(t[0])); + } + } + + /* Convert lambda to index form and compute deg(lambda(x)) */ + deg_lambda = 0; + for (i = 0; i < rs->nroots + 1; i++) { + lambda[i] = rs->index_of[lambda[i]]; + if (lambda[i] != A0) + deg_lambda = i; + } + /* Find roots of the error+erasure locator polynomial by Chien search */ + memcpy(®[1], &lambda[1], rs->nroots * sizeof(reg[0])); + count = 0; /* Number of roots of lambda(x) */ + for (i = 1, k = rs->iprim - 1; i <= rs->nn; i++, k = modnn(rs, k + rs->iprim)) { + q = 1; /* lambda[0] is always 0 */ + for (j = deg_lambda; j > 0; j--) { + if (reg[j] != A0) { + reg[j] = modnn(rs, reg[j] + j); + q ^= rs->alpha_to[reg[j]]; + } + } + if (q != 0) + continue; /* Not a root */ + + /* store root (index-form) and error location number */ + root[count] = i; + loc[count] = k; + /* If we've already found max possible roots, abort the search to save time */ + if (++count == deg_lambda) + break; + } + + /* + * deg(lambda) unequal to number of roots => uncorrectable + * error detected + */ + if (deg_lambda != count) + return -1; + + /* + * Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo + * x**rs->nroots). in index form. Also find deg(omega). + */ + deg_omega = deg_lambda - 1; + for (i = 0; i <= deg_omega; i++) { + tmp = 0; + for (j = i; j >= 0; j--) { + if ((s[i - j] != A0) && (lambda[j] != A0)) + tmp ^= rs->alpha_to[modnn(rs, s[i - j] + lambda[j])]; + } + omega[i] = rs->index_of[tmp]; + } + + /* + * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = + * inv(X(l))**(rs->fcr-1) and den = lambda_pr(inv(X(l))) all in poly-form + */ + for (j = count - 1; j >= 0; j--) { + num1 = 0; + for (i = deg_omega; i >= 0; i--) { + if (omega[i] != A0) + num1 ^= rs->alpha_to[modnn(rs, omega[i] + i * root[j])]; + } + num2 = rs->alpha_to[modnn(rs, root[j] * (rs->fcr - 1) + rs->nn)]; + den = 0; + + /* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */ + for (i = RS_MIN(deg_lambda, rs->nroots - 1) & ~1; i >= 0; i -= 2) { + if (lambda[i + 1] != A0) + den ^= rs->alpha_to[modnn(rs, lambda[i + 1] + i * root[j])]; + } + + /* Apply error to data */ + if (num1 != 0 && loc[j] >= rs->pad) { + data[loc[j] - rs->pad] ^= rs->alpha_to[modnn(rs, rs->index_of[num1] + + rs->index_of[num2] + rs->nn - rs->index_of[den])]; + } + } + + return count; +} diff --git a/lib/verity/rs_encode_char.c b/lib/verity/rs_encode_char.c new file mode 100644 index 0000000..55b502a --- /dev/null +++ b/lib/verity/rs_encode_char.c @@ -0,0 +1,173 @@ +/* + * Reed-Solomon encoder, based on libfec + * + * Copyright (C) 2002, Phil Karn, KA9Q + * libcryptsetup modifications + * Copyright (C) 2017-2023 Red Hat, Inc. All rights reserved. + * + * This file is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <string.h> +#include <stdlib.h> + +#include "rs.h" + +/* Initialize a Reed-Solomon codec + * symsize = symbol size, bits + * gfpoly = Field generator polynomial coefficients + * fcr = first root of RS code generator polynomial, index form + * prim = primitive element to generate polynomial roots + * nroots = RS code generator polynomial degree (number of roots) + * pad = padding bytes at front of shortened block + */ +struct rs *init_rs_char(int symsize, int gfpoly, int fcr, int prim, int nroots, int pad) +{ + struct rs *rs; + int i, j, sr, root, iprim; + + /* Check parameter ranges */ + if (symsize < 0 || symsize > 8 * (int)sizeof(data_t)) + return NULL; + if (fcr < 0 || fcr >= (1<<symsize)) + return NULL; + if (prim <= 0 || prim >= (1<<symsize)) + return NULL; + if (nroots < 0 || nroots >= (1<<symsize)) + return NULL; /* Can't have more roots than symbol values! */ + + if (pad < 0 || pad >= ((1<<symsize) - 1 - nroots)) + return NULL; /* Too much padding */ + + rs = calloc(1, sizeof(struct rs)); + if (rs == NULL) + return NULL; + + rs->mm = symsize; + rs->nn = (1<<symsize) - 1; + rs->pad = pad; + + rs->alpha_to = malloc(sizeof(data_t) * (rs->nn + 1)); + if (rs->alpha_to == NULL) { + free(rs); + return NULL; + } + rs->index_of = malloc(sizeof(data_t) * (rs->nn + 1)); + if (rs->index_of == NULL) { + free(rs->alpha_to); + free(rs); + return NULL; + } + memset(rs->index_of, 0, sizeof(data_t) * (rs->nn + 1)); + + /* Generate Galois field lookup tables */ + rs->index_of[0] = A0; /* log(zero) = -inf */ + rs->alpha_to[A0] = 0; /* alpha**-inf = 0 */ + sr = 1; + for (i = 0; i < rs->nn; i++) { + rs->index_of[sr] = i; + rs->alpha_to[i] = sr; + sr <<= 1; + if(sr & (1<<symsize)) + sr ^= gfpoly; + sr &= rs->nn; + } + if (sr != 1) { + /* field generator polynomial is not primitive! */ + free(rs->alpha_to); + free(rs->index_of); + free(rs); + return NULL; + } + + /* Form RS code generator polynomial from its roots */ + rs->genpoly = malloc(sizeof(data_t) * (nroots + 1)); + if (rs->genpoly == NULL) { + free(rs->alpha_to); + free(rs->index_of); + free(rs); + return NULL; + } + + rs->fcr = fcr; + rs->prim = prim; + rs->nroots = nroots; + + /* Find prim-th root of 1, used in decoding */ + for (iprim = 1; (iprim % prim) != 0; iprim += rs->nn) + ; + rs->iprim = iprim / prim; + + rs->genpoly[0] = 1; + for (i = 0, root = fcr * prim; i < nroots; i++, root += prim) { + rs->genpoly[i + 1] = 1; + + /* Multiply rs->genpoly[] by @**(root + x) */ + for (j = i; j > 0; j--){ + if (rs->genpoly[j] != 0) + rs->genpoly[j] = rs->genpoly[j - 1] ^ rs->alpha_to[modnn(rs, rs->index_of[rs->genpoly[j]] + root)]; + else + rs->genpoly[j] = rs->genpoly[j - 1]; + } + /* rs->genpoly[0] can never be zero */ + rs->genpoly[0] = rs->alpha_to[modnn(rs, rs->index_of[rs->genpoly[0]] + root)]; + } + /* convert rs->genpoly[] to index form for quicker encoding */ + for (i = 0; i <= nroots; i++) + rs->genpoly[i] = rs->index_of[rs->genpoly[i]]; + + return rs; +} + +void free_rs_char(struct rs *rs) +{ + if (!rs) + return; + + free(rs->alpha_to); + free(rs->index_of); + free(rs->genpoly); + free(rs); +} + +void encode_rs_char(struct rs *rs, data_t *data, data_t *parity) +{ + int i, j; + data_t feedback; + + memset(parity, 0, rs->nroots * sizeof(data_t)); + + for (i = 0; i < rs->nn - rs->nroots - rs->pad; i++) { + feedback = rs->index_of[data[i] ^ parity[0]]; + if (feedback != A0) { + /* feedback term is non-zero */ +#ifdef UNNORMALIZED + /* This line is unnecessary when GENPOLY[NROOTS] is unity, as it must + * always be for the polynomials constructed by init_rs() */ + feedback = modnn(rs, rs->nn - rs->genpoly[rs->nroots] + feedback); +#endif + for (j = 1; j < rs->nroots; j++) + parity[j] ^= rs->alpha_to[modnn(rs, feedback + rs->genpoly[rs->nroots - j])]; + } + + /* Shift */ + memmove(&parity[0], &parity[1], sizeof(data_t) * (rs->nroots - 1)); + + if (feedback != A0) + parity[rs->nroots - 1] = rs->alpha_to[modnn(rs, feedback + rs->genpoly[0])]; + else + parity[rs->nroots - 1] = 0; + } +} diff --git a/lib/verity/verity.c b/lib/verity/verity.c new file mode 100644 index 0000000..0d7a8f5 --- /dev/null +++ b/lib/verity/verity.c @@ -0,0 +1,416 @@ +/* + * dm-verity volume handling + * + * Copyright (C) 2012-2023 Red Hat, Inc. All rights reserved. + * + * This file is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <uuid/uuid.h> + +#include "libcryptsetup.h" +#include "verity.h" +#include "internal.h" + +#define VERITY_SIGNATURE "verity\0\0" + +/* https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity#verity-superblock-format */ +struct verity_sb { + uint8_t signature[8]; /* "verity\0\0" */ + uint32_t version; /* superblock version */ + uint32_t hash_type; /* 0 - Chrome OS, 1 - normal */ + uint8_t uuid[16]; /* UUID of hash device */ + uint8_t algorithm[32];/* hash algorithm name */ + uint32_t data_block_size; /* data block in bytes */ + uint32_t hash_block_size; /* hash block in bytes */ + uint64_t data_blocks; /* number of data blocks */ + uint16_t salt_size; /* salt size */ + uint8_t _pad1[6]; + uint8_t salt[256]; /* salt */ + uint8_t _pad2[168]; +} __attribute__((packed)); + +/* Read verity superblock from disk */ +int VERITY_read_sb(struct crypt_device *cd, + uint64_t sb_offset, + char **uuid_string, + struct crypt_params_verity *params) +{ + struct device *device = crypt_metadata_device(cd); + struct verity_sb sb = {}; + ssize_t hdr_size = sizeof(struct verity_sb); + int devfd, sb_version; + + log_dbg(cd, "Reading VERITY header of size %zu on device %s, offset %" PRIu64 ".", + sizeof(struct verity_sb), device_path(device), sb_offset); + + if (params->flags & CRYPT_VERITY_NO_HEADER) { + log_err(cd, _("Verity device %s does not use on-disk header."), + device_path(device)); + return -EINVAL; + } + + if (MISALIGNED_512(sb_offset)) { + log_err(cd, _("Unsupported VERITY hash offset.")); + return -EINVAL; + } + + devfd = device_open(cd, device, O_RDONLY); + if (devfd < 0) { + log_err(cd, _("Cannot open device %s."), device_path(device)); + return -EINVAL; + } + + if (read_lseek_blockwise(devfd, device_block_size(cd, device), + device_alignment(device), &sb, hdr_size, + sb_offset) < hdr_size) + return -EIO; + + if (memcmp(sb.signature, VERITY_SIGNATURE, sizeof(sb.signature))) { + log_dbg(cd, "No VERITY signature detected."); + return -EINVAL; + } + + sb_version = le32_to_cpu(sb.version); + if (sb_version != 1) { + log_err(cd, _("Unsupported VERITY version %d."), sb_version); + return -EINVAL; + } + params->hash_type = le32_to_cpu(sb.hash_type); + if (params->hash_type > VERITY_MAX_HASH_TYPE) { + log_err(cd, _("Unsupported VERITY hash type %d."), params->hash_type); + return -EINVAL; + } + + params->data_block_size = le32_to_cpu(sb.data_block_size); + params->hash_block_size = le32_to_cpu(sb.hash_block_size); + if (VERITY_BLOCK_SIZE_OK(params->data_block_size) || + VERITY_BLOCK_SIZE_OK(params->hash_block_size)) { + log_err(cd, _("Unsupported VERITY block size.")); + return -EINVAL; + } + params->data_size = le64_to_cpu(sb.data_blocks); + + /* Update block size to be used for loop devices */ + device_set_block_size(crypt_metadata_device(cd), params->hash_block_size); + device_set_block_size(crypt_data_device(cd), params->data_block_size); + + params->hash_name = strndup((const char*)sb.algorithm, sizeof(sb.algorithm)); + if (!params->hash_name) + return -ENOMEM; + if (crypt_hash_size(params->hash_name) <= 0) { + log_err(cd, _("Hash algorithm %s not supported."), + params->hash_name); + free(CONST_CAST(char*)params->hash_name); + params->hash_name = NULL; + return -EINVAL; + } + + params->salt_size = le16_to_cpu(sb.salt_size); + if (params->salt_size > sizeof(sb.salt)) { + log_err(cd, _("VERITY header corrupted.")); + free(CONST_CAST(char*)params->hash_name); + params->hash_name = NULL; + return -EINVAL; + } + params->salt = malloc(params->salt_size); + if (!params->salt) { + free(CONST_CAST(char*)params->hash_name); + params->hash_name = NULL; + return -ENOMEM; + } + memcpy(CONST_CAST(char*)params->salt, sb.salt, params->salt_size); + + if ((*uuid_string = malloc(40))) + uuid_unparse(sb.uuid, *uuid_string); + + params->hash_area_offset = sb_offset; + return 0; +} + +static void _to_lower(char *str) +{ + for(; *str; str++) + if (isupper(*str)) + *str = tolower(*str); +} + +/* Write verity superblock to disk */ +int VERITY_write_sb(struct crypt_device *cd, + uint64_t sb_offset, + const char *uuid_string, + struct crypt_params_verity *params) +{ + struct device *device = crypt_metadata_device(cd); + struct verity_sb sb = {}; + ssize_t hdr_size = sizeof(struct verity_sb); + size_t block_size; + char *algorithm; + uuid_t uuid; + int r, devfd; + + log_dbg(cd, "Updating VERITY header of size %zu on device %s, offset %" PRIu64 ".", + sizeof(struct verity_sb), device_path(device), sb_offset); + + if (!uuid_string || uuid_parse(uuid_string, uuid) == -1) { + log_err(cd, _("Wrong VERITY UUID format provided on device %s."), + device_path(device)); + return -EINVAL; + } + + if (params->flags & CRYPT_VERITY_NO_HEADER) { + log_err(cd, _("Verity device %s does not use on-disk header."), + device_path(device)); + return -EINVAL; + } + + /* Avoid possible increasing of image size - FEC could fail later because of it */ + block_size = device_block_size(cd, device); + if (block_size > params->hash_block_size) { + device_disable_direct_io(device); + block_size = params->hash_block_size; + } + + devfd = device_open(cd, device, O_RDWR); + if (devfd < 0) { + log_err(cd, _("Cannot open device %s."), device_path(device)); + return -EINVAL; + } + + memcpy(&sb.signature, VERITY_SIGNATURE, sizeof(sb.signature)); + sb.version = cpu_to_le32(1); + sb.hash_type = cpu_to_le32(params->hash_type); + sb.data_block_size = cpu_to_le32(params->data_block_size); + sb.hash_block_size = cpu_to_le32(params->hash_block_size); + sb.salt_size = cpu_to_le16(params->salt_size); + sb.data_blocks = cpu_to_le64(params->data_size); + + /* Kernel always use lower-case */ + algorithm = (char *)sb.algorithm; + strncpy(algorithm, params->hash_name, sizeof(sb.algorithm)-1); + algorithm[sizeof(sb.algorithm)-1] = '\0'; + _to_lower(algorithm); + + memcpy(sb.salt, params->salt, params->salt_size); + memcpy(sb.uuid, uuid, sizeof(sb.uuid)); + + r = write_lseek_blockwise(devfd, block_size, device_alignment(device), + (char*)&sb, hdr_size, sb_offset) < hdr_size ? -EIO : 0; + if (r) + log_err(cd, _("Error during update of verity header on device %s."), + device_path(device)); + + device_sync(cd, device); + + return r; +} + +/* Calculate hash offset in hash blocks */ +uint64_t VERITY_hash_offset_block(struct crypt_params_verity *params) +{ + uint64_t hash_offset = params->hash_area_offset; + + if (params->flags & CRYPT_VERITY_NO_HEADER) + return hash_offset / params->hash_block_size; + + hash_offset += sizeof(struct verity_sb); + hash_offset += params->hash_block_size - 1; + + return hash_offset / params->hash_block_size; +} + +int VERITY_UUID_generate(char **uuid_string) +{ + uuid_t uuid; + + *uuid_string = malloc(40); + if (!*uuid_string) + return -ENOMEM; + uuid_generate(uuid); + uuid_unparse(uuid, *uuid_string); + return 0; +} + +/* Activate verity device in kernel device-mapper */ +int VERITY_activate(struct crypt_device *cd, + const char *name, + const char *root_hash, + size_t root_hash_size, + const char *signature_description, + struct device *fec_device, + struct crypt_params_verity *verity_hdr, + uint32_t activation_flags) +{ + uint32_t dmv_flags; + unsigned int fec_errors = 0; + int r, v; + struct crypt_dm_active_device dmd = { + .size = verity_hdr->data_size * verity_hdr->data_block_size / 512, + .flags = activation_flags, + .uuid = crypt_get_uuid(cd), + }; + + log_dbg(cd, "Trying to activate VERITY device %s using hash %s.", + name ?: "[none]", verity_hdr->hash_name); + + if (verity_hdr->flags & CRYPT_VERITY_CHECK_HASH) { + if (signature_description) { + log_err(cd, _("Root hash signature verification is not supported.")); + return -EINVAL; + } + + log_dbg(cd, "Verification of data in userspace required."); + r = VERITY_verify(cd, verity_hdr, root_hash, root_hash_size); + + if ((r == -EPERM || r == -EFAULT) && fec_device) { + v = r; + log_dbg(cd, "Verification failed, trying to repair with FEC device."); + r = VERITY_FEC_process(cd, verity_hdr, fec_device, 1, &fec_errors); + if (r < 0) + log_err(cd, _("Errors cannot be repaired with FEC device.")); + else if (fec_errors) { + log_err(cd, _("Found %u repairable errors with FEC device."), + fec_errors); + /* If root hash failed, we cannot be sure it was properly repaired */ + } + if (v == -EFAULT) + r = -EPERM; + } + + if (r < 0) + return r; + } + + if (!name) + return 0; + + r = device_block_adjust(cd, crypt_metadata_device(cd), DEV_OK, + 0, NULL, NULL); + if (r) + return r; + + r = device_block_adjust(cd, crypt_data_device(cd), DEV_EXCL, + 0, &dmd.size, &dmd.flags); + if (r) + return r; + + if (fec_device) { + r = device_block_adjust(cd, fec_device, DEV_OK, + 0, NULL, NULL); + if (r) + return r; + } + + r = dm_verity_target_set(&dmd.segment, 0, dmd.size, crypt_data_device(cd), + crypt_metadata_device(cd), fec_device, root_hash, + root_hash_size, signature_description, + VERITY_hash_offset_block(verity_hdr), + VERITY_FEC_blocks(cd, fec_device, verity_hdr), verity_hdr); + + if (r) + return r; + + r = dm_create_device(cd, name, CRYPT_VERITY, &dmd); + if (r < 0 && (dm_flags(cd, DM_VERITY, &dmv_flags) || !(dmv_flags & DM_VERITY_SUPPORTED))) { + log_err(cd, _("Kernel does not support dm-verity mapping.")); + r = -ENOTSUP; + } + if (r < 0 && signature_description && !(dmv_flags & DM_VERITY_SIGNATURE_SUPPORTED)) { + log_err(cd, _("Kernel does not support dm-verity signature option.")); + r = -ENOTSUP; + } + if (r < 0) + goto out; + + r = dm_status_verity_ok(cd, name); + if (r < 0) + goto out; + + if (!r) + log_err(cd, _("Verity device detected corruption after activation.")); + + r = 0; +out: + dm_targets_free(cd, &dmd); + return r; +} + +int VERITY_dump(struct crypt_device *cd, + struct crypt_params_verity *verity_hdr, + const char *root_hash, + unsigned int root_hash_size, + struct device *fec_device) +{ + uint64_t hash_blocks, verity_blocks, fec_blocks = 0, rs_blocks = 0; + bool fec_on_hash_device = false; + + hash_blocks = VERITY_hash_blocks(cd, verity_hdr); + verity_blocks = VERITY_hash_offset_block(verity_hdr) + hash_blocks; + + if (fec_device && verity_hdr->fec_roots) { + fec_blocks = VERITY_FEC_blocks(cd, fec_device, verity_hdr); + rs_blocks = VERITY_FEC_RS_blocks(fec_blocks, verity_hdr->fec_roots); + fec_on_hash_device = device_is_identical(crypt_metadata_device(cd), fec_device) > 0; + /* + * No way to access fec_area_offset directly. + * Assume FEC area starts directly after hash blocks. + */ + if (fec_on_hash_device) + verity_blocks += rs_blocks; + } + + log_std(cd, "VERITY header information for %s\n", device_path(crypt_metadata_device(cd))); + log_std(cd, "UUID: \t%s\n", crypt_get_uuid(cd) ?: ""); + log_std(cd, "Hash type: \t%u\n", verity_hdr->hash_type); + log_std(cd, "Data blocks: \t%" PRIu64 "\n", verity_hdr->data_size); + log_std(cd, "Data block size: \t%u\n", verity_hdr->data_block_size); + log_std(cd, "Hash blocks: \t%" PRIu64 "\n", hash_blocks); + log_std(cd, "Hash block size: \t%u\n", verity_hdr->hash_block_size); + log_std(cd, "Hash algorithm: \t%s\n", verity_hdr->hash_name); + if (fec_device && fec_blocks) { + log_std(cd, "FEC RS roots: \t%" PRIu32 "\n", verity_hdr->fec_roots); + log_std(cd, "FEC blocks: \t%" PRIu64 "\n", rs_blocks); + } + + log_std(cd, "Salt: \t"); + if (verity_hdr->salt_size) + crypt_log_hex(cd, verity_hdr->salt, verity_hdr->salt_size, "", 0, NULL); + else + log_std(cd, "-"); + log_std(cd, "\n"); + + if (root_hash) { + log_std(cd, "Root hash: \t"); + crypt_log_hex(cd, root_hash, root_hash_size, "", 0, NULL); + log_std(cd, "\n"); + } + + /* As dump can take only hash device, we have no idea about offsets here. */ + if (verity_hdr->hash_area_offset == 0) + log_std(cd, "Hash device size: \t%" PRIu64 " [bytes]\n", verity_blocks * verity_hdr->hash_block_size); + + if (fec_device && verity_hdr->fec_area_offset == 0 && fec_blocks && !fec_on_hash_device) + log_std(cd, "FEC device size: \t%" PRIu64 " [bytes]\n", rs_blocks * verity_hdr->data_block_size); + + return 0; +} diff --git a/lib/verity/verity.h b/lib/verity/verity.h new file mode 100644 index 0000000..afc411e --- /dev/null +++ b/lib/verity/verity.h @@ -0,0 +1,87 @@ +/* + * dm-verity volume handling + * + * Copyright (C) 2012-2023 Red Hat, Inc. All rights reserved. + * + * This file is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _VERITY_H +#define _VERITY_H + +#include <stddef.h> +#include <stdint.h> + +#define VERITY_MAX_HASH_TYPE 1 +#define VERITY_BLOCK_SIZE_OK(x) ((x) % 512 || (x) < 512 || \ + (x) > (512 * 1024) || (x) & ((x)-1)) + +struct crypt_device; +struct crypt_params_verity; +struct device; + +int VERITY_read_sb(struct crypt_device *cd, + uint64_t sb_offset, + char **uuid, + struct crypt_params_verity *params); + +int VERITY_write_sb(struct crypt_device *cd, + uint64_t sb_offset, + const char *uuid_string, + struct crypt_params_verity *params); + +int VERITY_activate(struct crypt_device *cd, + const char *name, + const char *root_hash, + size_t root_hash_size, + const char *signature_description, + struct device *fec_device, + struct crypt_params_verity *verity_hdr, + uint32_t activation_flags); + +int VERITY_verify(struct crypt_device *cd, + struct crypt_params_verity *verity_hdr, + const char *root_hash, + size_t root_hash_size); + +int VERITY_create(struct crypt_device *cd, + struct crypt_params_verity *verity_hdr, + const char *root_hash, + size_t root_hash_size); + +int VERITY_FEC_process(struct crypt_device *cd, + struct crypt_params_verity *params, + struct device *fec_device, + int check_fec, + unsigned int *errors); + +uint64_t VERITY_hash_offset_block(struct crypt_params_verity *params); + +uint64_t VERITY_hash_blocks(struct crypt_device *cd, struct crypt_params_verity *params); + +uint64_t VERITY_FEC_blocks(struct crypt_device *cd, + struct device *fec_device, + struct crypt_params_verity *params); +uint64_t VERITY_FEC_RS_blocks(uint64_t blocks, uint32_t roots); + +int VERITY_UUID_generate(char **uuid_string); + +int VERITY_dump(struct crypt_device *cd, + struct crypt_params_verity *verity_hdr, + const char *root_hash, + unsigned int root_hash_size, + struct device *fec_device); + +#endif diff --git a/lib/verity/verity_fec.c b/lib/verity/verity_fec.c new file mode 100644 index 0000000..2dbf59e --- /dev/null +++ b/lib/verity/verity_fec.c @@ -0,0 +1,336 @@ +/* + * dm-verity Forward Error Correction (FEC) support + * + * Copyright (C) 2015 Google, Inc. All rights reserved. + * Copyright (C) 2017-2023 Red Hat, Inc. All rights reserved. + * + * This file is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdlib.h> +#include <errno.h> + +#include "verity.h" +#include "internal.h" +#include "rs.h" + +/* ecc parameters */ +#define FEC_RSM 255 +#define FEC_MIN_RSN 231 +#define FEC_MAX_RSN 253 + +#define FEC_INPUT_DEVICES 2 + +/* parameters to init_rs_char */ +#define FEC_PARAMS(roots) \ + 8, /* symbol size in bits */ \ + 0x11d, /* field generator polynomial coefficients */ \ + 0, /* first root of the generator */ \ + 1, /* primitive element to generate polynomial roots */ \ + (roots), /* polynomial degree (number of roots) */ \ + 0 /* padding bytes at the front of shortened block */ + +struct fec_input_device { + struct device *device; + int fd; + uint64_t start; + uint64_t count; +}; + +struct fec_context { + uint32_t rsn; + uint32_t roots; + uint64_t size; + uint64_t blocks; + uint64_t rounds; + uint32_t block_size; + struct fec_input_device *inputs; + size_t ninputs; +}; + +/* computes ceil(x / y) */ +static inline uint64_t FEC_div_round_up(uint64_t x, uint64_t y) +{ + return (x / y) + (x % y > 0 ? 1 : 0); +} + +/* returns a physical offset for the given RS offset */ +static inline uint64_t FEC_interleave(struct fec_context *ctx, uint64_t offset) +{ + return (offset / ctx->rsn) + + (offset % ctx->rsn) * ctx->rounds * ctx->block_size; +} + +/* returns data for a byte at the specified RS offset */ +static int FEC_read_interleaved(struct fec_context *ctx, uint64_t i, + void *output, size_t count) +{ + size_t n; + uint64_t offset = FEC_interleave(ctx, i); + + /* offsets outside input area are assumed to contain zeros */ + if (offset >= ctx->size) { + memset(output, 0, count); + return 0; + } + + /* find the correct input device and read from it */ + for (n = 0; n < ctx->ninputs; ++n) { + if (offset >= ctx->inputs[n].count) { + offset -= ctx->inputs[n].count; + continue; + } + + /* FIXME: read_lseek_blockwise candidate */ + if (lseek(ctx->inputs[n].fd, ctx->inputs[n].start + offset, SEEK_SET) < 0) + return -1; + return (read_buffer(ctx->inputs[n].fd, output, count) == (ssize_t)count) ? 0 : -1; + } + + /* should never be reached */ + return -1; +} + +/* encodes/decode inputs to/from fd */ +static int FEC_process_inputs(struct crypt_device *cd, + struct crypt_params_verity *params, + struct fec_input_device *inputs, + size_t ninputs, int fd, + int decode, unsigned int *errors) +{ + int r = 0; + unsigned int i; + struct fec_context ctx; + uint32_t b; + uint64_t n; + uint8_t rs_block[FEC_RSM]; + uint8_t *buf = NULL; + void *rs; + + /* initialize parameters */ + ctx.roots = params->fec_roots; + ctx.rsn = FEC_RSM - ctx.roots; + ctx.block_size = params->data_block_size; + ctx.inputs = inputs; + ctx.ninputs = ninputs; + + rs = init_rs_char(FEC_PARAMS(ctx.roots)); + if (!rs) { + log_err(cd, _("Failed to allocate RS context.")); + return -ENOMEM; + } + + /* calculate the total area covered by error correction codes */ + ctx.size = 0; + for (n = 0; n < ctx.ninputs; ++n) { + log_dbg(cd, "FEC input %s, offset %" PRIu64 " [bytes], length %" PRIu64 " [bytes]", + device_path(ctx.inputs[n].device), ctx.inputs[n].start, ctx.inputs[n].count); + ctx.size += ctx.inputs[n].count; + } + + /* each byte in a data block is covered by a different code */ + ctx.blocks = FEC_div_round_up(ctx.size, ctx.block_size); + ctx.rounds = FEC_div_round_up(ctx.blocks, ctx.rsn); + + buf = malloc((size_t)ctx.block_size * ctx.rsn); + if (!buf) { + log_err(cd, _("Failed to allocate buffer.")); + r = -ENOMEM; + goto out; + } + + /* encode/decode input */ + for (n = 0; n < ctx.rounds; ++n) { + for (i = 0; i < ctx.rsn; ++i) { + if (FEC_read_interleaved(&ctx, n * ctx.rsn * ctx.block_size + i, + &buf[i * ctx.block_size], ctx.block_size)) { + log_err(cd, _("Failed to read RS block %" PRIu64 " byte %d."), n, i); + r = -EIO; + goto out; + } + } + + for (b = 0; b < ctx.block_size; ++b) { + for (i = 0; i < ctx.rsn; ++i) + rs_block[i] = buf[i * ctx.block_size + b]; + + /* decoding from parity device */ + if (decode) { + if (read_buffer(fd, &rs_block[ctx.rsn], ctx.roots) < 0) { + log_err(cd, _("Failed to read parity for RS block %" PRIu64 "."), n); + r = -EIO; + goto out; + } + + /* coverity[tainted_data] */ + r = decode_rs_char(rs, rs_block); + if (r < 0) { + log_err(cd, _("Failed to repair parity for block %" PRIu64 "."), n); + r = -EPERM; + goto out; + } + /* return number of detected errors */ + if (errors) + *errors += r; + r = 0; + } else { + /* encoding and writing parity data to fec device */ + encode_rs_char(rs, rs_block, &rs_block[ctx.rsn]); + if (write_buffer(fd, &rs_block[ctx.rsn], ctx.roots) < 0) { + log_err(cd, _("Failed to write parity for RS block %" PRIu64 "."), n); + r = -EIO; + goto out; + } + } + } + } +out: + free_rs_char(rs); + free(buf); + return r; +} + +static int VERITY_FEC_validate(struct crypt_device *cd, struct crypt_params_verity *params) +{ + if (params->data_block_size != params->hash_block_size) { + log_err(cd, _("Block sizes must match for FEC.")); + return -EINVAL; + } + + if (params->fec_roots > FEC_RSM - FEC_MIN_RSN || + params->fec_roots < FEC_RSM - FEC_MAX_RSN) { + log_err(cd, _("Invalid number of parity bytes.")); + return -EINVAL; + } + + return 0; +} + +int VERITY_FEC_process(struct crypt_device *cd, + struct crypt_params_verity *params, + struct device *fec_device, int check_fec, + unsigned int *errors) +{ + int r = -EIO, fd = -1; + size_t ninputs = FEC_INPUT_DEVICES; + struct fec_input_device inputs[FEC_INPUT_DEVICES] = { + { + .device = crypt_data_device(cd), + .fd = -1, + .start = 0, + .count = params->data_size * params->data_block_size + },{ + .device = crypt_metadata_device(cd), + .fd = -1, + .start = VERITY_hash_offset_block(params) * params->data_block_size, + .count = (VERITY_FEC_blocks(cd, fec_device, params) - params->data_size) * params->data_block_size + } + }; + + /* validate parameters */ + r = VERITY_FEC_validate(cd, params); + if (r < 0) + return r; + + if (!inputs[0].count) { + log_err(cd, _("Invalid FEC segment length.")); + return -EINVAL; + } + if (!inputs[1].count) + ninputs--; + + if (check_fec) + fd = open(device_path(fec_device), O_RDONLY); + else + fd = open(device_path(fec_device), O_RDWR); + + if (fd == -1) { + log_err(cd, _("Cannot open device %s."), device_path(fec_device)); + goto out; + } + + if (lseek(fd, params->fec_area_offset, SEEK_SET) < 0) { + log_dbg(cd, "Cannot seek to requested position in FEC device."); + goto out; + } + + /* input devices */ + inputs[0].fd = open(device_path(inputs[0].device), O_RDONLY); + if (inputs[0].fd == -1) { + log_err(cd, _("Cannot open device %s."), device_path(inputs[0].device)); + goto out; + } + inputs[1].fd = open(device_path(inputs[1].device), O_RDONLY); + if (inputs[1].fd == -1) { + log_err(cd, _("Cannot open device %s."), device_path(inputs[1].device)); + goto out; + } + + r = FEC_process_inputs(cd, params, inputs, ninputs, fd, check_fec, errors); +out: + if (inputs[0].fd != -1) + close(inputs[0].fd); + if (inputs[1].fd != -1) + close(inputs[1].fd); + if (fd != -1) + close(fd); + + return r; +} + +/* All blocks that are covered by FEC */ +uint64_t VERITY_FEC_blocks(struct crypt_device *cd, + struct device *fec_device, + struct crypt_params_verity *params) +{ + uint64_t blocks = 0; + + if (!fec_device || VERITY_FEC_validate(cd, params) < 0) + return 0; + + /* + * FEC covers this data: + * | protected data | hash area | padding (optional foreign metadata) | + * + * If hash device is in a separate image, metadata covers the whole rest of the image after hash area. + * If hash and FEC device is in the image, metadata ends on the FEC area offset. + */ + if (device_is_identical(crypt_metadata_device(cd), fec_device) > 0) { + log_dbg(cd, "FEC and hash device is the same."); + blocks = params->fec_area_offset; + } else { + /* cover the entire hash device starting from hash_offset */ + if (device_size(crypt_metadata_device(cd), &blocks)) { + log_err(cd, _("Failed to determine size for device %s."), + device_path(crypt_metadata_device(cd))); + return 0; + } + } + + blocks /= params->data_block_size; + if (blocks) + blocks -= VERITY_hash_offset_block(params); + + /* Protected data */ + blocks += params->data_size; + + return blocks; +} + +/* Blocks needed to store FEC data, blocks must be validated/calculated by VERITY_FEC_blocks() */ +uint64_t VERITY_FEC_RS_blocks(uint64_t blocks, uint32_t roots) +{ + return FEC_div_round_up(blocks, FEC_RSM - roots) * roots; +} diff --git a/lib/verity/verity_hash.c b/lib/verity/verity_hash.c new file mode 100644 index 0000000..f33b737 --- /dev/null +++ b/lib/verity/verity_hash.c @@ -0,0 +1,444 @@ +/* + * dm-verity volume handling + * + * Copyright (C) 2012-2023 Red Hat, Inc. All rights reserved. + * + * This file is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +#include "verity.h" +#include "internal.h" + +#define VERITY_MAX_LEVELS 63 +#define VERITY_MAX_DIGEST_SIZE 1024 + +static unsigned get_bits_up(size_t u) +{ + unsigned i = 0; + while ((1U << i) < u) + i++; + return i; +} + +static unsigned get_bits_down(size_t u) +{ + unsigned i = 0; + while ((u >> i) > 1U) + i++; + return i; +} + +static int verify_zero(struct crypt_device *cd, FILE *wr, size_t bytes) +{ + char *block = NULL; + size_t i; + int r; + + block = malloc(bytes); + if (!block) + return -ENOMEM; + + if (fread(block, bytes, 1, wr) != 1) { + log_dbg(cd, "EIO while reading spare area."); + r = -EIO; + goto out; + } + for (i = 0; i < bytes; i++) + if (block[i]) { + log_err(cd, _("Spare area is not zeroed at position %" PRIu64 "."), + ftello(wr) - bytes); + r = -EPERM; + goto out; + } + r = 0; +out: + free(block); + return r; +} + +static int verify_hash_block(const char *hash_name, int version, + char *hash, size_t hash_size, + const char *data, size_t data_size, + const char *salt, size_t salt_size) +{ + struct crypt_hash *ctx = NULL; + int r; + + if (crypt_hash_init(&ctx, hash_name)) + return -EINVAL; + + if (version == 1 && (r = crypt_hash_write(ctx, salt, salt_size))) + goto out; + + if ((r = crypt_hash_write(ctx, data, data_size))) + goto out; + + if (version == 0 && (r = crypt_hash_write(ctx, salt, salt_size))) + goto out; + + r = crypt_hash_final(ctx, hash, hash_size); +out: + crypt_hash_destroy(ctx); + return r; +} + +static int hash_levels(size_t hash_block_size, size_t digest_size, + uint64_t data_file_blocks, uint64_t *hash_position, int *levels, + uint64_t *hash_level_block, uint64_t *hash_level_size) +{ + size_t hash_per_block_bits; + uint64_t s, s_shift; + int i; + + if (!digest_size) + return -EINVAL; + + hash_per_block_bits = get_bits_down(hash_block_size / digest_size); + if (!hash_per_block_bits) + return -EINVAL; + + *levels = 0; + while (hash_per_block_bits * *levels < 64 && + (data_file_blocks - 1) >> (hash_per_block_bits * *levels)) + (*levels)++; + + if (*levels > VERITY_MAX_LEVELS) + return -EINVAL; + + for (i = *levels - 1; i >= 0; i--) { + if (hash_level_block) + hash_level_block[i] = *hash_position; + // verity position of block data_file_blocks at level i + s_shift = (i + 1) * hash_per_block_bits; + if (s_shift > 63) + return -EINVAL; + s = (data_file_blocks + ((uint64_t)1 << s_shift) - 1) >> ((i + 1) * hash_per_block_bits); + if (hash_level_size) + hash_level_size[i] = s; + if ((*hash_position + s) < *hash_position) + return -EINVAL; + *hash_position += s; + } + + return 0; +} + +static int create_or_verify(struct crypt_device *cd, FILE *rd, FILE *wr, + uint64_t data_block, size_t data_block_size, + uint64_t hash_block, size_t hash_block_size, + uint64_t blocks, int version, + const char *hash_name, int verify, + char *calculated_digest, size_t digest_size, + const char *salt, size_t salt_size) +{ + char *left_block, *data_buffer; + char read_digest[VERITY_MAX_DIGEST_SIZE]; + size_t hash_per_block = 1 << get_bits_down(hash_block_size / digest_size); + size_t digest_size_full = 1 << get_bits_up(digest_size); + uint64_t blocks_to_write = (blocks + hash_per_block - 1) / hash_per_block; + uint64_t seek_rd, seek_wr; + size_t left_bytes; + unsigned i; + int r; + + if (digest_size > sizeof(read_digest)) + return -EINVAL; + + if (uint64_mult_overflow(&seek_rd, data_block, data_block_size) || + uint64_mult_overflow(&seek_wr, hash_block, hash_block_size)) { + log_err(cd, _("Device offset overflow.")); + return -EINVAL; + } + + if (fseeko(rd, seek_rd, SEEK_SET)) { + log_dbg(cd, "Cannot seek to requested position in data device."); + return -EIO; + } + + if (wr && fseeko(wr, seek_wr, SEEK_SET)) { + log_dbg(cd, "Cannot seek to requested position in hash device."); + return -EIO; + } + + left_block = malloc(hash_block_size); + data_buffer = malloc(data_block_size); + if (!left_block || !data_buffer) { + r = -ENOMEM; + goto out; + } + + memset(left_block, 0, hash_block_size); + while (blocks_to_write--) { + left_bytes = hash_block_size; + for (i = 0; i < hash_per_block; i++) { + if (!blocks) + break; + blocks--; + if (fread(data_buffer, data_block_size, 1, rd) != 1) { + log_dbg(cd, "Cannot read data device block."); + r = -EIO; + goto out; + } + + if (verify_hash_block(hash_name, version, + calculated_digest, digest_size, + data_buffer, data_block_size, + salt, salt_size)) { + r = -EINVAL; + goto out; + } + + if (!wr) + break; + if (verify) { + if (fread(read_digest, digest_size, 1, wr) != 1) { + log_dbg(cd, "Cannot read digest form hash device."); + r = -EIO; + goto out; + } + if (crypt_backend_memeq(read_digest, calculated_digest, digest_size)) { + log_err(cd, _("Verification failed at position %" PRIu64 "."), + ftello(rd) - data_block_size); + r = -EPERM; + goto out; + } + } else { + if (fwrite(calculated_digest, digest_size, 1, wr) != 1) { + log_dbg(cd, "Cannot write digest to hash device."); + r = -EIO; + goto out; + } + } + if (version == 0) { + left_bytes -= digest_size; + } else { + if (digest_size_full - digest_size) { + if (verify) { + r = verify_zero(cd, wr, digest_size_full - digest_size); + if (r) + goto out; + } else if (fwrite(left_block, digest_size_full - digest_size, 1, wr) != 1) { + log_dbg(cd, "Cannot write spare area to hash device."); + r = -EIO; + goto out; + } + } + left_bytes -= digest_size_full; + } + } + if (wr && left_bytes) { + if (verify) { + r = verify_zero(cd , wr, left_bytes); + if (r) + goto out; + } else if (fwrite(left_block, left_bytes, 1, wr) != 1) { + log_dbg(cd, "Cannot write remaining spare area to hash device."); + r = -EIO; + goto out; + } + } + } + r = 0; +out: + free(left_block); + free(data_buffer); + return r; +} + +static int VERITY_create_or_verify_hash(struct crypt_device *cd, bool verify, + struct crypt_params_verity *params, + char *root_hash, size_t digest_size) +{ + char calculated_digest[VERITY_MAX_DIGEST_SIZE]; + FILE *data_file = NULL; + FILE *hash_file = NULL, *hash_file_2; + uint64_t hash_level_block[VERITY_MAX_LEVELS]; + uint64_t hash_level_size[VERITY_MAX_LEVELS]; + uint64_t data_file_blocks; + uint64_t data_device_offset_max = 0, hash_device_offset_max = 0; + uint64_t hash_position = VERITY_hash_offset_block(params); + uint64_t dev_size; + int levels, i, r; + + log_dbg(cd, "Hash %s %s, data device %s, data blocks %" PRIu64 + ", hash_device %s, offset %" PRIu64 ".", + verify ? "verification" : "creation", params->hash_name, + device_path(crypt_data_device(cd)), params->data_size, + device_path(crypt_metadata_device(cd)), hash_position); + + if (digest_size > sizeof(calculated_digest)) + return -EINVAL; + + if (!params->data_size) { + r = device_size(crypt_data_device(cd), &dev_size); + if (r < 0) + return r; + + data_file_blocks = dev_size / params->data_block_size; + } else + data_file_blocks = params->data_size; + + if (uint64_mult_overflow(&data_device_offset_max, params->data_size, params->data_block_size)) { + log_err(cd, _("Device offset overflow.")); + return -EINVAL; + } + log_dbg(cd, "Data device size required: %" PRIu64 " bytes.", data_device_offset_max); + + if (hash_levels(params->hash_block_size, digest_size, data_file_blocks, &hash_position, + &levels, &hash_level_block[0], &hash_level_size[0])) { + log_err(cd, _("Hash area overflow.")); + return -EINVAL; + } + if (uint64_mult_overflow(&hash_device_offset_max, hash_position, params->hash_block_size)) { + log_err(cd, _("Device offset overflow.")); + return -EINVAL; + } + log_dbg(cd, "Hash device size required: %" PRIu64 " bytes.", + hash_device_offset_max - params->hash_area_offset); + log_dbg(cd, "Using %d hash levels.", levels); + + data_file = fopen(device_path(crypt_data_device(cd)), "r"); + if (!data_file) { + log_err(cd, _("Cannot open device %s."), + device_path(crypt_data_device(cd)) + ); + r = -EIO; + goto out; + } + + hash_file = fopen(device_path(crypt_metadata_device(cd)), verify ? "r" : "r+"); + if (!hash_file) { + log_err(cd, _("Cannot open device %s."), + device_path(crypt_metadata_device(cd))); + r = -EIO; + goto out; + } + + memset(calculated_digest, 0, digest_size); + + for (i = 0; i < levels; i++) { + if (!i) { + r = create_or_verify(cd, data_file, hash_file, + 0, params->data_block_size, + hash_level_block[i], params->hash_block_size, + data_file_blocks, params->hash_type, params->hash_name, verify, + calculated_digest, digest_size, params->salt, params->salt_size); + if (r) + goto out; + } else { + hash_file_2 = fopen(device_path(crypt_metadata_device(cd)), "r"); + if (!hash_file_2) { + log_err(cd, _("Cannot open device %s."), + device_path(crypt_metadata_device(cd))); + r = -EIO; + goto out; + } + r = create_or_verify(cd, hash_file_2, hash_file, + hash_level_block[i - 1], params->hash_block_size, + hash_level_block[i], params->hash_block_size, + hash_level_size[i - 1], params->hash_type, params->hash_name, verify, + calculated_digest, digest_size, params->salt, params->salt_size); + fclose(hash_file_2); + if (r) + goto out; + } + } + + if (levels) + r = create_or_verify(cd, hash_file, NULL, + hash_level_block[levels - 1], params->hash_block_size, + 0, params->hash_block_size, + 1, params->hash_type, params->hash_name, verify, + calculated_digest, digest_size, params->salt, params->salt_size); + else + r = create_or_verify(cd, data_file, NULL, + 0, params->data_block_size, + 0, params->hash_block_size, + data_file_blocks, params->hash_type, params->hash_name, verify, + calculated_digest, digest_size, params->salt, params->salt_size); +out: + if (verify) { + if (r) + log_err(cd, _("Verification of data area failed.")); + else { + log_dbg(cd, "Verification of data area succeeded."); + r = crypt_backend_memeq(root_hash, calculated_digest, digest_size) ? -EFAULT : 0; + if (r) + log_err(cd, _("Verification of root hash failed.")); + else + log_dbg(cd, "Verification of root hash succeeded."); + } + } else { + if (r == -EIO) + log_err(cd, _("Input/output error while creating hash area.")); + else if (r) + log_err(cd, _("Creation of hash area failed.")); + else { + fsync(fileno(hash_file)); + memcpy(root_hash, calculated_digest, digest_size); + } + } + + if (data_file) + fclose(data_file); + if (hash_file) + fclose(hash_file); + return r; +} + +/* Verify verity device using userspace crypto backend */ +int VERITY_verify(struct crypt_device *cd, + struct crypt_params_verity *verity_hdr, + const char *root_hash, + size_t root_hash_size) +{ + return VERITY_create_or_verify_hash(cd, 1, verity_hdr, CONST_CAST(char*)root_hash, root_hash_size); +} + +/* Create verity hash */ +int VERITY_create(struct crypt_device *cd, + struct crypt_params_verity *verity_hdr, + const char *root_hash, + size_t root_hash_size) +{ + unsigned pgsize = (unsigned)crypt_getpagesize(); + + if (verity_hdr->salt_size > 256) + return -EINVAL; + + if (verity_hdr->data_block_size > pgsize) + log_err(cd, _("WARNING: Kernel cannot activate device if data " + "block size exceeds page size (%u)."), pgsize); + + return VERITY_create_or_verify_hash(cd, 0, verity_hdr, CONST_CAST(char*)root_hash, root_hash_size); +} + +uint64_t VERITY_hash_blocks(struct crypt_device *cd, struct crypt_params_verity *params) +{ + uint64_t hash_position = 0; + int levels = 0; + + if (hash_levels(params->hash_block_size, crypt_get_volume_key_size(cd), + params->data_size, &hash_position, &levels, NULL, NULL)) + return 0; + + return (uint64_t)hash_position; +} |