Selaa lähdekoodia

feat(api): limit total length of RRset to ~64KB

Peter Thomassen 5 vuotta sitten
vanhempi
commit
cf10527068
2 muutettua tiedostoa jossa 29 lisäystä ja 0 poistoa
  1. 20 0
      api/desecapi/serializers.py
  2. 9 0
      api/desecapi/tests/test_rrsets.py

+ 20 - 0
api/desecapi/serializers.py

@@ -256,6 +256,26 @@ class RRsetSerializer(ConditionalExistenceModelSerializer):
             raise serializers.ValidationError('This field must not be empty when using POST.')
         return value
 
+    def validate(self, attrs):
+        if 'records' in attrs:
+            # There is a 12 byte baseline requirement per record, c.f.
+            # https://lists.isc.org/pipermail/bind-users/2008-April/070137.html
+            # There also seems to be a 32 byte (?) baseline requirement per RRset, plus the qname length, see
+            # https://lists.isc.org/pipermail/bind-users/2008-April/070148.html
+            # The binary length of the record depends actually on the type, but it's never longer than vanilla len()
+            qname = models.RRset.construct_name(attrs.get('subname', ''), self.domain.name)
+            conservative_total_length = 32 + len(qname) + sum(12 + len(rr['content']) for rr in attrs['records'])
+
+            # Add some leeway for RRSIG record (really ~110 bytes) and other data we have not thought of
+            conservative_total_length += 256
+
+            excess_length = conservative_total_length - 65535  # max response size
+            if excess_length > 0:
+                raise serializers.ValidationError(f'Total length of RRset exceeds limit by {excess_length} bytes.',
+                                                  code='max_length')
+
+        return attrs
+
     def exists(self, arg):
         if isinstance(arg, models.RRset):
             return arg.records.exists()

+ 9 - 0
api/desecapi/tests/test_rrsets.py

@@ -1,3 +1,4 @@
+from ipaddress import IPv4Network
 import re
 
 from django.conf import settings
@@ -209,6 +210,14 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
         self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
         self.assertIn('Ensure this field has no more than 500 characters.', str(response.data))
 
+    def test_create_my_rr_sets_too_large_rrset(self):
+        network = IPv4Network('127.0.0.0/20')  # size: 4096 IP addresses
+        data = {'records': [str(ip) for ip in network], 'ttl': 3600, 'type': 'A', 'subname': 'name'}
+        response = self.client.post_rr_set(self.my_empty_domain.name, **data)
+        self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
+        excess_length = 28743 + len(self.my_empty_domain.name)
+        self.assertIn(f'Total length of RRset exceeds limit by {excess_length} bytes.', str(response.data))
+
     def test_create_my_rr_sets_twice(self):
         data = {'records': ['1.2.3.4'], 'ttl': 3660, 'type': 'A'}
         with self.assertPdnsRequests(self.requests_desec_rr_sets_update(self.my_empty_domain.name)):