summaryrefslogtreecommitdiffstats
path: root/lib/verity
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 08:06:26 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 08:06:26 +0000
commit1660d4b7a65d9ad2ce0deaa19d35579ca4084ac5 (patch)
tree6cf8220b628ebd2ccfc1375dd6516c6996e9abcc /lib/verity
parentInitial commit. (diff)
downloadcryptsetup-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.h63
-rw-r--r--lib/verity/rs_decode_char.c201
-rw-r--r--lib/verity/rs_encode_char.c173
-rw-r--r--lib/verity/verity.c416
-rw-r--r--lib/verity/verity.h87
-rw-r--r--lib/verity/verity_fec.c336
-rw-r--r--lib/verity/verity_hash.c444
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(&reg[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;
+}