openssl/crypto/bio/bf_readbuff.c
Shane Lontis 251c48183b Fix DER reading from stdin for BIO_f_readbuffer
Fixes #14559

The intitial implementation of the gets() function tried using the next bio's gets() function.
For a file BIO this returned incorrect data for binary data containing 0x00.
Just buffering all data during gets() did not work however since some
applications open and close the bio multiple times when dealing with pem
files containing multiple entries.. This does not work
when reading from stdin unless the data if buffered one byte at a time.

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Paul Dale <pauli@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/14599)
2021-03-22 09:07:36 +10:00

289 lines
7.4 KiB
C

/*
* Copyright 2021 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
/*
* This is a read only BIO filter that can be used to add BIO_tell() and
* BIO_seek() support to source/sink BIO's (such as a file BIO that uses stdin).
* It does this by caching ALL data read from the BIO source/sink into a
* resizable memory buffer.
*/
#include <stdio.h>
#include <errno.h>
#include "bio_local.h"
#include "internal/cryptlib.h"
#define DEFAULT_BUFFER_SIZE 4096
static int readbuffer_write(BIO *h, const char *buf, int num);
static int readbuffer_read(BIO *h, char *buf, int size);
static int readbuffer_puts(BIO *h, const char *str);
static int readbuffer_gets(BIO *h, char *str, int size);
static long readbuffer_ctrl(BIO *h, int cmd, long arg1, void *arg2);
static int readbuffer_new(BIO *h);
static int readbuffer_free(BIO *data);
static long readbuffer_callback_ctrl(BIO *h, int cmd, BIO_info_cb *fp);
static const BIO_METHOD methods_readbuffer = {
BIO_TYPE_BUFFER,
"readbuffer",
bwrite_conv,
readbuffer_write,
bread_conv,
readbuffer_read,
readbuffer_puts,
readbuffer_gets,
readbuffer_ctrl,
readbuffer_new,
readbuffer_free,
readbuffer_callback_ctrl,
};
const BIO_METHOD *BIO_f_readbuffer(void)
{
return &methods_readbuffer;
}
static int readbuffer_new(BIO *bi)
{
BIO_F_BUFFER_CTX *ctx = OPENSSL_zalloc(sizeof(*ctx));
if (ctx == NULL)
return 0;
ctx->ibuf_size = DEFAULT_BUFFER_SIZE;
ctx->ibuf = OPENSSL_zalloc(DEFAULT_BUFFER_SIZE);
if (ctx->ibuf == NULL) {
OPENSSL_free(ctx);
return 0;
}
bi->init = 1;
bi->ptr = (char *)ctx;
bi->flags = 0;
return 1;
}
static int readbuffer_free(BIO *a)
{
BIO_F_BUFFER_CTX *b;
if (a == NULL)
return 0;
b = (BIO_F_BUFFER_CTX *)a->ptr;
OPENSSL_free(b->ibuf);
OPENSSL_free(a->ptr);
a->ptr = NULL;
a->init = 0;
a->flags = 0;
return 1;
}
static int readbuffer_resize(BIO_F_BUFFER_CTX *ctx, int sz)
{
char *tmp;
/* Figure out how many blocks are required */
sz += (ctx->ibuf_off + DEFAULT_BUFFER_SIZE - 1);
sz = DEFAULT_BUFFER_SIZE * (sz / DEFAULT_BUFFER_SIZE);
/* Resize if the buffer is not big enough */
if (sz > ctx->ibuf_size) {
tmp = OPENSSL_realloc(ctx->ibuf, sz);
if (tmp == NULL)
return 0;
ctx->ibuf = tmp;
ctx->ibuf_size = sz;
}
return 1;
}
static int readbuffer_read(BIO *b, char *out, int outl)
{
int i, num = 0;
BIO_F_BUFFER_CTX *ctx;
if (out == NULL || outl == 0)
return 0;
ctx = (BIO_F_BUFFER_CTX *)b->ptr;
if ((ctx == NULL) || (b->next_bio == NULL))
return 0;
BIO_clear_retry_flags(b);
for (;;) {
i = ctx->ibuf_len;
/* If there is something in the buffer just read it. */
if (i != 0) {
if (i > outl)
i = outl;
memcpy(out, &(ctx->ibuf[ctx->ibuf_off]), i);
ctx->ibuf_off += i;
ctx->ibuf_len -= i;
num += i;
/* Exit if we have read the bytes required out of the buffer */
if (outl == i)
return num;
outl -= i;
out += i;
}
/* Only gets here if the buffer has been consumed */
if (!readbuffer_resize(ctx, outl))
return 0;
/* Do some buffering by reading from the next bio */
i = BIO_read(b->next_bio, ctx->ibuf + ctx->ibuf_off, outl);
if (i <= 0) {
BIO_copy_next_retry(b);
if (i < 0)
return ((num > 0) ? num : i);
else
return num; /* i == 0 */
}
ctx->ibuf_len = i;
}
}
static int readbuffer_write(BIO *b, const char *in, int inl)
{
return 0;
}
static int readbuffer_puts(BIO *b, const char *str)
{
return 0;
}
static long readbuffer_ctrl(BIO *b, int cmd, long num, void *ptr)
{
BIO_F_BUFFER_CTX *ctx;
long ret = 1, sz;
ctx = (BIO_F_BUFFER_CTX *)b->ptr;
switch (cmd) {
case BIO_CTRL_EOF:
if (ctx->ibuf_len > 0)
return 0;
if (b->next_bio == NULL)
return 1;
ret = BIO_ctrl(b->next_bio, cmd, num, ptr);
break;
case BIO_C_FILE_SEEK:
case BIO_CTRL_RESET:
sz = ctx->ibuf_off + ctx->ibuf_len;
/* Assume it can only seek backwards */
if (num < 0 || num > sz)
return 0;
ctx->ibuf_off = num;
ctx->ibuf_len = sz - num;
break;
case BIO_C_FILE_TELL:
case BIO_CTRL_INFO:
ret = (long)ctx->ibuf_off;
break;
case BIO_CTRL_PENDING:
ret = (long)ctx->ibuf_len;
if (ret == 0) {
if (b->next_bio == NULL)
return 0;
ret = BIO_ctrl(b->next_bio, cmd, num, ptr);
}
break;
case BIO_CTRL_DUP:
case BIO_CTRL_FLUSH:
ret = 1;
break;
default:
ret = 0;
break;
}
return ret;
}
static long readbuffer_callback_ctrl(BIO *b, int cmd, BIO_info_cb *fp)
{
if (b->next_bio == NULL)
return 0;
return BIO_callback_ctrl(b->next_bio, cmd, fp);
}
static int readbuffer_gets(BIO *b, char *buf, int size)
{
BIO_F_BUFFER_CTX *ctx;
int num = 0, num_chars, found_newline;
char *p;
int i, j;
if (size == 0)
return 0;
--size; /* the passed in size includes the terminator - so remove it here */
ctx = (BIO_F_BUFFER_CTX *)b->ptr;
BIO_clear_retry_flags(b);
/* If data is already buffered then use this first */
if (ctx->ibuf_len > 0) {
p = ctx->ibuf + ctx->ibuf_off;
found_newline = 0;
for (num_chars = 0;
(num_chars < ctx->ibuf_len) && (num_chars < size);
num_chars++) {
*buf++ = p[num_chars];
if (p[num_chars] == '\n') {
found_newline = 1;
num_chars++;
break;
}
}
num += num_chars;
size -= num_chars;
ctx->ibuf_len -= num_chars;
ctx->ibuf_off += num_chars;
if (found_newline || size == 0) {
*buf = '\0';
return num;
}
}
/*
* If there is no buffered data left then read any remaining data from the
* next bio.
*/
/* Resize if we have to */
if (!readbuffer_resize(ctx, 1 + size))
return 0;
/*
* Read more data from the next bio using BIO_read_ex:
* Note we cannot use BIO_gets() here as it does not work on a
* binary stream that contains 0x00. (Since strlen() will stop at
* any 0x00 not at the last read '\n' in a FILE bio).
* Also note that some applications open and close the file bio
* multiple times and need to read the next available block when using
* stdin - so we need to READ one byte at a time!
*/
p = ctx->ibuf + ctx->ibuf_off;
for (i = 0; i < size; ++i) {
j = BIO_read(b->next_bio, p, 1);
if (j <= 0) {
BIO_copy_next_retry(b);
*buf = '\0';
return num > 0 ? num : j;
}
*buf++ = *p;
num++;
ctx->ibuf_off++;
if (*p == '\n')
break;
++p;
}
*buf = '\0';
return num;
}