summaryrefslogtreecommitdiff
path: root/library/mps_reader.c
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2024-10-08 13:56:50 -0600
committerTom Rini <trini@konsulko.com>2024-10-08 13:56:50 -0600
commit0344c602eadc0802776b65ff90f0a02c856cf53c (patch)
tree236a705740939b84ff37d68ae650061dd14c3449 /library/mps_reader.c
Squashed 'lib/mbedtls/external/mbedtls/' content from commit 2ca6c285a0dd
git-subtree-dir: lib/mbedtls/external/mbedtls git-subtree-split: 2ca6c285a0dd3f33982dd57299012dacab1ff206
Diffstat (limited to 'library/mps_reader.c')
-rw-r--r--library/mps_reader.c538
1 files changed, 538 insertions, 0 deletions
diff --git a/library/mps_reader.c b/library/mps_reader.c
new file mode 100644
index 00000000000..27d0c04c108
--- /dev/null
+++ b/library/mps_reader.c
@@ -0,0 +1,538 @@
+/*
+ * Message Processing Stack, Reader implementation
+ *
+ * Copyright The Mbed TLS Contributors
+ * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+ */
+
+#include "common.h"
+
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+
+#include "mps_reader.h"
+#include "mps_common.h"
+#include "mps_trace.h"
+
+#include <string.h>
+
+#if defined(MBEDTLS_MPS_ENABLE_TRACE)
+static int mbedtls_mps_trace_id = MBEDTLS_MPS_TRACE_BIT_READER;
+#endif /* MBEDTLS_MPS_ENABLE_TRACE */
+
+/*
+ * GENERAL NOTE ON CODING STYLE
+ *
+ * The following code intentionally separates memory loads
+ * and stores from other operations (arithmetic or branches).
+ * This leads to the introduction of many local variables
+ * and significantly increases the C-code line count, but
+ * should not increase the size of generated assembly.
+ *
+ * The reason for this is twofold:
+ * (1) It will ease verification efforts using the VST
+ * (Verified Software Toolchain)
+ * whose program logic cannot directly reason
+ * about instructions containing a load or store in
+ * addition to other operations (e.g. *p = *q or
+ * tmp = *p + 42).
+ * (2) Operating on local variables and writing the results
+ * back to the target contexts on success only
+ * allows to maintain structure invariants even
+ * on failure - this in turn has two benefits:
+ * (2.a) If for some reason an error code is not caught
+ * and operation continues, functions are nonetheless
+ * called with sane contexts, reducing the risk
+ * of dangerous behavior.
+ * (2.b) Randomized testing is easier if structures
+ * remain intact even in the face of failing
+ * and/or non-sensical calls.
+ * Moreover, it might even reduce code-size because
+ * the compiler need not write back temporary results
+ * to memory in case of failure.
+ *
+ */
+
+static inline int mps_reader_is_accumulating(
+ mbedtls_mps_reader const *rd)
+{
+ mbedtls_mps_size_t acc_remaining;
+ if (rd->acc == NULL) {
+ return 0;
+ }
+
+ acc_remaining = rd->acc_share.acc_remaining;
+ return acc_remaining > 0;
+}
+
+static inline int mps_reader_is_producing(
+ mbedtls_mps_reader const *rd)
+{
+ unsigned char *frag = rd->frag;
+ return frag == NULL;
+}
+
+static inline int mps_reader_is_consuming(
+ mbedtls_mps_reader const *rd)
+{
+ return !mps_reader_is_producing(rd);
+}
+
+static inline mbedtls_mps_size_t mps_reader_get_fragment_offset(
+ mbedtls_mps_reader const *rd)
+{
+ unsigned char *acc = rd->acc;
+ mbedtls_mps_size_t frag_offset;
+
+ if (acc == NULL) {
+ return 0;
+ }
+
+ frag_offset = rd->acc_share.frag_offset;
+ return frag_offset;
+}
+
+static inline mbedtls_mps_size_t mps_reader_serving_from_accumulator(
+ mbedtls_mps_reader const *rd)
+{
+ mbedtls_mps_size_t frag_offset, end;
+
+ frag_offset = mps_reader_get_fragment_offset(rd);
+ end = rd->end;
+
+ return end < frag_offset;
+}
+
+static inline void mps_reader_zero(mbedtls_mps_reader *rd)
+{
+ /* A plain memset() would likely be more efficient,
+ * but the current way of zeroing makes it harder
+ * to overlook fields which should not be zero-initialized.
+ * It's also more suitable for FV efforts since it
+ * doesn't require reasoning about structs being
+ * interpreted as unstructured binary blobs. */
+ static mbedtls_mps_reader const zero =
+ { .frag = NULL,
+ .frag_len = 0,
+ .commit = 0,
+ .end = 0,
+ .pending = 0,
+ .acc = NULL,
+ .acc_len = 0,
+ .acc_available = 0,
+ .acc_share = { .acc_remaining = 0 } };
+ *rd = zero;
+}
+
+int mbedtls_mps_reader_init(mbedtls_mps_reader *rd,
+ unsigned char *acc,
+ mbedtls_mps_size_t acc_len)
+{
+ MBEDTLS_MPS_TRACE_INIT("mbedtls_mps_reader_init");
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "* Accumulator size: %u bytes", (unsigned) acc_len);
+ mps_reader_zero(rd);
+ rd->acc = acc;
+ rd->acc_len = acc_len;
+ MBEDTLS_MPS_TRACE_RETURN(0);
+}
+
+int mbedtls_mps_reader_free(mbedtls_mps_reader *rd)
+{
+ MBEDTLS_MPS_TRACE_INIT("mbedtls_mps_reader_free");
+ mps_reader_zero(rd);
+ MBEDTLS_MPS_TRACE_RETURN(0);
+}
+
+int mbedtls_mps_reader_feed(mbedtls_mps_reader *rd,
+ unsigned char *new_frag,
+ mbedtls_mps_size_t new_frag_len)
+{
+ mbedtls_mps_size_t copy_to_acc;
+ MBEDTLS_MPS_TRACE_INIT("mbedtls_mps_reader_feed");
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "* Fragment length: %u bytes", (unsigned) new_frag_len);
+
+ if (new_frag == NULL) {
+ MBEDTLS_MPS_TRACE_RETURN(MBEDTLS_ERR_MPS_READER_INVALID_ARG);
+ }
+
+ MBEDTLS_MPS_STATE_VALIDATE_RAW(mps_reader_is_producing(
+ rd),
+ "mbedtls_mps_reader_feed() requires reader to be in producing mode");
+
+ if (mps_reader_is_accumulating(rd)) {
+ unsigned char *acc = rd->acc;
+ mbedtls_mps_size_t acc_remaining = rd->acc_share.acc_remaining;
+ mbedtls_mps_size_t acc_available = rd->acc_available;
+
+ /* Skip over parts of the accumulator that have already been filled. */
+ acc += acc_available;
+
+ copy_to_acc = acc_remaining;
+ if (copy_to_acc > new_frag_len) {
+ copy_to_acc = new_frag_len;
+ }
+
+ /* Copy new contents to accumulator. */
+ memcpy(acc, new_frag, copy_to_acc);
+
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "Copy new data of size %u of %u into accumulator at offset %u",
+ (unsigned) copy_to_acc, (unsigned) new_frag_len,
+ (unsigned) acc_available);
+
+ /* Check if, with the new fragment, we have enough data. */
+ acc_remaining -= copy_to_acc;
+ if (acc_remaining > 0) {
+ /* We need to accumulate more data. Stay in producing mode. */
+ acc_available += copy_to_acc;
+ rd->acc_share.acc_remaining = acc_remaining;
+ rd->acc_available = acc_available;
+ MBEDTLS_MPS_TRACE_RETURN(MBEDTLS_ERR_MPS_READER_NEED_MORE);
+ }
+
+ /* We have filled the accumulator: Move to consuming mode. */
+
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "Enough data available to serve user request");
+
+ /* Remember overlap of accumulator and fragment. */
+ rd->acc_share.frag_offset = acc_available;
+ acc_available += copy_to_acc;
+ rd->acc_available = acc_available;
+ } else { /* Not accumulating */
+ rd->acc_share.frag_offset = 0;
+ }
+
+ rd->frag = new_frag;
+ rd->frag_len = new_frag_len;
+ rd->commit = 0;
+ rd->end = 0;
+ MBEDTLS_MPS_TRACE_RETURN(0);
+}
+
+
+int mbedtls_mps_reader_get(mbedtls_mps_reader *rd,
+ mbedtls_mps_size_t desired,
+ unsigned char **buffer,
+ mbedtls_mps_size_t *buflen)
+{
+ unsigned char *frag;
+ mbedtls_mps_size_t frag_len, frag_offset, end, frag_fetched, frag_remaining;
+ MBEDTLS_MPS_TRACE_INIT("mbedtls_mps_reader_get");
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "* Bytes requested: %u", (unsigned) desired);
+
+ MBEDTLS_MPS_STATE_VALIDATE_RAW(mps_reader_is_consuming(
+ rd),
+ "mbedtls_mps_reader_get() requires reader to be in consuming mode");
+
+ end = rd->end;
+ frag_offset = mps_reader_get_fragment_offset(rd);
+
+ /* Check if we're still serving from the accumulator. */
+ if (mps_reader_serving_from_accumulator(rd)) {
+ /* Illustration of supported and unsupported cases:
+ *
+ * - Allowed #1
+ *
+ * +-----------------------------------+
+ * | frag |
+ * +-----------------------------------+
+ *
+ * end end+desired
+ * | |
+ * +-----v-------v-------------+
+ * | acc |
+ * +---------------------------+
+ * | |
+ * frag_offset acc_available
+ *
+ * - Allowed #2
+ *
+ * +-----------------------------------+
+ * | frag |
+ * +-----------------------------------+
+ *
+ * end end+desired
+ * | |
+ * +----------v----------------v
+ * | acc |
+ * +---------------------------+
+ * | |
+ * frag_offset acc_available
+ *
+ * - Not allowed #1 (could be served, but we don't actually use it):
+ *
+ * +-----------------------------------+
+ * | frag |
+ * +-----------------------------------+
+ *
+ * end end+desired
+ * | |
+ * +------v-------------v------+
+ * | acc |
+ * +---------------------------+
+ * | |
+ * frag_offset acc_available
+ *
+ *
+ * - Not allowed #2 (can't be served with a contiguous buffer):
+ *
+ * +-----------------------------------+
+ * | frag |
+ * +-----------------------------------+
+ *
+ * end end + desired
+ * | |
+ * +------v--------------------+ v
+ * | acc |
+ * +---------------------------+
+ * | |
+ * frag_offset acc_available
+ *
+ * In case of Allowed #2 we're switching to serve from
+ * `frag` starting from the next call to mbedtls_mps_reader_get().
+ */
+
+ unsigned char *acc;
+
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "Serve the request from the accumulator");
+ if (frag_offset - end < desired) {
+ mbedtls_mps_size_t acc_available;
+ acc_available = rd->acc_available;
+ if (acc_available - end != desired) {
+ /* It might be possible to serve some of these situations by
+ * making additional space in the accumulator, removing those
+ * parts that have already been committed.
+ * On the other hand, this brings additional complexity and
+ * enlarges the code size, while there doesn't seem to be a use
+ * case where we don't attempt exactly the same `get` calls when
+ * resuming on a reader than what we tried before pausing it.
+ * If we believe we adhere to this restricted usage throughout
+ * the library, this check is a good opportunity to
+ * validate this. */
+ MBEDTLS_MPS_TRACE_RETURN(
+ MBEDTLS_ERR_MPS_READER_INCONSISTENT_REQUESTS);
+ }
+ }
+
+ acc = rd->acc;
+ acc += end;
+
+ *buffer = acc;
+ if (buflen != NULL) {
+ *buflen = desired;
+ }
+
+ end += desired;
+ rd->end = end;
+ rd->pending = 0;
+
+ MBEDTLS_MPS_TRACE_RETURN(0);
+ }
+
+ /* Attempt to serve the request from the current fragment */
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "Serve the request from the current fragment.");
+
+ frag_len = rd->frag_len;
+ frag_fetched = end - frag_offset; /* The amount of data from the current
+ * fragment that has already been passed
+ * to the user. */
+ frag_remaining = frag_len - frag_fetched; /* Remaining data in fragment */
+
+ /* Check if we can serve the read request from the fragment. */
+ if (frag_remaining < desired) {
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "There's not enough data in the current fragment "
+ "to serve the request.");
+ /* There's not enough data in the current fragment,
+ * so either just RETURN what we have or fail. */
+ if (buflen == NULL) {
+ if (frag_remaining > 0) {
+ rd->pending = desired - frag_remaining;
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "Remember to collect %u bytes before re-opening",
+ (unsigned) rd->pending);
+ }
+ MBEDTLS_MPS_TRACE_RETURN(MBEDTLS_ERR_MPS_READER_OUT_OF_DATA);
+ }
+
+ desired = frag_remaining;
+ }
+
+ /* There's enough data in the current fragment to serve the
+ * (potentially modified) read request. */
+
+ frag = rd->frag;
+ frag += frag_fetched;
+
+ *buffer = frag;
+ if (buflen != NULL) {
+ *buflen = desired;
+ }
+
+ end += desired;
+ rd->end = end;
+ rd->pending = 0;
+ MBEDTLS_MPS_TRACE_RETURN(0);
+}
+
+int mbedtls_mps_reader_commit(mbedtls_mps_reader *rd)
+{
+ mbedtls_mps_size_t end;
+ MBEDTLS_MPS_TRACE_INIT("mbedtls_mps_reader_commit");
+ MBEDTLS_MPS_STATE_VALIDATE_RAW(mps_reader_is_consuming(
+ rd),
+ "mbedtls_mps_reader_commit() requires reader to be in consuming mode");
+
+ end = rd->end;
+ rd->commit = end;
+
+ MBEDTLS_MPS_TRACE_RETURN(0);
+}
+
+int mbedtls_mps_reader_reclaim(mbedtls_mps_reader *rd,
+ int *paused)
+{
+ unsigned char *frag, *acc;
+ mbedtls_mps_size_t pending, commit;
+ mbedtls_mps_size_t acc_len, frag_offset, frag_len;
+ MBEDTLS_MPS_TRACE_INIT("mbedtls_mps_reader_reclaim");
+
+ if (paused != NULL) {
+ *paused = 0;
+ }
+
+ MBEDTLS_MPS_STATE_VALIDATE_RAW(mps_reader_is_consuming(
+ rd),
+ "mbedtls_mps_reader_reclaim() requires reader to be in consuming mode");
+
+ frag = rd->frag;
+ acc = rd->acc;
+ pending = rd->pending;
+ commit = rd->commit;
+ frag_len = rd->frag_len;
+
+ frag_offset = mps_reader_get_fragment_offset(rd);
+
+ if (pending == 0) {
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "No unsatisfied read-request has been logged.");
+
+ /* Check if there's data left to be consumed. */
+ if (commit < frag_offset || commit - frag_offset < frag_len) {
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "There is data left to be consumed.");
+ rd->end = commit;
+ MBEDTLS_MPS_TRACE_RETURN(MBEDTLS_ERR_MPS_READER_DATA_LEFT);
+ }
+
+ rd->acc_available = 0;
+ rd->acc_share.acc_remaining = 0;
+
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "Fragment has been fully processed and committed.");
+ } else {
+ int overflow;
+
+ mbedtls_mps_size_t acc_backup_offset;
+ mbedtls_mps_size_t acc_backup_len;
+ mbedtls_mps_size_t frag_backup_offset;
+ mbedtls_mps_size_t frag_backup_len;
+
+ mbedtls_mps_size_t backup_len;
+ mbedtls_mps_size_t acc_len_needed;
+
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "There has been an unsatisfied read with %u bytes overhead.",
+ (unsigned) pending);
+
+ if (acc == NULL) {
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "No accumulator present");
+ MBEDTLS_MPS_TRACE_RETURN(
+ MBEDTLS_ERR_MPS_READER_NEED_ACCUMULATOR);
+ }
+ acc_len = rd->acc_len;
+
+ /* Check if the upper layer has already fetched
+ * and committed the contents of the accumulator. */
+ if (commit < frag_offset) {
+ /* No, accumulator is still being processed. */
+ frag_backup_offset = 0;
+ frag_backup_len = frag_len;
+ acc_backup_offset = commit;
+ acc_backup_len = frag_offset - commit;
+ } else {
+ /* Yes, the accumulator is already processed. */
+ frag_backup_offset = commit - frag_offset;
+ frag_backup_len = frag_len - frag_backup_offset;
+ acc_backup_offset = 0;
+ acc_backup_len = 0;
+ }
+
+ backup_len = acc_backup_len + frag_backup_len;
+ acc_len_needed = backup_len + pending;
+
+ overflow = 0;
+ overflow |= (backup_len < acc_backup_len);
+ overflow |= (acc_len_needed < backup_len);
+
+ if (overflow || acc_len < acc_len_needed) {
+ /* Except for the different return code, we behave as if
+ * there hadn't been a call to mbedtls_mps_reader_get()
+ * since the last commit. */
+ rd->end = commit;
+ rd->pending = 0;
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_ERROR,
+ "The accumulator is too small to handle the backup.");
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_ERROR,
+ "* Size: %u", (unsigned) acc_len);
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_ERROR,
+ "* Needed: %u (%u + %u)",
+ (unsigned) acc_len_needed,
+ (unsigned) backup_len, (unsigned) pending);
+ MBEDTLS_MPS_TRACE_RETURN(
+ MBEDTLS_ERR_MPS_READER_ACCUMULATOR_TOO_SMALL);
+ }
+
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "Fragment backup: %u", (unsigned) frag_backup_len);
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "Accumulator backup: %u", (unsigned) acc_backup_len);
+
+ /* Move uncommitted parts from the accumulator to the front
+ * of the accumulator. */
+ memmove(acc, acc + acc_backup_offset, acc_backup_len);
+
+ /* Copy uncommitted parts of the current fragment to the
+ * accumulator. */
+ memcpy(acc + acc_backup_len,
+ frag + frag_backup_offset, frag_backup_len);
+
+ rd->acc_available = backup_len;
+ rd->acc_share.acc_remaining = pending;
+
+ if (paused != NULL) {
+ *paused = 1;
+ }
+ }
+
+ rd->frag = NULL;
+ rd->frag_len = 0;
+
+ rd->commit = 0;
+ rd->end = 0;
+ rd->pending = 0;
+
+ MBEDTLS_MPS_TRACE(MBEDTLS_MPS_TRACE_TYPE_COMMENT,
+ "Final state: aa %u, al %u, ar %u",
+ (unsigned) rd->acc_available, (unsigned) rd->acc_len,
+ (unsigned) rd->acc_share.acc_remaining);
+ MBEDTLS_MPS_TRACE_RETURN(0);
+}
+
+#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */