view src/lib/buffer.c @ 23017:c1d36f2575c7 default tip

lib-imap: Fix "Don't accept strings with NULs" cherry-pick
author Timo Sirainen <timo.sirainen@open-xchange.com>
date Thu, 29 Aug 2019 09:55:25 +0300
parents d7ad84cec527
children
line wrap: on
line source

/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */

/* @UNSAFE: whole file */

#include "lib.h"
#include "buffer.h"

struct real_buffer {
	/* public: */
	const unsigned char *r_buffer;
	size_t used;

	/* private: */
	unsigned char *w_buffer;
	size_t dirty, alloc;

	pool_t pool;

	unsigned int alloced:1;
	unsigned int dynamic:1;
};
typedef int buffer_check_sizes[COMPILE_ERROR_IF_TRUE(sizeof(struct real_buffer) > sizeof(buffer_t)) ?1:1];

static void buffer_alloc(struct real_buffer *buf, size_t size)
{
	i_assert(buf->w_buffer == NULL || buf->alloced);

	if (size == buf->alloc)
		return;

	i_assert(size > buf->alloc);

	if (buf->w_buffer == NULL)
		buf->w_buffer = p_malloc(buf->pool, size);
	else
		buf->w_buffer = p_realloc(buf->pool, buf->w_buffer, buf->alloc, size);
	buf->alloc = size;

	buf->r_buffer = buf->w_buffer;
	buf->alloced = TRUE;
}

static inline void
buffer_check_limits(struct real_buffer *buf, size_t pos, size_t data_size)
{
	unsigned int extra;
	size_t new_size;

	if (unlikely((size_t)-1 - pos < data_size)) {
		i_panic("Buffer write out of range (%"PRIuSIZE_T
			" + %"PRIuSIZE_T")", pos, data_size);
	}
	new_size = pos + data_size;

	if (new_size > buf->used && buf->used < buf->dirty) {
		/* clear used..dirty area */
		size_t max = I_MIN(I_MIN(buf->alloc, buf->dirty), new_size);

		memset(buf->w_buffer + buf->used, 0, max - buf->used);
	}

	/* always keep +1 byte allocated available in case str_c() is called
	   for this buffer. this is mainly for cases where the buffer is
	   allocated from data stack, and str_c() is called in a separate stack
	   frame. */
	extra = buf->dynamic ? 1 : 0;
	if (new_size + extra > buf->alloc) {
		if (unlikely(!buf->dynamic)) {
			i_panic("Buffer full (%"PRIuSIZE_T" > %"PRIuSIZE_T", "
				"pool %s)", pos + data_size, buf->alloc,
				buf->pool == NULL ? "<none>" :
				pool_get_name(buf->pool));
		}

		buffer_alloc(buf, pool_get_exp_grown_size(buf->pool, buf->alloc,
							  new_size + extra));
	}
#if 0
	else if (new_size > buf->used && buf->alloced &&
		 !buf->pool->alloconly_pool && !buf->pool->datastack_pool) {
		void *new_buf;

		/* buffer's size increased: move the buffer's memory elsewhere.
		   this should help catch bugs where old pointers are tried to
		   be used to access the buffer's memory */
		new_buf = p_malloc(buf->pool, buf->alloc);
		memcpy(new_buf, buf->w_buffer, buf->alloc);
		p_free(buf->pool, buf->w_buffer);

		buf->w_buffer = new_buf;
		buf->r_buffer = new_buf;
	}
#endif

	if (new_size > buf->used)
		buf->used = new_size;
	i_assert(buf->used <= buf->alloc);
}

#undef buffer_create_from_data
void buffer_create_from_data(buffer_t *buffer, void *data, size_t size)
{
	struct real_buffer *buf;

	i_assert(sizeof(*buffer) >= sizeof(struct real_buffer));

	buf = (struct real_buffer *)buffer;
	i_zero(buf);
	buf->alloc = size;
	buf->r_buffer = buf->w_buffer = data;
	/* clear the whole memory area. unnecessary usually, but if the
	   buffer is used by e.g. str_c() it tries to access uninitialized
	   memory */
	memset(data, 0, size);
}

#undef buffer_create_from_const_data
void buffer_create_from_const_data(buffer_t *buffer,
				   const void *data, size_t size)
{
	struct real_buffer *buf;

	i_assert(sizeof(*buffer) >= sizeof(struct real_buffer));

	buf = (struct real_buffer *)buffer;
	i_zero(buf);

	buf->used = buf->alloc = size;
	buf->r_buffer = data;
	i_assert(buf->w_buffer == NULL);
}

buffer_t *buffer_create_dynamic(pool_t pool, size_t init_size)
{
	struct real_buffer *buf;

	buf = p_new(pool, struct real_buffer, 1);
	buf->pool = pool;
	buf->dynamic = TRUE;
	/* buffer_alloc() reserves +1 for str_c() NIL, so add +1 here to
	   init_size so we can actually write that much to the buffer without
	   realloc */
	buffer_alloc(buf, init_size+1);
	return (buffer_t *)buf;
}

void buffer_free(buffer_t **_buf)
{
	struct real_buffer *buf = (struct real_buffer *)*_buf;

	*_buf = NULL;
	if (buf->alloced)
		p_free(buf->pool, buf->w_buffer);
	if (buf->pool != NULL)
		p_free(buf->pool, buf);
}

void *buffer_free_without_data(buffer_t **_buf)
{
	struct real_buffer *buf = (struct real_buffer *)*_buf;
	void *data;

	*_buf = NULL;

	data = buf->w_buffer;
	p_free(buf->pool, buf);
	return data;
}

pool_t buffer_get_pool(const buffer_t *_buf)
{
	const struct real_buffer *buf = (const struct real_buffer *)_buf;

	return buf->pool;
}

void buffer_reset(buffer_t *buf)
{
	buffer_set_used_size(buf, 0);
}

void buffer_write(buffer_t *_buf, size_t pos,
		  const void *data, size_t data_size)
{
	struct real_buffer *buf = (struct real_buffer *)_buf;

	buffer_check_limits(buf, pos, data_size);
	if (data_size > 0)
		memcpy(buf->w_buffer + pos, data, data_size);
}

void buffer_append(buffer_t *buf, const void *data, size_t data_size)
{
	buffer_write(buf, buf->used, data, data_size);
}

void buffer_append_c(buffer_t *buf, unsigned char chr)
{
	buffer_append(buf, &chr, 1);
}

void buffer_insert(buffer_t *_buf, size_t pos,
		   const void *data, size_t data_size)
{
	struct real_buffer *buf = (struct real_buffer *)_buf;

	if (pos >= buf->used)
		buffer_write(_buf, pos, data, data_size);
	else {
		buffer_copy(_buf, pos + data_size, _buf, pos, (size_t)-1);
		memcpy(buf->w_buffer + pos, data, data_size);
	}
}

void buffer_delete(buffer_t *_buf, size_t pos, size_t size)
{
	struct real_buffer *buf = (struct real_buffer *)_buf;
	size_t end_size;

	if (pos >= buf->used)
		return;
	end_size = buf->used - pos;

	if (size < end_size) {
		/* delete from between */
		end_size -= size;
		memmove(buf->w_buffer + pos,
			buf->w_buffer + pos + size, end_size);
	} else {
		/* delete the rest of the buffer */
		end_size = 0;
	}

	buffer_set_used_size(_buf, pos + end_size);
}

void buffer_replace(buffer_t *_buf, size_t pos, size_t size,
		    const void *data, size_t data_size)
{
	struct real_buffer *buf = (struct real_buffer *)_buf;
	size_t end_size;

	if (pos >= buf->used) {
		buffer_write(_buf, pos, data, data_size);
		return;
	}
	end_size = buf->used - pos;

	if (size < end_size) {
		end_size -= size;
		if (data_size == 0) {
			/* delete from between */
			memmove(buf->w_buffer + pos,
				buf->w_buffer + pos + size, end_size);
		} else {
			/* insert */
			buffer_copy(_buf, pos + data_size, _buf, pos + size,
				    (size_t)-1);
			memcpy(buf->w_buffer + pos, data, data_size);
		}
	} else {
		/* overwrite the end */
		end_size = 0;
		buffer_write(_buf, pos, data, data_size);
	}

	buffer_set_used_size(_buf, pos + data_size + end_size);
}


void buffer_write_zero(buffer_t *_buf, size_t pos, size_t data_size)
{
	struct real_buffer *buf = (struct real_buffer *)_buf;

	buffer_check_limits(buf, pos, data_size);
	memset(buf->w_buffer + pos, 0, data_size);
}

void buffer_append_zero(buffer_t *buf, size_t data_size)
{
	buffer_write_zero(buf, buf->used, data_size);
}

void buffer_insert_zero(buffer_t *_buf, size_t pos, size_t data_size)
{
	struct real_buffer *buf = (struct real_buffer *)_buf;

	if (pos >= buf->used)
		buffer_write_zero(_buf, pos, data_size);
	else {
		buffer_copy(_buf, pos + data_size, _buf, pos, (size_t)-1);
		memset(buf->w_buffer + pos, 0, data_size);
	}
}

void buffer_copy(buffer_t *_dest, size_t dest_pos,
		 const buffer_t *_src, size_t src_pos, size_t copy_size)
{
	struct real_buffer *dest = (struct real_buffer *)_dest;
	const struct real_buffer *src = (const struct real_buffer *)_src;
	size_t max_size;

	i_assert(src_pos <= src->used);

	max_size = src->used - src_pos;
	if (copy_size > max_size)
		copy_size = max_size;

	buffer_check_limits(dest, dest_pos, copy_size);
	if (src == dest) {
		memmove(dest->w_buffer + dest_pos,
			src->r_buffer + src_pos, copy_size);
	} else {
		memcpy(dest->w_buffer + dest_pos,
		       src->r_buffer + src_pos, copy_size);
	}
}

void buffer_append_buf(buffer_t *dest, const buffer_t *src,
		       size_t src_pos, size_t copy_size)
{
	buffer_copy(dest, dest->used, src, src_pos, copy_size);
}

void *buffer_get_space_unsafe(buffer_t *_buf, size_t pos, size_t size)
{
	struct real_buffer *buf = (struct real_buffer *)_buf;

	buffer_check_limits(buf, pos, size);
	return buf->w_buffer + pos;
}

void *buffer_append_space_unsafe(buffer_t *buf, size_t size)
{
	return buffer_get_space_unsafe(buf, buf->used, size);
}

void *buffer_get_modifiable_data(const buffer_t *_buf, size_t *used_size_r)
{
	const struct real_buffer *buf = (const struct real_buffer *)_buf;

	if (used_size_r != NULL)
		*used_size_r = buf->used;
	return buf->w_buffer;
}

void buffer_set_used_size(buffer_t *_buf, size_t used_size)
{
	struct real_buffer *buf = (struct real_buffer *)_buf;

	i_assert(used_size <= buf->alloc);

	if (buf->used > buf->dirty)
		buf->dirty = buf->used;

	buf->used = used_size;
}

size_t buffer_get_size(const buffer_t *_buf)
{
	const struct real_buffer *buf = (const struct real_buffer *)_buf;

	return buf->alloc;
}

size_t buffer_get_writable_size(const buffer_t *_buf)
{
	const struct real_buffer *buf = (const struct real_buffer *)_buf;

	if (!buf->dynamic || buf->alloc == 0)
		return buf->alloc;

	/* we reserve +1 for str_c() NUL in buffer_check_limits(), so don't
	   include that in our return value. otherwise the caller might
	   increase the buffer's alloc size unnecessarily when it just wants
	   to access the entire buffer. */
	return buf->alloc-1;
}

bool buffer_cmp(const buffer_t *buf1, const buffer_t *buf2)
{
	if (buf1->used != buf2->used)
		return FALSE;

	return memcmp(buf1->data, buf2->data, buf1->used) == 0;
}

void buffer_verify_pool(buffer_t *_buf)
{
	const struct real_buffer *buf = (const struct real_buffer *)_buf;
	void *ret;

	if (buf->pool != NULL && buf->pool->datastack_pool && buf->alloc > 0) {
		/* this doesn't really do anything except verify the
		   stack frame */
		ret = p_realloc(buf->pool, buf->w_buffer,
				buf->alloc, buf->alloc);
		i_assert(ret == buf->w_buffer);
	}
}

void buffer_truncate_rshift_bits(buffer_t *buf, size_t bits)
{
	/* no-op if it's shorten than bits in any case.. */
	if (buf->used * 8 < bits) return;

	if (bits > 0) {
		/* truncate it to closest byte boundary */
		size_t bytes = ((bits + 7) & -8U)/8;
		/* remainding bits */
		bits = bits % 8;
		buffer_set_used_size(buf, I_MIN(bytes, buf->used));
		unsigned char *ptr = buffer_get_modifiable_data(buf, &bytes);
		/* right shift over byte array */
		if (bits > 0) {
			for(size_t i=bytes-1;i>0;i--)
				ptr[i] = (ptr[i]>>(8-bits)) +
					 ((ptr[i-1]&(0xff>>(bits)))<<bits);
			ptr[0] = ptr[0]>>(8-bits);
		}
	} else {
		buffer_set_used_size(buf, 0);
	}
}