Repository: agronholm/cbor2 (language: python)

Issue:
Negative CBORTag values are encoding without error in the C encoder
Example:

```python
>>> # Pure python module
>>> from cbor2.encoder import dumps
>>> from cbor2.types import CBORTag
>>> dumps(CBORTag(-1, 'val'))
Traceback (most recent call last):
  ...
struct.error: ubyte format requires 0 <= number <= 255

>>> # c module
>>> import cbor2
>>> cbor2.dumps(cbor2.CBORTag(-1, 'val'))
b'\xdb\xff\xff\xff\xff\xff\xff\xff\xffcval'
```

CBORTag should raise a value error with a negative int


Discussion / hints:

Fix patch:
diff --git a/cbor2/types.py b/cbor2/types.py
index 04d1050b..b0ab530c 100644
--- a/cbor2/types.py
+++ b/cbor2/types.py
@@ -44,8 +44,8 @@ class CBORTag:
     __slots__ = 'tag', 'value'
 
     def __init__(self, tag, value):
-        if not isinstance(tag, int):
-            raise TypeError('CBORTag tags must be integer numbers')
+        if not isinstance(tag, int) or tag not in range(2**64):
+            raise TypeError('CBORTag tags must be positive integers less than 2**64')
         self.tag = tag
         self.value = value
 
diff --git a/source/tags.c b/source/tags.c
index b900e866..0885eb32 100644
--- a/source/tags.c
+++ b/source/tags.c
@@ -52,11 +52,25 @@ static int
 CBORTag_init(CBORTagObject *self, PyObject *args, PyObject *kwargs)
 {
     static char *keywords[] = {"tag", "value", NULL};
-    PyObject *tmp, *value = NULL;
+    PyObject *tmp, *value, *tmp_tag = NULL;
+    uint64_t tag = 0;
 
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|KO", keywords,
-                &self->tag, &value))
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OO", keywords,
+                &tmp_tag, &value))
         return -1;
+    // Raises an overflow error if it doesn't work
+    tag = PyLong_AsUnsignedLongLong(tmp_tag);
+
+    if (tag == (uint64_t)-1) {
+        if (PyErr_Occurred()){
+            if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
+                PyErr_Clear(); // clear the overflow error
+                PyErr_SetString(PyExc_TypeError, "CBORTag tags must be positive integers less than 2**64");
+            } // otherwise must be some other exception probably type err
+            return -1;
+        } // otherwise it's 2**64-1 which is fine :)
+    }
+    self->tag = tag;
 
     if (value) {
         tmp = self->value;
@@ -67,7 +81,7 @@ CBORTag_init(CBORTagObject *self, PyObject *args, PyObject *kwargs)
     return 0;
 }
 
-
+
 // Special methods ///////////////////////////////////////////////////////////
 
 static PyObject *


Test patch:
diff --git a/tests/test_encoder.py b/tests/test_encoder.py
index 3e9346b2..8c400009 100644
--- a/tests/test_encoder.py
+++ b/tests/test_encoder.py
@@ -520,3 +520,14 @@ def test_encode_stringrefs_dict(impl):
         'd81901' 'd81900'
     )
     assert impl.dumps(value, string_referencing=True, canonical=True) == expected
+
+
+@pytest.mark.parametrize('tag', [-1, 2**64, 'f'], ids=['too small', 'too large', 'wrong type'])
+def test_invalid_tag(impl, tag):
+    with pytest.raises(TypeError):
+        impl.dumps(impl.CBORTag(tag, 'value'))
+
+
+def test_largest_tag(impl):
+    expected = unhexlify('dbffffffffffffffff6176')
+    assert impl.dumps(impl.CBORTag(2**64-1, 'v')) == expected