[tor-commits] [stem/master] Descriptors couldn't be pickled

atagar at torproject.org atagar at torproject.org
Mon May 18 19:24:05 UTC 2015


commit cbce47082a9ffb1cab375a6e8307e0eec60775d0
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun May 17 13:41:02 2015 -0700

    Descriptors couldn't be pickled
    
    When being unpickled there's a time when descriptors lack their attributes.
    This caused an infinite loop where...
    
      1. Try to get an attribute.
      2. We call the descriptor's __getattr__()
      3. The __getattr__ tries to use _lazy_loading which doesn't exist.
      4. We call the descriptor's __getattr__()
      ... loop 2-5 repeatedly...
    
    Adding a hasattr() check to break the cycle, and a unit test for pickleability.
    
    This was caught by Tom on...
    
      https://trac.torproject.org/projects/tor/ticket/16054
---
 stem/descriptor/__init__.py               |    2 +-
 test/unit/descriptor/server_descriptor.py |   48 +++++++++++++++++++----------
 2 files changed, 33 insertions(+), 17 deletions(-)

diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index 1ebe578..5b07bc4 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -596,7 +596,7 @@ class Descriptor(object):
   def __getattr__(self, name):
     # If attribute isn't already present we might be lazy loading it...
 
-    if self._lazy_loading and name in self.ATTRIBUTES:
+    if hasattr(self, '_lazy_loading') and self._lazy_loading and name in self.ATTRIBUTES:
       default, parsing_function = self.ATTRIBUTES[name]
 
       try:
diff --git a/test/unit/descriptor/server_descriptor.py b/test/unit/descriptor/server_descriptor.py
index a0c328b..226cdcd 100644
--- a/test/unit/descriptor/server_descriptor.py
+++ b/test/unit/descriptor/server_descriptor.py
@@ -4,6 +4,7 @@ Unit tests for stem.descriptor.server_descriptor.
 
 import datetime
 import io
+import pickle
 import tarfile
 import unittest
 
@@ -70,8 +71,6 @@ class TestServerDescriptor(unittest.TestCase):
     Parses and checks our results against a server descriptor from metrics.
     """
 
-    descriptor_file = open(get_resource('example_descriptor'), 'rb')
-
     expected_family = set([
       '$0CE3CFB1E9CC47B63EA8869813BF6FAB7D4540C1',
       '$1FD187E8F69A9B74C9202DC16A25B9E7744AB9F6',
@@ -101,7 +100,9 @@ dskLSPz8beUW7bzwDjR6EVNGpyoZde83Ejvau+5F2c6cGnlu91fiZN3suE88iE6e
 Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
 -----END SIGNATURE-----"""
 
-    desc = next(stem.descriptor.parse_file(descriptor_file, 'server-descriptor 1.0'))
+    with open(get_resource('example_descriptor'), 'rb') as descriptor_file:
+      desc = next(stem.descriptor.parse_file(descriptor_file, 'server-descriptor 1.0'))
+
     self.assertEqual('caerSidi', desc.nickname)
     self.assertEqual('A7569A83B5706AB1B1A9CB52EFF7D2D32E4553EB', desc.fingerprint)
     self.assertEqual('71.35.133.197', desc.address)
@@ -153,9 +154,9 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     Parses a relay server descriptor from 2005.
     """
 
-    descriptor_file = open(get_resource('old_descriptor'), 'rb')
+    with open(get_resource('old_descriptor'), 'rb') as descriptor_file:
+      desc = next(stem.descriptor.parse_file(descriptor_file, 'server-descriptor 1.0', validate = True))
 
-    desc = next(stem.descriptor.parse_file(descriptor_file, 'server-descriptor 1.0', validate = True))
     self.assertEqual('krypton', desc.nickname)
     self.assertEqual('3E2F63E2356F52318B536A12B6445373808A5D6C', desc.fingerprint)
     self.assertEqual('212.37.39.59', desc.address)
@@ -199,11 +200,9 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     Parses a descriptor with non-ascii content.
     """
 
-    descriptor_file = open(get_resource('non-ascii_descriptor'), 'rb')
-
-    expected_contact = b'1024D/04D2E818 L\xc3\xa9na\xc3\xafc Huard <lenaic dot huard AT laposte dot net>'
+    with open(get_resource('non-ascii_descriptor'), 'rb') as descriptor_file:
+      desc = next(stem.descriptor.parse_file(descriptor_file, 'server-descriptor 1.0', validate = True))
 
-    desc = next(stem.descriptor.parse_file(descriptor_file, 'server-descriptor 1.0', validate = True))
     self.assertEqual('Coruscant', desc.nickname)
     self.assertEqual('0B9821545C48E496AEED9ECC0DB506C49FF8158D', desc.fingerprint)
     self.assertEqual('88.182.161.122', desc.address)
@@ -215,7 +214,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     self.assertEqual('Linux', desc.operating_system)
     self.assertEqual(259738, desc.uptime)
     self.assertEqual(datetime.datetime(2013, 5, 18, 11, 16, 19), desc.published)
-    self.assertEqual(expected_contact, desc.contact)
+    self.assertEqual(b'1024D/04D2E818 L\xc3\xa9na\xc3\xafc Huard <lenaic dot huard AT laposte dot net>', desc.contact)
     self.assertEqual(['1', '2'], desc.link_protocols)
     self.assertEqual(['1'], desc.circuit_protocols)
     self.assertEqual(False, desc.hibernating)
@@ -243,8 +242,8 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     returns ('\r' entries).
     """
 
-    descriptor_file = open(get_resource('cr_in_contact_line'), 'rb')
-    desc = next(stem.descriptor.parse_file(descriptor_file, 'server-descriptor 1.0', validate = True))
+    with open(get_resource('cr_in_contact_line'), 'rb') as descriptor_file:
+      desc = next(stem.descriptor.parse_file(descriptor_file, 'server-descriptor 1.0', validate = True))
 
     self.assertEqual('pogonip', desc.nickname)
     self.assertEqual('6DABD62BC65D4E6FE620293157FC76968DAB9C9B', desc.fingerprint)
@@ -265,8 +264,8 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     where we shouldn't be.
     """
 
-    descriptor_file = open(get_resource('negative_uptime'), 'rb')
-    desc = next(stem.descriptor.parse_file(descriptor_file, 'server-descriptor 1.0', validate = True))
+    with open(get_resource('negative_uptime'), 'rb') as descriptor_file:
+      desc = next(stem.descriptor.parse_file(descriptor_file, 'server-descriptor 1.0', validate = True))
 
     self.assertEqual('TipTor', desc.nickname)
     self.assertEqual('137962D4931DBF08A24E843288B8A155D6D2AEDD', desc.fingerprint)
@@ -283,9 +282,9 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     Parses a bridge descriptor.
     """
 
-    descriptor_file = open(get_resource('bridge_descriptor'), 'rb')
+    with open(get_resource('bridge_descriptor'), 'rb') as descriptor_file:
+      desc = next(stem.descriptor.parse_file(descriptor_file, 'bridge-server-descriptor 1.0', validate = True))
 
-    desc = next(stem.descriptor.parse_file(descriptor_file, 'bridge-server-descriptor 1.0', validate = True))
     self.assertEqual('Unnamed', desc.nickname)
     self.assertEqual('4ED573582B16ACDAF6E42AA044A038F83A7F6333', desc.fingerprint)
     self.assertEqual('10.18.111.71', desc.address)
@@ -719,3 +718,20 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
     else:
       # check a default attribute
       self.assertEqual('caerSidi', desc.nickname)
+
+  def test_pickleability(self):
+    """
+    Checks that we can unpickle lazy loaded server descriptors.
+    """
+
+    with open(get_resource('example_descriptor'), 'rb') as descriptor_file:
+      desc = next(stem.descriptor.parse_file(descriptor_file, 'server-descriptor 1.0'))
+
+      encoded_desc = pickle.dumps(desc)
+      restored_desc = pickle.loads(encoded_desc)
+
+      self.assertEqual('caerSidi', restored_desc.nickname)
+      self.assertEqual('A7569A83B5706AB1B1A9CB52EFF7D2D32E4553EB', restored_desc.fingerprint)
+      self.assertEqual('71.35.133.197', restored_desc.address)
+      self.assertEqual(9001, restored_desc.or_port)
+      self.assertEqual(None, restored_desc.socks_port)





More information about the tor-commits mailing list