|
46 | 46 | import os
|
47 | 47 | import pickle as py_pickle
|
48 | 48 | import pstats
|
| 49 | +import re |
49 | 50 | import signal
|
50 | 51 | import socket
|
51 | 52 | import struct
|
@@ -769,6 +770,45 @@ def find_class(self, module, func):
|
769 | 770 | _Unpickler = pickle.Unpickler
|
770 | 771 |
|
771 | 772 |
|
| 773 | +#: A list of compiled regex patterns which allow end-users to selectively opt |
| 774 | +#: into deserializing certain globals. |
| 775 | +_PICKLE_GLOBAL_WHITELIST_PATTERNS = None |
| 776 | +_PICKLE_GLOBAL_WHITELIST = None |
| 777 | + |
| 778 | + |
| 779 | +def set_pickle_whitelist(pattern_strings): |
| 780 | + """ |
| 781 | + Specify regex patterns that control allowable global unpickling functions. |
| 782 | +
|
| 783 | + `pattern_strings` is sequence of pattern strings that will be fed into |
| 784 | + `re.compile` and then used to authenticate pickle calls. In order for a |
| 785 | + non-trivially typed message to unpickle, one of these patterns must |
| 786 | + match against a complete [module].[function] string. |
| 787 | + """ |
| 788 | + if not isinstance(pattern_strings, (tuple, list, set)): |
| 789 | + pattern_strings = (pattern_strings,) |
| 790 | + |
| 791 | + global _PICKLE_GLOBAL_WHITELIST |
| 792 | + global _PICKLE_GLOBAL_WHITELIST_PATTERNS |
| 793 | + |
| 794 | + _PICKLE_GLOBAL_WHITELIST = pattern_strings |
| 795 | + _PICKLE_GLOBAL_WHITELIST_PATTERNS = [] |
| 796 | + |
| 797 | + for patt_str in pattern_strings: |
| 798 | + if not patt_str.endswith('$'): |
| 799 | + patt_str += '$' |
| 800 | + _PICKLE_GLOBAL_WHITELIST_PATTERNS.append(re.compile(patt_str)) |
| 801 | + |
| 802 | + |
| 803 | +def _test_pickle_whitelist_accept(module, func): |
| 804 | + if not _PICKLE_GLOBAL_WHITELIST_PATTERNS: |
| 805 | + return False |
| 806 | + |
| 807 | + test_str = "{}.{}".format(module, func) |
| 808 | + return bool(any( |
| 809 | + patt.match(test_str) for patt in _PICKLE_GLOBAL_WHITELIST_PATTERNS)) |
| 810 | + |
| 811 | + |
772 | 812 | class Message(object):
|
773 | 813 | """
|
774 | 814 | Messages are the fundamental unit of communication, comprising fields from
|
@@ -868,7 +908,14 @@ def _find_global(self, module, func):
|
868 | 908 | return BytesType
|
869 | 909 | elif SimpleNamespace and module == 'types' and func == 'SimpleNamespace':
|
870 | 910 | return SimpleNamespace
|
871 |
| - raise StreamError('cannot unpickle %r/%r', module, func) |
| 911 | + elif _test_pickle_whitelist_accept(module, func): |
| 912 | + try: |
| 913 | + return getattr(import_module(module), func) |
| 914 | + except AttributeError as e: |
| 915 | + LOG.info(str(e)) |
| 916 | + raise StreamError( |
| 917 | + 'cannot unpickle %r/%r - try using `set_pickle_whitelist`', |
| 918 | + module, func) |
872 | 919 |
|
873 | 920 | @property
|
874 | 921 | def is_dead(self):
|
@@ -968,8 +1015,8 @@ def unpickle(self, throw=True, throw_dead=True):
|
968 | 1015 | # Must occur off the broker thread.
|
969 | 1016 | try:
|
970 | 1017 | obj = unpickler.load()
|
971 |
| - except: |
972 |
| - LOG.error('raw pickle was: %r', self.data) |
| 1018 | + except Exception as e: |
| 1019 | + LOG.error('raw pickle was: %r (exc: %r)', self.data, e) |
973 | 1020 | raise
|
974 | 1021 | self._unpickled = obj
|
975 | 1022 | except (TypeError, ValueError):
|
@@ -3845,6 +3892,9 @@ def _setup_master(self):
|
3845 | 3892 | Router.max_message_size = self.config['max_message_size']
|
3846 | 3893 | if self.config['profiling']:
|
3847 | 3894 | enable_profiling()
|
| 3895 | + if self.config['pickle_whitelist_patterns']: |
| 3896 | + set_pickle_whitelist(self.config['pickle_whitelist_patterns']) |
| 3897 | + |
3848 | 3898 | self.broker = Broker(activate_compat=False)
|
3849 | 3899 | self.router = Router(self.broker)
|
3850 | 3900 | self.router.debug = self.config.get('debug', False)
|
|
0 commit comments