Skip to content

Commit e03226c

Browse files
authored
enforce the depth limit for cbor decoder (#736)
1 parent b2b01dd commit e03226c

2 files changed

Lines changed: 85 additions & 37 deletions

File tree

source/cbor.c

Lines changed: 84 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
#include <aws/common/cbor.h>
88

9+
/* Maximum nesting depth for recursive CBOR decoding to prevent stack exhaustion */
10+
static const size_t s_cbor_max_decode_depth = 128;
11+
912
/*******************************************************************************
1013
* ENCODE
1114
******************************************************************************/
@@ -185,30 +188,49 @@ static PyObject *s_cbor_encoder_write_pyobject(struct aws_cbor_encoder *encoder,
185188
/**
186189
* TODO: timestamp <-> datetime?? Decimal fraction <-> decimal??
187190
*/
188-
if (PyLong_CheckExact(py_object)) {
189-
return s_cbor_encoder_write_pylong(encoder, py_object);
190-
} else if (PyFloat_CheckExact(py_object)) {
191-
return s_cbor_encoder_write_pyobject_as_float(encoder, py_object);
192-
} else if (PyBool_Check(py_object)) {
193-
return s_cbor_encoder_write_pyobject_as_bool(encoder, py_object);
194-
} else if (PyBytes_CheckExact(py_object)) {
195-
return s_cbor_encoder_write_pyobject_as_bytes(encoder, py_object);
196-
} else if (PyUnicode_Check(py_object)) {
191+
192+
/* Handle None first as it's a singleton, not a type */
193+
if (py_object == Py_None) {
194+
aws_cbor_encoder_write_null(encoder);
195+
Py_RETURN_NONE;
196+
}
197+
198+
/* Get type once for efficiency - PyObject_Type returns a new reference */
199+
/* https://docs.python.org/3/c-api/structures.html#c.Py_TYPE is not a stable API until 3.14, so that we cannot use
200+
* it. */
201+
PyObject *type = PyObject_Type(py_object);
202+
if (!type) {
203+
return NULL;
204+
}
205+
206+
PyObject *result = NULL;
207+
208+
/* Exact type matches first (no subclasses) */
209+
if (type == (PyObject *)&PyLong_Type) {
210+
result = s_cbor_encoder_write_pylong(encoder, py_object);
211+
} else if (type == (PyObject *)&PyFloat_Type) {
212+
result = s_cbor_encoder_write_pyobject_as_float(encoder, py_object);
213+
} else if (type == (PyObject *)&PyBool_Type) {
214+
result = s_cbor_encoder_write_pyobject_as_bool(encoder, py_object);
215+
} else if (type == (PyObject *)&PyBytes_Type) {
216+
result = s_cbor_encoder_write_pyobject_as_bytes(encoder, py_object);
217+
} else if (PyType_IsSubtype((PyTypeObject *)type, &PyUnicode_Type)) {
197218
/* Allow subclasses of `str` */
198-
return s_cbor_encoder_write_pyobject_as_text(encoder, py_object);
199-
} else if (PyList_Check(py_object)) {
219+
result = s_cbor_encoder_write_pyobject_as_text(encoder, py_object);
220+
} else if (PyType_IsSubtype((PyTypeObject *)type, &PyList_Type)) {
200221
/* Write py_list, allow subclasses of `list` */
201-
return s_cbor_encoder_write_pylist(encoder, py_object);
202-
} else if (PyDict_Check(py_object)) {
222+
result = s_cbor_encoder_write_pylist(encoder, py_object);
223+
} else if (PyType_IsSubtype((PyTypeObject *)type, &PyDict_Type)) {
203224
/* Write py_dict, allow subclasses of `dict` */
204-
return s_cbor_encoder_write_pydict(encoder, py_object);
205-
} else if (py_object == Py_None) {
206-
aws_cbor_encoder_write_null(encoder);
225+
result = s_cbor_encoder_write_pydict(encoder, py_object);
207226
} else {
208-
PyErr_Format(PyExc_ValueError, "Not supported type %R", (PyObject *)Py_TYPE(py_object));
227+
/* Unsupported type */
228+
PyErr_Format(PyExc_ValueError, "Not supported type %R", type);
209229
}
210230

211-
Py_RETURN_NONE;
231+
/* Release the type reference */
232+
Py_DECREF(type);
233+
return result;
212234
}
213235

214236
/*********************************** BINDINGS ***********************************************/
@@ -290,6 +312,9 @@ struct decoder_binding {
290312

291313
/* Encoder has simple lifetime, no async/multi-thread allowed. */
292314
PyObject *self_py;
315+
316+
/* Current recursion depth for pop_next_data_item */
317+
size_t current_depth;
293318
};
294319

295320
static const char *s_capsule_name_cbor_decoder = "aws_cbor_decoder";
@@ -646,62 +671,85 @@ static PyObject *s_cbor_decoder_pop_next_tag_to_pyobject(struct decoder_binding
646671
* Generic helper to convert a cbor encoded data to PyObject
647672
*/
648673
static PyObject *s_cbor_decoder_pop_next_data_item_to_pyobject(struct decoder_binding *binding) {
674+
if (binding->current_depth >= s_cbor_max_decode_depth) {
675+
PyErr_SetString(PyExc_RecursionError, "CBOR nesting depth exceeds maximum allowed (128)");
676+
return NULL;
677+
}
678+
++binding->current_depth;
679+
680+
PyObject *result = NULL;
649681
struct aws_cbor_decoder *decoder = binding->native;
650682
enum aws_cbor_type out_type = AWS_CBOR_TYPE_UNKNOWN;
651683
if (aws_cbor_decoder_peek_type(decoder, &out_type)) {
652-
return PyErr_AwsLastError();
684+
result = PyErr_AwsLastError();
685+
goto done;
653686
}
654687
switch (out_type) {
655688
case AWS_CBOR_TYPE_UINT:
656-
return s_cbor_decoder_pop_next_unsigned_int_val_to_pyobject(decoder);
689+
result = s_cbor_decoder_pop_next_unsigned_int_val_to_pyobject(decoder);
690+
break;
657691
case AWS_CBOR_TYPE_NEGINT: {
658692
/* The value from native code is -1 - val. */
659693
PyObject *minus_one = PyLong_FromLong(-1);
660694
if (!minus_one) {
661-
return NULL;
695+
break;
662696
}
663697
PyObject *val = s_cbor_decoder_pop_next_negative_int_val_to_pyobject(decoder);
664698
if (!val) {
665699
Py_DECREF(minus_one);
666-
return NULL;
700+
break;
667701
}
668-
PyObject *ret_val = PyNumber_Subtract(minus_one, val);
702+
result = PyNumber_Subtract(minus_one, val);
669703
Py_DECREF(minus_one);
670704
Py_DECREF(val);
671-
return ret_val;
705+
break;
672706
}
673707
case AWS_CBOR_TYPE_FLOAT:
674-
return s_cbor_decoder_pop_next_float_val_to_pyobject(decoder);
708+
result = s_cbor_decoder_pop_next_float_val_to_pyobject(decoder);
709+
break;
675710
case AWS_CBOR_TYPE_BYTES:
676-
return s_cbor_decoder_pop_next_bytes_val_to_pyobject(decoder);
711+
result = s_cbor_decoder_pop_next_bytes_val_to_pyobject(decoder);
712+
break;
677713
case AWS_CBOR_TYPE_TEXT:
678-
return s_cbor_decoder_pop_next_text_val_to_pyobject(decoder);
714+
result = s_cbor_decoder_pop_next_text_val_to_pyobject(decoder);
715+
break;
679716
case AWS_CBOR_TYPE_BOOL:
680-
return s_cbor_decoder_pop_next_boolean_val_to_pyobject(decoder);
717+
result = s_cbor_decoder_pop_next_boolean_val_to_pyobject(decoder);
718+
break;
681719
case AWS_CBOR_TYPE_NULL:
682720
/* fall through */
683721
case AWS_CBOR_TYPE_UNDEFINED:
684722
aws_cbor_decoder_consume_next_single_element(decoder);
685-
Py_RETURN_NONE;
723+
Py_INCREF(Py_None);
724+
result = Py_None;
725+
break;
686726
case AWS_CBOR_TYPE_MAP_START:
687727
/* fall through */
688728
case AWS_CBOR_TYPE_INDEF_MAP_START:
689-
return s_cbor_decoder_pop_next_data_item_to_py_dict(binding);
729+
result = s_cbor_decoder_pop_next_data_item_to_py_dict(binding);
730+
break;
690731
case AWS_CBOR_TYPE_ARRAY_START:
691732
/* fall through */
692733
case AWS_CBOR_TYPE_INDEF_ARRAY_START:
693-
return s_cbor_decoder_pop_next_data_item_to_py_list(binding);
734+
result = s_cbor_decoder_pop_next_data_item_to_py_list(binding);
735+
break;
694736
case AWS_CBOR_TYPE_INDEF_BYTES_START:
695-
return s_cbor_decoder_pop_next_inf_bytes_to_py_bytes(decoder);
737+
result = s_cbor_decoder_pop_next_inf_bytes_to_py_bytes(decoder);
738+
break;
696739
case AWS_CBOR_TYPE_INDEF_TEXT_START:
697-
return s_cbor_decoder_pop_next_inf_string_to_py_str(decoder);
740+
result = s_cbor_decoder_pop_next_inf_string_to_py_str(decoder);
741+
break;
698742
case AWS_CBOR_TYPE_TAG:
699-
return s_cbor_decoder_pop_next_tag_to_pyobject(binding);
743+
result = s_cbor_decoder_pop_next_tag_to_pyobject(binding);
744+
break;
700745
default:
701746
aws_raise_error(AWS_ERROR_CBOR_UNEXPECTED_TYPE);
702-
return PyErr_AwsLastError();
747+
result = PyErr_AwsLastError();
748+
break;
703749
}
704-
return NULL;
750+
done:
751+
--binding->current_depth;
752+
return result;
705753
}
706754

707755
/*********************************** BINDINGS ***********************************************/

0 commit comments

Comments
 (0)