From 2b1ca0dc16dd77ac69449a7f43a9170426cc4de7 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Sat, 19 Sep 2026 07:16:28 -0600 Subject: [PATCH 1/6] variant_utils: bound nested MessagePack attributes Signed-off-by: Eduardo Silva --- include/cprofiles/cprof_variant_utils.h | 84 ++++++++++++++++++++----- 1 file changed, 68 insertions(+), 16 deletions(-) diff --git a/include/cprofiles/cprof_variant_utils.h b/include/cprofiles/cprof_variant_utils.h index f26bc93..f99e0e2 100644 --- a/include/cprofiles/cprof_variant_utils.h +++ b/include/cprofiles/cprof_variant_utils.h @@ -25,6 +25,7 @@ #define CFL_VARIANT_UTILS_MAXIMUM_FIXED_ARRAY_SIZE 100 #define CFL_VARIANT_UTILS_INITIAL_ARRAY_SIZE 100 #define CFL_VARIANT_UTILS_SERIALIZED_ARRAY_SIZE_LIMIT 100000 +#define CFL_VARIANT_UTILS_MAXIMUM_NESTING_DEPTH 32 /* These are the only functions meant for general use, * the reason why the kvlist packing and unpacking @@ -51,9 +52,17 @@ static inline int pack_cfl_variant_kvlist(mpack_writer_t *writer, static inline int unpack_cfl_variant(mpack_reader_t *reader, struct cfl_variant **value); +static inline int unpack_cfl_variant_depth(mpack_reader_t *reader, + struct cfl_variant **value, + size_t depth); + static inline int unpack_cfl_kvlist(mpack_reader_t *reader, struct cfl_kvlist **result_kvlist); +static inline int unpack_cfl_kvlist_depth(mpack_reader_t *reader, + struct cfl_kvlist **result_kvlist, + size_t depth); + /* Packers */ static inline int pack_cfl_variant_string(mpack_writer_t *writer, char *value) @@ -212,8 +221,9 @@ static inline int unpack_cfl_variant_read_tag(mpack_reader_t *reader, return 0; } -static inline int unpack_cfl_array(mpack_reader_t *reader, - struct cfl_array **result_array) +static inline int unpack_cfl_array_depth(mpack_reader_t *reader, + struct cfl_array **result_array, + size_t depth) { struct cfl_array *internal_array; size_t entry_count; @@ -222,6 +232,10 @@ static inline int unpack_cfl_array(mpack_reader_t *reader, size_t index; mpack_tag_t tag; + if (depth >= CFL_VARIANT_UTILS_MAXIMUM_NESTING_DEPTH) { + return -2; + } + result = unpack_cfl_variant_read_tag(reader, &tag, mpack_type_array); if (result != 0) { @@ -250,7 +264,7 @@ static inline int unpack_cfl_array(mpack_reader_t *reader, } for (index = 0 ; index < entry_count ; index++) { - result = unpack_cfl_variant(reader, &entry_value); + result = unpack_cfl_variant_depth(reader, &entry_value, depth + 1); if (result != 0) { cfl_array_destroy(internal_array); @@ -280,8 +294,9 @@ static inline int unpack_cfl_array(mpack_reader_t *reader, return 0; } -static inline int unpack_cfl_kvlist(mpack_reader_t *reader, - struct cfl_kvlist **result_kvlist) +static inline int unpack_cfl_kvlist_depth(mpack_reader_t *reader, + struct cfl_kvlist **result_kvlist, + size_t depth) { struct cfl_kvlist *internal_kvlist; char key_name[256]; @@ -293,6 +308,10 @@ static inline int unpack_cfl_kvlist(mpack_reader_t *reader, size_t index; mpack_tag_t tag; + if (depth >= CFL_VARIANT_UTILS_MAXIMUM_NESTING_DEPTH) { + return -2; + } + result = unpack_cfl_variant_read_tag(reader, &tag, mpack_type_map); if (result != 0) { @@ -339,7 +358,7 @@ static inline int unpack_cfl_kvlist(mpack_reader_t *reader, break; } - result = unpack_cfl_variant(reader, &key_value); + result = unpack_cfl_variant_depth(reader, &key_value, depth + 1); if (result != 0) { result = -7; @@ -378,6 +397,12 @@ static inline int unpack_cfl_kvlist(mpack_reader_t *reader, return result; } +static inline int unpack_cfl_kvlist(mpack_reader_t *reader, + struct cfl_kvlist **result_kvlist) +{ + return unpack_cfl_kvlist_depth(reader, result_kvlist, 0); +} + static inline int unpack_cfl_variant_string(mpack_reader_t *reader, struct cfl_variant **value) { @@ -552,13 +577,14 @@ static inline int unpack_cfl_variant_double(mpack_reader_t *reader, return 0; } -static inline int unpack_cfl_variant_array(mpack_reader_t *reader, - struct cfl_variant **value) +static inline int unpack_cfl_variant_array_depth(mpack_reader_t *reader, + struct cfl_variant **value, + size_t depth) { struct cfl_array *unpacked_array; int result; - result = unpack_cfl_array(reader, &unpacked_array); + result = unpack_cfl_array_depth(reader, &unpacked_array, depth); if (result != 0) { return result; @@ -573,13 +599,14 @@ static inline int unpack_cfl_variant_array(mpack_reader_t *reader, return 0; } -static inline int unpack_cfl_variant_kvlist(mpack_reader_t *reader, - struct cfl_variant **value) +static inline int unpack_cfl_variant_kvlist_depth(mpack_reader_t *reader, + struct cfl_variant **value, + size_t depth) { struct cfl_kvlist *unpacked_kvlist; int result; - result = unpack_cfl_kvlist(reader, &unpacked_kvlist); + result = unpack_cfl_kvlist_depth(reader, &unpacked_kvlist, depth); if (result != 0) { return result; @@ -594,8 +621,9 @@ static inline int unpack_cfl_variant_kvlist(mpack_reader_t *reader, return 0; } -static inline int unpack_cfl_variant(mpack_reader_t *reader, - struct cfl_variant **value) +static inline int unpack_cfl_variant_depth(mpack_reader_t *reader, + struct cfl_variant **value, + size_t depth) { mpack_type_t value_type; int result; @@ -625,10 +653,10 @@ static inline int unpack_cfl_variant(mpack_reader_t *reader, result = unpack_cfl_variant_double(reader, value); } else if (value_type == mpack_type_array) { - result = unpack_cfl_variant_array(reader, value); + result = unpack_cfl_variant_array_depth(reader, value, depth); } else if (value_type == mpack_type_map) { - result = unpack_cfl_variant_kvlist(reader, value); + result = unpack_cfl_variant_kvlist_depth(reader, value, depth); } else if (value_type == mpack_type_bin) { result = unpack_cfl_variant_binary(reader, value); @@ -640,4 +668,28 @@ static inline int unpack_cfl_variant(mpack_reader_t *reader, return result; } +static inline int unpack_cfl_variant(mpack_reader_t *reader, + struct cfl_variant **value) +{ + return unpack_cfl_variant_depth(reader, value, 0); +} + +static inline int unpack_cfl_array(mpack_reader_t *reader, + struct cfl_array **result_array) +{ + return unpack_cfl_array_depth(reader, result_array, 0); +} + +static inline int unpack_cfl_variant_array(mpack_reader_t *reader, + struct cfl_variant **value) +{ + return unpack_cfl_variant_array_depth(reader, value, 0); +} + +static inline int unpack_cfl_variant_kvlist(mpack_reader_t *reader, + struct cfl_variant **value) +{ + return unpack_cfl_variant_kvlist_depth(reader, value, 0); +} + #endif From 15414362437e510d11ea4aa07962187062e3d662 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Sat, 19 Sep 2026 07:16:28 -0600 Subject: [PATCH 2/6] decode_opentelemetry: bound nested attribute conversion Signed-off-by: Eduardo Silva --- src/cprof_decode_opentelemetry.c | 2 +- src/cprof_opentelemetry_variant_helpers.c | 43 +++++++++++++---------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/cprof_decode_opentelemetry.c b/src/cprof_decode_opentelemetry.c index afd80fc..2381b4b 100644 --- a/src/cprof_decode_opentelemetry.c +++ b/src/cprof_decode_opentelemetry.c @@ -535,7 +535,7 @@ static int decode_profile_entry(struct cprof_profile *profile, result = clone_variant(&indexed_attribute_value, indexed_attribute_entry->value, dictionary->string_table, - dictionary->n_string_table); + dictionary->n_string_table, 1); if (result != CPROF_DECODE_OPENTELEMETRY_SUCCESS) { return result; diff --git a/src/cprof_opentelemetry_variant_helpers.c b/src/cprof_opentelemetry_variant_helpers.c index 5d37f48..460a373 100644 --- a/src/cprof_opentelemetry_variant_helpers.c +++ b/src/cprof_opentelemetry_variant_helpers.c @@ -1,27 +1,28 @@ #include #include +#include static int clone_variant(struct cfl_variant **result_instance, Opentelemetry__Proto__Common__V1__AnyValue *source, char **string_table, - size_t string_table_len); + size_t string_table_len, size_t depth); static int clone_array(struct cfl_array *target, Opentelemetry__Proto__Common__V1__ArrayValue *source, char **string_table, - size_t string_table_len); + size_t string_table_len, size_t depth); static int clone_array_entry(struct cfl_array *target, Opentelemetry__Proto__Common__V1__AnyValue *source, char **string_table, - size_t string_table_len); + size_t string_table_len, size_t depth); static int clone_kvlist(struct cfl_kvlist *target, Opentelemetry__Proto__Common__V1__KeyValueList *source, char **string_table, - size_t string_table_len); + size_t string_table_len, size_t depth); static int clone_kvlist_entry(struct cfl_kvlist *target, Opentelemetry__Proto__Common__V1__KeyValue *source, char **string_table, - size_t string_table_len); + size_t string_table_len, size_t depth); static int convert_kvarray_to_kvlist(struct cfl_kvlist *target, Opentelemetry__Proto__Common__V1__KeyValue **source, size_t source_length, @@ -38,7 +39,7 @@ static int convert_keyvalueandunit_array_to_kvlist(struct cfl_kvlist *target, static int clone_variant(struct cfl_variant **result_instance, Opentelemetry__Proto__Common__V1__AnyValue *source, char **string_table, - size_t string_table_len) + size_t string_table_len, size_t depth) { struct cfl_kvlist *new_child_kvlist; struct cfl_array *new_child_array; @@ -47,6 +48,12 @@ static int clone_variant(struct cfl_variant **result_instance, *result_instance = NULL; + if (source != NULL && depth >= CFL_VARIANT_UTILS_MAXIMUM_NESTING_DEPTH && + (source->value_case == OPENTELEMETRY__PROTO__COMMON__V1__ANY_VALUE__VALUE_KVLIST_VALUE || + source->value_case == OPENTELEMETRY__PROTO__COMMON__V1__ANY_VALUE__VALUE_ARRAY_VALUE)) { + return CPROF_DECODE_OPENTELEMETRY_INVALID_ARGUMENT_ERROR; + } + if (source == NULL) { *result_instance = cfl_variant_create_from_string(""); @@ -102,7 +109,7 @@ static int clone_variant(struct cfl_variant **result_instance, result = clone_kvlist(new_child_kvlist, source->kvlist_value, string_table, - string_table_len); + string_table_len, depth); if (result != CPROF_DECODE_OPENTELEMETRY_SUCCESS) { cfl_variant_destroy(*result_instance); *result_instance = NULL; @@ -135,7 +142,7 @@ static int clone_variant(struct cfl_variant **result_instance, result = clone_array(new_child_array, source->array_value, string_table, - string_table_len); + string_table_len, depth); if (result != CPROF_DECODE_OPENTELEMETRY_SUCCESS) { cfl_variant_destroy(*result_instance); *result_instance = NULL; @@ -159,7 +166,7 @@ static int clone_variant(struct cfl_variant **result_instance, static int clone_array(struct cfl_array *target, Opentelemetry__Proto__Common__V1__ArrayValue *source, char **string_table, - size_t string_table_len) + size_t string_table_len, size_t depth) { int result; size_t index; @@ -173,7 +180,7 @@ static int clone_array(struct cfl_array *target, result = clone_array_entry(target, source->values[index], string_table, - string_table_len); + string_table_len, depth + 1); } return result; @@ -182,12 +189,12 @@ static int clone_array(struct cfl_array *target, static int clone_array_entry(struct cfl_array *target, Opentelemetry__Proto__Common__V1__AnyValue *source, char **string_table, - size_t string_table_len) + size_t string_table_len, size_t depth) { struct cfl_variant *new_child_instance; int result; - result = clone_variant(&new_child_instance, source, string_table, string_table_len); + result = clone_variant(&new_child_instance, source, string_table, string_table_len, depth); if (result != CPROF_DECODE_OPENTELEMETRY_SUCCESS) { return result; } @@ -204,7 +211,7 @@ static int clone_array_entry(struct cfl_array *target, static int clone_kvlist(struct cfl_kvlist *target, Opentelemetry__Proto__Common__V1__KeyValueList *source, char **string_table, - size_t string_table_len) + size_t string_table_len, size_t depth) { int result; size_t index; @@ -218,7 +225,7 @@ static int clone_kvlist(struct cfl_kvlist *target, result = clone_kvlist_entry(target, source->values[index], string_table, - string_table_len); + string_table_len, depth + 1); } return result; @@ -242,7 +249,7 @@ static int convert_kvarray_to_kvlist(struct cfl_kvlist *target, result = clone_kvlist_entry(target, source[index], string_table, - string_table_len); + string_table_len, 1); } return result; @@ -251,7 +258,7 @@ static int convert_kvarray_to_kvlist(struct cfl_kvlist *target, static int clone_kvlist_entry(struct cfl_kvlist *target, Opentelemetry__Proto__Common__V1__KeyValue *source, char **string_table, - size_t string_table_len) + size_t string_table_len, size_t depth) { struct cfl_variant *new_child_instance; int result; @@ -280,7 +287,7 @@ static int clone_kvlist_entry(struct cfl_kvlist *target, key = (char *) resolved_key; - result = clone_variant(&new_child_instance, source->value, string_table, string_table_len); + result = clone_variant(&new_child_instance, source->value, string_table, string_table_len, depth); if (result != CPROF_DECODE_OPENTELEMETRY_SUCCESS) { return result; } @@ -337,7 +344,7 @@ static int convert_keyvalueandunit_array_to_kvlist(struct cfl_kvlist *target, } } else { - result = clone_variant(&val, entry->value, string_table, string_table_len); + result = clone_variant(&val, entry->value, string_table, string_table_len, 1); if (result != CPROF_DECODE_OPENTELEMETRY_SUCCESS) { return result; } From a47563c0e1ef399c8a9003d1b1888050bc211c6c Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Sat, 19 Sep 2026 07:16:28 -0600 Subject: [PATCH 3/6] encode_opentelemetry: free nested attribute values Signed-off-by: Eduardo Silva --- src/cprof_encode_opentelemetry.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/cprof_encode_opentelemetry.c b/src/cprof_encode_opentelemetry.c index 2a86fb7..fc6612f 100644 --- a/src/cprof_encode_opentelemetry.c +++ b/src/cprof_encode_opentelemetry.c @@ -92,7 +92,7 @@ static inline void otlp_any_value_destroy(Opentelemetry__Proto__Common__V1__AnyV { if (value != NULL) { if (value->value_case == OPENTELEMETRY__PROTO__COMMON__V1__ANY_VALUE__VALUE_STRING_VALUE) { - if (value->string_value != NULL) { + if (is_string_releaseable(value->string_value)) { free(value->string_value); } } @@ -546,16 +546,7 @@ static Opentelemetry__Proto__Common__V1__KeyValue ** static void destroy_attribute(Opentelemetry__Proto__Common__V1__KeyValue *attribute) { if (attribute != NULL) { - if (attribute->value != NULL) { - if (attribute->value->value_case == \ - OPENTELEMETRY__PROTO__COMMON__V1__ANY_VALUE__VALUE_STRING_VALUE) { - if (is_string_releaseable(attribute->value->string_value)) { - free(attribute->value->string_value); - } - } - - free(attribute->value); - } + otlp_any_value_destroy(attribute->value); if (is_string_releaseable(attribute->key)) { free(attribute->key); From d3e339b08bdef0a898acef1457a3fa832ae530ec Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Sat, 19 Sep 2026 07:16:28 -0600 Subject: [PATCH 4/6] tests: cover attribute nesting boundaries and cleanup Signed-off-by: Eduardo Silva --- tests/CMakeLists.txt | 1 + tests/opentelemetry_transcoder.c | 64 ++++++++++++++++++++ tests/variant_depth.c | 100 +++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 tests/variant_depth.c diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d59f79e..3078965 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,5 @@ set(UNIT_TESTS_FILES + variant_depth.c profile.c msgpack_transcoder.c opentelemetry_transcoder.c diff --git a/tests/opentelemetry_transcoder.c b/tests/opentelemetry_transcoder.c index f458059..89a8172 100644 --- a/tests/opentelemetry_transcoder.c +++ b/tests/opentelemetry_transcoder.c @@ -1533,7 +1533,71 @@ static void test_decoder_rejects_invalid_sample_link_reference() request, NULL); } + +static void check_otlp_depth(size_t depth, int shape) +{ + struct cprof *original; + struct cprof *decoded; + struct cprof_resource_profiles *resource; + struct cfl_variant *value; + struct cfl_variant *parent; + struct cfl_kvlist *map; + struct cfl_array *array; + cfl_sds_t wire; + size_t index; + size_t offset; + int result; + + original = create_minimal_cprof(); + TEST_ASSERT(original != NULL); + resource = cfl_list_entry(original->profiles.next, struct cprof_resource_profiles, _head); + value = cfl_variant_create_from_string("leaf"); + TEST_ASSERT(value != NULL); + for (index = 0; index < depth; index++) { + if (shape == 0 || (shape == 2 && index % 2 == 0)) { + map = cfl_kvlist_create(); + TEST_ASSERT(map != NULL); + TEST_ASSERT(cfl_kvlist_insert(map, "k", value) == 0); + parent = cfl_variant_create_from_kvlist(map); + } + else { + array = cfl_array_create(1); + TEST_ASSERT(array != NULL); + TEST_ASSERT(cfl_array_append(array, value) == 0); + parent = cfl_variant_create_from_array(array); + } + TEST_ASSERT(parent != NULL); + value = parent; + } + TEST_ASSERT(cfl_kvlist_insert(resource->resource->attributes, "deep", value) == 0); + TEST_ASSERT(cprof_encode_opentelemetry_create(&wire, original) == 0); + TEST_ASSERT(wire != NULL); + cprof_destroy(original); + decoded = NULL; + offset = 0; + result = cprof_decode_opentelemetry_create(&decoded, (unsigned char *) wire, + cfl_sds_len(wire), &offset); + TEST_CHECK((result == 0) == (depth < 32)); + if (decoded != NULL) { + cprof_destroy(decoded); + } + cprof_encode_opentelemetry_destroy(wire); +} + +static void test_otlp_depth_boundary(void) +{ + int shape; + + for (shape = 0; shape < 3; shape++) { + check_otlp_depth(8, shape); + check_otlp_depth(31, shape); + check_otlp_depth(32, shape); + check_otlp_depth(400, shape); + } +} + TEST_LIST = { + {"otlp_depth_boundary", test_otlp_depth_boundary}, {"encoder", test_encoder}, {"decoder", test_decoder}, {"encoder_dictionary_tables", test_encoder_dictionary_tables}, diff --git a/tests/variant_depth.c b/tests/variant_depth.c new file mode 100644 index 0000000..0aff20d --- /dev/null +++ b/tests/variant_depth.c @@ -0,0 +1,100 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* CProfiles + * ========= + * Copyright (C) 2024 The CProfiles Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include +#include +#include "cprof_tests.h" + +static void check_depth(size_t depth, int shape, int expected) +{ + mpack_writer_t writer; + mpack_reader_t reader; + struct cfl_kvlist *decoded; + char *data; + size_t size; + size_t index; + int result; + int is_map; + + data = NULL; + size = 0; + decoded = NULL; + mpack_writer_init_growable(&writer, &data, &size); + mpack_start_map(&writer, 1); + mpack_write_cstr(&writer, "deep"); + for (index = 0; index < depth; index++) { + is_map = shape == 0 || (shape == 2 && index % 2 == 0); + if (is_map) { + mpack_start_map(&writer, 1); + mpack_write_cstr(&writer, "k"); + } + else { + mpack_start_array(&writer, 1); + } + } + mpack_write_cstr(&writer, "leaf"); + for (index = depth; index > 0; index--) { + is_map = shape == 0 || (shape == 2 && (index - 1) % 2 == 0); + if (is_map) { + mpack_finish_map(&writer); + } + else { + mpack_finish_array(&writer); + } + } + mpack_finish_map(&writer); + TEST_ASSERT(mpack_writer_destroy(&writer) == mpack_ok); + mpack_reader_init_data(&reader, data, size); + result = unpack_cfl_kvlist(&reader, &decoded); + TEST_CHECK((result == 0) == expected); + if (decoded != NULL) { + cfl_kvlist_destroy(decoded); + } + mpack_reader_destroy(&reader); + free(data); +} + +static void test_depth_controls(void) +{ + int shape; + + for (shape = 0; shape < 3; shape++) { + check_depth(0, shape, 1); + check_depth(8, shape, 1); + check_depth(30, shape, 1); + check_depth(31, shape, 1); + } +} + +static void test_depth_limit(void) +{ + int shape; + + for (shape = 0; shape < 3; shape++) { + check_depth(32, shape, 0); + check_depth(400, shape, 0); + } +} + +TEST_LIST = { + {"depth_controls", test_depth_controls}, + {"depth_limit", test_depth_limit}, + {NULL, NULL} +}; From 37c66dfadaba583b78538aec4316a474c738fa5c Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Sat, 19 Sep 2026 08:52:34 -0600 Subject: [PATCH 5/6] decode_opentelemetry: validate nesting before protobuf unpacking Signed-off-by: Eduardo Silva --- src/cprof_decode_opentelemetry.c | 11 +++ src/cprof_protobuf.h | 139 +++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 src/cprof_protobuf.h diff --git a/src/cprof_decode_opentelemetry.c b/src/cprof_decode_opentelemetry.c index 2381b4b..529f7d7 100644 --- a/src/cprof_decode_opentelemetry.c +++ b/src/cprof_decode_opentelemetry.c @@ -18,6 +18,7 @@ */ +#include "cprof_protobuf.h" #include #include @@ -785,6 +786,16 @@ int cprof_decode_opentelemetry_create(struct cprof **result_context, *result_context = NULL; } + if (result_context == NULL || in_buf == NULL || offset == NULL || *offset > in_size) { + return CPROF_DECODE_OPENTELEMETRY_INVALID_ARGUMENT_ERROR; + } + + if (cprof_protobuf_validate( + &opentelemetry__proto__collector__profiles__v1development__export_profiles_service_request__descriptor, + &in_buf[*offset], in_size - *offset) != 0) { + return CPROF_DECODE_OPENTELEMETRY_INVALID_ARGUMENT_ERROR; + } + service_request = opentelemetry__proto__collector__profiles__v1development__export_profiles_service_request__unpack( NULL, in_size - *offset, diff --git a/src/cprof_protobuf.h b/src/cprof_protobuf.h new file mode 100644 index 0000000..8040776 --- /dev/null +++ b/src/cprof_protobuf.h @@ -0,0 +1,139 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2026 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPROF_PROTOBUF_H +#define CPROF_PROTOBUF_H + +#include + +/* Budget includes export/resource/scope wrappers and scalar AnyValue messages. */ +#define CPROF_PROTOBUF_MAX_DEPTH 100 + +struct cprof_protobuf_frame { + const ProtobufCMessageDescriptor *descriptor; + const unsigned char *cursor; + const unsigned char *end; +}; + +static int cprof_protobuf_read_varint(struct cprof_protobuf_frame *frame, uint64_t *value) +{ + unsigned int shift; + unsigned char byte; + + *value = 0; + for (shift = 0; shift < 64; shift += 7) { + if (frame->cursor == frame->end) { + return -1; + } + byte = *frame->cursor++; + if (shift == 63 && byte > 1) { + return -1; + } + *value |= (uint64_t) (byte & 0x7f) << shift; + if ((byte & 0x80) == 0) { + return 0; + } + } + return -1; +} + +/* + * Inspect known message fields before protobuf-c recursively allocates them. + * Strings, bytes, packed scalars and unknown fields are opaque. The explicit + * stack bounds our own stack usage as well as the subsequent unpack operation. + */ +static int cprof_protobuf_validate(const ProtobufCMessageDescriptor *descriptor, + const void *data, size_t size) +{ + struct cprof_protobuf_frame frames[CPROF_PROTOBUF_MAX_DEPTH]; + struct cprof_protobuf_frame *frame; + const ProtobufCFieldDescriptor *field; + const unsigned char *message; + uint64_t tag; + uint64_t length; + size_t depth; + + if (descriptor == NULL || (data == NULL && size != 0)) { + return -1; + } + if (size == 0) { + return 0; + } + + depth = 1; + frames[0].descriptor = descriptor; + frames[0].cursor = data; + frames[0].end = frames[0].cursor + size; + + while (depth > 0) { + frame = &frames[depth - 1]; + if (frame->cursor == frame->end) { + depth--; + continue; + } + if (cprof_protobuf_read_varint(frame, &tag) != 0 || tag >> 3 == 0 || tag >> 3 > 0x1fffffff) { + return -1; + } + + switch (tag & 7) { + case 0: + if (cprof_protobuf_read_varint(frame, &length) != 0) { + return -1; + } + continue; + case 1: + length = 8; + break; + case 2: + if (cprof_protobuf_read_varint(frame, &length) != 0) { + return -1; + } + break; + case 5: + length = 4; + break; + default: + /* Groups are unsupported by protobuf-c. */ + return -1; + } + + if (length > (uint64_t) (frame->end - frame->cursor)) { + return -1; + } + message = frame->cursor; + frame->cursor += (size_t) length; + if ((tag & 7) != 2) { + continue; + } + field = protobuf_c_message_descriptor_get_field(frame->descriptor, tag >> 3); + if (field == NULL || field->type != PROTOBUF_C_TYPE_MESSAGE) { + continue; + } + if (depth >= CPROF_PROTOBUF_MAX_DEPTH) { + return -1; + } + frames[depth].descriptor = field->descriptor; + frames[depth].cursor = message; + frames[depth].end = message + (size_t) length; + depth++; + } + return 0; +} + +#endif From 805585ed61371762f01836d60620fe74569f49b8 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Sat, 19 Sep 2026 08:52:34 -0600 Subject: [PATCH 6/6] tests: cover protobuf wire depth and malformed fields Signed-off-by: Eduardo Silva --- tests/CMakeLists.txt | 1 + tests/protobuf.c | 62 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 tests/protobuf.c diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3078965..5492969 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,5 @@ set(UNIT_TESTS_FILES + protobuf.c variant_depth.c profile.c msgpack_transcoder.c diff --git a/tests/protobuf.c b/tests/protobuf.c new file mode 100644 index 0000000..749fcb5 --- /dev/null +++ b/tests/protobuf.c @@ -0,0 +1,62 @@ +#include "cprof_tests.h" +#include "../src/cprof_protobuf.h" + +static void test_protobuf_depth(void) +{ + Opentelemetry__Proto__Common__V1__AnyValue values[51]; + Opentelemetry__Proto__Common__V1__ArrayValue arrays[50]; + Opentelemetry__Proto__Common__V1__AnyValue *children[50]; + unsigned char buffer[1024]; + size_t size; + int index; + + for (index = 0; index < 51; index++) { + opentelemetry__proto__common__v1__any_value__init(&values[index]); + } + values[50].value_case = OPENTELEMETRY__PROTO__COMMON__V1__ANY_VALUE__VALUE_INT_VALUE; + values[50].int_value = 7; + for (index = 49; index >= 0; index--) { + opentelemetry__proto__common__v1__array_value__init(&arrays[index]); + children[index] = &values[index + 1]; + arrays[index].values = &children[index]; + arrays[index].n_values = 1; + values[index].value_case = OPENTELEMETRY__PROTO__COMMON__V1__ANY_VALUE__VALUE_ARRAY_VALUE; + values[index].array_value = &arrays[index]; + } + + /* 99, 100 and 101 schema messages, including scalar AnyValue leaves. */ + size = opentelemetry__proto__common__v1__any_value__pack(&values[1], buffer); + TEST_CHECK(cprof_protobuf_validate(values[1].base.descriptor, buffer, size) == 0); + size = opentelemetry__proto__common__v1__array_value__pack(&arrays[0], buffer); + TEST_CHECK(cprof_protobuf_validate(arrays[0].base.descriptor, buffer, size) == 0); + size = opentelemetry__proto__common__v1__any_value__pack(&values[0], buffer); + TEST_CHECK(cprof_protobuf_validate(values[0].base.descriptor, buffer, size) != 0); +} + +static void test_protobuf_wire_boundaries(void) +{ + const ProtobufCMessageDescriptor *descriptor; + unsigned char truncated[] = {0x2a, 0x02, 0x0a}; + unsigned char overflow[] = {0x2a, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x02}; + unsigned char opaque[] = {0x0a, 0x03, 0xff, 0xff, 0xff}; + unsigned char unknown[] = {0x7a, 0x03, 0xff, 0xff, 0xff}; + unsigned char zero_tag[] = {0x00, 0x00}; + unsigned char fixed[] = {0x21, 0, 0, 0, 0, 0, 0, 0, 0}; + + descriptor = &opentelemetry__proto__common__v1__any_value__descriptor; + TEST_CHECK(cprof_protobuf_validate(descriptor, NULL, 0) == 0); + TEST_CHECK(cprof_protobuf_validate(descriptor, truncated, sizeof(truncated)) != 0); + TEST_CHECK(cprof_protobuf_validate(descriptor, overflow, sizeof(overflow)) != 0); + TEST_CHECK(cprof_protobuf_validate(descriptor, opaque, sizeof(opaque)) == 0); + TEST_CHECK(cprof_protobuf_validate(descriptor, unknown, sizeof(unknown)) == 0); + TEST_CHECK(cprof_protobuf_validate(descriptor, zero_tag, sizeof(zero_tag)) != 0); + TEST_CHECK(cprof_protobuf_validate(descriptor, fixed, sizeof(fixed)) == 0); + TEST_CHECK(cprof_protobuf_validate(descriptor, fixed, sizeof(fixed) - 1) != 0); +} + +TEST_LIST = { + {"protobuf_depth", test_protobuf_depth}, + {"protobuf_wire_boundaries", test_protobuf_wire_boundaries}, + {0} +};