diff --git a/Documentation/devicetree/bindings/barebox/barebox,state.rst b/Documentation/devicetree/bindings/barebox/barebox,state.rst index 438cc43..00fb592 100644 --- a/Documentation/devicetree/bindings/barebox/barebox,state.rst +++ b/Documentation/devicetree/bindings/barebox/barebox,state.rst @@ -29,7 +29,8 @@ * ``compatible``: should be ``barebox,state``; * ``magic``: A 32bit number used as a magic to identify the state -* ``backend``: describes where the data for this state is stored +* ``backend``: contains a phandle to the device/partition which holds the + actual state data. * ``backend-type``: should be ``raw`` or ``dtb``. Optional properties: @@ -39,9 +40,9 @@ e.g. ``hmac(sha256)``. Only used for ``raw``. * ``backend-stridesize``: Maximum size per copy of the data. Only important for non-MTD devices -* ``backend-storage-type``: Type of the storage. This has two options at the - moment. For MTD with erasing the correct type is ``circular``. For all other - devices and files, ``direct`` is the needed type. +* ``backend-storage-type``: Normally the correct storage type is detected auto- + matically. The circular backend supports the option ``noncircular`` to fall + back to an old storage format. Variable nodes -------------- @@ -77,19 +78,31 @@ magic = <0x27031977>; compatible = "barebox,state"; backend-type = "raw"; - backend = &eeprom, "partname:state"; + backend = &state_part; foo { - reg = <0x00 0x4>; - type = "uint32"; + reg = <0x00 0x4>; + type = "uint32"; default = <0x0>; }; bar { - reg = <0x10 0x4>; - type = "enum32"; + reg = <0x10 0x4>; + type = "enum32"; names = "baz", "qux"; - default = <1>; + default = <1>; + }; + }; + + &nand_flash { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + state_part: state@10000 { + label = "state"; + reg = <0x10000 0x10000>; + }; }; }; diff --git a/Documentation/user/state.rst b/Documentation/user/state.rst index 5dd5c48..73c4be8 100644 --- a/Documentation/user/state.rst +++ b/Documentation/user/state.rst @@ -23,16 +23,35 @@ Basically these are serializers. The raw serializer additionally supports a HMAC algorithm to detect manipulations. +The data is always stored in a logical unit called ``bucket``. A ``bucket`` has +its own size depending on some external contraints. These contraints are listed +in more detail below depending on the used memory type and storage backend. A +``bucket`` stores exactly one state. A default number of three buckets is used +to store data redundantely. + +Redundancy +---------- + +The state framework is safe against powerfailures during write operations. To +archieve that multiple buckets are stored to disk. When writing all buckets are +written in order. When reading, the buckets are read in order and the first +one found that passes CRC tests is used. When all data is read the buckets +containing invalid or outdated data are written with the data just read. Also +NAND blocks need cleanup due to excessive bitflips are rewritten in this step. +With this it is made sure that after successful initialization of a state the +data on the storage device is consistent and redundant. + Storage Backends ---------------- -The serialized data can be stored to different backends which are automatically -selected depending on the defined backend in the devicetree. Currently two -implementations exist, ``circular`` and ``direct``. ``circular`` writes the -data sequentially on the backend storage device. Each save is appended until -the storage area is full. It then erases the block and starts from offset 0. -``circular`` is used for MTD devices with erase functionality. ``direct`` -writes the data directly to the file without erasing. +The serialized data can be stored to different backends. Currently two +implementations exist, ``circular`` and ``direct``. The state framework automatically +selects the correct backend depending on the storage medium. Media requiring +erase operations (NAND, NOR flash) use the ``circular`` backend, others use the ``direct`` +backend. The purpose of the ``circular`` backend is to save erase cycles which may +wear out the flash blocks. It continuously fills eraseblocks with updated data +and only when an eraseblock if fully written erases it and starts over writing +new data to the same eraseblock again. For all backends multiple copies are written to handle read errors. diff --git a/commands/Kconfig b/commands/Kconfig index 43b8ded..ae2dc4b 100644 --- a/commands/Kconfig +++ b/commands/Kconfig @@ -1967,6 +1967,12 @@ Provides the "firmwareload" command which deals with devices which need firmware to work. It is also used to upload firmware to FPGA devices. +config CMD_KEYSTORE + depends on CRYPTO_KEYSTORE + bool + prompt "keystore" + help + keystore provides access to the barebox keystore. config CMD_LINUX_EXEC bool "linux exec" diff --git a/commands/Makefile b/commands/Makefile index edd713c..37486dc 100644 --- a/commands/Makefile +++ b/commands/Makefile @@ -93,6 +93,7 @@ obj-$(CONFIG_CMD_LET) += let.o obj-$(CONFIG_CMD_LN) += ln.o obj-$(CONFIG_CMD_CLK) += clk.o +obj-$(CONFIG_CMD_KEYSTORE) += keystore.o obj-$(CONFIG_CMD_TFTP) += tftp.o obj-$(CONFIG_CMD_FILETYPE) += filetype.o obj-$(CONFIG_CMD_BAREBOX_UPDATE)+= barebox-update.o diff --git a/commands/keystore.c b/commands/keystore.c new file mode 100644 index 0000000..52c4be2 --- /dev/null +++ b/commands/keystore.c @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include +#include + +static int do_keystore(int argc, char *argv[]) +{ + int opt; + int ret; + int do_remove = 0; + const char *name; + const char *file = NULL; + char *secret_str = NULL; + void *secret; + int s_len; + + while ((opt = getopt(argc, argv, "rs:f:")) > 0) { + switch (opt) { + case 'r': + do_remove = 1; + break; + case 's': + secret_str = optarg; + break; + case 'f': + file = optarg; + break; + default: + return COMMAND_ERROR_USAGE; + } + } + + if (argc == optind) + return COMMAND_ERROR_USAGE; + + if (!do_remove && !file && !secret_str) + return COMMAND_ERROR_USAGE; + + if (file && secret_str) + return COMMAND_ERROR_USAGE; + + name = argv[optind]; + + if (do_remove) { + keystore_forget_secret(name); + printf("forgotten secret for key %s\n", name); + return 0; + } + + if (file) { + ret = read_file_2(file, &s_len, (void *)&secret_str, FILESIZE_MAX); + if (ret) { + printf("Cannot open %s: %s\n", file, strerror(-ret)); + return 1; + } + } else if (secret_str) { + s_len = strlen(secret_str); + } + + if (s_len & 1) { + printf("invalid secret len. Must be whole bytes\n"); + return 1; + } + + secret = xzalloc(s_len / 2); + ret = hex2bin(secret, secret_str, s_len / 2); + if (ret) { + printf("Cannot convert %s to binary: %s\n", secret_str, strerror(-ret)); + return 1; + } + + ret = keystore_set_secret(name, secret, s_len / 2); + if (ret) + printf("cannot set secret for key %s: %s\n", name, strerror(-ret)); + else + printf("Added secret for key %s\n", name); + + free(secret); + + return ret ? 1 : 0; +} + +BAREBOX_CMD_HELP_START(keystore) +BAREBOX_CMD_HELP_TEXT("") +BAREBOX_CMD_HELP_TEXT("Options:") +BAREBOX_CMD_HELP_OPT("-r", "remove a key from the keystore") +BAREBOX_CMD_HELP_OPT("-s ", "set a key in the keystore") +BAREBOX_CMD_HELP_OPT("-f ", "set a key in the keystore, read secret from file") +BAREBOX_CMD_HELP_END + +BAREBOX_CMD_START(keystore) + .cmd = do_keystore, + BAREBOX_CMD_DESC("manage keys") + BAREBOX_CMD_OPTS("[-rsf] ") + BAREBOX_CMD_GROUP(CMD_GRP_MISC) + BAREBOX_CMD_HELP(cmd_keystore_help) +BAREBOX_CMD_END diff --git a/commands/state.c b/commands/state.c index 4b51759..c57a906 100644 --- a/commands/state.c +++ b/commands/state.c @@ -21,20 +21,27 @@ { int opt, ret = 0; struct state *state = NULL; - int do_save = 0; + int do_save = 0, do_load = 0; const char *statename = "state"; + int no_auth = 0; - while ((opt = getopt(argc, argv, "s")) > 0) { + while ((opt = getopt(argc, argv, "sln")) > 0) { switch (opt) { case 's': do_save = 1; break; + case 'l': + do_load = 1; + break; + case 'n': + no_auth = 1; + break; default: return COMMAND_ERROR_USAGE; } } - if (!do_save) { + if (!do_save && !do_load) { state_info(); return 0; } @@ -48,8 +55,14 @@ return -ENOENT; } - if (do_save) + if (do_load) { + if (no_auth) + ret = state_load_no_auth(state); + else + ret = state_load(state); + } else if (do_save) { ret = state_save(state); + } return ret; } diff --git a/common/state/Makefile b/common/state/Makefile index 3e0e2c6..fcf9add 100644 --- a/common/state/Makefile +++ b/common/state/Makefile @@ -1,9 +1,7 @@ obj-y += state.o obj-y += state_variables.o -obj-y += backend.o obj-y += backend_format_dtb.o obj-y += backend_format_raw.o obj-y += backend_storage.o obj-y += backend_bucket_direct.o obj-$(CONFIG_MTD) += backend_bucket_circular.o -obj-y += backend_bucket_cached.o diff --git a/common/state/backend.c b/common/state/backend.c deleted file mode 100644 index 5235bb0..0000000 --- a/common/state/backend.c +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2016 Pengutronix, Markus Pargmann - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2, as published by the Free Software Foundation. - * - * This program 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 General Public License for more details. - * - */ - -#include -#include -#include -#include -#include - -#include "state.h" - - -/** - * Save the state - * @param state - * @return - */ -int state_save(struct state *state) -{ - uint8_t *buf; - ssize_t len; - int ret; - struct state_backend *backend = &state->backend; - - if (!state->dirty) - return 0; - - ret = backend->format->pack(backend->format, state, &buf, &len); - if (ret) { - dev_err(&state->dev, "Failed to pack state with backend format %s, %d\n", - backend->format->name, ret); - return ret; - } - - ret = state_storage_write(&backend->storage, buf, len); - if (ret) { - dev_err(&state->dev, "Failed to write packed state, %d\n", ret); - goto out; - } - - state->dirty = 0; - -out: - free(buf); - return ret; -} - -/** - * state_load - Loads a state from the backend - * @param state The state that should be updated to contain the loaded data - * @return 0 on success, -errno on failure. If no state is loaded the previous - * values remain in the state. - * - * This function uses the registered storage backend to read data. All data that - * we read is checked for integrity by the formatter. After that we unpack the - * data into our state. - */ -int state_load(struct state *state) -{ - uint8_t *buf; - ssize_t len; - ssize_t len_hint = 0; - int ret; - struct state_backend *backend = &state->backend; - - if (backend->format->get_packed_len) - len_hint = backend->format->get_packed_len(backend->format, - state); - ret = state_storage_read(&backend->storage, backend->format, - state->magic, &buf, &len, len_hint); - if (ret) { - dev_err(&state->dev, "Failed to read state with format %s, %d\n", - backend->format->name, ret); - return ret; - } - - ret = backend->format->unpack(backend->format, state, buf, len); - if (ret) { - dev_err(&state->dev, "Failed to unpack read data with format %s although verified, %d\n", - backend->format->name, ret); - goto out; - } - - state->dirty = 0; - -out: - free(buf); - return ret; -} - -static int state_format_init(struct state_backend *backend, - struct device_d *dev, const char *backend_format, - struct device_node *node, const char *state_name) -{ - int ret; - - if (!strcmp(backend_format, "raw")) { - ret = backend_format_raw_create(&backend->format, node, - state_name, dev); - } else if (!strcmp(backend_format, "dtb")) { - ret = backend_format_dtb_create(&backend->format, dev); - } else { - dev_err(dev, "Invalid backend format %s\n", - backend_format); - return -EINVAL; - } - - if (ret && ret != -EPROBE_DEFER) - dev_err(dev, "Failed to initialize format %s, %d\n", - backend_format, ret); - - return ret; -} - -static void state_format_free(struct state_backend_format *format) -{ - if (format->free) - format->free(format); -} - -/** - * state_backend_init - Initiates the backend storage and format using the - * passed arguments - * @param backend state backend - * @param dev Device pointer used for prints - * @param node the DT device node corresponding to the state - * @param backend_format a string describing the format. Valid values are 'raw' - * and 'dtb' currently - * @param storage_path Path to the backend storage file/device/partition/... - * @param state_name Name of the state - * @param of_path Path in the devicetree - * @param stridesize stridesize in case we have a medium without eraseblocks. - * stridesize describes how far apart copies of the same data should be stored. - * For blockdevices it makes sense to align them on blocksize. - * @param storagetype Type of the storage backend. This may be NULL where we - * autoselect some backwardscompatible backend options - * @return 0 on success, -errno otherwise - */ -int state_backend_init(struct state_backend *backend, struct device_d *dev, - struct device_node *node, const char *backend_format, - const char *storage_path, const char *state_name, const - char *of_path, off_t offset, size_t max_size, - uint32_t stridesize, const char *storagetype) -{ - int ret; - - ret = state_format_init(backend, dev, backend_format, node, state_name); - if (ret) - return ret; - - ret = state_storage_init(&backend->storage, dev, storage_path, offset, - max_size, stridesize, storagetype); - if (ret) - goto out_free_format; - - backend->of_path = xstrdup(of_path); - - return 0; - -out_free_format: - state_format_free(backend->format); - backend->format = NULL; - - return ret; -} - -void state_backend_set_readonly(struct state_backend *backend) -{ - state_storage_set_readonly(&backend->storage); -} - -void state_backend_free(struct state_backend *backend) -{ - state_storage_free(&backend->storage); - if (backend->format) - state_format_free(backend->format); - free(backend->of_path); -} diff --git a/common/state/backend_bucket_cached.c b/common/state/backend_bucket_cached.c deleted file mode 100644 index ba0af7f..0000000 --- a/common/state/backend_bucket_cached.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2016 Pengutronix, Markus Pargmann - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2, as published by the Free Software Foundation. - * - * This program 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 General Public License for more details. - * - */ - -#include -#include "state.h" - -struct state_backend_storage_bucket_cache { - struct state_backend_storage_bucket bucket; - - struct state_backend_storage_bucket *raw; - - u8 *data; - ssize_t data_len; - bool force_write; - - /* For outputs */ - struct device_d *dev; -}; - -static inline struct state_backend_storage_bucket_cache - *get_bucket_cache(struct state_backend_storage_bucket *bucket) -{ - return container_of(bucket, - struct state_backend_storage_bucket_cache, - bucket); -} - -static inline void state_backend_bucket_cache_drop( - struct state_backend_storage_bucket_cache *cache) -{ - if (cache->data) { - free(cache->data); - cache->data = NULL; - cache->data_len = 0; - } -} - -static int state_backend_bucket_cache_fill( - struct state_backend_storage_bucket_cache *cache) -{ - int ret; - - ret = cache->raw->read(cache->raw, &cache->data, &cache->data_len); - if (ret == -EUCLEAN) { - cache->force_write = true; - ret = 0; - } - - return ret; -} - -static int state_backend_bucket_cache_read(struct state_backend_storage_bucket *bucket, - uint8_t ** buf_out, - ssize_t * len_hint) -{ - struct state_backend_storage_bucket_cache *cache = - get_bucket_cache(bucket); - int ret; - - if (!cache->data) { - ret = state_backend_bucket_cache_fill(cache); - if (ret) - return ret; - } - - if (cache->data) { - *buf_out = xmemdup(cache->data, cache->data_len); - if (!*buf_out) - return -ENOMEM; - *len_hint = cache->data_len; - } - - return 0; -} - -static int state_backend_bucket_cache_write(struct state_backend_storage_bucket *bucket, - const uint8_t * buf, ssize_t len) -{ - struct state_backend_storage_bucket_cache *cache = - get_bucket_cache(bucket); - int ret; - - if (!cache->force_write) { - if (!cache->data) - ret = state_backend_bucket_cache_fill(cache); - - if (cache->data_len == len && !memcmp(cache->data, buf, len)) - return 0; - } - - state_backend_bucket_cache_drop(cache); - - ret = cache->raw->write(cache->raw, buf, len); - if (ret) - return ret; - - cache->data = xmemdup(buf, len); - cache->data_len = len; - return 0; -} - -static int state_backend_bucket_cache_init( - struct state_backend_storage_bucket *bucket) -{ - struct state_backend_storage_bucket_cache *cache = - get_bucket_cache(bucket); - - if (cache->raw->init) { - return cache->raw->init(cache->raw); - } - - return 0; -} - -static void state_backend_bucket_cache_free( - struct state_backend_storage_bucket *bucket) -{ - struct state_backend_storage_bucket_cache *cache = - get_bucket_cache(bucket); - - state_backend_bucket_cache_drop(cache); - cache->raw->free(cache->raw); - free(cache); -} - -int state_backend_bucket_cached_create(struct device_d *dev, - struct state_backend_storage_bucket *raw, - struct state_backend_storage_bucket **out) -{ - struct state_backend_storage_bucket_cache *cache; - - cache = xzalloc(sizeof(*cache)); - cache->raw = raw; - cache->dev = dev; - - cache->bucket.free = state_backend_bucket_cache_free; - cache->bucket.read = state_backend_bucket_cache_read; - cache->bucket.write = state_backend_bucket_cache_write; - cache->bucket.init = state_backend_bucket_cache_init; - - *out = &cache->bucket; - - return 0; -} diff --git a/common/state/backend_bucket_circular.c b/common/state/backend_bucket_circular.c index 0bce900..5279ec9 100644 --- a/common/state/backend_bucket_circular.c +++ b/common/state/backend_bucket_circular.c @@ -25,7 +25,21 @@ #include "state.h" - +/* + * The circular backend bucket code. The circular backend bucket is intended + * for mtd devices which need an erase operation. + * + * Erasing blocks is an operation that should be avoided. On NOR flashes erasing + * blocks is very time consuming and on NAND flashes each block only has a limited + * number of erase cycles allowed. For this reason we continuously write more data + * into each eraseblock and only erase it when no more free space is available. + * Don't confuse these multiple writes into a single eraseblock with buckets. A bucket + * is the whole eraseblock, we just happen to reuse the same bucket for storing + * new data. + * + * If your device is a mtd device, but does not have eraseblocks, like MRAMs, then + * the direct bucket is used instead. + */ struct state_backend_storage_bucket_circular { struct state_backend_storage_bucket bucket; @@ -47,6 +61,11 @@ struct device_d *dev; }; +/* + * The metadata will be written directly before writesize aligned offsets. + * When searching backwards through the pages it allows us to find the + * beginning of the data. + */ struct __attribute__((__packed__)) state_backend_storage_bucket_circular_meta { uint32_t magic; uint32_t written_length; @@ -65,7 +84,7 @@ #ifdef __BAREBOX__ static int state_mtd_peb_read(struct state_backend_storage_bucket_circular *circ, - char *buf, int offset, int len) + void *buf, int offset, int len) { int ret; @@ -102,7 +121,7 @@ } static int state_mtd_peb_write(struct state_backend_storage_bucket_circular *circ, - const char *buf, int offset, int len) + const void *buf, int offset, int len) { int ret; @@ -133,7 +152,7 @@ } #else static int state_mtd_peb_read(struct state_backend_storage_bucket_circular *circ, - char *buf, int suboffset, int len) + void *buf, int suboffset, int len) { int ret; off_t offset = suboffset; @@ -152,40 +171,19 @@ dev_dbg(circ->dev, "Read state from %ld length %zd\n", offset, len); - ret = ioctl(circ->fd, ECCGETSTATS, &stat1); - if (ret) - nostats = true; ret = read_full(circ->fd, buf, len); if (ret < 0) { dev_err(circ->dev, "Failed to read circular storage len %zd, %d\n", len, ret); free(buf); - return ret; - } - - if (nostats) - return 0; - - ret = ioctl(circ->fd, ECCGETSTATS, &stat2); - if (ret) - return 0; - - if (stat2.failed - stat1.failed > 0) { - ret = -EUCLEAN; - dev_dbg(circ->dev, "PEB %u has ECC error, forcing rewrite\n", - circ->eraseblock); - } else if (stat2.corrected - stat1.corrected > 0) { - ret = -EUCLEAN; - dev_dbg(circ->dev, "PEB %u is unclean, forcing rewrite\n", - circ->eraseblock); } return ret; } static int state_mtd_peb_write(struct state_backend_storage_bucket_circular *circ, - const char *buf, int suboffset, int len) + const void *buf, int suboffset, int len) { int ret; off_t offset = suboffset; @@ -226,14 +224,14 @@ #endif static int state_backend_bucket_circular_read(struct state_backend_storage_bucket *bucket, - uint8_t ** buf_out, - ssize_t * len_hint) + void ** buf_out, + ssize_t * len_out) { struct state_backend_storage_bucket_circular *circ = get_bucket_circular(bucket); ssize_t read_len; off_t offset; - uint8_t *buf; + void *buf; int ret; /* Storage is empty */ @@ -282,13 +280,13 @@ } *buf_out = buf; - *len_hint = read_len - sizeof(struct state_backend_storage_bucket_circular_meta); + *len_out = read_len - sizeof(struct state_backend_storage_bucket_circular_meta); return ret; } static int state_backend_bucket_circular_write(struct state_backend_storage_bucket *bucket, - const uint8_t * buf, + const void * buf, ssize_t len) { struct state_backend_storage_bucket_circular *circ = @@ -297,7 +295,7 @@ struct state_backend_storage_bucket_circular_meta *meta; uint32_t written_length = ALIGN(len + sizeof(*meta), circ->writesize); int ret; - uint8_t *write_buf; + void *write_buf; if (written_length > circ->max_size) { dev_err(circ->dev, "Error, state data too big to be written, to write: %zd, writesize: %zd, length: %zd, available: %zd\n", @@ -379,26 +377,24 @@ int sub_offset; uint32_t written_length = 0; uint8_t *buf; + int ret; - buf = xmalloc(circ->writesize); + buf = xmalloc(circ->max_size); if (!buf) return -ENOMEM; + ret = state_mtd_peb_read(circ, buf, 0, circ->max_size); + if (ret && ret != -EUCLEAN) + return ret; + for (sub_offset = circ->max_size - circ->writesize; sub_offset >= 0; sub_offset -= circ->writesize) { - int ret; - - ret = state_mtd_peb_read(circ, buf, sub_offset, - circ->writesize); - if (ret && ret != -EUCLEAN) - return ret; - - ret = mtd_buf_all_ff(buf, circ->writesize); + ret = mtd_buf_all_ff(buf + sub_offset, circ->writesize); if (!ret) { struct state_backend_storage_bucket_circular_meta *meta; meta = (struct state_backend_storage_bucket_circular_meta *) - (buf + circ->writesize - sizeof(*meta)); + (buf + sub_offset + circ->writesize - sizeof(*meta)); if (meta->magic != circular_magic) written_length = 0; @@ -458,12 +454,14 @@ struct state_backend_storage_bucket **bucket, unsigned int eraseblock, ssize_t writesize, - struct mtd_info_user *mtd_uinfo, - bool lazy_init) + struct mtd_info_user *mtd_uinfo) { struct state_backend_storage_bucket_circular *circ; int ret; + if (writesize < 8) + writesize = 8; + circ = xzalloc(sizeof(*circ)); circ->eraseblock = eraseblock; circ->writesize = writesize; @@ -495,13 +493,9 @@ circ->bucket.free = state_backend_bucket_circular_free; *bucket = &circ->bucket; - if (!lazy_init) { - ret = state_backend_bucket_circular_init(*bucket); - if (ret) - goto out_free; - } else { - circ->bucket.init = state_backend_bucket_circular_init; - } + ret = state_backend_bucket_circular_init(*bucket); + if (ret) + goto out_free; return 0; diff --git a/common/state/backend_bucket_direct.c b/common/state/backend_bucket_direct.c index 5225433..4465ed0 100644 --- a/common/state/backend_bucket_direct.c +++ b/common/state/backend_bucket_direct.c @@ -46,14 +46,14 @@ } static int state_backend_bucket_direct_read(struct state_backend_storage_bucket - *bucket, uint8_t ** buf_out, - ssize_t * len_hint) + *bucket, void ** buf_out, + ssize_t * len_out) { struct state_backend_storage_bucket_direct *direct = get_bucket_direct(bucket); struct state_backend_storage_bucket_direct_meta meta; ssize_t read_len; - uint8_t *buf; + void *buf; int ret; ret = lseek(direct->fd, direct->offset, SEEK_SET); @@ -69,18 +69,13 @@ if (meta.magic == direct_magic) { read_len = meta.written_length; } else { - if (*len_hint) - read_len = *len_hint; - else - read_len = direct->max_size; + read_len = direct->max_size; ret = lseek(direct->fd, direct->offset, SEEK_SET); if (ret < 0) { dev_err(direct->dev, "Failed to seek file, %d\n", ret); return ret; } } - if (direct->max_size) - read_len = min(read_len, direct->max_size); buf = xmalloc(read_len); if (!buf) @@ -94,13 +89,13 @@ } *buf_out = buf; - *len_hint = read_len; + *len_out = read_len; return 0; } static int state_backend_bucket_direct_write(struct state_backend_storage_bucket - *bucket, const uint8_t * buf, + *bucket, const void * buf, ssize_t len) { struct state_backend_storage_bucket_direct *direct = @@ -108,7 +103,7 @@ int ret; struct state_backend_storage_bucket_direct_meta meta; - if (direct->max_size && len > direct->max_size) + if (len > direct->max_size - sizeof(meta)) return -E2BIG; ret = lseek(direct->fd, direct->offset, SEEK_SET); @@ -161,7 +156,6 @@ fd = open(path, O_RDWR); if (fd < 0) { dev_err(dev, "Failed to open file '%s', %d\n", path, -errno); - close(fd); return -errno; } diff --git a/common/state/backend_format_dtb.c b/common/state/backend_format_dtb.c index dc19c88..55fa1fc 100644 --- a/common/state/backend_format_dtb.c +++ b/common/state/backend_format_dtb.c @@ -39,13 +39,14 @@ } static int state_backend_format_dtb_verify(struct state_backend_format *format, - uint32_t magic, const uint8_t * buf, - ssize_t len) + uint32_t magic, const void * buf, + ssize_t *lenp, enum state_flags flags) { struct state_backend_format_dtb *fdtb = get_format_dtb(format); struct device_node *root; struct fdt_header *fdt = (struct fdt_header *)buf; size_t dtb_len = fdt32_to_cpu(fdt->totalsize); + size_t len = *lenp; if (dtb_len > len) { dev_err(fdtb->dev, "Error, stored DTB length (%d) longer than read buffer (%d)\n", @@ -67,18 +68,20 @@ fdtb->root = root; + *lenp = be32_to_cpu(fdt->totalsize); + return 0; } static int state_backend_format_dtb_unpack(struct state_backend_format *format, struct state *state, - const uint8_t * buf, ssize_t len) + const void * buf, ssize_t len) { struct state_backend_format_dtb *fdtb = get_format_dtb(format); int ret; if (!fdtb->root) { - state_backend_format_dtb_verify(format, 0, buf, len); + state_backend_format_dtb_verify(format, 0, buf, &len, 0); } ret = state_from_node(state, fdtb->root, 0); @@ -89,7 +92,7 @@ } static int state_backend_format_dtb_pack(struct state_backend_format *format, - struct state *state, uint8_t ** buf, + struct state *state, void ** buf, ssize_t * len) { struct state_backend_format_dtb *fdtb = get_format_dtb(format); diff --git a/common/state/backend_format_raw.c b/common/state/backend_format_raw.c index e028ea6..232856a 100644 --- a/common/state/backend_format_raw.c +++ b/common/state/backend_format_raw.c @@ -35,6 +35,10 @@ /* For outputs */ struct device_d *dev; + + char *secret_name; + int needs_secret; + char *algo; }; struct __attribute__((__packed__)) backend_raw_header { @@ -53,15 +57,53 @@ return container_of(format, struct state_backend_format_raw, format); } +static int backend_raw_digest_init(struct state_backend_format_raw *raw) +{ + const unsigned char *key; + int key_len; + int ret; + + if (!raw->digest) { + raw->digest = digest_alloc(raw->algo); + if (!raw->digest) { + dev_err(raw->dev, "algo %s not found\n", + raw->algo); + return -ENODEV; + } + raw->digest_length = digest_length(raw->digest); + } + + ret = keystore_get_secret(raw->secret_name, &key, &key_len); + if (ret) { + dev_err(raw->dev, "Could not get secret '%s'\n", + raw->secret_name); + return ret; + } + + ret = digest_set_key(raw->digest, key, key_len); + if (ret) + return ret; + + ret = digest_init(raw->digest); + if (ret) { + dev_err(raw->dev, "Failed to initialize digest: %s\n", + strerror(-ret)); + return ret; + } + + return 0; +} + static int backend_format_raw_verify(struct state_backend_format *format, - uint32_t magic, const uint8_t * buf, - ssize_t len) + uint32_t magic, const void * buf, + ssize_t *lenp, enum state_flags flags) { uint32_t crc; struct backend_raw_header *header; int d_len = 0; int ret; - const uint8_t *data; + const void *data; + ssize_t len = *lenp; struct state_backend_format_raw *backend_raw = get_format_raw(format); ssize_t complete_len; @@ -85,7 +127,11 @@ return -EINVAL; } - if (backend_raw->digest) { + if (backend_raw->algo && !(flags & STATE_FLAG_NO_AUTHENTIFICATION)) { + ret = backend_raw_digest_init(backend_raw); + if (ret) + return ret; + d_len = digest_length(backend_raw->digest); } @@ -105,26 +151,20 @@ return -EINVAL; } - if (backend_raw->digest) { - struct digest *d = backend_raw->digest; + *lenp = header->data_len + sizeof(*header); + + if (backend_raw->algo && !(flags & STATE_FLAG_NO_AUTHENTIFICATION)) { const void *hmac = data + header->data_len; - ret = digest_init(d); - if (ret) { - dev_err(backend_raw->dev, "Failed to initialize digest, %d\n", - ret); - return ret; - } - /* hmac over header and data */ - ret = digest_update(d, buf, sizeof(*header) + header->data_len); + ret = digest_update(backend_raw->digest, buf, sizeof(*header) + header->data_len); if (ret) { dev_err(backend_raw->dev, "Failed to update digest, %d\n", ret); return ret; } - ret = digest_verify(d, hmac); + ret = digest_verify(backend_raw->digest, hmac); if (ret < 0) { dev_err(backend_raw->dev, "Failed to verify data, hmac, %d\n", ret); @@ -136,12 +176,12 @@ } static int backend_format_raw_unpack(struct state_backend_format *format, - struct state *state, const uint8_t * buf, + struct state *state, const void * buf, ssize_t len) { struct state_variable *sv; const struct backend_raw_header *header; - const uint8_t *data; + const void *data; struct state_backend_format_raw *backend_raw = get_format_raw(format); header = (const struct backend_raw_header *)buf; @@ -160,7 +200,7 @@ } static int backend_format_raw_pack(struct state_backend_format *format, - struct state *state, uint8_t ** buf_out, + struct state *state, void ** buf_out, ssize_t * len_out) { struct state_backend_format_raw *backend_raw = get_format_raw(format); @@ -192,25 +232,20 @@ header->header_crc = crc32(0, header, sizeof(*header) - sizeof(uint32_t)); - if (backend_raw->digest) { - struct digest *d = backend_raw->digest; - - ret = digest_init(d); - if (ret) { - dev_err(backend_raw->dev, "Failed to initialize digest for packing, %d\n", - ret); - goto out_free; - } + if (backend_raw->algo) { + ret = backend_raw_digest_init(backend_raw); + if (ret) + return ret; /* hmac over header and data */ - ret = digest_update(d, buf, sizeof(*header) + size_data); + ret = digest_update(backend_raw->digest, buf, sizeof(*header) + size_data); if (ret) { dev_err(backend_raw->dev, "Failed to update digest for packing, %d\n", ret); goto out_free; } - ret = digest_final(d, hmac); + ret = digest_final(backend_raw->digest, hmac); if (ret < 0) { dev_err(backend_raw->dev, "Failed to finish digest for packing, %d\n", ret); @@ -240,11 +275,9 @@ struct device_node *root, const char *secret_name) { - struct digest *digest; struct property *p; const char *algo; - const unsigned char *key; - int key_len, ret; + int ret; p = of_find_property(root, "algo", NULL); if (!p) /* does not exist */ @@ -260,30 +293,7 @@ return -EINVAL; } - ret = keystore_get_secret(secret_name, &key, &key_len); - if (ret == -ENOENT) { /* -ENOENT == does not exist */ - dev_info(raw->dev, "Could not get secret '%s' - probe deferred\n", - secret_name); - return -EPROBE_DEFER; - } else if (ret) { - return ret; - } - - digest = digest_alloc(algo); - if (!digest) { - dev_info(raw->dev, "algo %s not found - probe deferred\n", - algo); - return -EPROBE_DEFER; - } - - ret = digest_set_key(digest, key, key_len); - if (ret) { - digest_free(digest); - return ret; - } - - raw->digest = digest; - raw->digest_length = digest_length(digest); + raw->algo = xstrdup(algo); return 0; } @@ -301,15 +311,14 @@ raw->dev = dev; ret = backend_format_raw_init_digest(raw, node, secret_name); - if (ret == -EPROBE_DEFER) { - return ret; - } else if (ret) { + if (ret) { dev_err(raw->dev, "Failed initializing digest for raw format, %d\n", ret); free(raw); return ret; } + raw->secret_name = xstrdup(secret_name); raw->format.pack = backend_format_raw_pack; raw->format.unpack = backend_format_raw_unpack; raw->format.verify = backend_format_raw_verify; diff --git a/common/state/backend_storage.c b/common/state/backend_storage.c index 5dc8c50..9ed6ad7 100644 --- a/common/state/backend_storage.c +++ b/common/state/backend_storage.c @@ -25,24 +25,29 @@ #include "state.h" -const unsigned int min_copies_written = 1; +/* + * The state framework stores data in so called buckets. A bucket is + * exactly one copy of the state we want to store. On flash type media + * a bucket corresponds to a single eraseblock. On media which do not + * need an erase operation a bucket corresponds to a storage area of + * @stridesize bytes. + * + * For redundancy and to make sure that we have valid data on the storage + * device at any time the state framework stores multiple buckets. The strategy + * is as follows: + * + * When loading the state from the storage we iterate over the buckets. We + * take the first one we find which has valid crcs. The next step is to + * restore consistency between the different buckets. This means rewriting + * a bucket when it signalled it needs refresh (i.e. returned -EUCLEAN) + * or when contains data different from the bucket we use. + * + * When the state backend initialized successfully we already restored + * consistency which means all buckets contain the same data. This means + * when storing a new state we can just write all buckets in order. + */ -static int bucket_lazy_init(struct state_backend_storage_bucket *bucket) -{ - int ret; - - if (bucket->initialized) - return 0; - - if (bucket->init) { - ret = bucket->init(bucket); - if (ret) - return ret; - } - bucket->initialized = true; - - return 0; -} +static const unsigned int min_buckets_written = 1; /** * state_storage_write - Writes the given data to the storage @@ -55,60 +60,64 @@ * operation on all of them. Writes are always in the same sequence. This * ensures, that reading in the same sequence will always return the latest * written valid data first. - * We try to at least write min_copies_written. If this fails we return with an + * We try to at least write min_buckets_written. If this fails we return with an * error. */ int state_storage_write(struct state_backend_storage *storage, - const uint8_t * buf, ssize_t len) + const void * buf, ssize_t len) { struct state_backend_storage_bucket *bucket; int ret; - int copies_written = 0; + int buckets_written = 0; if (storage->readonly) return 0; list_for_each_entry(bucket, &storage->buckets, bucket_list) { - ret = bucket_lazy_init(bucket); - if (ret) { - dev_warn(storage->dev, "Failed to init bucket/write state backend bucket, %d\n", - ret); - continue; - } - ret = bucket->write(bucket, buf, len); if (ret) { dev_warn(storage->dev, "Failed to write state backend bucket, %d\n", ret); } else { - ++copies_written; + ++buckets_written; } } - if (copies_written >= min_copies_written) + if (buckets_written >= min_buckets_written) return 0; dev_err(storage->dev, "Failed to write state to at least %d buckets. Successfully written to %d buckets\n", - min_copies_written, copies_written); + min_buckets_written, buckets_written); return -EIO; } -/** - * state_storage_restore_consistency - Restore consistency on all storage backends - * @param storage Storage object - * @param buf Buffer with valid data that should be on all buckets after this operation - * @param len Length of the buffer - * @return 0 on success, -errno otherwise - * - * This function brings valid data onto all buckets we have to ensure that all - * data copies are in sync. In the current implementation we just write the data - * to all buckets. Bucket implementations that need to keep the number of writes - * low, can read their own copy first and compare it. - */ -int state_storage_restore_consistency(struct state_backend_storage *storage, - const uint8_t * buf, ssize_t len) +static int bucket_refresh(struct state_backend_storage *storage, + struct state_backend_storage_bucket *bucket, void *buf, ssize_t len) { - return state_storage_write(storage, buf, len); + int ret; + + if (bucket->needs_refresh) + goto refresh; + + if (bucket->len != len) + goto refresh; + + if (memcmp(bucket->buf, buf, len)) + goto refresh; + + return 0; + +refresh: + ret = bucket->write(bucket, buf, len); + + if (ret) + dev_warn(storage->dev, "Failed to restore bucket %d@0x%08lx\n", + bucket->num, bucket->offset); + else + dev_info(storage->dev, "restored bucket %d@0x%08lx\n", + bucket->num, bucket->offset); + + return ret; } /** @@ -118,7 +127,7 @@ * @param magic state magic value * @param buf The newly allocated data area will be stored in this pointer * @param len The resulting length of the buffer - * @param len_hint Hint of how big the data may be. + * @param flags flags controlling how to load state * @return 0 on success, -errno otherwise. buf and len will be set to valid * values on success. * @@ -129,43 +138,65 @@ */ int state_storage_read(struct state_backend_storage *storage, struct state_backend_format *format, - uint32_t magic, uint8_t ** buf, ssize_t * len, - ssize_t len_hint) + uint32_t magic, void **buf, ssize_t *len, + enum state_flags flags) { - struct state_backend_storage_bucket *bucket; + struct state_backend_storage_bucket *bucket, *bucket_used = NULL; int ret; + /* + * Iterate over all buckets. The first valid one we find is the + * one we want to use. + */ list_for_each_entry(bucket, &storage->buckets, bucket_list) { - *len = len_hint; - ret = bucket_lazy_init(bucket); - if (ret) { - dev_warn(storage->dev, "Failed to init bucket/read state backend bucket, %d\n", - ret); + ret = bucket->read(bucket, &bucket->buf, &bucket->len); + if (ret == -EUCLEAN) + bucket->needs_refresh = 1; + else if (ret) continue; - } - ret = bucket->read(bucket, buf, len); - if (ret) { - dev_warn(storage->dev, "Failed to read from state backend bucket, trying next, %d\n", - ret); - continue; - } - ret = format->verify(format, magic, *buf, *len); - if (!ret) { - goto found; - } - free(*buf); - dev_warn(storage->dev, "Failed to verify read copy, trying next bucket, %d\n", - ret); + /* + * Verify the buffer crcs. The buffer length is passed in the len argument, + * .verify overwrites it with the length actually used. + */ + ret = format->verify(format, magic, bucket->buf, &bucket->len, flags); + if (!ret && !bucket_used) + bucket_used = bucket; } - dev_err(storage->dev, "Failed to find any valid state copy in any bucket\n"); + if (!bucket_used) { + dev_err(storage->dev, "Failed to find any valid state copy in any bucket\n"); - return -ENOENT; + return -ENOENT; + } -found: - /* A failed restore consistency is not a failure of reading the state */ - state_storage_restore_consistency(storage, *buf, *len); + dev_info(storage->dev, "Using bucket %d@0x%08lx\n", bucket_used->num, bucket_used->offset); + + /* + * Restore/refresh all buckets except the one we currently use (in case + * it's the only usable bucket at the moment) + */ + list_for_each_entry(bucket, &storage->buckets, bucket_list) { + if (bucket == bucket_used) + continue; + + ret = bucket_refresh(storage, bucket, bucket_used->buf, bucket_used->len); + + /* Free buffer from the unused buckets */ + free(bucket->buf); + bucket->buf = NULL; + } + + /* + * Restore/refresh the bucket we currently use + */ + ret = bucket_refresh(storage, bucket_used, bucket_used->buf, bucket_used->len); + + *buf = bucket_used->buf; + *len = bucket_used->len; + + /* buffer from the used bucket is passed to the caller, do not free */ + bucket_used->buf = NULL; return 0; } @@ -187,267 +218,127 @@ return ret; } -#ifdef __BAREBOX__ -#define STAT_GIVES_SIZE(s) (S_ISREG(s.st_mode) || S_ISCHR(s.st_mode)) -#define BLKGET_GIVES_SIZE(s) 0 -#else -#define STAT_GIVES_SIZE(s) (S_ISREG(s.st_mode)) -#define BLKGET_GIVES_SIZE(s) (S_ISBLK(s.st_mode)) -#endif -#ifndef BLKGETSIZE64 -#define BLKGETSIZE64 -1 -#endif - -static int state_backend_storage_get_size(const char *path, size_t * out_size) -{ - struct mtd_info_user meminfo; - struct stat s; - int ret; - - ret = stat(path, &s); - if (ret) - return -errno; - - /* - * under Linux, stat() gives the size only on regular files - * under barebox, it works on char dev, too - */ - if (STAT_GIVES_SIZE(s)) { - *out_size = s.st_size; - return 0; - } - - /* this works under Linux on block devs */ - if (BLKGET_GIVES_SIZE(s)) { - int fd; - - fd = open(path, O_RDONLY); - if (fd < 0) - return -errno; - - ret = ioctl(fd, BLKGETSIZE64, out_size); - close(fd); - if (!ret) - return 0; - } - - /* try mtd next */ - ret = mtd_get_meminfo(path, &meminfo); - if (!ret) { - *out_size = meminfo.size; - return 0; - } - - return ret; -} - -/* Number of copies that should be allocated */ -const int desired_copies = 3; +/* Number of buckets that should be used */ +static const int desired_buckets = 3; /** * state_storage_mtd_buckets_init - Creates storage buckets for mtd devices * @param storage Storage object * @param meminfo Info about the mtd device - * @param path Path to the device - * @param non_circular Use non-circular mode to write data that is compatible with the old on-flash format - * @param dev_offset Offset to start at in the device. - * @param max_size Maximum size to use for data. May be 0 for infinite. + * @param circular If false, use non-circular mode to write data that is compatible with the old on-flash format * @return 0 on success, -errno otherwise * - * Starting from offset 0 this function tries to create circular buckets on - * different offsets in the device. Different copies of the data are located in - * different eraseblocks. - * For MTD devices we use circular buckets to minimize the number of erases. - * Circular buckets write new data always in the next free space. + * This function iterates over the eraseblocks and creates one bucket on + * each eraseblock until we have the number of desired buckets. Bad blocks + * will be skipped and the next block will be used. */ static int state_storage_mtd_buckets_init(struct state_backend_storage *storage, - struct mtd_info_user *meminfo, - const char *path, bool non_circular, - off_t dev_offset, size_t max_size) + struct mtd_info_user *meminfo, bool circular) { struct state_backend_storage_bucket *bucket; - ssize_t end = dev_offset + max_size; - int nr_copies = 0; + ssize_t end = storage->offset + storage->max_size; + int n_buckets = 0; off_t offset; + ssize_t writesize; if (!end || end > meminfo->size) end = meminfo->size; - if (!IS_ALIGNED(dev_offset, meminfo->erasesize)) { + if (!IS_ALIGNED(storage->offset, meminfo->erasesize)) { dev_err(storage->dev, "Offset within the device is not aligned to eraseblocks. Offset is %ld, erasesize %zu\n", - dev_offset, meminfo->erasesize); + storage->offset, meminfo->erasesize); return -EINVAL; } - for (offset = dev_offset; offset < end; offset += meminfo->erasesize) { + if (circular) + writesize = meminfo->writesize; + else + writesize = meminfo->erasesize; + + for (offset = storage->offset; offset < end; offset += meminfo->erasesize) { int ret; - ssize_t writesize = meminfo->writesize; unsigned int eraseblock = offset / meminfo->erasesize; - bool lazy_init = true; - if (non_circular) - writesize = meminfo->erasesize; - - ret = state_backend_bucket_circular_create(storage->dev, path, + ret = state_backend_bucket_circular_create(storage->dev, storage->path, &bucket, eraseblock, writesize, - meminfo, - lazy_init); - if (ret) { - dev_warn(storage->dev, "Failed to create bucket at '%s' eraseblock %u\n", - path, eraseblock); + meminfo); + if (ret) continue; - } - ret = state_backend_bucket_cached_create(storage->dev, bucket, - &bucket); - if (ret) { - dev_warn(storage->dev, "Failed to setup cache bucket, continuing without cache, %d\n", - ret); - } + bucket->offset = offset; + bucket->num = n_buckets; list_add_tail(&bucket->bucket_list, &storage->buckets); - ++nr_copies; - if (nr_copies >= desired_copies) + ++n_buckets; + if (n_buckets >= desired_buckets) return 0; } - if (!nr_copies) { + if (!n_buckets) { dev_err(storage->dev, "Failed to initialize any state storage bucket\n"); return -EIO; } dev_warn(storage->dev, "Failed to initialize desired amount of buckets, only %d of %d succeeded\n", - nr_copies, desired_copies); + n_buckets, desired_buckets); return 0; } -static int state_storage_file_create(struct device_d *dev, const char *path, - size_t fd_size) -{ - int fd; - uint8_t *buf; - int ret; - - fd = open(path, O_RDWR | O_CREAT, 0600); - if (fd < 0) { - dev_err(dev, "Failed to open/create file '%s', %d\n", path, - -errno); - return -errno; - } - - buf = xzalloc(fd_size); - if (!buf) { - ret = -ENOMEM; - goto out_close; - } - - ret = write_full(fd, buf, fd_size); - if (ret < 0) { - dev_err(dev, "Failed to initialize empty file '%s', %d\n", path, - ret); - goto out_free; - } - ret = 0; - -out_free: - free(buf); -out_close: - close(fd); - return ret; -} - /** * state_storage_file_buckets_init - Create buckets for a conventional file descriptor * @param storage Storage object - * @param path Path to file/device - * @param dev_offset Offset in the device to start writing at. - * @param max_size Maximum size of the data. May be 0 for infinite. - * @param stridesize How far apart the different data copies are placed. If - * stridesize is 0, only one copy can be created. * @return 0 on success, -errno otherwise * - * For blockdevices and other regular files we create direct buckets beginning - * at offset 0. Direct buckets are simple and write data always to offset 0. + * direct buckets are simpler than circular buckets and can be used on blockdevices + * and mtd devices that don't need erase (MRAM). Also used for EEPROMs. */ -static int state_storage_file_buckets_init(struct state_backend_storage *storage, - const char *path, off_t dev_offset, - size_t max_size, uint32_t stridesize) +static int state_storage_file_buckets_init(struct state_backend_storage *storage) { struct state_backend_storage_bucket *bucket; - size_t fd_size = 0; - int ret; + int ret, n; off_t offset; - int nr_copies = 0; - - ret = state_backend_storage_get_size(path, &fd_size); - if (ret) { - if (ret != -ENOENT) { - dev_err(storage->dev, "Failed to get the filesize of '%s', %d\n", - path, ret); - return ret; - } - if (!stridesize) { - dev_err(storage->dev, "File '%s' does not exist and no information about the needed size. Please specify stridesize\n", - path); - return ret; - } - - if (max_size) - fd_size = min(dev_offset + stridesize * desired_copies, - dev_offset + max_size); - else - fd_size = dev_offset + stridesize * desired_copies; - dev_info(storage->dev, "File '%s' does not exist, creating file of size %zd\n", - path, fd_size); - ret = state_storage_file_create(storage->dev, path, fd_size); - if (ret) { - dev_info(storage->dev, "Failed to create file '%s', %d\n", - path, ret); - return ret; - } - } else if (max_size) { - fd_size = min(fd_size, (size_t)dev_offset + max_size); - } + int n_buckets = 0; + uint32_t stridesize = storage->stridesize; + size_t max_size = storage->max_size; if (!stridesize) { - dev_warn(storage->dev, "WARNING, no stridesize given although we use a direct file write. Starting in degraded mode\n"); - stridesize = fd_size; + dev_err(storage->dev, "stridesize unspecified\n"); + return -EINVAL; } - for (offset = dev_offset; offset < fd_size; offset += stridesize) { - size_t maxsize = min((size_t)stridesize, - (size_t)(fd_size - offset)); + if (max_size && max_size < desired_buckets * stridesize) { + dev_err(storage->dev, "device is too small to hold %d copies\n", desired_buckets); + return -EINVAL; + } - ret = state_backend_bucket_direct_create(storage->dev, path, + for (n = 0; n < desired_buckets; n++) { + offset = storage->offset + n * stridesize; + ret = state_backend_bucket_direct_create(storage->dev, storage->path, &bucket, offset, - maxsize); + stridesize); if (ret) { dev_warn(storage->dev, "Failed to create direct bucket at '%s' offset %ld\n", - path, offset); + storage->path, offset); continue; } - ret = state_backend_bucket_cached_create(storage->dev, bucket, - &bucket); - if (ret) { - dev_warn(storage->dev, "Failed to setup cache bucket, continuing without cache, %d\n", - ret); - } + bucket->offset = offset; + bucket->num = n_buckets; list_add_tail(&bucket->bucket_list, &storage->buckets); - ++nr_copies; - if (nr_copies >= desired_copies) - return 0; + ++n_buckets; } - if (!nr_copies) { + if (!n_buckets) { dev_err(storage->dev, "Failed to initialize any state direct storage bucket\n"); return -EIO; } - dev_warn(storage->dev, "Failed to initialize desired amount of direct buckets, only %d of %d succeeded\n", - nr_copies, desired_copies); + + if (n_buckets < desired_buckets) + dev_warn(storage->dev, "Failed to initialize desired amount of direct buckets, only %d of %d succeeded\n", + n_buckets, desired_buckets); return 0; } @@ -455,7 +346,6 @@ /** * state_storage_init - Init backend storage - * @param storage Storage object * @param path Path to the backend storage file * @param dev_offset Offset in the device to start writing at. * @param max_size Maximum size of the data. May be 0 for infinite. @@ -466,37 +356,39 @@ * * Depending on the filetype, we create mtd buckets or normal file buckets. */ -int state_storage_init(struct state_backend_storage *storage, - struct device_d *dev, const char *path, +int state_storage_init(struct state *state, const char *path, off_t offset, size_t max_size, uint32_t stridesize, const char *storagetype) { + struct state_backend_storage *storage = &state->storage; int ret = -ENODEV; struct mtd_info_user meminfo; INIT_LIST_HEAD(&storage->buckets); - storage->dev = dev; + storage->dev = &state->dev; storage->name = storagetype; storage->stridesize = stridesize; + storage->offset = offset; + storage->max_size = max_size; + storage->path = xstrdup(path); if (IS_ENABLED(CONFIG_MTD)) ret = mtd_get_meminfo(path, &meminfo); if (!ret && !(meminfo.flags & MTD_NO_ERASE)) { - bool non_circular = false; - if (!storagetype) { - non_circular = true; - } else if (strcmp(storagetype, "circular")) { - dev_warn(storage->dev, "Unknown storagetype '%s', falling back to old format circular storage type.\n", - storagetype); - non_circular = true; + bool circular; + if (!storagetype || !strcmp(storagetype, "circular")) { + circular = true; + } else if (!strcmp(storagetype, "noncircular")) { + dev_warn(storage->dev, "using old format circular storage type.\n"); + circular = false; + } else { + dev_warn(storage->dev, "unknown storage type '%s'\n", storagetype); + return -EINVAL; } - return state_storage_mtd_buckets_init(storage, &meminfo, path, - non_circular, offset, - max_size); + return state_storage_mtd_buckets_init(storage, &meminfo, circular); } else { - return state_storage_file_buckets_init(storage, path, offset, - max_size, stridesize); + return state_storage_file_buckets_init(storage); } dev_err(storage->dev, "storage init done\n"); @@ -524,4 +416,6 @@ list_del(&bucket->bucket_list); bucket->free(bucket); } + + free(storage->path); } diff --git a/common/state/state.c b/common/state/state.c index 02bb1bb..8369aed 100644 --- a/common/state/state.c +++ b/common/state/state.c @@ -34,6 +34,125 @@ /* list of all registered state instances */ static LIST_HEAD(state_list); +/** + * Save the state + * @param state + * @return + */ +int state_save(struct state *state) +{ + void *buf; + ssize_t len; + int ret; + + if (!state->dirty) + return 0; + + ret = state->format->pack(state->format, state, &buf, &len); + if (ret) { + dev_err(&state->dev, "Failed to pack state with backend format %s, %d\n", + state->format->name, ret); + return ret; + } + + ret = state_storage_write(&state->storage, buf, len); + if (ret) { + dev_err(&state->dev, "Failed to write packed state, %d\n", ret); + goto out; + } + + state->dirty = 0; + +out: + free(buf); + return ret; +} + +/** + * state_load - Loads a state from the backend + * @param state The state that should be updated to contain the loaded data + * @return 0 on success, -errno on failure. If no state is loaded the previous + * values remain in the state. + * + * This function uses the registered storage backend to read data. All data that + * we read is checked for integrity by the formatter. After that we unpack the + * data into our state. + */ +static int state_do_load(struct state *state, enum state_flags flags) +{ + void *buf; + ssize_t len; + int ret; + + ret = state_storage_read(&state->storage, state->format, + state->magic, &buf, &len, flags); + if (ret) { + dev_err(&state->dev, "Failed to read state with format %s, %d\n", + state->format->name, ret); + return ret; + } + + ret = state->format->unpack(state->format, state, buf, len); + if (ret) { + dev_err(&state->dev, "Failed to unpack read data with format %s although verified, %d\n", + state->format->name, ret); + goto out; + } + + state->dirty = 0; + +out: + free(buf); + return ret; +} + +int state_load(struct state *state) +{ + return state_do_load(state, 0); +} + +int state_load_no_auth(struct state *state) +{ + return state_do_load(state, STATE_FLAG_NO_AUTHENTIFICATION); +} + +static int state_format_init(struct state *state, const char *backend_format, + struct device_node *node, const char *state_name) +{ + int ret; + + if (!backend_format || !strcmp(backend_format, "raw")) { + ret = backend_format_raw_create(&state->format, node, + state_name, &state->dev); + } else if (!strcmp(backend_format, "dtb")) { + ret = backend_format_dtb_create(&state->format, &state->dev); + } else { + dev_err(&state->dev, "Invalid backend format %s\n", + backend_format); + return -EINVAL; + } + + if (ret && ret != -EPROBE_DEFER) + dev_err(&state->dev, "Failed to initialize format %s, %d\n", + backend_format, ret); + + return ret; +} + +static void state_format_free(struct state_backend_format *format) +{ + if (!format) + return; + + if (format->free) + format->free(format); +} + +void state_backend_set_readonly(struct state *state) +{ + state_storage_set_readonly(&state->storage); +} + static struct state *state_new(const char *name) { struct state *state; @@ -152,7 +271,6 @@ sv->name = name; sv->start = start_size[0]; - sv->type = vtype->type; state_add_var(state, sv); } else { sv = state_find_var(state, name); @@ -328,21 +446,21 @@ } /* backend-type */ - if (!state->backend.format) { + if (!state->format) { ret = -ENODEV; goto out; } p = of_new_property(new_node, "backend-type", - state->backend.format->name, - strlen(state->backend.format->name) + 1); + state->format->name, + strlen(state->format->name) + 1); if (!p) { ret = -ENOMEM; goto out; } /* backend phandle */ - backend_node = of_find_node_by_path_from(root, state->backend.of_path); + backend_node = of_find_node_by_devpath(root, state->backend_path); if (!backend_node) { ret = -ENODEV; goto out; @@ -353,9 +471,9 @@ if (ret) goto out; - if (!strcmp("raw", state->backend.format->name)) { + if (!strcmp("raw", state->format->name)) { struct digest *digest = - state_backend_format_raw_get_digest(state->backend.format); + state_backend_format_raw_get_digest(state->format); if (digest) { p = of_new_property(new_node, "algo", digest_name(digest), @@ -367,19 +485,19 @@ } } - if (state->backend.storage.name) { + if (state->storage.name) { p = of_new_property(new_node, "backend-storage-type", - state->backend.storage.name, - strlen(state->backend.storage.name) + 1); + state->storage.name, + strlen(state->storage.name) + 1); if (!p) { ret = -ENOMEM; goto out; } } - if (state->backend.storage.stridesize) { + if (state->storage.stridesize) { ret = of_property_write_u32(new_node, "backend-stridesize", - state->backend.storage.stridesize); + state->storage.stridesize); if (ret) goto out; } @@ -408,7 +526,9 @@ of_unregister_fixup(of_state_fixup, state); list_del(&state->list); unregister_device(&state->dev); - state_backend_free(&state->backend); + state_storage_free(&state->storage); + state_format_free(state->format); + free(state->backend_path); free(state->of_path); free(state); } @@ -452,19 +572,16 @@ } if (!path) { - /* guess if of_path is a path, not a phandle */ - if (of_path[0] == '/' && len > 1) { - ret = of_find_path(node, "backend", &path, 0); - } else { - struct device_node *partition_node; + struct device_node *partition_node; - partition_node = of_parse_phandle(node, "backend", 0); - if (!partition_node) - goto out_release_state; - - of_path = partition_node->full_name; - ret = of_find_path_by_node(partition_node, &path, 0); + partition_node = of_parse_phandle(node, "backend", 0); + if (!partition_node) { + dev_err(&state->dev, "Cannot resolve \"backend\" phandle\n"); + goto out_release_state; } + + of_path = partition_node->full_name; + ret = of_find_path_by_node(partition_node, &path, 0); if (ret) { if (ret != -EPROBE_DEFER) dev_err(&state->dev, "state failed to parse path to backend: %s\n", @@ -473,6 +590,8 @@ } } + state->backend_path = xstrdup(path); + ret = of_property_read_string(node, "backend-type", &backend_type); if (ret) { goto out_release_state; @@ -490,14 +609,17 @@ dev_info(&state->dev, "No backend-storage-type found, using default.\n"); } - ret = state_backend_init(&state->backend, &state->dev, node, - backend_type, path, alias, of_path, offset, + ret = state_format_init(state, backend_type, node, alias); + if (ret) + goto out_release_state; + + ret = state_storage_init(state, path, offset, max_size, stridesize, storage_type); if (ret) goto out_release_state; if (readonly) - state_backend_set_readonly(&state->backend); + state_backend_set_readonly(state); ret = state_from_node(state, node, 1); if (ret) { @@ -509,11 +631,6 @@ goto out_release_state; } - ret = state_load(state); - if (ret) { - dev_warn(&state->dev, "Failed to load persistent state, continuing with defaults, %d\n", ret); - } - dev_info(&state->dev, "New state registered '%s'\n", alias); return state; @@ -572,10 +689,10 @@ list_for_each_entry(state, &state_list, list) { printf("%-20s ", state->name); - if (state->backend.format) + if (state->format) printf("(backend: %s, path: %s)\n", - state->backend.format->name, - state->backend.of_path); + state->format->name, + state->backend_path); else printf("(no backend)\n"); } diff --git a/common/state/state.h b/common/state/state.h index bc6917d..81aaec2 100644 --- a/common/state/state.h +++ b/common/state/state.h @@ -5,6 +5,10 @@ struct state; struct mtd_info_user; +enum state_flags { + STATE_FLAG_NO_AUTHENTIFICATION = (1 << 0), +}; + /** * state_backend_storage_bucket - This class describes a single backend storage * object copy @@ -20,15 +24,20 @@ * @bucket_list A list element struct to attach this bucket to a list */ struct state_backend_storage_bucket { - int (*init) (struct state_backend_storage_bucket * bucket); int (*write) (struct state_backend_storage_bucket * bucket, - const uint8_t * buf, ssize_t len); + const void * buf, ssize_t len); int (*read) (struct state_backend_storage_bucket * bucket, - uint8_t ** buf, ssize_t * len_hint); + void ** buf, ssize_t * len_hint); void (*free) (struct state_backend_storage_bucket * bucket); - bool initialized; + int num; + off_t offset; + struct list_head bucket_list; + + void *buf; + ssize_t len; + bool needs_refresh; }; /** @@ -48,13 +57,11 @@ */ struct state_backend_format { int (*verify) (struct state_backend_format * format, uint32_t magic, - const uint8_t * buf, ssize_t len); + const void * buf, ssize_t *lenp, enum state_flags flags); int (*pack) (struct state_backend_format * format, struct state * state, - uint8_t ** buf, ssize_t * len); + void ** buf, ssize_t * len); int (*unpack) (struct state_backend_format * format, - struct state * state, const uint8_t * buf, ssize_t len); - ssize_t(*get_packed_len) (struct state_backend_format * format, - struct state * state); + struct state * state, const void * buf, ssize_t len); void (*free) (struct state_backend_format * format); const char *name; }; @@ -63,6 +70,9 @@ * state_backend_storage - Storage backend of the state. * * @buckets List of storage buckets that are available + * @stridesize The distance between copies + * @offset Offset in the backend device where the data starts + * @max_size The maximum size of the data we can use */ struct state_backend_storage { struct list_head buckets; @@ -73,23 +83,13 @@ const char *name; uint32_t stridesize; + off_t offset; + size_t max_size; + char *path; bool readonly; }; -/** - * state_backend - State Backend object - * - * @format Backend format object - * @storage Backend storage object - * @of_path Path to the DT node - */ -struct state_backend { - struct state_backend_format *format; - struct state_backend_storage storage; - char *of_path; -}; - struct state { struct list_head list; /* Entry to enqueue on list of states */ @@ -102,7 +102,9 @@ unsigned int dirty; unsigned int save_on_shutdown; - struct state_backend backend; + struct state_backend_format *format; + struct state_backend_storage storage; + char *backend_path; }; enum state_convert { @@ -112,20 +114,10 @@ STATE_CONVERT_FIXUP, }; -enum state_variable_type { - STATE_TYPE_INVALID = 0, - STATE_TYPE_ENUM, - STATE_TYPE_U8, - STATE_TYPE_U32, - STATE_TYPE_MAC, - STATE_TYPE_STRING, -}; - struct state_variable; /* A variable type (uint32, enum32) */ struct variable_type { - enum state_variable_type type; const char *type_name; struct list_head list; int (*export) (struct state_variable *, struct device_node *, @@ -139,7 +131,6 @@ /* instance of a single variable */ struct state_variable { struct state *state; - enum state_variable_type type; struct list_head list; const char *name; unsigned int start; @@ -199,8 +190,7 @@ struct device_d *dev); int backend_format_dtb_create(struct state_backend_format **format, struct device_d *dev); -int state_storage_init(struct state_backend_storage *storage, - struct device_d *dev, const char *path, +int state_storage_init(struct state *state, const char *path, off_t offset, size_t max_size, uint32_t stridesize, const char *storagetype); void state_storage_set_readonly(struct state_backend_storage *storage); @@ -210,34 +200,24 @@ struct state_backend_storage_bucket **bucket, unsigned int eraseblock, ssize_t writesize, - struct mtd_info_user *mtd_uinfo, - bool lazy_init); + struct mtd_info_user *mtd_uinfo); int state_backend_bucket_cached_create(struct device_d *dev, struct state_backend_storage_bucket *raw, struct state_backend_storage_bucket **out); struct state_variable *state_find_var(struct state *state, const char *name); struct digest *state_backend_format_raw_get_digest(struct state_backend_format *format); -int state_backend_init(struct state_backend *backend, struct device_d *dev, - struct device_node *node, const char *backend_format, - const char *storage_path, const char *state_name, const - char *of_path, off_t offset, size_t max_size, - uint32_t stridesize, const char *storagetype); -void state_backend_set_readonly(struct state_backend *backend); -void state_backend_free(struct state_backend *backend); +void state_backend_set_readonly(struct state *state); void state_storage_free(struct state_backend_storage *storage); int state_backend_bucket_direct_create(struct device_d *dev, const char *path, struct state_backend_storage_bucket **bucket, off_t offset, ssize_t max_size); int state_storage_write(struct state_backend_storage *storage, - const uint8_t * buf, ssize_t len); -int state_storage_restore_consistency(struct state_backend_storage - *storage, const uint8_t * buf, - ssize_t len); + const void * buf, ssize_t len); int state_storage_read(struct state_backend_storage *storage, struct state_backend_format *format, - uint32_t magic, uint8_t **buf, ssize_t *len, - ssize_t len_hint); + uint32_t magic, void **buf, ssize_t *len, + enum state_flags flags); static inline struct state_uint32 *to_state_uint32(struct state_variable *s) { diff --git a/common/state/state_variables.c b/common/state/state_variables.c index fd072a0..5b8e628 100644 --- a/common/state/state_variables.c +++ b/common/state/state_variables.c @@ -439,31 +439,26 @@ static struct variable_type types[] = { { - .type = STATE_TYPE_U8, .type_name = "uint8", .export = state_uint32_export, .import = state_uint32_import, .create = state_uint8_create, }, { - .type = STATE_TYPE_U32, .type_name = "uint32", .export = state_uint32_export, .import = state_uint32_import, .create = state_uint32_create, }, { - .type = STATE_TYPE_ENUM, .type_name = "enum32", .export = state_enum32_export, .import = state_enum32_import, .create = state_enum32_create, }, { - .type = STATE_TYPE_MAC, .type_name = "mac", .export = state_mac_export, .import = state_mac_import, .create = state_mac_create, }, { - .type = STATE_TYPE_STRING, .type_name = "string", .export = state_string_export, .import = state_string_import, diff --git a/crypto/hmac.c b/crypto/hmac.c index 05b9b50..37b270d 100644 --- a/crypto/hmac.c +++ b/crypto/hmac.c @@ -196,4 +196,4 @@ return 0; } -crypto_initcall(digest_hmac_initcall); +coredevice_initcall(digest_hmac_initcall); diff --git a/crypto/keystore.c b/crypto/keystore.c index 90b470f..f2b25ca 100644 --- a/crypto/keystore.c +++ b/crypto/keystore.c @@ -16,8 +16,8 @@ struct keystore_key { struct list_head list; - const char *name; - const u8 *secret; + char *name; + u8 *secret; int secret_len; }; @@ -29,6 +29,17 @@ return strcmp(na, nb); } +static struct keystore_key *get_key(const char *name) +{ + struct keystore_key *key; + + for_each_key(key) + if (!strcmp(name, key->name)) + return key; + + return NULL; +}; + /** * @param[in] name Name of the secret to get * @param[out] secret Double pointer to memory representing the secret, do _not_ free() after use @@ -38,19 +49,17 @@ { struct keystore_key *key; - for_each_key(key) { - if (!strcmp(name, key->name)) { - if (!secret || !secret_len) - return 0; + if (!secret || !secret_len) + return 0; - *secret = key->secret; - *secret_len = key->secret_len; + key = get_key(name); + if (!key) + return -ENOENT; - return 0; - } - } + *secret = key->secret; + *secret_len = key->secret_len; - return -ENOENT; + return 0; } /** @@ -61,11 +70,10 @@ int keystore_set_secret(const char *name, const u8 *secret, int secret_len) { struct keystore_key *key; - int ret; /* check if key is already in store */ - ret = keystore_get_secret(name, NULL, NULL); - if (!ret) + key = get_key(name); + if (key) return -EBUSY; key = xzalloc(sizeof(*key)); @@ -78,3 +86,18 @@ return 0; } + +void keystore_forget_secret(const char *name) +{ + struct keystore_key *key; + + key = get_key(name); + if (!key) + return; + + list_del(&key->list); + + free(key->name); + free(key->secret); + free(key); +} diff --git a/crypto/sha1.c b/crypto/sha1.c index cbde4d2..f4b2ded 100644 --- a/crypto/sha1.c +++ b/crypto/sha1.c @@ -303,4 +303,4 @@ { return digest_algo_register(&m); } -device_initcall(sha1_digest_register); +coredevice_initcall(sha1_digest_register); diff --git a/crypto/sha2.c b/crypto/sha2.c index cb0f11c..c62ddb8 100644 --- a/crypto/sha2.c +++ b/crypto/sha2.c @@ -372,4 +372,4 @@ return digest_algo_register(&m256); } -device_initcall(sha256_digest_register); +coredevice_initcall(sha256_digest_register); diff --git a/crypto/sha4.c b/crypto/sha4.c index 4ce37b7..aad8081 100644 --- a/crypto/sha4.c +++ b/crypto/sha4.c @@ -292,4 +292,4 @@ return digest_algo_register(&m512); } -device_initcall(sha512_digest_register); +coredevice_initcall(sha512_digest_register); diff --git a/drivers/misc/state.c b/drivers/misc/state.c index b43aee6..98ed42e 100644 --- a/drivers/misc/state.c +++ b/drivers/misc/state.c @@ -26,6 +26,7 @@ struct device_node *np = dev->device_node; struct state *state; bool readonly = false; + int ret; state = state_new_from_node(np, NULL, 0, 0, readonly); if (IS_ERR(state)) { @@ -35,6 +36,11 @@ return ret; } + ret = state_load(state); + if (ret) + dev_warn(dev, "Failed to load persistent state, continuing with defaults, %d\n", + ret); + return 0; } diff --git a/include/crypto/keystore.h b/include/crypto/keystore.h index 2991585..89d9626 100644 --- a/include/crypto/keystore.h +++ b/include/crypto/keystore.h @@ -12,6 +12,7 @@ #ifdef CONFIG_CRYPTO_KEYSTORE int keystore_get_secret(const char *name, const u8 **secret, int *secret_len); int keystore_set_secret(const char *name, const u8 *secret, int secret_len); +void keystore_forget_secret(const char *name); #else static inline int keystore_get_secret(const char *name, const u8 **secret, int *secret_len) { @@ -21,6 +22,9 @@ { return 0; } +static inline void keystore_forget_secret(const char *name) +{ +} #endif #endif diff --git a/include/state.h b/include/state.h index bc9a574..63164f9 100644 --- a/include/state.h +++ b/include/state.h @@ -18,6 +18,7 @@ struct state *state_by_node(const struct device_node *node); int state_get_name(const struct state *state, char const **name); +int state_load_no_auth(struct state *state); int state_load(struct state *state); int state_save(struct state *state); void state_info(void);