Compare commits

...

16 Commits

Author SHA1 Message Date
Petteri Aimonen
b947dc6e2c Publishing nanopb-0.3.1 2014-09-11 19:36:14 +03:00
Petteri Aimonen
8d7deb4952 Update changelog 2014-09-11 19:26:32 +03:00
Petteri Aimonen
07e9ffb97b Add a fuzz testing stub for ability to use external generators also 2014-09-11 19:22:57 +03:00
Petteri Aimonen
d2099cc8f1 Protect against size_t overflows in pb_dec_bytes/pb_dec_string.
Possible consequences of bug:
1) Denial of service by causing a crash
   Possible when all of the following apply:
      - Untrusted data is passed to pb_decode()
      - The top-level message contains a static string field as the first field.
   Causes a single write of '0' byte to 1 byte before the message struct.

2) Remote code execution
   Possible when all of the following apply:
      - 64-bit platform
      - The message or a submessage contains a static/pointer string field.
      - Decoding directly from a custom pb_istream_t
      - bytes_left on the stream is set to larger than 4 GB
   Causes a write of up to 4 GB of data past the string field.

3) Possible heap corruption or remote code execution
   Possible when all of the following apply:
      - less than 64-bit platform
      - The message or a submessage contains a pointer-type bytes field.
   Causes a write of sizeof(pb_size_t) bytes of data past a 0-byte long
   malloc()ed buffer. On many malloc() implementations, this causes at
   most a crash. However, remote code execution through a controlled jump
   cannot be ruled out.

--

Detailed analysis follows

In the following consideration, I define "platform bitness" as equal to
number of bits in size_t datatype. Therefore most 8-bit platforms are
regarded as 16-bit for the purposes of this discussion.

1. The overflow in pb_dec_string

The overflow happens in this computation:

uint32_t size;
size_t alloc_size;
alloc_size = size + 1;

There are two ways in which the overflow can occur: In the uint32_t
addition, or in the cast to size_t. This depends on the platform
bitness.

On 32- and 64-bit platforms, the size has to be UINT32_MAX for the
overflow to occur. In that case alloc_size will be 0.

On 16-bit platforms, overflow will happen whenever size is more than
UINT16_MAX, and resulting alloc_size is attacker controlled.

For static fields, the alloc_size value is just checked against the
field data size. For pointer fields, the alloc_size value is passed to
malloc(). End result in both cases is the same, the storage is 0 or
just a few bytes in length.

On 16-bit platforms, another overflow occurs in the call to pb_read(),
when passing the original size. An attacker will want the passed value
to be larger than the alloc_size, therefore the only reasonable choice
is to have size = UINT16_MAX and alloc_size = 0. Any larger multiple
will truncate to the same values.

At this point we have read atleast the tag and the string length of the
message, i.e. atleast 3 bytes. The maximum initial value for stream
bytes_left is SIZE_MAX, thus at this point at most SIZE_MAX-3 bytes are
remaining.

On 32-bit and 16-bit platforms this means that the size passed to
pb_read() is always larger than the number of remaining bytes. This
causes pb_read() to fail immediately, before reading any bytes.

On 64-bit platforms, it is possible for the bytes_left value to be set
to a value larger than UINT32_MAX, which is the wraparound point in
size calculation. In this case pb_read() will succeed and write up to 4
GB of attacker controlled data over the RAM that comes after the string
field.

On all platforms, there is an unconditional write of a terminating null
byte. Because the size of size_t typically reflects the size of the
processor address space, a write at UINT16_MAX or UINT32_MAX bytes
after the string field actually wraps back to before the string field.
Consequently, on 32-bit and 16-bit platforms, the bug causes a single
write of '0' byte at one byte before the string field.

If the string field is in the middle of a message, this will just
corrupt other data in the message struct. Because the message contents
is attacker controlled anyway, this is a non-issue. However, if the
string field is the first field in the top-level message, it can
corrupt other data on the stack/heap before it. Typically a single '0'
write at a location not controlled by attacker is enough only for a
denial-of-service attack.

When using pointer fields and malloc(), the attacker controlled
alloc_size will cause a 0-size allocation to happen. By the same logic
as before, on 32-bit and 16-bit platforms this causes a '0' byte write
only. On 64-bit platforms, however, it will again allow up to 4 GB of
malicious data to be written over memory, if the stream length allows
the read.

2. The overflow in pb_dec_bytes

This overflow happens in the PB_BYTES_ARRAY_T_ALLOCSIZE macro:

The computation is done in size_t data type this time. This means that
an overflow is possible only when n is larger than SIZE_MAX -
offsetof(..). The offsetof value in this case is equal to
sizeof(pb_size_t) bytes.

Because the incoming size value is limited to 32 bits, no overflow can
happen here on 64-bit platforms.

The size will be passed to pb_read(). Like before, on 32-bit and 16-bit
platforms the read will always fail before writing anything.

This leaves only the write of bdest->size as exploitable. On statically
allocated fields, the size field will always be allocated, regardless
of alloc_size. In this case, no buffer overflow is possible here, but
user code could possibly use the attacker controlled size value and
read past a buffer.

If the field is allocated through malloc(), this will allow a write of
sizeof(pb_size_t) attacker controlled bytes to past a 0-byte long
buffer. In typical malloc implementations, this will either fit in
unused alignment padding area, or cause a heap corruption and a crash.
Under very exceptional situation it could allow attacker to influence
the behaviour of malloc(), possibly jumping into an attacker-controlled
location and thus leading to remote code execution.
2014-09-11 19:22:57 +03:00
Petteri Aimonen
d0466bdf43 Add just-to-be-sure check to allocate_field().
This check will help to detect bugs earlier, and is quite lightweight
compared to malloc() anyway.
2014-09-11 19:22:57 +03:00
Petteri Aimonen
5e3edb5415 Fix memory leak with duplicated fields and PB_ENABLE_MALLOC.
If a required or optional field appeared twice in the message data,
pb_decode will overwrite the old data with new one. That is fine, but
with submessage fields, it didn't release the allocated subfields before
overwriting.

This bug can manifest if all of the following conditions are true:

1. There is a message with a "optional" or "required" submessage field
   that has type:FT_POINTER.

2. The submessage contains atleast one field with type:FT_POINTER.

3. The message data to be decoded has the submessage field twice in it.
2014-09-11 19:22:57 +03:00
Petteri Aimonen
13a07e35b6 Fix crash in pb_release() if called twice on same message.
There was a double-free bug in pb_release() because it didn't set size fields
to zero after deallocation. Most commonly this happens if pb_decode() fails,
internally calls pb_release() and then application code also calls pb_release().
2014-09-11 19:22:57 +03:00
Petteri Aimonen
0dce9ef635 Add a better fuzz test.
Attempts to verify all the properties defined in the security model,
while also being portable and able to run on many platforms.
2014-09-11 19:22:57 +03:00
Petteri Aimonen
8189d538dd Add test case for simulated io errors.
Update issue 126
Status: FixedInGit
2014-09-07 20:31:36 +03:00
Petteri Aimonen
38613acdb4 Add a few missing unit tests 2014-09-07 20:30:17 +03:00
Petteri Aimonen
cdbf51db08 Fix compilation error with generated initializers for repeated pointer fields 2014-09-07 19:49:00 +03:00
Petteri Aimonen
d0299d87ec Code coverage results were ignoring the data from encode/decode unittests.
Update issue 126
Status: Started
2014-09-07 19:25:09 +03:00
Petteri Aimonen
d82a264c41 Update security model with regards to pointer fields 2014-09-07 15:58:38 +03:00
Petteri Aimonen
df7234fd8b Fix cyclic messages support in generator. Beginnings of test.
Update issue 130
Status: Started
2014-08-28 21:23:28 +03:00
Petteri Aimonen
9e866b4853 Add missing * in migration docs 2014-08-26 18:22:13 +03:00
Petteri Aimonen
71b81ad573 Setting version to 0.3.1-dev 2014-08-26 18:20:48 +03:00
31 changed files with 1314 additions and 66 deletions

View File

@@ -1,3 +1,11 @@
nanopb-0.3.1 (2014-09-11)
Fix security issue due to size_t overflows. (issue 132)
Fix memory leak with duplicated fields and PB_ENABLE_MALLOC
Fix crash if pb_release() is called twice.
Fix cyclic message support (issue 130)
Fix error in generated initializers for repeated pointer fields.
Improve tests (issues 113, 126)
nanopb-0.3.0 (2014-08-26)
NOTE: See docs/migration.html or online at
http://koti.kapsi.fi/~jpa/nanopb/docs/migration.html

View File

@@ -151,12 +151,12 @@ Callback function signature
as *void\**. This allowed passing of any data, but made it unnecessarily
complex to return a pointer from callback.
**Changes:** The callback function parameter was changed to *void\**.
**Changes:** The callback function parameter was changed to *void\*\**.
**Required actions:** You can continue using the old callback style by
defining *PB_OLD_CALLBACK_STYLE*. Recommended action is to:
* Change the callback signatures to contain *void\** for decoders and
* Change the callback signatures to contain *void\*\** for decoders and
*void \* const \** for encoders.
* Change the callback function body to use *\*arg* instead of *arg*.

View File

@@ -26,9 +26,9 @@ The following data is regarded as **trusted**. It must be under the control of
the application writer. Malicious data in these structures could cause
security issues, such as execution of arbitrary code:
1. Callback and extension fields in message structures given to pb_encode()
and pb_decode(). These fields are memory pointers, and are generated
depending on the .proto file.
1. Callback, pointer and extension fields in message structures given to
pb_encode() and pb_decode(). These fields are memory pointers, and are
generated depending on the message definition in the .proto file.
2. The automatically generated field definitions, i.e. *pb_field_t* lists.
3. Contents of the *pb_istream_t* and *pb_ostream_t* structures (this does not
mean the contents of the stream itself, just the stream definition).
@@ -38,7 +38,7 @@ these will cause "garbage in, garbage out" behaviour. It will not cause
buffer overflows, information disclosure or other security problems:
1. All data read from *pb_istream_t*.
2. All fields in message structures, except callbacks and extensions.
2. All fields in message structures, except callbacks, pointers and extensions.
(Beginning with nanopb-0.2.4, in earlier versions the field sizes are partially unchecked.)
Invariants
@@ -76,4 +76,6 @@ The following list is not comprehensive:
stop a denial of service attack from using an infinite message.
4. If using network sockets as streams, a timeout should be set to stop
denial of service attacks.
5. If using *malloc()* support, some method of limiting memory use should be
employed. This can be done by defining custom *pb_realloc()* function.
Nanopb will properly detect and handle failed memory allocations.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
'''Generate header file for nanopb from a ProtoBuf FileDescriptorSet.'''
nanopb_version = "nanopb-0.3.0"
nanopb_version = "nanopb-0.3.1"
import sys
@@ -546,7 +546,7 @@ class Message:
def get_dependencies(self):
'''Get list of type names that this structure refers to.'''
return [str(field.ctype) for field in self.fields]
return [str(field.ctype) for field in self.fields if field.allocation == 'STATIC']
def __str__(self):
result = 'typedef struct _%s {\n' % self.name
@@ -596,6 +596,8 @@ class Message:
else:
parts.append(field.get_initializer(null_init))
elif field.allocation == 'POINTER':
if field.rules == 'REPEATED':
parts.append('0')
parts.append('NULL')
elif field.allocation == 'CALLBACK':
if field.pbtype == 'EXTENSION':

2
pb.h
View File

@@ -46,7 +46,7 @@
/* Version of the nanopb library. Just in case you want to check it in
* your own program. */
#define NANOPB_VERSION nanopb-0.3.0
#define NANOPB_VERSION nanopb-0.3.1
/* Include all the system headers needed by nanopb. You will need the
* definitions of the following:

View File

@@ -44,6 +44,11 @@ static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_t
static bool checkreturn pb_skip_varint(pb_istream_t *stream);
static bool checkreturn pb_skip_string(pb_istream_t *stream);
#ifdef PB_ENABLE_MALLOC
static bool checkreturn allocate_field(pb_istream_t *stream, void *pData, size_t data_size, size_t array_size);
static void pb_release_single_field(const pb_field_iter_t *iter);
#endif
/* --- Function pointers to field decoders ---
* Order in the array must match pb_action_t LTYPE numbering.
*/
@@ -400,18 +405,23 @@ static bool checkreturn allocate_field(pb_istream_t *stream, void *pData, size_t
{
void *ptr = *(void**)pData;
if (data_size == 0 || array_size == 0)
PB_RETURN_ERROR(stream, "invalid size");
/* Check for multiplication overflows.
* This code avoids the costly division if the sizes are small enough.
* Multiplication is safe as long as only half of bits are set
* in either multiplicand.
*/
const size_t check_limit = (size_t)1 << (sizeof(size_t) * 4);
if (data_size >= check_limit || array_size >= check_limit)
{
const size_t size_max = (size_t)-1;
if (size_max / array_size < data_size)
const size_t check_limit = (size_t)1 << (sizeof(size_t) * 4);
if (data_size >= check_limit || array_size >= check_limit)
{
PB_RETURN_ERROR(stream, "size too large");
const size_t size_max = (size_t)-1;
if (size_max / array_size < data_size)
{
PB_RETURN_ERROR(stream, "size too large");
}
}
}
@@ -458,6 +468,13 @@ static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_
{
case PB_HTYPE_REQUIRED:
case PB_HTYPE_OPTIONAL:
if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE &&
*(void**)iter->pData != NULL)
{
/* Duplicate field, have to release the old allocation first. */
pb_release_single_field(iter);
}
if (PB_LTYPE(type) == PB_LTYPE_STRING ||
PB_LTYPE(type) == PB_LTYPE_BYTES)
{
@@ -869,6 +886,55 @@ bool pb_decode_delimited(pb_istream_t *stream, const pb_field_t fields[], void *
}
#ifdef PB_ENABLE_MALLOC
static void pb_release_single_field(const pb_field_iter_t *iter)
{
pb_type_t type;
type = iter->pos->type;
if (PB_ATYPE(type) == PB_ATYPE_POINTER)
{
if (PB_HTYPE(type) == PB_HTYPE_REPEATED &&
(PB_LTYPE(type) == PB_LTYPE_STRING ||
PB_LTYPE(type) == PB_LTYPE_BYTES))
{
/* Release entries in repeated string or bytes array */
void **pItem = *(void***)iter->pData;
pb_size_t count = *(pb_size_t*)iter->pSize;
while (count--)
{
pb_free(*pItem);
*pItem++ = NULL;
}
*(pb_size_t*)iter->pSize = 0;
}
else if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE)
{
/* Release fields in submessages */
void *pItem = *(void**)iter->pData;
if (pItem)
{
pb_size_t count = 1;
if (PB_HTYPE(type) == PB_HTYPE_REPEATED)
{
count = *(pb_size_t*)iter->pSize;
*(pb_size_t*)iter->pSize = 0;
}
while (count--)
{
pb_release((const pb_field_t*)iter->pos->ptr, pItem);
pItem = (uint8_t*)pItem + iter->pos->data_size;
}
}
}
/* Release main item */
pb_free(*(void**)iter->pData);
*(void**)iter->pData = NULL;
}
}
void pb_release(const pb_field_t fields[], void *dest_struct)
{
pb_field_iter_t iter;
@@ -878,46 +944,7 @@ void pb_release(const pb_field_t fields[], void *dest_struct)
do
{
pb_type_t type;
type = iter.pos->type;
if (PB_ATYPE(type) == PB_ATYPE_POINTER)
{
if (PB_HTYPE(type) == PB_HTYPE_REPEATED &&
(PB_LTYPE(type) == PB_LTYPE_STRING ||
PB_LTYPE(type) == PB_LTYPE_BYTES))
{
/* Release entries in repeated string or bytes array */
void **pItem = *(void***)iter.pData;
pb_size_t count = *(pb_size_t*)iter.pSize;
while (count--)
{
pb_free(*pItem);
*pItem++ = NULL;
}
}
else if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE)
{
/* Release fields in submessages */
void *pItem = *(void**)iter.pData;
pb_size_t count = (pItem ? 1 : 0);
if (PB_HTYPE(type) == PB_HTYPE_REPEATED)
{
count = *(pb_size_t*)iter.pSize;
}
while (count--)
{
pb_release((const pb_field_t*)iter.pos->ptr, pItem);
pItem = (uint8_t*)pItem + iter.pos->data_size;
}
}
/* Release main item */
pb_free(*(void**)iter.pData);
*(void**)iter.pData = NULL;
}
pb_release_single_field(&iter);
} while (pb_field_iter_next(&iter));
}
#endif
@@ -1045,32 +1072,35 @@ static bool checkreturn pb_dec_fixed64(pb_istream_t *stream, const pb_field_t *f
static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest)
{
uint32_t size;
size_t alloc_size;
pb_bytes_array_t *bdest;
if (!pb_decode_varint32(stream, &size))
return false;
if (size > PB_SIZE_MAX)
PB_RETURN_ERROR(stream, "bytes overflow");
alloc_size = PB_BYTES_ARRAY_T_ALLOCSIZE(size);
if (size > alloc_size)
PB_RETURN_ERROR(stream, "size too large");
if (PB_ATYPE(field->type) == PB_ATYPE_POINTER)
{
#ifndef PB_ENABLE_MALLOC
PB_RETURN_ERROR(stream, "no malloc support");
#else
if (!allocate_field(stream, dest, PB_BYTES_ARRAY_T_ALLOCSIZE(size), 1))
if (!allocate_field(stream, dest, alloc_size, 1))
return false;
bdest = *(pb_bytes_array_t**)dest;
#endif
}
else
{
if (PB_BYTES_ARRAY_T_ALLOCSIZE(size) > field->data_size)
if (alloc_size > field->data_size)
PB_RETURN_ERROR(stream, "bytes overflow");
bdest = (pb_bytes_array_t*)dest;
}
if (size > PB_SIZE_MAX)
{
PB_RETURN_ERROR(stream, "bytes overflow");
}
bdest->size = (pb_size_t)size;
return pb_read(stream, bdest->bytes, size);
@@ -1087,6 +1117,9 @@ static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_t *fi
/* Space for null terminator */
alloc_size = size + 1;
if (alloc_size < size)
PB_RETURN_ERROR(stream, "size too large");
if (PB_ATYPE(field->type) == PB_ATYPE_POINTER)
{
#ifndef PB_ENABLE_MALLOC

View File

@@ -10,12 +10,12 @@ coverage:
# LCOV does not like the newer gcov format
scons CC=gcc-4.6 CXX=gcc-4.6
# We are only interested in pb_encode.o and pb_decode.o
find build -name '*.gcda' -and \! \( -name '*pb_encode*' -or -name '*pb_decode*' \) -exec rm '{}' \;
# Collect the data
mkdir build/coverage
lcov --base-directory . --directory build/ --gcov-tool gcov-4.6 -c -o build/coverage/nanopb.info
# Remove the test code from results
lcov -r build/coverage/nanopb.info '*tests*' -o build/coverage/nanopb.info
# Generate HTML
genhtml -o build/coverage build/coverage/nanopb.info

View File

@@ -57,6 +57,7 @@ if not env.GetOption('clean'):
if not stdbool or not stdint or not stddef or not string:
conf.env.Append(CPPDEFINES = {'PB_SYSTEM_HEADER': '\\"pb_syshdr.h\\"'})
conf.env.Append(CPPPATH = "#../extra")
conf.env.Append(SYSHDR = '\\"pb_syshdr.h\\"')
if stdbool: conf.env.Append(CPPDEFINES = {'HAVE_STDBOOL_H': 1})
if stdint: conf.env.Append(CPPDEFINES = {'HAVE_STDINT_H': 1})
@@ -101,7 +102,7 @@ if 'gcc' in env['CC']:
# GNU Compiler Collection
# Debug info, warnings as errors
env.Append(CFLAGS = '-ansi -pedantic -g -Wall -Werror -fprofile-arcs -ftest-coverage -fstack-protector-all')
env.Append(CFLAGS = '-ansi -pedantic -g -Wall -Werror -fprofile-arcs -ftest-coverage ')
env.Append(CORECFLAGS = '-Wextra')
env.Append(LINKFLAGS = '-g --coverage')

View File

@@ -34,3 +34,8 @@ message CallbackContainer {
message CallbackContainerContainer {
required CallbackContainer submsg = 1;
}
message StringPointerContainer {
repeated string rep_str = 1 [(nanopb).type = FT_POINTER];
}

View File

@@ -0,0 +1,11 @@
Import("env")
# Encode cyclic messages with callback fields
c = Copy("$TARGET", "$SOURCE")
env.Command("cyclic_callback.proto", "cyclic.proto", c)
env.NanopbProto(["cyclic_callback", "cyclic_callback.options"])
enc_callback = env.Program(["encode_cyclic_callback.c", "cyclic_callback.pb.c", "$COMMON/pb_encode.o", "$COMMON/pb_common.o"])

View File

@@ -0,0 +1,25 @@
// Test structures with cyclic references.
// These can only be handled in pointer/callback mode,
// see associated .options files.
message TreeNode
{
optional int32 leaf = 1;
optional TreeNode left = 2;
optional TreeNode right = 3;
}
message Dictionary
{
repeated KeyValuePair dictItem = 1;
}
message KeyValuePair
{
required string key = 1;
optional string stringValue = 2;
optional int32 intValue = 3;
optional Dictionary dictValue = 4;
optional TreeNode treeValue = 5;
}

View File

@@ -0,0 +1,7 @@
TreeNode.left type:FT_CALLBACK
TreeNode.right type:FT_CALLBACK
Dictionary.data type:FT_CALLBACK
KeyValuePair.key max_size:8
KeyValuePair.stringValue max_size:8
KeyValuePair.treeValue type:FT_CALLBACK

View File

@@ -0,0 +1,148 @@
/* This program parses an input string in a format a bit like JSON:
* {'foobar': 1234, 'xyz': 'abc', 'tree': [[[1, 2], 3], [4, 5]]}
* and encodes it as protobuf
*
* Note: The string parsing here is not in any way intended to be robust
* nor safe against buffer overflows. It is just for this test.
*/
#include <pb_encode.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "cyclic_callback.pb.h"
static char *find_end_of_item(char *p)
{
int depth = 0;
do {
if (*p == '[' || *p == '{') depth++;
if (*p == ']' || *p == '}') depth--;
p++;
} while (depth > 0 || (*p != ',' && *p != '}'));
if (*p == '}')
return p; /* End of parent dict */
p++;
while (*p == ' ') p++;
return p;
}
/* Parse a tree in format [[1 2] 3] and encode it directly to protobuf */
static bool encode_tree(pb_ostream_t *stream, const pb_field_t *field, void * const *arg)
{
TreeNode tree = TreeNode_init_zero;
char *p = (char*)*arg;
if (*p == '[')
{
/* This is a tree branch */
p++;
tree.left.funcs.encode = encode_tree;
tree.left.arg = p;
p = find_end_of_item(p);
tree.right.funcs.encode = encode_tree;
tree.right.arg = p;
}
else
{
/* This is a leaf node */
tree.has_leaf = true;
tree.leaf = atoi(p);
}
return pb_encode_tag_for_field(stream, field) &&
pb_encode_submessage(stream, TreeNode_fields, &tree);
}
/* Parse a dictionary in format {'name': value} and encode it directly to protobuf */
static bool encode_dictionary(pb_ostream_t *stream, const pb_field_t *field, void * const *arg)
{
int textlen;
char *p = (char*)*arg;
if (*p == '{') p++;
while (*p != '}')
{
KeyValuePair pair = KeyValuePair_init_zero;
if (*p != '\'')
PB_RETURN_ERROR(stream, "invalid key, missing quote");
p++; /* Starting quote of key */
textlen = strchr(p, '\'') - p;
strncpy(pair.key, p, textlen);
pair.key[textlen] = 0;
p += textlen + 2;
while (*p == ' ') p++;
if (*p == '[')
{
/* Value is a tree */
pair.treeValue.funcs.encode = encode_tree;
pair.treeValue.arg = p;
}
else if (*p == '\'')
{
/* Value is a string */
pair.has_stringValue = true;
p++;
textlen = strchr(p, '\'') - p;
strncpy(pair.stringValue, p, textlen);
pair.stringValue[textlen] = 0;
}
else if (*p == '{')
{
/* Value is a dictionary */
pair.has_dictValue = true;
pair.dictValue.dictItem.funcs.encode = encode_dictionary;
pair.dictValue.dictItem.arg = p;
}
else
{
/* Value is integer */
pair.has_intValue = true;
pair.intValue = atoi(p);
}
p = find_end_of_item(p);
if (!pb_encode_tag_for_field(stream, field))
return false;
if (!pb_encode_submessage(stream, KeyValuePair_fields, &pair))
return false;
}
return true;
}
int main(int argc, char *argv[])
{
uint8_t buffer[256];
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
Dictionary dict = Dictionary_init_zero;
if (argc <= 1)
{
fprintf(stderr, "Usage: %s \"{'foobar': 1234, ...}\"\n", argv[0]);
return 1;
}
dict.dictItem.funcs.encode = encode_dictionary;
dict.dictItem.arg = argv[1];
if (!pb_encode(&stream, Dictionary_fields, &dict))
{
fprintf(stderr, "Encoding error: %s\n", PB_GET_ERROR(&stream));
return 1;
}
fwrite(buffer, 1, stream.bytes_written, stdout);
return 0;
}

View File

@@ -87,6 +87,20 @@ int main()
pb_decode_varint(&s, (uint64_t*)&i) && i == -1));
TEST((s = S("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01"),
pb_decode_varint(&s, &u) && u == UINT64_MAX));
TEST((s = S("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01"),
!pb_decode_varint(&s, &u)));
}
{
pb_istream_t s;
uint32_t u;
COMMENT("Test pb_decode_varint32");
TEST((s = S("\x00"), pb_decode_varint32(&s, &u) && u == 0));
TEST((s = S("\x01"), pb_decode_varint32(&s, &u) && u == 1));
TEST((s = S("\xAC\x02"), pb_decode_varint32(&s, &u) && u == 300));
TEST((s = S("\xFF\xFF\xFF\xFF\x0F"), pb_decode_varint32(&s, &u) && u == UINT32_MAX));
TEST((s = S("\xFF\xFF\xFF\xFF\xFF\x01"), !pb_decode_varint32(&s, &u)));
}
{

View File

@@ -331,6 +331,23 @@ int main()
TEST(s.bytes_written == StringMessage_size);
}
{
uint8_t buffer[128];
pb_ostream_t s;
StringPointerContainer msg = StringPointerContainer_init_zero;
char *strs[1] = {NULL};
char zstr[] = "Z";
COMMENT("Test string pointer encoding.");
msg.rep_str = strs;
msg.rep_str_count = 1;
TEST(WRITES(pb_encode(&s, StringPointerContainer_fields, &msg), "\x0a\x00"))
strs[0] = zstr;
TEST(WRITES(pb_encode(&s, StringPointerContainer_fields, &msg), "\x0a\x01Z"))
}
if (status != 0)
fprintf(stdout, "\n\nSome tests FAILED!\n");

58
tests/fuzztest/SConscript Normal file
View File

@@ -0,0 +1,58 @@
# Run a fuzz test to verify robustness against corrupted/malicious data.
Import("env")
# We need our own pb_decode.o for the malloc support
env = env.Clone()
env.Append(CPPDEFINES = {'PB_ENABLE_MALLOC': 1,
'PB_SYSTEM_HEADER': '\\"fuzz_syshdr.h\\"'})
env.Append(CPPPATH = ".")
if 'SYSHDR' in env:
env.Append(CPPDEFINES = {'PB_OLD_SYSHDR': env['SYSHDR']})
# Disable libmudflap, because it will confuse valgrind
# and other memory leak detection tools.
if '-fmudflap' in env["CCFLAGS"]:
env["CCFLAGS"].remove("-fmudflap")
env["LINKFLAGS"].remove("-fmudflap")
env["LIBS"].remove("mudflap")
strict = env.Clone()
strict.Append(CFLAGS = strict['CORECFLAGS'])
strict.Object("pb_decode_with_malloc.o", "$NANOPB/pb_decode.c")
strict.Object("pb_encode_with_malloc.o", "$NANOPB/pb_encode.c")
strict.Object("pb_common_with_malloc.o", "$NANOPB/pb_common.c")
# We want both pointer and static versions of the AllTypes message
env.Command("alltypes_static.proto", "#alltypes/alltypes.proto",
lambda target, source, env:
open(str(target[0]), 'w').write("package alltypes_static;\n"
+ open(str(source[0])).read()))
env.Command("alltypes_pointer.proto", "#alltypes/alltypes.proto",
lambda target, source, env:
open(str(target[0]), 'w').write("package alltypes_pointer;\n"
+ open(str(source[0])).read()))
p1 = env.NanopbProto(["alltypes_pointer", "alltypes_pointer.options"])
p2 = env.NanopbProto(["alltypes_static", "alltypes_static.options"])
fuzz = env.Program(["fuzztest.c",
"alltypes_pointer.pb.c",
"alltypes_static.pb.c",
"pb_encode_with_malloc.o",
"pb_decode_with_malloc.o",
"pb_common_with_malloc.o",
"malloc_wrappers.c"])
Depends([p1, p2, fuzz], ["fuzz_syshdr.h", "malloc_wrappers.h"])
env.RunTest(fuzz)
fuzzstub = env.Program(["fuzzstub.c",
"alltypes_pointer.pb.c",
"alltypes_static.pb.c",
"pb_encode_with_malloc.o",
"pb_decode_with_malloc.o",
"pb_common_with_malloc.o",
"malloc_wrappers.c"])

View File

@@ -0,0 +1,3 @@
# Generate all fields as pointers.
* type:FT_POINTER

View File

@@ -0,0 +1,3 @@
* max_size:32
* max_count:8
*.extensions type:FT_IGNORE

View File

@@ -0,0 +1,15 @@
/* This is just a wrapper in order to get our own malloc wrappers into nanopb core. */
#define pb_realloc(ptr,size) counting_realloc(ptr,size)
#define pb_free(ptr) counting_free(ptr)
#ifdef PB_OLD_SYSHDR
#include PB_OLD_SYSHDR
#else
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#endif
#include <malloc_wrappers.h>

189
tests/fuzztest/fuzzstub.c Normal file
View File

@@ -0,0 +1,189 @@
/* Fuzz testing for the nanopb core.
* This can be used with external fuzzers, e.g. radamsa.
* It performs most of the same checks as fuzztest, but does not feature data generation.
*/
#include <pb_decode.h>
#include <pb_encode.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include "malloc_wrappers.h"
#include "alltypes_static.pb.h"
#include "alltypes_pointer.pb.h"
#define BUFSIZE 4096
static bool do_static_decode(uint8_t *buffer, size_t msglen, bool assert_success)
{
pb_istream_t stream;
bool status;
alltypes_static_AllTypes *msg = malloc_with_check(sizeof(alltypes_static_AllTypes));
stream = pb_istream_from_buffer(buffer, msglen);
status = pb_decode(&stream, alltypes_static_AllTypes_fields, msg);
if (!status && assert_success)
{
/* Anything that was successfully encoded, should be decodeable.
* One exception: strings without null terminator are encoded up
* to end of buffer, but refused on decode because the terminator
* would not fit. */
if (strcmp(stream.errmsg, "string overflow") != 0)
assert(status);
}
free_with_check(msg);
return status;
}
static bool do_pointer_decode(uint8_t *buffer, size_t msglen, bool assert_success)
{
pb_istream_t stream;
bool status;
alltypes_pointer_AllTypes *msg;
msg = malloc_with_check(sizeof(alltypes_pointer_AllTypes));
memset(msg, 0, sizeof(alltypes_pointer_AllTypes));
stream = pb_istream_from_buffer(buffer, msglen);
assert(get_alloc_count() == 0);
status = pb_decode(&stream, alltypes_pointer_AllTypes_fields, msg);
if (assert_success)
assert(status);
pb_release(alltypes_pointer_AllTypes_fields, msg);
assert(get_alloc_count() == 0);
free_with_check(msg);
return status;
}
/* Do a decode -> encode -> decode -> encode roundtrip */
static void do_static_roundtrip(uint8_t *buffer, size_t msglen)
{
bool status;
uint8_t *buf2 = malloc_with_check(BUFSIZE);
uint8_t *buf3 = malloc_with_check(BUFSIZE);
size_t msglen2, msglen3;
alltypes_static_AllTypes *msg1 = malloc_with_check(sizeof(alltypes_static_AllTypes));
alltypes_static_AllTypes *msg2 = malloc_with_check(sizeof(alltypes_static_AllTypes));
memset(msg1, 0, sizeof(alltypes_static_AllTypes));
memset(msg2, 0, sizeof(alltypes_static_AllTypes));
{
pb_istream_t stream = pb_istream_from_buffer(buffer, msglen);
status = pb_decode(&stream, alltypes_static_AllTypes_fields, msg1);
assert(status);
}
{
pb_ostream_t stream = pb_ostream_from_buffer(buf2, BUFSIZE);
status = pb_encode(&stream, alltypes_static_AllTypes_fields, msg1);
assert(status);
msglen2 = stream.bytes_written;
}
{
pb_istream_t stream = pb_istream_from_buffer(buf2, msglen2);
status = pb_decode(&stream, alltypes_static_AllTypes_fields, msg2);
assert(status);
}
{
pb_ostream_t stream = pb_ostream_from_buffer(buf3, BUFSIZE);
status = pb_encode(&stream, alltypes_static_AllTypes_fields, msg2);
assert(status);
msglen3 = stream.bytes_written;
}
assert(msglen2 == msglen3);
assert(memcmp(buf2, buf3, msglen2) == 0);
free_with_check(msg1);
free_with_check(msg2);
free_with_check(buf2);
free_with_check(buf3);
}
/* Do decode -> encode -> decode -> encode roundtrip */
static void do_pointer_roundtrip(uint8_t *buffer, size_t msglen)
{
bool status;
uint8_t *buf2 = malloc_with_check(BUFSIZE);
uint8_t *buf3 = malloc_with_check(BUFSIZE);
size_t msglen2, msglen3;
alltypes_pointer_AllTypes *msg1 = malloc_with_check(sizeof(alltypes_pointer_AllTypes));
alltypes_pointer_AllTypes *msg2 = malloc_with_check(sizeof(alltypes_pointer_AllTypes));
memset(msg1, 0, sizeof(alltypes_pointer_AllTypes));
memset(msg2, 0, sizeof(alltypes_pointer_AllTypes));
{
pb_istream_t stream = pb_istream_from_buffer(buffer, msglen);
status = pb_decode(&stream, alltypes_pointer_AllTypes_fields, msg1);
assert(status);
}
{
pb_ostream_t stream = pb_ostream_from_buffer(buf2, BUFSIZE);
status = pb_encode(&stream, alltypes_pointer_AllTypes_fields, msg1);
assert(status);
msglen2 = stream.bytes_written;
}
{
pb_istream_t stream = pb_istream_from_buffer(buf2, msglen2);
status = pb_decode(&stream, alltypes_pointer_AllTypes_fields, msg2);
assert(status);
}
{
pb_ostream_t stream = pb_ostream_from_buffer(buf3, BUFSIZE);
status = pb_encode(&stream, alltypes_pointer_AllTypes_fields, msg2);
assert(status);
msglen3 = stream.bytes_written;
}
assert(msglen2 == msglen3);
assert(memcmp(buf2, buf3, msglen2) == 0);
pb_release(alltypes_pointer_AllTypes_fields, msg1);
pb_release(alltypes_pointer_AllTypes_fields, msg2);
free_with_check(msg1);
free_with_check(msg2);
free_with_check(buf2);
free_with_check(buf3);
}
static void run_iteration()
{
uint8_t *buffer = malloc_with_check(BUFSIZE);
size_t msglen;
bool status;
msglen = fread(buffer, BUFSIZE, 1, stdin);
status = do_static_decode(buffer, msglen, false);
if (status)
do_static_roundtrip(buffer, msglen);
status = do_pointer_decode(buffer, msglen, false);
if (status)
do_pointer_roundtrip(buffer, msglen);
free_with_check(buffer);
}
int main(int argc, char **argv)
{
run_iteration();
return 0;
}

431
tests/fuzztest/fuzztest.c Normal file
View File

@@ -0,0 +1,431 @@
/* Fuzz testing for the nanopb core.
* Attempts to verify all the properties defined in the security model document.
*/
#include <pb_decode.h>
#include <pb_encode.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include "malloc_wrappers.h"
#include "alltypes_static.pb.h"
#include "alltypes_pointer.pb.h"
static uint64_t random_seed;
/* Uses xorshift64 here instead of rand() for both speed and
* reproducibility across platforms. */
static uint32_t rand_word()
{
random_seed ^= random_seed >> 12;
random_seed ^= random_seed << 25;
random_seed ^= random_seed >> 27;
return random_seed * 2685821657736338717ULL;
}
/* Get a random integer in range, with approximately flat distribution. */
static int rand_int(int min, int max)
{
return rand_word() % (max + 1 - min) + min;
}
static bool rand_bool()
{
return rand_word() & 1;
}
/* Get a random byte, with skewed distribution.
* Important corner cases like 0xFF, 0x00 and 0xFE occur more
* often than other values. */
static uint8_t rand_byte()
{
uint32_t w = rand_word();
uint8_t b = w & 0xFF;
if (w & 0x100000)
b >>= (w >> 8) & 7;
if (w & 0x200000)
b <<= (w >> 12) & 7;
if (w & 0x400000)
b ^= 0xFF;
return b;
}
/* Get a random length, with skewed distribution.
* Favors the shorter lengths, but always atleast 1. */
static size_t rand_len(size_t max)
{
uint32_t w = rand_word();
size_t s;
if (w & 0x800000)
w &= 3;
else if (w & 0x400000)
w &= 15;
else if (w & 0x200000)
w &= 255;
s = (w % max);
if (s == 0)
s = 1;
return s;
}
/* Fills a buffer with random data with skewed distribution. */
static void rand_fill(uint8_t *buf, size_t count)
{
while (count--)
*buf++ = rand_byte();
}
/* Fill with random protobuf-like data */
static size_t rand_fill_protobuf(uint8_t *buf, size_t min_bytes, size_t max_bytes, int min_tag)
{
pb_ostream_t stream = pb_ostream_from_buffer(buf, max_bytes);
while(stream.bytes_written < min_bytes)
{
pb_wire_type_t wt = rand_int(0, 3);
if (wt == 3) wt = 5; /* Gap in values */
if (!pb_encode_tag(&stream, wt, rand_int(min_tag, min_tag + 512)))
break;
if (wt == PB_WT_VARINT)
{
uint64_t value;
rand_fill((uint8_t*)&value, sizeof(value));
pb_encode_varint(&stream, value);
}
else if (wt == PB_WT_64BIT)
{
uint64_t value;
rand_fill((uint8_t*)&value, sizeof(value));
pb_encode_fixed64(&stream, &value);
}
else if (wt == PB_WT_32BIT)
{
uint32_t value;
rand_fill((uint8_t*)&value, sizeof(value));
pb_encode_fixed32(&stream, &value);
}
else if (wt == PB_WT_STRING)
{
size_t len;
uint8_t *buf;
if (min_bytes > stream.bytes_written)
len = rand_len(min_bytes - stream.bytes_written);
else
len = 0;
buf = malloc(len);
pb_encode_varint(&stream, len);
rand_fill(buf, len);
pb_write(&stream, buf, len);
free(buf);
}
}
return stream.bytes_written;
}
/* Given a buffer of data, mess it up a bit */
static void rand_mess(uint8_t *buf, size_t count)
{
int m = rand_int(0, 3);
if (m == 0)
{
/* Replace random substring */
int s = rand_int(0, count - 1);
int l = rand_len(count - s);
rand_fill(buf + s, l);
}
else if (m == 1)
{
/* Swap random bytes */
int a = rand_int(0, count - 1);
int b = rand_int(0, count - 1);
int x = buf[a];
buf[a] = buf[b];
buf[b] = x;
}
else if (m == 2)
{
/* Duplicate substring */
int s = rand_int(0, count - 2);
int l = rand_len((count - s) / 2);
memcpy(buf + s + l, buf + s, l);
}
else if (m == 3)
{
/* Add random protobuf noise */
int s = rand_int(0, count - 1);
int l = rand_len(count - s);
rand_fill_protobuf(buf + s, l, count - s, 1);
}
}
/* Some default data to put in the message */
static const alltypes_static_AllTypes initval = alltypes_static_AllTypes_init_default;
#define BUFSIZE 4096
static bool do_static_encode(uint8_t *buffer, size_t *msglen)
{
pb_ostream_t stream;
bool status;
/* Allocate a message and fill it with defaults */
alltypes_static_AllTypes *msg = malloc_with_check(sizeof(alltypes_static_AllTypes));
memcpy(msg, &initval, sizeof(initval));
/* Apply randomness to the data before encoding */
while (rand_int(0, 7))
rand_mess((uint8_t*)msg, sizeof(alltypes_static_AllTypes));
stream = pb_ostream_from_buffer(buffer, BUFSIZE);
status = pb_encode(&stream, alltypes_static_AllTypes_fields, msg);
assert(stream.bytes_written <= BUFSIZE);
assert(stream.bytes_written <= alltypes_static_AllTypes_size);
*msglen = stream.bytes_written;
free_with_check(msg);
return status;
}
/* Append or prepend protobuf noise */
static void do_protobuf_noise(uint8_t *buffer, size_t *msglen)
{
int m = rand_int(0, 2);
size_t max_size = BUFSIZE - 32 - *msglen;
if (m == 1)
{
/* Prepend */
uint8_t *tmp = malloc_with_check(BUFSIZE);
size_t s = rand_fill_protobuf(tmp, rand_len(max_size), BUFSIZE - *msglen, 512);
memmove(buffer + s, buffer, *msglen);
memcpy(buffer, tmp, s);
free_with_check(tmp);
*msglen += s;
}
else if (m == 2)
{
/* Append */
size_t s = rand_fill_protobuf(buffer + *msglen, rand_len(max_size), BUFSIZE - *msglen, 512);
*msglen += s;
}
}
static bool do_static_decode(uint8_t *buffer, size_t msglen, bool assert_success)
{
pb_istream_t stream;
bool status;
alltypes_static_AllTypes *msg = malloc_with_check(sizeof(alltypes_static_AllTypes));
rand_fill((uint8_t*)msg, sizeof(alltypes_static_AllTypes));
stream = pb_istream_from_buffer(buffer, msglen);
status = pb_decode(&stream, alltypes_static_AllTypes_fields, msg);
if (!status && assert_success)
{
/* Anything that was successfully encoded, should be decodeable.
* One exception: strings without null terminator are encoded up
* to end of buffer, but refused on decode because the terminator
* would not fit. */
if (strcmp(stream.errmsg, "string overflow") != 0)
assert(status);
}
free_with_check(msg);
return status;
}
static bool do_pointer_decode(uint8_t *buffer, size_t msglen, bool assert_success)
{
pb_istream_t stream;
bool status;
alltypes_pointer_AllTypes *msg;
msg = malloc_with_check(sizeof(alltypes_pointer_AllTypes));
memset(msg, 0, sizeof(alltypes_pointer_AllTypes));
stream = pb_istream_from_buffer(buffer, msglen);
assert(get_alloc_count() == 0);
status = pb_decode(&stream, alltypes_pointer_AllTypes_fields, msg);
if (assert_success)
assert(status);
pb_release(alltypes_pointer_AllTypes_fields, msg);
assert(get_alloc_count() == 0);
free_with_check(msg);
return status;
}
/* Do a decode -> encode -> decode -> encode roundtrip */
static void do_static_roundtrip(uint8_t *buffer, size_t msglen)
{
bool status;
uint8_t *buf2 = malloc_with_check(BUFSIZE);
uint8_t *buf3 = malloc_with_check(BUFSIZE);
size_t msglen2, msglen3;
alltypes_static_AllTypes *msg1 = malloc_with_check(sizeof(alltypes_static_AllTypes));
alltypes_static_AllTypes *msg2 = malloc_with_check(sizeof(alltypes_static_AllTypes));
memset(msg1, 0, sizeof(alltypes_static_AllTypes));
memset(msg2, 0, sizeof(alltypes_static_AllTypes));
{
pb_istream_t stream = pb_istream_from_buffer(buffer, msglen);
status = pb_decode(&stream, alltypes_static_AllTypes_fields, msg1);
assert(status);
}
{
pb_ostream_t stream = pb_ostream_from_buffer(buf2, BUFSIZE);
status = pb_encode(&stream, alltypes_static_AllTypes_fields, msg1);
assert(status);
msglen2 = stream.bytes_written;
}
{
pb_istream_t stream = pb_istream_from_buffer(buf2, msglen2);
status = pb_decode(&stream, alltypes_static_AllTypes_fields, msg2);
assert(status);
}
{
pb_ostream_t stream = pb_ostream_from_buffer(buf3, BUFSIZE);
status = pb_encode(&stream, alltypes_static_AllTypes_fields, msg2);
assert(status);
msglen3 = stream.bytes_written;
}
assert(msglen2 == msglen3);
assert(memcmp(buf2, buf3, msglen2) == 0);
free_with_check(msg1);
free_with_check(msg2);
free_with_check(buf2);
free_with_check(buf3);
}
/* Do decode -> encode -> decode -> encode roundtrip */
static void do_pointer_roundtrip(uint8_t *buffer, size_t msglen)
{
bool status;
uint8_t *buf2 = malloc_with_check(BUFSIZE);
uint8_t *buf3 = malloc_with_check(BUFSIZE);
size_t msglen2, msglen3;
alltypes_pointer_AllTypes *msg1 = malloc_with_check(sizeof(alltypes_pointer_AllTypes));
alltypes_pointer_AllTypes *msg2 = malloc_with_check(sizeof(alltypes_pointer_AllTypes));
memset(msg1, 0, sizeof(alltypes_pointer_AllTypes));
memset(msg2, 0, sizeof(alltypes_pointer_AllTypes));
{
pb_istream_t stream = pb_istream_from_buffer(buffer, msglen);
status = pb_decode(&stream, alltypes_pointer_AllTypes_fields, msg1);
assert(status);
}
{
pb_ostream_t stream = pb_ostream_from_buffer(buf2, BUFSIZE);
status = pb_encode(&stream, alltypes_pointer_AllTypes_fields, msg1);
assert(status);
msglen2 = stream.bytes_written;
}
{
pb_istream_t stream = pb_istream_from_buffer(buf2, msglen2);
status = pb_decode(&stream, alltypes_pointer_AllTypes_fields, msg2);
assert(status);
}
{
pb_ostream_t stream = pb_ostream_from_buffer(buf3, BUFSIZE);
status = pb_encode(&stream, alltypes_pointer_AllTypes_fields, msg2);
assert(status);
msglen3 = stream.bytes_written;
}
assert(msglen2 == msglen3);
assert(memcmp(buf2, buf3, msglen2) == 0);
pb_release(alltypes_pointer_AllTypes_fields, msg1);
pb_release(alltypes_pointer_AllTypes_fields, msg2);
free_with_check(msg1);
free_with_check(msg2);
free_with_check(buf2);
free_with_check(buf3);
}
static void run_iteration()
{
uint8_t *buffer = malloc_with_check(BUFSIZE);
size_t msglen;
bool status;
rand_fill(buffer, BUFSIZE);
if (do_static_encode(buffer, &msglen))
{
do_protobuf_noise(buffer, &msglen);
status = do_static_decode(buffer, msglen, true);
if (status)
do_static_roundtrip(buffer, msglen);
status = do_pointer_decode(buffer, msglen, true);
if (status)
do_pointer_roundtrip(buffer, msglen);
/* Apply randomness to the encoded data */
while (rand_bool())
rand_mess(buffer, BUFSIZE);
/* Apply randomness to encoded data length */
if (rand_bool())
msglen = rand_int(0, BUFSIZE);
status = do_static_decode(buffer, msglen, false);
do_pointer_decode(buffer, msglen, status);
if (status)
{
do_static_roundtrip(buffer, msglen);
do_pointer_roundtrip(buffer, msglen);
}
}
free_with_check(buffer);
}
int main(int argc, char **argv)
{
int i;
if (argc > 1)
{
random_seed = atol(argv[1]);
}
else
{
random_seed = time(NULL);
}
fprintf(stderr, "Random seed: %llu\n", (long long unsigned)random_seed);
for (i = 0; i < 10000; i++)
{
run_iteration();
}
return 0;
}

View File

@@ -0,0 +1,54 @@
#include "malloc_wrappers.h"
#include <stdint.h>
#include <assert.h>
#include <string.h>
static size_t alloc_count = 0;
/* Allocate memory and place check values before and after. */
void* malloc_with_check(size_t size)
{
size_t size32 = (size + 3) / 4 + 3;
uint32_t *buf = malloc(size32 * sizeof(uint32_t));
buf[0] = size32;
buf[1] = 0xDEADBEEF;
buf[size32 - 1] = 0xBADBAD;
return buf + 2;
}
/* Free memory allocated with malloc_with_check() and do the checks. */
void free_with_check(void *mem)
{
uint32_t *buf = (uint32_t*)mem - 2;
assert(buf[1] == 0xDEADBEEF);
assert(buf[buf[0] - 1] == 0xBADBAD);
free(buf);
}
/* Track memory usage */
void* counting_realloc(void *ptr, size_t size)
{
/* Don't allocate crazy amounts of RAM when fuzzing */
if (size > 1000000)
return NULL;
if (!ptr && size)
alloc_count++;
return realloc(ptr, size);
}
void counting_free(void *ptr)
{
if (ptr)
{
assert(alloc_count > 0);
alloc_count--;
free(ptr);
}
}
size_t get_alloc_count()
{
return alloc_count;
}

View File

@@ -0,0 +1,7 @@
#include <stdlib.h>
void* malloc_with_check(size_t size);
void free_with_check(void *mem);
void* counting_realloc(void *ptr, size_t size);
void counting_free(void *ptr);
size_t get_alloc_count();

12
tests/fuzztest/run_radamsa.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
TMP=`tempfile`
echo $TMP
while true
do
radamsa sample_data/* > $TMP
$1 < $TMP
test $? -gt 127 && break
done

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,15 @@
# Simulate io errors when encoding and decoding
Import("env")
c = Copy("$TARGET", "$SOURCE")
env.Command("alltypes.proto", "#alltypes/alltypes.proto", c)
env.NanopbProto(["alltypes", "alltypes.options"])
ioerr = env.Program(["io_errors.c", "alltypes.pb.c",
"$COMMON/pb_encode.o", "$COMMON/pb_decode.o", "$COMMON/pb_common.o"])
env.RunTest("io_errors.output", [ioerr, "$BUILD/alltypes/encode_alltypes.output"])

View File

@@ -0,0 +1,3 @@
* max_size:16
* max_count:5

140
tests/io_errors/io_errors.c Normal file
View File

@@ -0,0 +1,140 @@
/* Simulate IO errors after each byte in a stream.
* Verifies proper error propagation.
*/
#include <stdio.h>
#include <pb_decode.h>
#include <pb_encode.h>
#include "alltypes.pb.h"
#include "test_helpers.h"
typedef struct
{
uint8_t *buffer;
size_t fail_after;
} faulty_stream_t;
bool read_callback(pb_istream_t *stream, uint8_t *buf, size_t count)
{
faulty_stream_t *state = stream->state;
while (count--)
{
if (state->fail_after == 0)
PB_RETURN_ERROR(stream, "simulated");
state->fail_after--;
*buf++ = *state->buffer++;
}
return true;
}
bool write_callback(pb_ostream_t *stream, const uint8_t *buf, size_t count)
{
faulty_stream_t *state = stream->state;
while (count--)
{
if (state->fail_after == 0)
PB_RETURN_ERROR(stream, "simulated");
state->fail_after--;
*state->buffer++ = *buf++;
}
return true;
}
int main()
{
uint8_t buffer[2048];
size_t msglen;
AllTypes msg = AllTypes_init_zero;
/* Get some base data to run the tests with */
SET_BINARY_MODE(stdin);
msglen = fread(buffer, 1, sizeof(buffer), stdin);
/* Test IO errors on decoding */
{
bool status;
pb_istream_t stream = {&read_callback, NULL, SIZE_MAX};
faulty_stream_t fs;
size_t i;
for (i = 0; i < msglen; i++)
{
stream.bytes_left = msglen;
stream.state = &fs;
fs.buffer = buffer;
fs.fail_after = i;
status = pb_decode(&stream, AllTypes_fields, &msg);
if (status != false)
{
fprintf(stderr, "Unexpected success in decode\n");
return 2;
}
else if (strcmp(stream.errmsg, "simulated") != 0)
{
fprintf(stderr, "Wrong error in decode: %s\n", stream.errmsg);
return 3;
}
}
stream.bytes_left = msglen;
stream.state = &fs;
fs.buffer = buffer;
fs.fail_after = msglen;
status = pb_decode(&stream, AllTypes_fields, &msg);
if (!status)
{
fprintf(stderr, "Decoding failed: %s\n", stream.errmsg);
return 4;
}
}
/* Test IO errors on encoding */
{
bool status;
pb_ostream_t stream = {&write_callback, NULL, SIZE_MAX, 0};
faulty_stream_t fs;
size_t i;
for (i = 0; i < msglen; i++)
{
stream.max_size = msglen;
stream.bytes_written = 0;
stream.state = &fs;
fs.buffer = buffer;
fs.fail_after = i;
status = pb_encode(&stream, AllTypes_fields, &msg);
if (status != false)
{
fprintf(stderr, "Unexpected success in encode\n");
return 5;
}
else if (strcmp(stream.errmsg, "simulated") != 0)
{
fprintf(stderr, "Wrong error in encode: %s\n", stream.errmsg);
return 6;
}
}
stream.max_size = msglen;
stream.bytes_written = 0;
stream.state = &fs;
fs.buffer = buffer;
fs.fail_after = msglen;
status = pb_encode(&stream, AllTypes_fields, &msg);
if (!status)
{
fprintf(stderr, "Encoding failed: %s\n", stream.errmsg);
return 7;
}
}
return 0;
}

View File

@@ -0,0 +1,42 @@
# Simulate io errors when encoding and decoding
Import("env")
# We need our own pb_decode.o for the malloc support
env = env.Clone()
env.Append(CPPDEFINES = {'PB_ENABLE_MALLOC': 1});
# Disable libmudflap, because it will confuse valgrind
# and other memory leak detection tools.
if '-fmudflap' in env["CCFLAGS"]:
env["CCFLAGS"].remove("-fmudflap")
env["LINKFLAGS"].remove("-fmudflap")
env["LIBS"].remove("mudflap")
strict = env.Clone()
strict.Append(CFLAGS = strict['CORECFLAGS'])
strict.Object("pb_decode_with_malloc.o", "$NANOPB/pb_decode.c")
strict.Object("pb_encode_with_malloc.o", "$NANOPB/pb_encode.c")
strict.Object("pb_common_with_malloc.o", "$NANOPB/pb_common.c")
c = Copy("$TARGET", "$SOURCE")
env.Command("alltypes.proto", "#alltypes/alltypes.proto", c)
env.Command("io_errors.c", "#io_errors/io_errors.c", c)
env.NanopbProto(["alltypes", "alltypes.options"])
ioerr = env.Program(["io_errors.c", "alltypes.pb.c",
"pb_encode_with_malloc.o",
"pb_decode_with_malloc.o",
"pb_common_with_malloc.o"])
# Run tests under valgrind if available
valgrind = env.WhereIs('valgrind')
kwargs = {}
if valgrind:
kwargs['COMMAND'] = valgrind
kwargs['ARGS'] = ["-q", ioerr[0].abspath]
env.RunTest("io_errors.output", [ioerr, "$BUILD/alltypes/encode_alltypes.output"], **kwargs)

View File

@@ -0,0 +1,3 @@
# Generate all fields as pointers.
* type:FT_POINTER