Skip to content
File

Blob: src/pyodide/internal/topLevelEntropy/entropy_import_context.py

python152 lines
1"""
2Define import patches that are needed to fix top level entropy calls in the standard library:
3
41. random: Give it poison seed. Install overlays to crash if called. Check that the seed state has
5 not advanced by asserting that the value gotten out of the rng after the snapshot restored
6 matches the value that would be generated at poison seed state. Then remove overlays.
7
82. tempfile: Use a deterministic sequence of names when used at top level.
9
103. multiprocessing: Just give it poision entropy, the value is ordinarily used for IPC which can't
11 happen because we have no processes.
12
13Package import context is defined in entropy_import_context_packages.py
14"""
15 
16from contextlib import contextmanager
17from functools import wraps
18 
19try:
20 from . import (
21 entropy_import_context_packages, # noqa: F401 Imported for side effects
22 )
23except ImportError:
24 # When the python_process_pth_files compatibility flag is enabled, package entropy import
25 # context is provided by workers-runtime-sdk via .pth files instead of being bundled here.
26 pass
27from .allow_entropy import (
28 allow_bad_entropy_calls,
29 get_bad_entropy_flag,
30 raise_unless_entropy_allowed,
31)
32from .import_patch_manager import (
33 block_calls,
34 register_after_snapshot,
35 register_before_first_request,
36 register_exec_patch,
37)
38 
39# Exported for snapshot backwards compatibility
40__all__ = ["get_bad_entropy_flag"]
41 
42# Builtin modules:
43# random, tempfile, and multiprocessing
44 
45RANDOM_STATE = None
46 
47 
48@register_exec_patch("random")
49@contextmanager
50def random_exec(random):
51 """Importing random calls getentropy() 10 times it seems"""
52 with allow_bad_entropy_calls(10):
53 yield
54 
55 # Block calls to functions that use the bad random seed we produced from the
56 # ten getentropy() calls. Instantiating Random with a given seed is fine,
57 # instantiating it without a seed will call getentropy() and fail.
58 # Instantiating SystemRandom is fine, calling its methods will call
59 # getentropy() and fail.
60 global RANDOM_STATE
61 RANDOM_STATE = random.getstate()
62 block_calls(random, allowlist=("Random", "SystemRandom"))
63 
64 
65@register_after_snapshot("random")
66def random_after_snapshot(random):
67 # Check that random seed hasn't been advanced somehow while executing top level scope
68 r1 = random.random()
69 random.setstate(RANDOM_STATE)
70 r2 = random.random()
71 if r1 != r2:
72 raise RuntimeError("random seed in bad state")
73 
74 
75@register_before_first_request("random")
76def random_before_first_request(random):
77 random.seed()
78 
79 
80orig_Random_seed = None
81 
82 
83@register_exec_patch("_random")
84@contextmanager
85def _random_exec(_random):
86 yield
87 global orig_Random_seed
88 orig_Random_seed = _random.Random.seed
89 
90 @wraps(orig_Random_seed)
91 def patched_seed(self, val):
92 """
93 Random.seed calls _PyOs_URandom which will fatally fail in top level.
94 Prevent this by raising a RuntimeError instead.
95 """
96 if val is None:
97 raise_unless_entropy_allowed()
98 return orig_Random_seed(self, val)
99 
100 _random.Random.seed = patched_seed
101 
102 
103@register_before_first_request("_random")
104def _random_before_first_request(_random):
105 _random.Random.seed = orig_Random_seed
106 
107 
108class DeterministicRandomNameSequence:
109 characters = "abcdefghijklmnopqrstuvwxyz0123456789_"
110 
111 def __init__(self):
112 self.index = 0
113 
114 def __iter__(self):
115 return self
116 
117 def index_to_chars(self):
118 base = len(self.characters)
119 idx = self.index
120 s = []
121 for _ in range(8):
122 s.append(self.characters[idx % base])
123 idx //= base
124 return "".join(s)
125 
126 def __next__(self):
127 self.index += 1
128 return self.index_to_chars()
129 
130 
131@register_exec_patch("tempfile")
132@contextmanager
133def tempfile_context(module):
134 yield
135 module._orig_RandomNameSequence = module._RandomNameSequence
136 module._RandomNameSequence = DeterministicRandomNameSequence
137 
138 
139@register_before_first_request("tempfile")
140def tempfile_restore_random_name_sequence(tempfile):
141 tempfile._RandomNameSequence = tempfile._orig_RandomNameSequence
142 del tempfile._orig_RandomNameSequence
143 
144 
145@register_exec_patch("multiprocessing.process")
146@contextmanager
147def multiprocessing_process_context(module):
148 # multiprocessing.process calls os.urandom() on import. It's harmless b/c multiprocessing is
149 # useless.
150 with allow_bad_entropy_calls(1):
151 yield