diff options
author | Tomas Mraz <tomas@openssl.org> | 2023-01-09 18:39:50 +0100 |
---|---|---|
committer | Tomas Mraz <tomas@openssl.org> | 2023-01-27 16:11:38 +0100 |
commit | 3a857b9532169b1ffaa739ba29cd67a5d93cbe8a (patch) | |
tree | c335a84a2b4a7f99c35a6e8b78ecdc2fe2f15558 /crypto | |
parent | 6e193d4d03f6c7bdf95e82e226c5fccbd67562f2 (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.c | 339 |
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; |