summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamian Johnson <atagar@torproject.org>2019-05-08 11:05:15 -0700
committerDamian Johnson <atagar@torproject.org>2019-05-08 11:09:51 -0700
commit87cea952a27b024981e70efacc09253e22a7658f (patch)
tree854b68f578a6492af26847203f069defaecac2d6
parent80647b6a64551ba725da8433da90e263741a707a (diff)
Fix ExitPolicy thread safety bug
Interesting catch from juga! I don't have a repro for the stacktrace they cite, but the only way our input_rules could potentially be None is if two threads attempt to parse the input_rules at the same time. Creating a local reference for the input_rules to avoid this while remaining lock-free... https://trac.torproject.org/projects/tor/ticket/29899
-rw-r--r--docs/change_log.rst1
-rw-r--r--stem/exit_policy.py13
2 files changed, 10 insertions, 4 deletions
diff --git a/docs/change_log.rst b/docs/change_log.rst
index 44153d55..cdb9e450 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -51,6 +51,7 @@ The following are only available within Stem's `git repository
* Added :func:`~stem.control.Controller.get_uptime` method to the :class:`~stem.control.Controller`
* Controller events could fail to be delivered in a timely fashion (:trac:`27173`)
* Adjusted :func:`~stem.control.Controller.get_microdescriptors` fallback to also use '.new' cache files (:trac:`28508`)
+ * ExitPolicies could raise TypeError when read concurrently (:trac:`29899`)
* **DORMANT** and **ACTIVE** :data:`~stem.Signal` (:spec:`4421149`)
* **Descriptors**
diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index f02ec146..5bdadf9b 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -452,14 +452,19 @@ class ExitPolicy(object):
return ExitPolicy(*[rule for rule in self._get_rules() if not rule.is_default()])
def _get_rules(self):
- if self._rules is None:
+ # Local reference to our input_rules so this can be lock free. Otherwise
+ # another thread might unset our input_rules while processing them.
+
+ input_rules = self._input_rules
+
+ if self._rules is None and input_rules is not None:
rules = []
is_all_accept, is_all_reject = True, True
- if isinstance(self._input_rules, bytes):
- decompressed_rules = zlib.decompress(self._input_rules).split(b',')
+ if isinstance(input_rules, bytes):
+ decompressed_rules = zlib.decompress(input_rules).split(b',')
else:
- decompressed_rules = self._input_rules
+ decompressed_rules = input_rules
for rule in decompressed_rules:
if isinstance(rule, bytes):