diff options
author | Petteri Aimonen <jpa@git.mail.kapsi.fi> | 2019-10-02 10:56:49 +0300 |
---|---|---|
committer | Petteri Aimonen <jpa@git.mail.kapsi.fi> | 2019-10-02 10:56:49 +0300 |
commit | 252f4199540a113a37d19de5ba051922909c810d (patch) | |
tree | e9a17d77cef03ec5d49062de674c72afba7d8a21 | |
parent | 7b0a42f10ce3676ae629aedca96f465e8d1edc80 (diff) | |
download | nanopb-c-252f4199540a113a37d19de5ba051922909c810d.tar.gz |
Fix undefined behavior with bool fields (#434)
Previously nanopb didn't enforce that decoded bool fields
had valid true/false values. This could lead to undefined
behavior in user code.
This has potential security implications when
1) message contains bool field (has_ fields are safe)
and
2) user code uses ternary operator dependent on the field value,
such as: int value = msg.my_bool ? 1234 : 0
and
3) the value returned from ternary operator affects a memory access,
such as: data_array[value] = 9999
-rw-r--r-- | docs/migration.rst | 16 | ||||
-rw-r--r-- | docs/security.rst | 1 | ||||
-rw-r--r-- | pb.h | 27 | ||||
-rw-r--r-- | pb_decode.c | 18 | ||||
-rw-r--r-- | pb_decode.h | 7 | ||||
-rw-r--r-- | pb_encode.c | 30 |
6 files changed, 82 insertions, 17 deletions
diff --git a/docs/migration.rst b/docs/migration.rst index a1843e3..d358600 100644 --- a/docs/migration.rst +++ b/docs/migration.rst @@ -28,6 +28,22 @@ define always has the largest value. are not defined in ascending order, user code behaviour may change. Check that user code doesn't expect the old, incorrect first/last behaviour. +Fix undefined behavior related to bool fields +--------------------------------------------- + +**Rationale:** In C99, `bool` variables are not allowed to have other values +than `true` and `false`. Compilers use this fact in optimization, and constructs +like `int foo = msg.has_field ? 100 : 0` will give unexpected results otherwise. +Previously nanopb didn't enforce that decoded bool fields had valid values. + +**Changes:** Bool fields are now handled separately as `PB_LTYPE_BOOL`. The +`LTYPE` descriptor numbers for other field types were renumbered. + +**Required actions:** Source code files must be recompiled, but regenerating +`.pb.h`/`.pb.c` files from `.proto` is not required. If user code directly uses +the nanopb internal field representation (search for `PB_LTYPE_VARINT` in source), +it may need updating. + Nanopb-0.3.9.1, 0.4.0 (2018-04-14) ================================== diff --git a/docs/security.rst b/docs/security.rst index d854612..6f7152e 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -58,6 +58,7 @@ untrusted data has been maliciously crafted: - The *count* fields of arrays will not exceed the array size. - The *size* field of bytes will not exceed the allocated size. - All string fields will have null terminator. + - bool fields will have valid true/false values (since nanopb-0.3.9.4) 5. After pb_encode() returns successfully, the resulting message is a valid protocol buffers message. (Except if user-defined callbacks write incorrect @@ -150,39 +150,40 @@ typedef uint_least8_t pb_type_t; /**** Field data types ****/ /* Numeric types */ -#define PB_LTYPE_VARINT 0x00 /* int32, int64, enum, bool */ -#define PB_LTYPE_UVARINT 0x01 /* uint32, uint64 */ -#define PB_LTYPE_SVARINT 0x02 /* sint32, sint64 */ -#define PB_LTYPE_FIXED32 0x03 /* fixed32, sfixed32, float */ -#define PB_LTYPE_FIXED64 0x04 /* fixed64, sfixed64, double */ +#define PB_LTYPE_BOOL 0x00 /* bool */ +#define PB_LTYPE_VARINT 0x01 /* int32, int64, enum, bool */ +#define PB_LTYPE_UVARINT 0x02 /* uint32, uint64 */ +#define PB_LTYPE_SVARINT 0x03 /* sint32, sint64 */ +#define PB_LTYPE_FIXED32 0x04 /* fixed32, sfixed32, float */ +#define PB_LTYPE_FIXED64 0x05 /* fixed64, sfixed64, double */ /* Marker for last packable field type. */ -#define PB_LTYPE_LAST_PACKABLE 0x04 +#define PB_LTYPE_LAST_PACKABLE 0x05 /* Byte array with pre-allocated buffer. * data_size is the length of the allocated PB_BYTES_ARRAY structure. */ -#define PB_LTYPE_BYTES 0x05 +#define PB_LTYPE_BYTES 0x06 /* String with pre-allocated buffer. * data_size is the maximum length. */ -#define PB_LTYPE_STRING 0x06 +#define PB_LTYPE_STRING 0x07 /* Submessage * submsg_fields is pointer to field descriptions */ -#define PB_LTYPE_SUBMESSAGE 0x07 +#define PB_LTYPE_SUBMESSAGE 0x08 /* Extension pseudo-field * The field contains a pointer to pb_extension_t */ -#define PB_LTYPE_EXTENSION 0x08 +#define PB_LTYPE_EXTENSION 0x09 /* Byte array with inline, pre-allocated byffer. * data_size is the length of the inline, allocated buffer. * This differs from PB_LTYPE_BYTES by defining the element as * pb_byte_t[data_size] rather than pb_bytes_array_t. */ -#define PB_LTYPE_FIXED_LENGTH_BYTES 0x09 +#define PB_LTYPE_FIXED_LENGTH_BYTES 0x0A /* Number of declared LTYPES */ -#define PB_LTYPES_COUNT 0x0A +#define PB_LTYPES_COUNT 0x0B #define PB_LTYPE_MASK 0x0F /**** Field repetition rules ****/ @@ -491,7 +492,7 @@ struct pb_extension_s { PB_OPTIONAL_CALLBACK(tag, st, m, fd, ltype, ptr) /* The mapping from protobuf types to LTYPEs is done using these macros. */ -#define PB_LTYPE_MAP_BOOL PB_LTYPE_VARINT +#define PB_LTYPE_MAP_BOOL PB_LTYPE_BOOL #define PB_LTYPE_MAP_BYTES PB_LTYPE_BYTES #define PB_LTYPE_MAP_DOUBLE PB_LTYPE_FIXED64 #define PB_LTYPE_MAP_ENUM PB_LTYPE_VARINT diff --git a/pb_decode.c b/pb_decode.c index a7caeff..1779503 100644 --- a/pb_decode.c +++ b/pb_decode.c @@ -34,6 +34,7 @@ static bool checkreturn decode_extension(pb_istream_t *stream, uint32_t tag, pb_ static bool checkreturn find_extension_field(pb_field_iter_t *iter); static void pb_field_set_to_default(pb_field_iter_t *iter); static void pb_message_set_to_defaults(const pb_field_t fields[], void *dest_struct); +static bool checkreturn pb_dec_bool(pb_istream_t *stream, const pb_field_t *field, void *dest); static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_t *field, void *dest); static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *dest, bool *eof); static bool checkreturn pb_dec_uvarint(pb_istream_t *stream, const pb_field_t *field, void *dest); @@ -65,6 +66,7 @@ static void pb_release_single_field(const pb_field_iter_t *iter); * Order in the array must match pb_action_t LTYPE numbering. */ static const pb_decoder_t PB_DECODERS[PB_LTYPES_COUNT] = { + &pb_dec_bool, &pb_dec_varint, &pb_dec_uvarint, &pb_dec_svarint, @@ -1245,6 +1247,11 @@ void pb_release(const pb_field_t fields[], void *dest_struct) /* Field decoders */ +bool pb_decode_bool(pb_istream_t *stream, bool *dest) +{ + return pb_dec_bool(stream, NULL, (void*)dest); +} + bool pb_decode_svarint(pb_istream_t *stream, pb_int64_t *dest) { pb_uint64_t value; @@ -1294,6 +1301,17 @@ bool pb_decode_fixed64(pb_istream_t *stream, void *dest) } #endif +static bool checkreturn pb_dec_bool(pb_istream_t *stream, const pb_field_t *field, void *dest) +{ + uint32_t value; + PB_UNUSED(field); + if (!pb_decode_varint32(stream, &value)) + return false; + + *(bool*)dest = (value != 0); + return true; +} + static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_t *field, void *dest) { pb_uint64_t value; diff --git a/pb_decode.h b/pb_decode.h index 398b24a..3577c20 100644 --- a/pb_decode.h +++ b/pb_decode.h @@ -134,7 +134,7 @@ bool pb_decode_tag(pb_istream_t *stream, pb_wire_type_t *wire_type, uint32_t *ta /* Skip the field payload data, given the wire type. */ bool pb_skip_field(pb_istream_t *stream, pb_wire_type_t wire_type); -/* Decode an integer in the varint format. This works for bool, enum, int32, +/* Decode an integer in the varint format. This works for enum, int32, * int64, uint32 and uint64 field types. */ #ifndef PB_WITHOUT_64BIT bool pb_decode_varint(pb_istream_t *stream, uint64_t *dest); @@ -142,10 +142,13 @@ bool pb_decode_varint(pb_istream_t *stream, uint64_t *dest); #define pb_decode_varint pb_decode_varint32 #endif -/* Decode an integer in the varint format. This works for bool, enum, int32, +/* Decode an integer in the varint format. This works for enum, int32, * and uint32 field types. */ bool pb_decode_varint32(pb_istream_t *stream, uint32_t *dest); +/* Decode a bool value in varint format. */ +bool pb_decode_bool(pb_istream_t *stream, bool *dest); + /* Decode an integer in the zig-zagged svarint format. This works for sint32 * and sint64. */ #ifndef PB_WITHOUT_64BIT diff --git a/pb_encode.c b/pb_encode.c index 64b62b4..a8cd14e 100644 --- a/pb_encode.c +++ b/pb_encode.c @@ -28,6 +28,7 @@ static bool checkreturn encode_field(pb_ostream_t *stream, const pb_field_t *fie static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension); static bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_t *field, const void *pData); static void *pb_const_cast(const void *p); +static bool checkreturn pb_enc_bool(pb_ostream_t *stream, const pb_field_t *field, const void *src); static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_t *field, const void *src); static bool checkreturn pb_enc_uvarint(pb_ostream_t *stream, const pb_field_t *field, const void *src); static bool checkreturn pb_enc_svarint(pb_ostream_t *stream, const pb_field_t *field, const void *src); @@ -52,6 +53,7 @@ static bool checkreturn pb_encode_negative_varint(pb_ostream_t *stream, pb_uint6 * Order in the array must match pb_action_t LTYPE numbering. */ static const pb_encoder_t PB_ENCODERS[PB_LTYPES_COUNT] = { + &pb_enc_bool, &pb_enc_varint, &pb_enc_uvarint, &pb_enc_svarint, @@ -122,6 +124,22 @@ bool checkreturn pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t cou * Encode a single field * *************************/ +/* Read a bool value without causing undefined behavior even if the value + * is invalid. See issue #434 and + * https://stackoverflow.com/questions/27661768/weird-results-for-conditional + */ +static bool safe_read_bool(const void *pSize) +{ + const char *p = (const char *)pSize; + size_t i; + for (i = 0; i < sizeof(bool); i++) + { + if (p[i] != 0) + return true; + } + return false; +} + /* Encode a static array. Handles the size calculations and possible packing. */ static bool checkreturn encode_array(pb_ostream_t *stream, const pb_field_t *field, const void *pData, size_t count, pb_encoder_t func) @@ -240,7 +258,7 @@ static bool pb_check_proto3_default_value(const pb_field_t *field, const void *p else if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL && field->size_offset != 0) { /* Proto2 optional fields inside proto3 submessage */ - return *(const bool*)pSize == false; + return safe_read_bool(pSize) == false; } /* Rest is proto3 singular fields */ @@ -356,7 +374,7 @@ static bool checkreturn encode_basic_field(pb_ostream_t *stream, break; case PB_HTYPE_OPTIONAL: - if (*(const bool*)pSize) + if (safe_read_bool(pSize)) { if (!pb_encode_tag_for_field(stream, field)) return false; @@ -648,6 +666,7 @@ bool checkreturn pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_t pb_wire_type_t wiretype; switch (PB_LTYPE(field->type)) { + case PB_LTYPE_BOOL: case PB_LTYPE_VARINT: case PB_LTYPE_UVARINT: case PB_LTYPE_SVARINT: @@ -736,6 +755,13 @@ bool checkreturn pb_encode_submessage(pb_ostream_t *stream, const pb_field_t fie /* Field encoders */ +static bool checkreturn pb_enc_bool(pb_ostream_t *stream, const pb_field_t *field, const void *src) +{ + uint32_t value = (uint32_t)safe_read_bool(src); + PB_UNUSED(field); + return pb_encode_varint(stream, value); +} + static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_t *field, const void *src) { pb_int64_t value = 0; |