Skip to content

Commit

Permalink
Merge pull request #5203 from OlegHahm/cbor_bound_checking
Browse files Browse the repository at this point in the history
cbor: bounds checking on read and no_copy string deserialization
  • Loading branch information
OlegHahm committed Jan 19, 2017
2 parents b92ce8a + 63bcccf commit b9b6e49
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 5 deletions.
98 changes: 93 additions & 5 deletions sys/cbor/cbor.c
Expand Up @@ -78,6 +78,10 @@
if (stream->pos + bytes >= stream->size) { return 0; } \
} while(0)

#define CBOR_ENSURE_SIZE_READ(stream, bytes) do { \
if (bytes > stream->size) { return 0; } \
} while(0)

/* Extra defines not related to the protocol itself */
#define CBOR_STREAM_PRINT_BUFFERSIZE 1024 /* bytes */

Expand All @@ -91,7 +95,20 @@
#define NAN (0.0/0.0)
#endif


/* pack to force aligned access on ARMv7 (buggy GCC) */
#pragma GCC diagnostic error "-Wcast-align"
typedef struct __attribute__((packed)) {
unsigned char u8;
union {
uint16_t u16;
uint32_t u32;
uint64_t u64;
} u;
} cast_align_u8_t;

#ifndef CBOR_NO_FLOAT

/**
* Convert float @p x to network format
*/
Expand Down Expand Up @@ -323,10 +340,14 @@ static size_t decode_int(const cbor_stream_t *s, size_t offset, uint64_t *val)

*val = 0; /* clear val first */

CBOR_ENSURE_SIZE_READ(s, offset + 1);

unsigned char *in = &s->data[offset];
unsigned char additional_info = CBOR_ADDITIONAL_INFO(s, offset);
unsigned char bytes_follow = uint_bytes_follow(additional_info);

CBOR_ENSURE_SIZE_READ(s, offset + 1 + bytes_follow);

switch (bytes_follow) {
case 0:
*val = (in[0] & CBOR_INFO_MASK);
Expand All @@ -337,15 +358,15 @@ static size_t decode_int(const cbor_stream_t *s, size_t offset, uint64_t *val)
break;

case 2:
*val = HTONS(*((uint16_t *)&in[1]));
*val = HTONS(((cast_align_u8_t *)in)->u.u16);
break;

case 4:
*val = HTONL(*((uint32_t *)&in[1]));
*val = HTONL(((cast_align_u8_t *)in)->u.u32);
break;

default:
*val = HTONLL(*((uint64_t *)&in[1]));
*val = HTONLL(((cast_align_u8_t *)in)->u.u64);
break;
}

Expand All @@ -371,6 +392,8 @@ static size_t encode_bytes(unsigned char major_type, cbor_stream_t *s, const cha

static size_t decode_bytes(const cbor_stream_t *s, size_t offset, char *out, size_t length)
{
CBOR_ENSURE_SIZE_READ(s, offset + 1);

if ((CBOR_TYPE(s, offset) != CBOR_BYTES && CBOR_TYPE(s, offset) != CBOR_TEXT) || !out) {
return 0;
}
Expand All @@ -386,20 +409,54 @@ static size_t decode_bytes(const cbor_stream_t *s, size_t offset, char *out, siz
return 0;
}

CBOR_ENSURE_SIZE_READ(s, offset + bytes_start + bytes_length);

memcpy(out, &s->data[offset + bytes_start], bytes_length);
out[bytes_length] = '\0';
return (bytes_start + bytes_length);
}

/* A zero copy version of decode_bytes.
Will not null termiante input, but will tell you the size of what you read.
Great for reading byte strings which could contain nulls inside of unknown size
without forced copies.
*/
static size_t decode_bytes_no_copy(const cbor_stream_t *s, size_t offset, unsigned char **out, size_t *length)
{
CBOR_ENSURE_SIZE_READ(s, offset + 1);

if ((CBOR_TYPE(s, offset) != CBOR_BYTES && CBOR_TYPE(s, offset) != CBOR_TEXT) || !out) {
return 0;
}

uint64_t bytes_length;
size_t bytes_start = decode_int(s, offset, &bytes_length);

if (!bytes_start) {
return 0;
}

CBOR_ENSURE_SIZE_READ(s, offset + bytes_start + bytes_length);
*out = &(s->data[offset + bytes_start]);
*length = bytes_length;
return (bytes_start + bytes_length);
}

size_t cbor_deserialize_int(const cbor_stream_t *stream, size_t offset, int *val)
{
CBOR_ENSURE_SIZE_READ(stream, offset + 1);

if ((CBOR_TYPE(stream, offset) != CBOR_UINT && CBOR_TYPE(stream, offset) != CBOR_NEGINT) || !val) {
return 0;
}

uint64_t buf;
size_t read_bytes = decode_int(stream, offset, &buf);

if (!read_bytes) {
return 0;
}

if (CBOR_TYPE(stream, offset) == CBOR_UINT) {
*val = buf; /* resolve as CBOR_UINT */
}
Expand Down Expand Up @@ -521,7 +578,7 @@ size_t cbor_deserialize_float(const cbor_stream_t *stream, size_t offset, float
unsigned char *data = &stream->data[offset];

if (*data == CBOR_FLOAT32) {
*val = ntohf(*(uint32_t *)(data + 1));
*val = ntohf(((cast_align_u8_t *)data)->u.u32);
return 5;
}

Expand All @@ -540,14 +597,17 @@ size_t cbor_serialize_float(cbor_stream_t *s, float val)

size_t cbor_deserialize_double(const cbor_stream_t *stream, size_t offset, double *val)
{
CBOR_ENSURE_SIZE_READ(stream, offset + 1);

if (CBOR_TYPE(stream, offset) != CBOR_7 || !val) {
return 0;
}

unsigned char *data = &stream->data[offset];

if (*data == CBOR_FLOAT64) {
*val = ntohd(*(uint64_t *)(data + 1));
CBOR_ENSURE_SIZE_READ(stream, offset + 9);
*val = ntohd(((cast_align_u8_t *)data)->u.u64);
return 9;
}

Expand All @@ -568,13 +628,27 @@ size_t cbor_serialize_double(cbor_stream_t *s, double val)
size_t cbor_deserialize_byte_string(const cbor_stream_t *stream, size_t offset, char *val,
size_t length)
{
CBOR_ENSURE_SIZE_READ(stream, offset + 1);

if (CBOR_TYPE(stream, offset) != CBOR_BYTES) {
return 0;
}

return decode_bytes(stream, offset, val, length);
}

size_t cbor_deserialize_byte_string_no_copy(const cbor_stream_t *stream, size_t offset, unsigned char **val,
size_t *length)
{
CBOR_ENSURE_SIZE_READ(stream, offset + 1);

if (CBOR_TYPE(stream, offset) != CBOR_BYTES) {
return 0;
}

return decode_bytes_no_copy(stream, offset, val, length);
}

size_t cbor_serialize_byte_string(cbor_stream_t *stream, const char *val)
{
return encode_bytes(CBOR_BYTES, stream, val, strlen(val));
Expand All @@ -588,13 +662,27 @@ size_t cbor_serialize_byte_stringl(cbor_stream_t *stream, const char *val, size_
size_t cbor_deserialize_unicode_string(const cbor_stream_t *stream, size_t offset, char *val,
size_t length)
{
CBOR_ENSURE_SIZE_READ(stream, offset + 1);

if (CBOR_TYPE(stream, offset) != CBOR_TEXT) {
return 0;
}

return decode_bytes(stream, offset, val, length);
}

size_t cbor_deserialize_unicode_string_no_copy(const cbor_stream_t *stream, size_t offset, unsigned char **val,
size_t *length)
{
CBOR_ENSURE_SIZE_READ(stream, offset + 1);

if (CBOR_TYPE(stream, offset) != CBOR_TEXT) {
return 0;
}

return decode_bytes_no_copy(stream, offset, val, length);
}

size_t cbor_serialize_unicode_string(cbor_stream_t *stream, const char *val)
{
return encode_bytes(CBOR_TEXT, stream, val, strlen(val));
Expand Down
16 changes: 16 additions & 0 deletions sys/include/cbor.h
Expand Up @@ -391,6 +391,22 @@ size_t cbor_deserialize_byte_string(const cbor_stream_t *stream, size_t offset,

size_t cbor_serialize_unicode_string(cbor_stream_t *stream, const char *val);

/**
* @brief Deserialize bytes/unicode from @p stream to @p val (without copy)
*
* @param[in] stream The stream to deserialize
* @param[in] offset The offset within the stream where to start deserializing
* @param[out] val Pointer to a char *
* @param[out] length Pointer to a size_t to store the size of the string
*
* @return Number of bytes written into @p val
*/
size_t cbor_deserialize_byte_string_no_copy(const cbor_stream_t *stream, size_t offset,
unsigned char **val, size_t *length);

size_t cbor_deserialize_unicode_string_no_copy(const cbor_stream_t *stream, size_t offset,
unsigned char **val, size_t *length);

/**
* @brief Deserialize unicode string from @p stream to @p val
*
Expand Down
68 changes: 68 additions & 0 deletions tests/unittests/tests-cbor/tests-cbor.c
Expand Up @@ -279,6 +279,39 @@ static void test_byte_string(void)
}
}

static void test_byte_string_no_copy(void)
{
char buffer[128];

{
const char *input = "";
unsigned char data[] = {0x40};
unsigned char *out;
size_t size;
TEST_ASSERT(cbor_serialize_byte_string(&stream, input));
CBOR_CHECK_SERIALIZED(stream, data, sizeof(data));
TEST_ASSERT(cbor_deserialize_byte_string_no_copy(&stream, 0, &out, &size));
memcpy(buffer, out, size);
buffer[size] = '\0';
CBOR_CHECK_DESERIALIZED(input, buffer, EQUAL_STRING);
}

cbor_clear(&stream);

{
const char *input = "a";
unsigned char data[] = {0x41, 0x61};
unsigned char *out;
size_t size;
TEST_ASSERT(cbor_serialize_byte_string(&stream, input));
CBOR_CHECK_SERIALIZED(stream, data, sizeof(data));
TEST_ASSERT(cbor_deserialize_byte_string_no_copy(&stream, 0, &out, &size));
memcpy(buffer, out, size);
buffer[size] = '\0';
CBOR_CHECK_DESERIALIZED(input, buffer, EQUAL_STRING);
}
}

static void test_byte_string_invalid(void)
{
{
Expand Down Expand Up @@ -318,6 +351,39 @@ static void test_unicode_string(void)
}
}

static void test_unicode_string_no_copy(void)
{
char buffer[128];

{
const char *input = "";
unsigned char data[] = {0x60};
unsigned char *out;
size_t size;
TEST_ASSERT(cbor_serialize_unicode_string(&stream, input));
CBOR_CHECK_SERIALIZED(stream, data, sizeof(data));
TEST_ASSERT(cbor_deserialize_unicode_string_no_copy(&stream, 0, &out, &size));
memcpy(buffer, out, size);
buffer[size] = '\0';
CBOR_CHECK_DESERIALIZED(input, buffer, EQUAL_STRING);
}

cbor_clear(&stream);

{
const char *input = "a";
unsigned char data[] = {0x61, 0x61};
unsigned char *out;
size_t size;
TEST_ASSERT(cbor_serialize_unicode_string(&stream, input));
CBOR_CHECK_SERIALIZED(stream, data, sizeof(data));
TEST_ASSERT(cbor_deserialize_unicode_string_no_copy(&stream, 0, &out, &size));
memcpy(buffer, out, size);
buffer[size] = '\0';
CBOR_CHECK_DESERIALIZED(input, buffer, EQUAL_STRING);
}
}

static void test_unicode_string_invalid(void)
{
{
Expand Down Expand Up @@ -761,8 +827,10 @@ TestRef tests_cbor_all(void)
new_TestFixture(test_int64_t),
new_TestFixture(test_int64_t_invalid),
new_TestFixture(test_byte_string),
new_TestFixture(test_byte_string_no_copy),
new_TestFixture(test_byte_string_invalid),
new_TestFixture(test_unicode_string),
new_TestFixture(test_unicode_string_no_copy),
new_TestFixture(test_unicode_string_invalid),
new_TestFixture(test_array),
new_TestFixture(test_array_indefinite),
Expand Down

0 comments on commit b9b6e49

Please sign in to comment.