summaryrefslogtreecommitdiffstats
path: root/crypto
diff options
context:
space:
mode:
authorTomas Mraz <tomas@openssl.org>2023-01-09 18:39:50 +0100
committerTomas Mraz <tomas@openssl.org>2023-01-27 16:11:38 +0100
commit3a857b9532169b1ffaa739ba29cd67a5d93cbe8a (patch)
treec335a84a2b4a7f99c35a6e8b78ecdc2fe2f15558 /crypto
parent6e193d4d03f6c7bdf95e82e226c5fccbd67562f2 (diff)
Implement BIO_s_dgram_mem() reusing the BIO_s_dgram_pair() code
Reviewed-by: Matt Caswell <matt@openssl.org> Reviewed-by: Hugo Landau <hlandau@openssl.org> (Merged from https://github.com/openssl/openssl/pull/20012)
Diffstat (limited to 'crypto')
-rw-r--r--crypto/bio/bss_dgram_pair.c339
1 files changed, 263 insertions, 76 deletions
diff --git a/crypto/bio/bss_dgram_pair.c b/crypto/bio/bss_dgram_pair.c
index 7210a9906a..3685d51f55 100644
--- a/crypto/bio/bss_dgram_pair.c
+++ b/crypto/bio/bss_dgram_pair.c
@@ -11,15 +11,18 @@
#include <errno.h>
#include "bio_local.h"
#include "internal/cryptlib.h"
+#include "internal/safe_math.h"
#if !defined(OPENSSL_NO_DGRAM) && !defined(OPENSSL_NO_SOCK)
+OSSL_SAFE_MATH_UNSIGNED(size_t, size_t)
+
/* ===========================================================================
* Byte-wise ring buffer which supports pushing and popping blocks of multiple
* bytes at a time.
*/
struct ring_buf {
- unsigned char *start; /* start of buffer, never changes */
+ unsigned char *start; /* start of buffer */
size_t len; /* size of buffer allocation in bytes */
size_t count; /* number of bytes currently pushed */
/*
@@ -114,6 +117,44 @@ static void ring_buf_clear(struct ring_buf *r)
r->idx[0] = r->idx[1] = r->count = 0;
}
+static int ring_buf_resize(struct ring_buf *r, size_t nbytes)
+{
+ unsigned char *new_start;
+
+ if (r->start == NULL)
+ return ring_buf_init(r, nbytes);
+
+ if (nbytes == r->len)
+ return 1;
+
+ if (r->count > 0 && nbytes < r->len)
+ /* fail shrinking the ring buffer when there is any data in it */
+ return 0;
+
+ new_start = OPENSSL_realloc(r->start, nbytes);
+ if (new_start == NULL)
+ return 0;
+
+ /* Moving tail if it is after (or equal to) head */
+ if (r->count > 0) {
+ if (r->idx[0] <= r->idx[1]) {
+ size_t offset = nbytes - r->len;
+
+ memmove(new_start + r->idx[1] + offset, new_start + r->idx[1],
+ r->len - r->idx[1]);
+ r->idx[1] += offset;
+ }
+ } else {
+ /* just reset the head/tail because it might be pointing outside */
+ r->idx[0] = r->idx[1] = 0;
+ }
+
+ r->start = new_start;
+ r->len = nbytes;
+
+ return 1;
+}
+
/* ===========================================================================
* BIO_s_dgram_pair is documented in BIO_s_dgram_pair(3).
*
@@ -136,8 +177,11 @@ static void ring_buf_clear(struct ring_buf *r)
struct bio_dgram_pair_st;
static int dgram_pair_write(BIO *bio, const char *buf, int sz_);
static int dgram_pair_read(BIO *bio, char *buf, int sz_);
+static int dgram_mem_read(BIO *bio, char *buf, int sz_);
static long dgram_pair_ctrl(BIO *bio, int cmd, long num, void *ptr);
+static long dgram_mem_ctrl(BIO *bio, int cmd, long num, void *ptr);
static int dgram_pair_init(BIO *bio);
+static int dgram_mem_init(BIO *bio);
static int dgram_pair_free(BIO *bio);
static int dgram_pair_sendmmsg(BIO *b, BIO_MSG *msg, size_t stride,
size_t num_msg, uint64_t flags,
@@ -169,18 +213,40 @@ static const BIO_METHOD dgram_pair_method = {
dgram_pair_recvmmsg,
};
+static const BIO_METHOD dgram_mem_method = {
+ BIO_TYPE_DGRAM_MEM,
+ "BIO dgram mem",
+ bwrite_conv,
+ dgram_pair_write,
+ bread_conv,
+ dgram_mem_read,
+ NULL, /* dgram_pair_puts */
+ NULL, /* dgram_pair_gets */
+ dgram_mem_ctrl,
+ dgram_mem_init,
+ dgram_pair_free,
+ NULL, /* dgram_pair_callback_ctrl */
+ dgram_pair_sendmmsg,
+ dgram_pair_recvmmsg,
+};
+
const BIO_METHOD *BIO_s_dgram_pair(void)
{
return &dgram_pair_method;
}
+const BIO_METHOD *BIO_s_dgram_mem(void)
+{
+ return &dgram_mem_method;
+}
+
struct dgram_hdr {
size_t len; /* payload length in bytes, not including this struct */
BIO_ADDR src_addr, dst_addr; /* family == 0: not present */
};
struct bio_dgram_pair_st {
- /* The other half of the BIO pair. */
+ /* The other half of the BIO pair. NULL for dgram_mem. */
BIO *peer;
/* Writes are directed to our own ringbuf and reads to our peer. */
struct ring_buf rbuf;
@@ -199,10 +265,13 @@ struct bio_dgram_pair_st {
unsigned int no_trunc : 1; /* Reads fail if they would truncate */
unsigned int local_addr_enable : 1; /* Can use BIO_MSG->local? */
unsigned int role : 1; /* Determines lock order */
+ unsigned int fixed_size : 1; /* Affects BIO_s_dgram_mem only */
};
#define MIN_BUF_LEN (1024)
+#define is_dgram_pair(b) (b->peer != NULL)
+
static int dgram_pair_init(BIO *bio)
{
struct bio_dgram_pair_st *b = OPENSSL_zalloc(sizeof(*b));
@@ -223,6 +292,24 @@ static int dgram_pair_init(BIO *bio)
return 1;
}
+static int dgram_mem_init(BIO *bio)
+{
+ struct bio_dgram_pair_st *b;
+
+ if (!dgram_pair_init(bio))
+ return 0;
+
+ b = bio->ptr;
+
+ if (ring_buf_init(&b->rbuf, b->req_buf_len) == 0) {
+ ERR_raise(ERR_LIB_BIO, ERR_R_BIO_LIB);
+ return 0;
+ }
+
+ bio->init = 1;
+ return 1;
+}
+
static int dgram_pair_free(BIO *bio)
{
struct bio_dgram_pair_st *b;
@@ -312,22 +399,23 @@ static int dgram_pair_ctrl_destroy_bio_pair(BIO *bio1)
BIO *bio2;
struct bio_dgram_pair_st *b1 = bio1->ptr, *b2;
- /* If we already don't have a peer, treat this as a no-op. */
+ ring_buf_destroy(&b1->rbuf);
+ bio1->init = 0;
+
+ /* Early return if we don't have a peer. */
if (b1->peer == NULL)
return 1;
bio2 = b1->peer;
b2 = bio2->ptr;
- /* Invariants. */
- if (!ossl_assert(b1->peer == bio2 && b2->peer == bio1))
+ /* Invariant. */
+ if (!ossl_assert(b2->peer == bio1))
return 0;
/* Free buffers. */
- ring_buf_destroy(&b1->rbuf);
ring_buf_destroy(&b2->rbuf);
- bio1->init = 0;
bio2->init = 0;
b1->peer = NULL;
b2->peer = NULL;
@@ -342,9 +430,12 @@ static int dgram_pair_ctrl_eof(BIO *bio)
if (!ossl_assert(b != NULL))
return -1;
- /* If we have no peer, we can never read anything */
- if (b->peer == NULL)
+ /* If we aren't initialized, we can never read anything */
+ if (!bio->init)
return 1;
+ if (!is_dgram_pair(b))
+ return 0;
+
peerb = b->peer->ptr;
if (!ossl_assert(peerb != NULL))
@@ -372,14 +463,13 @@ static int dgram_pair_ctrl_set_write_buf_size(BIO *bio, size_t len)
if (len < MIN_BUF_LEN)
len = MIN_BUF_LEN;
- /*
- * We have no peer yet, therefore the ring buffer should not have been
- * allocated yet.
- */
- if (!ossl_assert(b->rbuf.start == NULL))
- return 0;
+ if (b->rbuf.start != NULL) {
+ if (!ring_buf_resize(&b->rbuf, len))
+ return 0;
+ }
b->req_buf_len = len;
+ b->fixed_size = 1;
return 1;
}
@@ -396,28 +486,30 @@ static int dgram_pair_ctrl_reset(BIO *bio)
static size_t dgram_pair_ctrl_pending(BIO *bio)
{
size_t saved_idx, saved_count;
- struct bio_dgram_pair_st *b = bio->ptr, *peerb;
+ struct bio_dgram_pair_st *b = bio->ptr, *readb;
struct dgram_hdr hdr;
size_t l;
- /* Safe to check; peer may not change during this call */
- if (b->peer == NULL)
+ /* Safe to check; init may not change during this call */
+ if (!bio->init)
return 0;
+ if (is_dgram_pair(b))
+ readb = b->peer->ptr;
+ else
+ readb = b;
- peerb = b->peer->ptr;
-
- if (CRYPTO_THREAD_write_lock(peerb->lock) == 0)
+ if (CRYPTO_THREAD_write_lock(readb->lock) == 0)
return 0;
- saved_idx = peerb->rbuf.idx[1];
- saved_count = peerb->rbuf.count;
+ saved_idx = readb->rbuf.idx[1];
+ saved_count = readb->rbuf.count;
- l = dgram_pair_read_inner(peerb, (uint8_t *)&hdr, sizeof(hdr));
+ l = dgram_pair_read_inner(readb, (uint8_t *)&hdr, sizeof(hdr));
- peerb->rbuf.idx[1] = saved_idx;
- peerb->rbuf.count = saved_count;
+ readb->rbuf.idx[1] = saved_idx;
+ readb->rbuf.count = saved_count;
- CRYPTO_THREAD_unlock(peerb->lock);
+ CRYPTO_THREAD_unlock(readb->lock);
if (!ossl_assert(l == 0 || l == sizeof(hdr)))
return 0;
@@ -452,15 +544,18 @@ static size_t dgram_pair_ctrl_get_write_guarantee(BIO *bio)
/* BIO_dgram_get_local_addr_cap (BIO_CTRL_DGRAM_GET_LOCAL_ADDR_CAP) */
static int dgram_pair_ctrl_get_local_addr_cap(BIO *bio)
{
- struct bio_dgram_pair_st *b = bio->ptr, *peerb;
+ struct bio_dgram_pair_st *b = bio->ptr, *readb;
- if (b->peer == NULL)
+ if (!bio->init)
return 0;
- peerb = b->peer->ptr;
+ if (is_dgram_pair(b))
+ readb = b->peer->ptr;
+ else
+ readb = b;
- return (~peerb->cap & (BIO_DGRAM_CAP_HANDLES_SRC_ADDR
- | BIO_DGRAM_CAP_PROVIDES_DST_ADDR)) == 0;
+ return (~readb->cap & (BIO_DGRAM_CAP_HANDLES_SRC_ADDR
+ | BIO_DGRAM_CAP_PROVIDES_DST_ADDR)) == 0;
}
/* BIO_dgram_get_effective_caps (BIO_CTRL_DGRAM_GET_EFFECTIVE_CAPS) */
@@ -537,7 +632,7 @@ static int dgram_pair_ctrl_set_mtu(BIO *bio, size_t mtu)
}
/* Partially threadsafe (some commands) */
-static long dgram_pair_ctrl(BIO *bio, int cmd, long num, void *ptr)
+static long dgram_mem_ctrl(BIO *bio, int cmd, long num, void *ptr)
{
long ret = 1;
struct bio_dgram_pair_st *b = bio->ptr;
@@ -563,23 +658,6 @@ static long dgram_pair_ctrl(BIO *bio, int cmd, long num, void *ptr)
break;
/*
- * BIO_make_bio_pair: this is usually used by BIO_new_dgram_pair, though it
- * may be used manually after manually creating each half of a BIO pair
- * using BIO_new. This only needs to be called on one of the BIOs.
- */
- case BIO_C_MAKE_BIO_PAIR: /* Non-threadsafe */
- ret = (long)dgram_pair_ctrl_make_bio_pair(bio, (BIO *)ptr);
- break;
-
- /*
- * BIO_destroy_bio_pair: Manually disconnect two halves of a BIO pair so
- * that they are no longer peers.
- */
- case BIO_C_DESTROY_BIO_PAIR: /* Non-threadsafe */
- dgram_pair_ctrl_destroy_bio_pair(bio);
- break;
-
- /*
* BIO_reset: Clear all data which was written to this side of the pair.
*/
case BIO_CTRL_RESET: /* Non-threadsafe */
@@ -630,9 +708,6 @@ static long dgram_pair_ctrl(BIO *bio, int cmd, long num, void *ptr)
/* BIO_dgram_get_effective_caps */
case BIO_CTRL_DGRAM_GET_EFFECTIVE_CAPS: /* Non-threadsafe */
- ret = (long)dgram_pair_ctrl_get_effective_caps(bio);
- break;
-
/* BIO_dgram_get_caps */
case BIO_CTRL_DGRAM_GET_CAPS: /* Non-threadsafe */
ret = (long)dgram_pair_ctrl_get_caps(bio);
@@ -669,6 +744,41 @@ static long dgram_pair_ctrl(BIO *bio, int cmd, long num, void *ptr)
return ret;
}
+static long dgram_pair_ctrl(BIO *bio, int cmd, long num, void *ptr)
+{
+ long ret = 1;
+
+ switch (cmd) {
+ /*
+ * BIO_make_bio_pair: this is usually used by BIO_new_dgram_pair, though it
+ * may be used manually after manually creating each half of a BIO pair
+ * using BIO_new. This only needs to be called on one of the BIOs.
+ */
+ case BIO_C_MAKE_BIO_PAIR: /* Non-threadsafe */
+ ret = (long)dgram_pair_ctrl_make_bio_pair(bio, (BIO *)ptr);
+ break;
+
+ /*
+ * BIO_destroy_bio_pair: Manually disconnect two halves of a BIO pair so
+ * that they are no longer peers.
+ */
+ case BIO_C_DESTROY_BIO_PAIR: /* Non-threadsafe */
+ dgram_pair_ctrl_destroy_bio_pair(bio);
+ break;
+
+ /* BIO_dgram_get_effective_caps */
+ case BIO_CTRL_DGRAM_GET_EFFECTIVE_CAPS: /* Non-threadsafe */
+ ret = (long)dgram_pair_ctrl_get_effective_caps(bio);
+ break;
+
+ default:
+ ret = dgram_mem_ctrl(bio, cmd, num, ptr);
+ break;
+ }
+
+ return ret;
+}
+
int BIO_new_bio_dgram_pair(BIO **pbio1, size_t writebuf1,
BIO **pbio2, size_t writebuf2)
{
@@ -763,7 +873,7 @@ static ossl_ssize_t dgram_pair_read_actual(BIO *bio, char *buf, size_t sz,
int is_multi)
{
size_t l, trunc = 0, saved_idx, saved_count;
- struct bio_dgram_pair_st *b = bio->ptr, *peerb;
+ struct bio_dgram_pair_st *b = bio->ptr, *readb;
struct dgram_hdr hdr;
if (!is_multi)
@@ -772,11 +882,14 @@ static ossl_ssize_t dgram_pair_read_actual(BIO *bio, char *buf, size_t sz,
if (!bio->init)
return -BIO_R_UNINITIALIZED;
- if (!ossl_assert(b != NULL && b->peer != NULL))
+ if (!ossl_assert(b != NULL))
return -BIO_R_TRANSFER_ERROR;
- peerb = b->peer->ptr;
- if (!ossl_assert(peerb != NULL && peerb->rbuf.start != NULL))
+ if (is_dgram_pair(b))
+ readb = b->peer->ptr;
+ else
+ readb = b;
+ if (!ossl_assert(readb != NULL && readb->rbuf.start != NULL))
return -BIO_R_TRANSFER_ERROR;
if (sz > 0 && buf == NULL)
@@ -787,9 +900,9 @@ static ossl_ssize_t dgram_pair_read_actual(BIO *bio, char *buf, size_t sz,
return -BIO_R_LOCAL_ADDR_NOT_AVAILABLE;
/* Read the header. */
- saved_idx = peerb->rbuf.idx[1];
- saved_count = peerb->rbuf.count;
- l = dgram_pair_read_inner(peerb, (uint8_t *)&hdr, sizeof(hdr));
+ saved_idx = readb->rbuf.idx[1];
+ saved_count = readb->rbuf.count;
+ l = dgram_pair_read_inner(readb, (uint8_t *)&hdr, sizeof(hdr));
if (l == 0) {
/* Buffer was empty. */
if (!is_multi)
@@ -811,13 +924,13 @@ static ossl_ssize_t dgram_pair_read_actual(BIO *bio, char *buf, size_t sz,
trunc = hdr.len - sz;
if (b->no_trunc) {
/* Restore original state. */
- peerb->rbuf.idx[1] = saved_idx;
- peerb->rbuf.count = saved_count;
+ readb->rbuf.idx[1] = saved_idx;
+ readb->rbuf.count = saved_count;
return -BIO_R_NON_FATAL;
}
}
- l = dgram_pair_read_inner(peerb, (uint8_t *)buf, sz);
+ l = dgram_pair_read_inner(readb, (uint8_t *)buf, sz);
if (!ossl_assert(l == sz))
/* We were somehow not able to read the entire datagram. */
return -BIO_R_TRANSFER_ERROR;
@@ -826,7 +939,7 @@ static ossl_ssize_t dgram_pair_read_actual(BIO *bio, char *buf, size_t sz,
* If the datagram was truncated due to an inadequate buffer, discard the
* remainder.
*/
- if (trunc > 0 && !ossl_assert(dgram_pair_read_inner(peerb, NULL, trunc) == trunc))
+ if (trunc > 0 && !ossl_assert(dgram_pair_read_inner(readb, NULL, trunc) == trunc))
/* We were somehow not able to read/skip the entire datagram. */
return -BIO_R_TRANSFER_ERROR;
@@ -902,7 +1015,8 @@ static int dgram_pair_read(BIO *bio, char *buf, int sz_)
l = dgram_pair_read_actual(bio, buf, (size_t)sz_, NULL, NULL, 0);
if (l < 0) {
- ERR_raise(ERR_LIB_BIO, -l);
+ if (l != -BIO_R_NON_FATAL)
+ ERR_raise(ERR_LIB_BIO, -l);
ret = -1;
} else {
ret = (int)l;
@@ -922,22 +1036,25 @@ static int dgram_pair_recvmmsg(BIO *bio, BIO_MSG *msg,
ossl_ssize_t l;
BIO_MSG *m;
size_t i;
- struct bio_dgram_pair_st *b = bio->ptr, *peerb;
+ struct bio_dgram_pair_st *b = bio->ptr, *readb;
if (num_msg == 0) {
*num_processed = 0;
return 1;
}
- if (b->peer == NULL) {
+ if (!bio->init) {
ERR_raise(ERR_LIB_BIO, BIO_R_BROKEN_PIPE);
*num_processed = 0;
return 0;
}
- peerb = b->peer->ptr;
+ if (is_dgram_pair(b))
+ readb = b->peer->ptr;
+ else
+ readb = b;
- if (CRYPTO_THREAD_write_lock(peerb->lock) == 0) {
+ if (CRYPTO_THREAD_write_lock(readb->lock) == 0) {
ERR_raise(ERR_LIB_BIO, ERR_R_UNABLE_TO_GET_WRITE_LOCK);
*num_processed = 0;
return 0;
@@ -965,10 +1082,68 @@ static int dgram_pair_recvmmsg(BIO *bio, BIO_MSG *msg,
*num_processed = i;
ret = 1;
out:
- CRYPTO_THREAD_unlock(peerb->lock);
+ CRYPTO_THREAD_unlock(readb->lock);
return ret;
}
+/* Threadsafe */
+static int dgram_mem_read(BIO *bio, char *buf, int sz_)
+{
+ int ret;
+ ossl_ssize_t l;
+ struct bio_dgram_pair_st *b = bio->ptr;
+
+ if (sz_ < 0) {
+ ERR_raise(ERR_LIB_BIO, BIO_R_INVALID_ARGUMENT);
+ return -1;
+ }
+
+ if (CRYPTO_THREAD_write_lock(b->lock) == 0) {
+ ERR_raise(ERR_LIB_BIO, ERR_R_UNABLE_TO_GET_WRITE_LOCK);
+ return -1;
+ }
+
+ l = dgram_pair_read_actual(bio, buf, (size_t)sz_, NULL, NULL, 0);
+ if (l < 0) {
+ if (l != -BIO_R_NON_FATAL)
+ ERR_raise(ERR_LIB_BIO, -l);
+ ret = -1;
+ } else {
+ ret = (int)l;
+ }
+
+ CRYPTO_THREAD_unlock(b->lock);
+ return ret;
+}
+
+/*
+ * Calculate the array growth based on the target size.
+ *
+ * The growth factor is a rational number and is defined by a numerator
+ * and a denominator. According to Andrew Koenig in his paper "Why Are
+ * Vectors Efficient?" from JOOP 11(5) 1998, this factor should be less
+ * than the golden ratio (1.618...).
+ *
+ * We use an expansion factor of 8 / 5 = 1.6
+ */
+static const size_t max_rbuf_size = SIZE_MAX / 2; /* unlimited in practice */
+static ossl_inline size_t compute_rbuf_growth(size_t target, size_t current)
+{
+ int err = 0;
+
+ while (current < target) {
+ if (current >= max_rbuf_size)
+ return 0;
+
+ current = safe_muldiv_size_t(current, 8, 5, &err);
+ if (err)
+ return 0;
+ if (current >= max_rbuf_size)
+ current = max_rbuf_size;
+ }
+ return current;
+}
+
/* Must hold local write lock */
static size_t dgram_pair_write_inner(struct bio_dgram_pair_st *b, const uint8_t *buf, size_t sz)
{
@@ -988,8 +1163,17 @@ static size_t dgram_pair_write_inner(struct bio_dgram_pair_st *b, const uint8_t
* ringbuf and read from the peer ringbuf.
*/
ring_buf_head(&b->rbuf, &dst_buf, &dst_len);
- if (dst_len == 0)
- break;
+ if (dst_len == 0) {
+ size_t new_len;
+
+ if (!b->fixed_size) /* resizeable only unless size not set explicitly */
+ break;
+ /* increase the size */
+ new_len = compute_rbuf_growth(b->req_buf_len + sz, b->req_buf_len);
+ if (new_len == 0 || !ring_buf_resize(&b->rbuf, new_len))
+ break;
+ b->req_buf_len = new_len;
+ }
if (dst_len > sz)
dst_len = sz;
@@ -1015,7 +1199,7 @@ static ossl_ssize_t dgram_pair_write_actual(BIO *bio, const char *buf, size_t sz
{
static const BIO_ADDR zero_addr;
size_t saved_idx, saved_count;
- struct bio_dgram_pair_st *b = bio->ptr, *peerb;
+ struct bio_dgram_pair_st *b = bio->ptr, *readb;
struct dgram_hdr hdr = {0};
if (!is_multi)
@@ -1024,7 +1208,7 @@ static ossl_ssize_t dgram_pair_write_actual(BIO *bio, const char *buf, size_t sz
if (!bio->init)
return -BIO_R_UNINITIALIZED;
- if (!ossl_assert(b != NULL && b->peer != NULL && b->rbuf.start != NULL))
+ if (!ossl_assert(b != NULL && b->rbuf.start != NULL))
return -BIO_R_TRANSFER_ERROR;
if (sz > 0 && buf == NULL)
@@ -1033,8 +1217,11 @@ static ossl_ssize_t dgram_pair_write_actual(BIO *bio, const char *buf, size_t sz
if (local != NULL && b->local_addr_enable == 0)
return -BIO_R_LOCAL_ADDR_NOT_AVAILABLE;
- peerb = b->peer->ptr;
- if (peer != NULL && (peerb->cap & BIO_DGRAM_CAP_HANDLES_DST_ADDR) == 0)
+ if (is_dgram_pair(b))
+ readb = b->peer->ptr;
+ else
+ readb = b;
+ if (peer != NULL && (readb->cap & BIO_DGRAM_CAP_HANDLES_DST_ADDR) == 0)
return -BIO_R_PEER_ADDR_NOT_AVAILABLE;
hdr.len = sz;