Repository: kjd/idna

Issue:
[Uncaught exception] UnicodeError (punycode) when calling decode with arbitrary data
Hey,

I just found another case making the program crash where `idna.decode()` raise a `UnicodeError` (due to punycode).

In the same way as #108, this could be problematic if users of the library are dealing with untrusted data since this issue will lead to a DoS. This should be detected and an `idna.IDNAError` should be triggered as detailed in the Readme [Exception](https://github.com/kjd/idna#exceptions) section.

# Reproduction

``` python
import idna

idna.decode(b'A.A.0.a.a.A.0.a.A.A.0.a.A.0A.2.a.A.A.0.a.A.0.A.a.A0.a.a.A.0.a.fB.A.A.a.A.A.B.A.A.a.A.A.B.A.A.a.A.A.0.a.A.a.a.A.A.0.a.A.0.A.a.A0.a.a.A.0.a.fB.A.A.a.A.A.B.0A.A.a.A.A.B.A.A.a.A.A.a.A.A.B.A.A.a.A.0.a.B.A.A.a.A.B.A.a.A.A.5.a.A.0.a.Ba.A.B.A.A.a.A.0.a.Xn--B.A.A.A.a')
```

# Traceback

``` sh
python3 replay_crash2.py
Traceback (most recent call last):
  File "/usr/lib/python3.8/encodings/punycode.py", line 134, in decode_generalized_number
    char = ord(extended[extpos])
IndexError: string index out of range

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.8/encodings/punycode.py", line 207, in decode
    res = punycode_decode(input, errors)
  File "/usr/lib/python3.8/encodings/punycode.py", line 194, in punycode_decode
    return insertion_sort(base, extended, errors)
  File "/usr/lib/python3.8/encodings/punycode.py", line 164, in insertion_sort
    newpos, delta = decode_generalized_number(extended, extpos,
  File "/usr/lib/python3.8/encodings/punycode.py", line 137, in decode_generalized_number
    raise UnicodeError("incomplete punicode string")
UnicodeError: incomplete punicode string

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "replay_crash2.py", line 3, in <module>
    idna.decode(b'A.A.0.a.a.A.0.a.A.A.0.a.A.0A.2.a.A.A.0.a.A.0.A.a.A0.a.a.A.0.a.fB.A.A.a.A.A.B.A.A.a.A.A.B.A.A.a.A.A.0.a.A.a.a.A.A.0.a.A.0.A.a.A0.a.a.A.0.a.fB.A.A.a.A.A.B.0A.A.a.A.A.B.A.A.a.A.A.a.A.A.B.A.A.a.A.0.a.B.A.A.a.A.B.A.a.A.A.5.a.A.0.a.Ba.A.B.A.A.a.A.0.a.Xn--B.A.A.A.a')
  File "/home/scop/.local/lib/python3.8/site-packages/idna/core.py", line 402, in decode
    s = ulabel(label)
  File "/home/scop/.local/lib/python3.8/site-packages/idna/core.py", line 315, in ulabel
    label = label_bytes.decode('punycode')
UnicodeError: decoding with 'punycode' codec failed (UnicodeError: incomplete punicode string)

```

Happy to help if you have any questions ;)

Discussion:

Patch:
diff --git a/idna/core.py b/idna/core.py
index 871ebd3..c66fb9f 100644
--- a/idna/core.py
+++ b/idna/core.py
@@ -312,7 +312,10 @@ def ulabel(label):
         check_label(label_bytes)
         return label_bytes.decode('ascii')
 
-    label = label_bytes.decode('punycode')
+    try:
+        label = label_bytes.decode('punycode')
+    except UnicodeError:
+        raise IDNAError('Invalid A-label')
     check_label(label)
     return label
 


Test patch:
diff --git a/tests/test_idna.py b/tests/test_idna.py
index 7ac6057..f0c74d2 100755
--- a/tests/test_idna.py
+++ b/tests/test_idna.py
@@ -260,6 +260,7 @@ class IDNATests(unittest.TestCase):
         self.assertRaises(idna.IDNAError, idna.decode, 'xn---------90gglbagaac.aa')
         self.assertRaises(idna.IDNAError, idna.decode, 'xn--')
         self.assertRaises(idna.IDNAError, idna.decode, b'\x8d\xd2')
+        self.assertRaises(idna.IDNAError, idna.decode, b'A.A.0.a.a.A.0.a.A.A.0.a.A.0A.2.a.A.A.0.a.A.0.A.a.A0.a.a.A.0.a.fB.A.A.a.A.A.B.A.A.a.A.A.B.A.A.a.A.A.0.a.A.a.a.A.A.0.a.A.0.A.a.A0.a.a.A.0.a.fB.A.A.a.A.A.B.0A.A.a.A.A.B.A.A.a.A.A.a.A.A.B.A.A.a.A.0.a.B.A.A.a.A.B.A.a.A.A.5.a.A.0.a.Ba.A.B.A.A.a.A.0.a.Xn--B.A.A.A.a')
 
 if __name__ == '__main__':
     unittest.main()