mirror of https://github.com/c-ares/c-ares.git
Fix for TCP back to back queries (#552)
As per #266, TCP queries are basically broken. If we get a partial reply, things just don't work, but unlike UDP, TCP may get fragmented and we need to properly handle that. I've started creating a basic parser/buffer framework for c-ares for memory safety reasons, but it also helps for things like this where we shouldn't be manually tracking positions and fetching only a couple of bytes at a time from a socket. This parser/buffer will be expanded and used more in the future. This also resolves #206 by allowing NULL to be specified for some socket callbacks so they will auto-route to the built-in c-ares functions. Fixes: #206, #266 Fix By: Brad House (@bradh352)pull/557/head
parent
17ab747945
commit
fab4039b9b
18 changed files with 715 additions and 486 deletions
@ -0,0 +1,320 @@ |
||||
/* Copyright (C) 2023 by Brad House
|
||||
* |
||||
* Permission to use, copy, modify, and distribute this |
||||
* software and its documentation for any purpose and without |
||||
* fee is hereby granted, provided that the above copyright |
||||
* notice appear in all copies and that both that copyright |
||||
* notice and this permission notice appear in supporting |
||||
* documentation, and that the name of M.I.T. not be used in |
||||
* advertising or publicity pertaining to distribution of the |
||||
* software without specific, written prior permission. |
||||
* M.I.T. makes no representations about the suitability of |
||||
* this software for any purpose. It is provided "as is" |
||||
* without express or implied warranty. |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
#include "ares_setup.h" |
||||
#include "ares.h" |
||||
#include "ares_private.h" |
||||
#include "ares__buf.h" |
||||
#include <limits.h> |
||||
#ifdef HAVE_STDINT_H |
||||
# include <stdint.h> |
||||
#endif |
||||
|
||||
struct ares__buf { |
||||
const unsigned char *data; /*!< pointer to start of data buffer */ |
||||
size_t data_len; /*!< total size of data in buffer */ |
||||
|
||||
unsigned char *alloc_buf; /*!< Pointer to allocated data buffer,
|
||||
* not used for const buffers */ |
||||
size_t alloc_buf_len; /*!< Size of allocated data buffer */ |
||||
|
||||
size_t offset; /*!< Current working offset in buffer */ |
||||
size_t tag_offset; /*!< Tagged offset in buffer. Uses
|
||||
* SIZE_MAX if not set. */ |
||||
}; |
||||
|
||||
ares__buf_t *ares__buf_create(void) |
||||
{ |
||||
ares__buf_t *buf = ares_malloc(sizeof(*buf)); |
||||
if (buf == NULL) |
||||
return NULL; |
||||
|
||||
memset(buf, 0, sizeof(*buf)); |
||||
buf->tag_offset = SIZE_MAX; |
||||
return buf; |
||||
} |
||||
|
||||
|
||||
ares__buf_t *ares__buf_create_const(const unsigned char *data, size_t data_len) |
||||
{ |
||||
ares__buf_t *buf; |
||||
|
||||
if (data == NULL || data_len == 0) |
||||
return NULL; |
||||
|
||||
buf = ares__buf_create(); |
||||
if (buf == NULL) |
||||
return NULL; |
||||
|
||||
buf->data = data; |
||||
buf->data_len = data_len; |
||||
|
||||
return buf; |
||||
} |
||||
|
||||
|
||||
void ares__buf_destroy(ares__buf_t *buf) |
||||
{ |
||||
if (buf == NULL) |
||||
return; |
||||
ares_free(buf->alloc_buf); |
||||
ares_free(buf); |
||||
} |
||||
|
||||
|
||||
static int ares__buf_is_const(const ares__buf_t *buf) |
||||
{ |
||||
if (buf == NULL) |
||||
return 0; |
||||
|
||||
if (buf->data != NULL && buf->alloc_buf == NULL) |
||||
return 1; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
|
||||
static void ares__buf_reclaim(ares__buf_t *buf, size_t needed_size) |
||||
{ |
||||
size_t prefix_size; |
||||
size_t remaining_size; |
||||
size_t data_size; |
||||
|
||||
if (buf == NULL) |
||||
return; |
||||
|
||||
if (ares__buf_is_const(buf)) |
||||
return; |
||||
|
||||
remaining_size = buf->alloc_buf_len - buf->data_len; |
||||
|
||||
/* No need to do an expensive move operation, we have enough to just append */ |
||||
if (remaining_size >= needed_size) |
||||
return; |
||||
|
||||
if (buf->tag_offset != SIZE_MAX) { |
||||
prefix_size = buf->tag_offset; |
||||
} else { |
||||
prefix_size = buf->offset; |
||||
} |
||||
|
||||
if (prefix_size == 0) |
||||
return; |
||||
|
||||
data_size = buf->data_len - prefix_size; |
||||
|
||||
memmove(buf->alloc_buf, buf->alloc_buf + prefix_size, data_size); |
||||
buf->data = buf->alloc_buf; |
||||
buf->data_len = data_size; |
||||
buf->offset -= prefix_size; |
||||
if (buf->tag_offset != SIZE_MAX) |
||||
buf->tag_offset -= prefix_size; |
||||
|
||||
return; |
||||
} |
||||
|
||||
|
||||
static int ares__buf_ensure_space(ares__buf_t *buf, size_t needed_size) |
||||
{ |
||||
size_t remaining_size; |
||||
size_t alloc_size; |
||||
unsigned char *ptr; |
||||
|
||||
if (buf == NULL) |
||||
return 0; |
||||
|
||||
if (ares__buf_is_const(buf)) |
||||
return 0; |
||||
|
||||
/* See if just moving consumed data frees up enough space */ |
||||
ares__buf_reclaim(buf, needed_size); |
||||
|
||||
remaining_size = buf->alloc_buf_len - buf->data_len; |
||||
if (remaining_size >= needed_size) |
||||
return 1; |
||||
|
||||
alloc_size = buf->alloc_buf_len; |
||||
|
||||
/* Not yet started */ |
||||
if (alloc_size == 0) |
||||
alloc_size = 512; |
||||
|
||||
/* Increase allocation by powers of 2 */ |
||||
do { |
||||
alloc_size <<= 1; |
||||
remaining_size = alloc_size - buf->data_len; |
||||
} while (remaining_size < needed_size); |
||||
|
||||
ptr = ares_realloc(buf->alloc_buf, alloc_size); |
||||
if (ptr == NULL) |
||||
return 0; |
||||
|
||||
buf->alloc_buf = ptr; |
||||
buf->alloc_buf_len = alloc_size; |
||||
buf->data = ptr; |
||||
|
||||
return 1; |
||||
} |
||||
|
||||
|
||||
int ares__buf_append(ares__buf_t *buf, const unsigned char *data, |
||||
size_t data_len) |
||||
{ |
||||
if (data == NULL || data_len == 0) |
||||
return 0; |
||||
|
||||
if (!ares__buf_ensure_space(buf, data_len)) |
||||
return 0; |
||||
|
||||
memcpy(buf->alloc_buf + buf->data_len, data, data_len); |
||||
buf->data_len += data_len; |
||||
return 1; |
||||
} |
||||
|
||||
|
||||
unsigned char *ares__buf_append_start(ares__buf_t *buf, size_t *len) |
||||
{ |
||||
if (len == NULL || *len == 0) |
||||
return 0; |
||||
|
||||
if (!ares__buf_ensure_space(buf, *len)) |
||||
return 0; |
||||
|
||||
*len = buf->alloc_buf_len - buf->data_len; |
||||
return buf->alloc_buf + buf->data_len; |
||||
} |
||||
|
||||
|
||||
void ares__buf_append_finish(ares__buf_t *buf, size_t len) |
||||
{ |
||||
if (buf == NULL) |
||||
return; |
||||
|
||||
buf->data_len += len; |
||||
} |
||||
|
||||
|
||||
void ares__buf_tag(ares__buf_t *buf) |
||||
{ |
||||
if (buf == NULL) |
||||
return; |
||||
|
||||
buf->tag_offset = buf->offset; |
||||
} |
||||
|
||||
|
||||
int ares__buf_tag_rollback(ares__buf_t *buf) |
||||
{ |
||||
if (buf == NULL || buf->tag_offset == SIZE_MAX) |
||||
return 0; |
||||
|
||||
buf->offset = buf->tag_offset; |
||||
buf->tag_offset = SIZE_MAX; |
||||
return 1; |
||||
} |
||||
|
||||
|
||||
int ares__buf_tag_clear(ares__buf_t *buf) |
||||
{ |
||||
if (buf == NULL || buf->tag_offset == SIZE_MAX) |
||||
return 0; |
||||
|
||||
buf->tag_offset = SIZE_MAX; |
||||
return 1; |
||||
} |
||||
|
||||
|
||||
const unsigned char *ares__buf_tag_fetch(const ares__buf_t *buf, size_t *len) |
||||
{ |
||||
if (buf == NULL || buf->tag_offset == SIZE_MAX || len == NULL) |
||||
return NULL; |
||||
|
||||
*len = buf->offset - buf->tag_offset; |
||||
return buf->data + buf->tag_offset; |
||||
} |
||||
|
||||
|
||||
static const unsigned char *ares__buf_fetch(const ares__buf_t *buf, size_t *len) |
||||
{ |
||||
if (len != NULL) |
||||
*len = 0; |
||||
|
||||
if (buf == NULL || len == NULL || buf->data == NULL) |
||||
return NULL; |
||||
|
||||
*len = buf->data_len - buf->offset; |
||||
return buf->data + buf->offset; |
||||
} |
||||
|
||||
|
||||
int ares__buf_consume(ares__buf_t *buf, size_t len) |
||||
{ |
||||
size_t remaining_len; |
||||
|
||||
ares__buf_fetch(buf, &remaining_len); |
||||
|
||||
if (remaining_len < len) |
||||
return 0; |
||||
|
||||
buf->offset += len; |
||||
return 1; |
||||
} |
||||
|
||||
|
||||
int ares__buf_fetch_be16(ares__buf_t *buf, unsigned short *u16) |
||||
{ |
||||
size_t remaining_len; |
||||
const unsigned char *ptr = ares__buf_fetch(buf, &remaining_len); |
||||
|
||||
if (buf == NULL || u16 == NULL || remaining_len < sizeof(*u16)) |
||||
return 0; |
||||
|
||||
*u16 = (unsigned short)((unsigned short)(ptr[0]) << 8 | (unsigned short)ptr[1]); |
||||
|
||||
return ares__buf_consume(buf, sizeof(*u16)); |
||||
} |
||||
|
||||
|
||||
int ares__buf_fetch_bytes(ares__buf_t *buf, unsigned char *bytes, |
||||
size_t len) |
||||
{ |
||||
size_t remaining_len; |
||||
const unsigned char *ptr = ares__buf_fetch(buf, &remaining_len); |
||||
|
||||
if (buf == NULL || bytes == NULL || len == 0 || remaining_len < len) |
||||
return 0; |
||||
|
||||
memcpy(bytes, ptr, len); |
||||
return ares__buf_consume(buf, len); |
||||
} |
||||
|
||||
|
||||
size_t ares__buf_len(const ares__buf_t *buf) |
||||
{ |
||||
size_t len = 0; |
||||
ares__buf_fetch(buf, &len); |
||||
return len; |
||||
} |
||||
|
||||
|
||||
const unsigned char *ares__buf_peek(const ares__buf_t *buf, size_t *len) |
||||
{ |
||||
return ares__buf_fetch(buf, len); |
||||
} |
||||
|
||||
#if 0 |
||||
int ares__buf_fetch_dnsheader(ares__buf_t *buf, ares_dns_header_t *header); |
||||
#endif |
@ -0,0 +1,180 @@ |
||||
/* Copyright (C) 2023 by Brad House
|
||||
* |
||||
* Permission to use, copy, modify, and distribute this |
||||
* software and its documentation for any purpose and without |
||||
* fee is hereby granted, provided that the above copyright |
||||
* notice appear in all copies and that both that copyright |
||||
* notice and this permission notice appear in supporting |
||||
* documentation, and that the name of M.I.T. not be used in |
||||
* advertising or publicity pertaining to distribution of the |
||||
* software without specific, written prior permission. |
||||
* M.I.T. makes no representations about the suitability of |
||||
* this software for any purpose. It is provided "as is" |
||||
* without express or implied warranty. |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
#ifndef __ARES__BUF_H |
||||
#define __ARES__BUF_H |
||||
|
||||
/*! \addtogroup ares__buf Safe Data Builder and buffer
|
||||
* |
||||
* This is a buffer building and parsing framework with a focus on security over |
||||
* performance. All data to be read from the buffer will perform explicit length |
||||
* validation and return a success/fail result. There are also various helpers |
||||
* for writing data to the buffer which dynamically grows. |
||||
* |
||||
* The helpers for this object are meant to be added as needed. If you can't |
||||
* find it, write it! |
||||
* |
||||
* @{ |
||||
*/ |
||||
struct ares__buf; |
||||
|
||||
/*! Opaque data type for generic hash table implementation */ |
||||
typedef struct ares__buf ares__buf_t; |
||||
|
||||
/*! Create a new buffer object that dynamically allocates buffers for data.
|
||||
*
|
||||
* \return initialized buffer object or NULL if out of memory. |
||||
*/ |
||||
ares__buf_t *ares__buf_create(void); |
||||
|
||||
/*! Create a new buffer object that uses a user-provided data pointer. The
|
||||
* data provided will not be manipulated, and cannot be appended to. This |
||||
* is strictly used for parsing. |
||||
* |
||||
* \param[in] data Data to provide to buffer, must not be NULL. |
||||
* \param[in] data_len Size of buffer provided, must be > 0 |
||||
* |
||||
* \return initialized buffer object or NULL if out of memory or misuse. |
||||
*/ |
||||
ares__buf_t *ares__buf_create_const(const unsigned char *data, size_t data_len); |
||||
|
||||
/*! Destroy an initialized buffer object.
|
||||
* |
||||
* \param[in] buf Initialized buf object |
||||
*/ |
||||
void ares__buf_destroy(ares__buf_t *buf); |
||||
|
||||
/*! Append to a dynamic buffer object
|
||||
* |
||||
* \param[in] buf Initialized buffer object |
||||
* \param[in] data Data to copy to buffer object |
||||
* \param[in] data_len Length of data to copy to buffer object. |
||||
* \return 1 on success, 0 on failure (out of memory, const buffer, usage, etc) |
||||
*/ |
||||
int ares__buf_append(ares__buf_t *buf, const unsigned char *data, |
||||
size_t data_len); |
||||
|
||||
|
||||
/*! Start a dynamic append operation that returns a buffer suitable for
|
||||
* writing. A desired minimum length is passed in, and the actual allocated |
||||
* buffer size is returned which may be greater than the requested size. |
||||
* No operation other than ares__buf_append_finish() is allowed on the |
||||
* buffer after this request. |
||||
* |
||||
* \param[in] buf Initialized buffer object |
||||
* \param[in,out] len Desired non-zero length passed in, actual buffer size |
||||
* returned. |
||||
* \return Pointer to writable buffer or NULL on failure (usage, out of mem) |
||||
*/ |
||||
unsigned char *ares__buf_append_start(ares__buf_t *buf, size_t *len); |
||||
|
||||
/*! Finish a dynamic append operation. Called after
|
||||
* ares__buf_append_start() once desired data is written. |
||||
* |
||||
* \param[in] buf Initialized buffer object. |
||||
* \param[in] len Length of data written. May be zero to terminate |
||||
* operation. Must not be greater than returned from |
||||
* ares__buf_append_start(). |
||||
*/ |
||||
void ares__buf_append_finish(ares__buf_t *buf, size_t len); |
||||
|
||||
/*! Tag a position to save in the buffer in case parsing needs to rollback,
|
||||
* such as if insufficient data is available, but more data may be added in |
||||
* the future. Only a single tag can be set per buffer object. Setting a |
||||
* tag will override any pre-existing tag. |
||||
* |
||||
* \param[in] buf Initialized buffer object |
||||
*/ |
||||
void ares__buf_tag(ares__buf_t *buf); |
||||
|
||||
/*! Rollback to a tagged position. Will automatically clear the tag.
|
||||
* |
||||
* \param[in] buf Initialized buffer object |
||||
* \return 1 on success, 0 if no tag |
||||
*/ |
||||
int ares__buf_tag_rollback(ares__buf_t *buf); |
||||
|
||||
/*! Clear the tagged position without rolling back. You should do this any
|
||||
* time a tag is no longer needed as future append operations can reclaim |
||||
* buffer space. |
||||
* |
||||
* \param[in] buf Initialized buffer object |
||||
* \return 1 on success, 0 if no tag |
||||
*/ |
||||
int ares__buf_tag_clear(ares__buf_t *buf); |
||||
|
||||
/*! Fetch the buffer and length of data starting from the tagged position up
|
||||
* to the _current_ position. It will not unset the tagged position. The |
||||
* data may be invalidated by any future ares__buf_*() calls. |
||||
* |
||||
* \param[in] buf Initialized buffer object |
||||
* \param[out] len Length between tag and current offset in buffer |
||||
* \return NULL on failure (such as no tag), otherwise pointer to start of |
||||
* buffer |
||||
*/ |
||||
const unsigned char *ares__buf_tag_fetch(const ares__buf_t *buf, size_t *len); |
||||
|
||||
/*! Consume the given number of bytes without reading them.
|
||||
* |
||||
* \param[in] buf Initialized buffer object |
||||
* \param[in] len Length to consume |
||||
* \return 1 on success, 0 if insufficient buffer remaining |
||||
*/ |
||||
int ares__buf_consume(ares__buf_t *buf, size_t len); |
||||
|
||||
/*! Fetch a 16bit Big Endian number from the buffer.
|
||||
* |
||||
* \param[in] buf Initialized buffer object |
||||
* \param[out] u16 Buffer to hold 16bit integer |
||||
* \return 1 on success, 0 if insufficient buffer remaining |
||||
*/ |
||||
int ares__buf_fetch_be16(ares__buf_t *buf, unsigned short *u16); |
||||
|
||||
/*! Fetch the requested number of bytes into the provided buffer
|
||||
* |
||||
* \param[in] buf Initialized buffer object |
||||
* \param[out] bytes Buffer to hold data |
||||
* \param[in] len Requested number of bytes (must be > 0) |
||||
* \return 1 on success, 0 if insufficient buffer remaining (or misuse) |
||||
*/ |
||||
int ares__buf_fetch_bytes(ares__buf_t *buf, unsigned char *bytes, |
||||
size_t len); |
||||
|
||||
/*! Size of unprocessed remaining data length
|
||||
* |
||||
* \param[in] buf Initialized buffer object |
||||
* \return length remaining |
||||
*/ |
||||
size_t ares__buf_len(const ares__buf_t *buf); |
||||
|
||||
/*! Retrieve a pointer to the currently unprocessed data. Generally this isn't
|
||||
* recommended to be used in practice. The returned pointer may be invalidated |
||||
* by any future ares__buf_*() calls. |
||||
* |
||||
* \param[in] buf Initialized buffer object |
||||
* \param[out] len Length of available data |
||||
* \return Pointer to buffer of unprocessed data |
||||
*/ |
||||
const unsigned char *ares__buf_peek(const ares__buf_t *buf, |
||||
size_t *len); |
||||
|
||||
#if 0 |
||||
int ares__buf_fetch_dnsheader(ares__buf_t *buf, ares_dns_header_t *header); |
||||
#endif |
||||
|
||||
/*! @} */ |
||||
|
||||
#endif /* __ARES__buffer_H */ |
@ -1,79 +0,0 @@ |
||||
/* Copyright 1998 by the Massachusetts Institute of Technology.
|
||||
* |
||||
* Permission to use, copy, modify, and distribute this |
||||
* software and its documentation for any purpose and without |
||||
* fee is hereby granted, provided that the above copyright |
||||
* notice appear in all copies and that both that copyright |
||||
* notice and this permission notice appear in supporting |
||||
* documentation, and that the name of M.I.T. not be used in |
||||
* advertising or publicity pertaining to distribution of the |
||||
* software without specific, written prior permission. |
||||
* M.I.T. makes no representations about the suitability of |
||||
* this software for any purpose. It is provided "as is" |
||||
* without express or implied warranty. |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
#include "ares_setup.h" |
||||
|
||||
#ifdef HAVE_LIMITS_H |
||||
# include <limits.h> |
||||
#endif |
||||
|
||||
#include "ares.h" |
||||
#include "ares_private.h" |
||||
|
||||
#ifndef HAVE_WRITEV |
||||
ares_ssize_t ares_writev(ares_socket_t s, const struct iovec *iov, int iovcnt) |
||||
{ |
||||
char *buffer, *bp; |
||||
int i; |
||||
size_t bytes = 0; |
||||
ares_ssize_t result; |
||||
|
||||
/* Validate iovcnt */ |
||||
if (iovcnt <= 0) |
||||
{ |
||||
SET_ERRNO(EINVAL); |
||||
return (-1); |
||||
} |
||||
|
||||
/* Validate and find the sum of the iov_len values in the iov array */ |
||||
for (i = 0; i < iovcnt; i++) |
||||
{ |
||||
if (iov[i].iov_len > INT_MAX - bytes) |
||||
{ |
||||
SET_ERRNO(EINVAL); |
||||
return (-1); |
||||
} |
||||
bytes += iov[i].iov_len; |
||||
} |
||||
|
||||
if (bytes == 0) |
||||
return (0); |
||||
|
||||
/* Allocate a temporary buffer to hold the data */ |
||||
buffer = ares_malloc(bytes); |
||||
if (!buffer) |
||||
{ |
||||
SET_ERRNO(ENOMEM); |
||||
return (-1); |
||||
} |
||||
|
||||
/* Copy the data into buffer */ |
||||
for (bp = buffer, i = 0; i < iovcnt; ++i) |
||||
{ |
||||
memcpy (bp, iov[i].iov_base, iov[i].iov_len); |
||||
bp += iov[i].iov_len; |
||||
} |
||||
|
||||
/* Send buffer contents */ |
||||
result = swrite(s, buffer, bytes); |
||||
|
||||
ares_free(buffer); |
||||
|
||||
return (result); |
||||
} |
||||
#endif |
||||
|
@ -1,38 +0,0 @@ |
||||
#ifndef HEADER_CARES_WRITEV_H |
||||
#define HEADER_CARES_WRITEV_H |
||||
|
||||
|
||||
/* Copyright 1998 by the Massachusetts Institute of Technology.
|
||||
* |
||||
* Permission to use, copy, modify, and distribute this |
||||
* software and its documentation for any purpose and without |
||||
* fee is hereby granted, provided that the above copyright |
||||
* notice appear in all copies and that both that copyright |
||||
* notice and this permission notice appear in supporting |
||||
* documentation, and that the name of M.I.T. not be used in |
||||
* advertising or publicity pertaining to distribution of the |
||||
* software without specific, written prior permission. |
||||
* M.I.T. makes no representations about the suitability of |
||||
* this software for any purpose. It is provided "as is" |
||||
* without express or implied warranty. |
||||
* |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
#include "ares_setup.h" |
||||
#include "ares.h" |
||||
|
||||
#ifndef HAVE_WRITEV |
||||
|
||||
/* Structure for scatter/gather I/O. */ |
||||
struct iovec |
||||
{ |
||||
void *iov_base; /* Pointer to data. */ |
||||
size_t iov_len; /* Length of data. */ |
||||
}; |
||||
|
||||
extern ares_ssize_t ares_writev(ares_socket_t s, const struct iovec *iov, int iovcnt); |
||||
|
||||
#endif |
||||
|
||||
#endif /* HEADER_CARES_WRITEV_H */ |
Loading…
Reference in new issue