|
6 | 6 |
|
7 | 7 | #include <aws/common/cbor.h> |
8 | 8 |
|
| 9 | +/* Maximum nesting depth for recursive CBOR decoding to prevent stack exhaustion */ |
| 10 | +static const size_t s_cbor_max_decode_depth = 128; |
| 11 | + |
9 | 12 | /******************************************************************************* |
10 | 13 | * ENCODE |
11 | 14 | ******************************************************************************/ |
@@ -185,30 +188,49 @@ static PyObject *s_cbor_encoder_write_pyobject(struct aws_cbor_encoder *encoder, |
185 | 188 | /** |
186 | 189 | * TODO: timestamp <-> datetime?? Decimal fraction <-> decimal?? |
187 | 190 | */ |
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)) { |
197 | 218 | /* 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)) { |
200 | 221 | /* 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)) { |
203 | 224 | /* 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); |
207 | 226 | } 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); |
209 | 229 | } |
210 | 230 |
|
211 | | - Py_RETURN_NONE; |
| 231 | + /* Release the type reference */ |
| 232 | + Py_DECREF(type); |
| 233 | + return result; |
212 | 234 | } |
213 | 235 |
|
214 | 236 | /*********************************** BINDINGS ***********************************************/ |
@@ -290,6 +312,9 @@ struct decoder_binding { |
290 | 312 |
|
291 | 313 | /* Encoder has simple lifetime, no async/multi-thread allowed. */ |
292 | 314 | PyObject *self_py; |
| 315 | + |
| 316 | + /* Current recursion depth for pop_next_data_item */ |
| 317 | + size_t current_depth; |
293 | 318 | }; |
294 | 319 |
|
295 | 320 | 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 |
646 | 671 | * Generic helper to convert a cbor encoded data to PyObject |
647 | 672 | */ |
648 | 673 | 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; |
649 | 681 | struct aws_cbor_decoder *decoder = binding->native; |
650 | 682 | enum aws_cbor_type out_type = AWS_CBOR_TYPE_UNKNOWN; |
651 | 683 | if (aws_cbor_decoder_peek_type(decoder, &out_type)) { |
652 | | - return PyErr_AwsLastError(); |
| 684 | + result = PyErr_AwsLastError(); |
| 685 | + goto done; |
653 | 686 | } |
654 | 687 | switch (out_type) { |
655 | 688 | 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; |
657 | 691 | case AWS_CBOR_TYPE_NEGINT: { |
658 | 692 | /* The value from native code is -1 - val. */ |
659 | 693 | PyObject *minus_one = PyLong_FromLong(-1); |
660 | 694 | if (!minus_one) { |
661 | | - return NULL; |
| 695 | + break; |
662 | 696 | } |
663 | 697 | PyObject *val = s_cbor_decoder_pop_next_negative_int_val_to_pyobject(decoder); |
664 | 698 | if (!val) { |
665 | 699 | Py_DECREF(minus_one); |
666 | | - return NULL; |
| 700 | + break; |
667 | 701 | } |
668 | | - PyObject *ret_val = PyNumber_Subtract(minus_one, val); |
| 702 | + result = PyNumber_Subtract(minus_one, val); |
669 | 703 | Py_DECREF(minus_one); |
670 | 704 | Py_DECREF(val); |
671 | | - return ret_val; |
| 705 | + break; |
672 | 706 | } |
673 | 707 | 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; |
675 | 710 | 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; |
677 | 713 | 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; |
679 | 716 | 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; |
681 | 719 | case AWS_CBOR_TYPE_NULL: |
682 | 720 | /* fall through */ |
683 | 721 | case AWS_CBOR_TYPE_UNDEFINED: |
684 | 722 | aws_cbor_decoder_consume_next_single_element(decoder); |
685 | | - Py_RETURN_NONE; |
| 723 | + Py_INCREF(Py_None); |
| 724 | + result = Py_None; |
| 725 | + break; |
686 | 726 | case AWS_CBOR_TYPE_MAP_START: |
687 | 727 | /* fall through */ |
688 | 728 | 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; |
690 | 731 | case AWS_CBOR_TYPE_ARRAY_START: |
691 | 732 | /* fall through */ |
692 | 733 | 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; |
694 | 736 | 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; |
696 | 739 | 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; |
698 | 742 | 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; |
700 | 745 | default: |
701 | 746 | aws_raise_error(AWS_ERROR_CBOR_UNEXPECTED_TYPE); |
702 | | - return PyErr_AwsLastError(); |
| 747 | + result = PyErr_AwsLastError(); |
| 748 | + break; |
703 | 749 | } |
704 | | - return NULL; |
| 750 | +done: |
| 751 | + --binding->current_depth; |
| 752 | + return result; |
705 | 753 | } |
706 | 754 |
|
707 | 755 | /*********************************** BINDINGS ***********************************************/ |
|
0 commit comments