File
Blob: src/pyodide/internal/topLevelEntropy/entropy_patches.py
| 1 | """ |
| 2 | Handle the top level getentropy() mess: |
| 3 | |
| 4 | The C stdlib function getentropy() `getentropy()` calls |
| 5 | `crpyto.getRandomValues()` but this throws an error at top level which causes a |
| 6 | fatal error. |
| 7 | |
| 8 | Goals: |
| 9 | |
| 10 | 1. Avoid top-level calls to the C stdlib function getentropy(), these fatally |
| 11 | fail. Patch these to raise Python errors instead. |
| 12 | 2. Allow top level import of `random` and `numpy.random` modules. These seed |
| 13 | themselves with the functions that we patched in step 1, we temporarily |
| 14 | replace the `getentropy()` calls with no-ops to let them through. |
| 15 | 3. Install wrapper modules at top level that only allow calls to a whitelisted |
| 16 | set of functions from `random` and `numpy.random` that don't use the bad |
| 17 | seeds that came from step 2. |
| 18 | 4. Put it all back. |
| 19 | 5. Reseed the rng before entering the request scope for the first time. |
| 20 | |
| 21 | Steps 1, part of 4, and 5 are handled here, steps 2, 3, and part of 4 are |
| 22 | handled in _cloudflare_random_overlays. |
| 23 | """ |
| 24 | |
| 25 | import os |
| 26 | from functools import wraps |
| 27 | |
| 28 | # Import entropy_import_context for side effects |
| 29 | from . import entropy_import_context # noqa: F401 |
| 30 | from .allow_entropy import _set_in_request_context, raise_unless_entropy_allowed |
| 31 | from .import_patch_manager import ( |
| 32 | after_snapshot_handlers, |
| 33 | before_first_request_handlers, |
| 34 | install_import_patch_manager, |
| 35 | remove_import_patch_manager, |
| 36 | ) |
| 37 | |
| 38 | # Prevent calls to getentropy(). The intended way for `getentropy()` to fail is |
| 39 | # to set an EIO error, which turns into a Python OSError, so we raise this same |
| 40 | # error so that if we patch `getentropy` from the Emscripten C stdlib we can |
| 41 | # remove these patches without changing the behavior. |
| 42 | |
| 43 | |
| 44 | orig_urandom = os.urandom |
| 45 | |
| 46 | |
| 47 | @wraps(orig_urandom) |
| 48 | def patch_urandom(*args): |
| 49 | raise_unless_entropy_allowed() |
| 50 | return orig_urandom(*args) |
| 51 | |
| 52 | |
| 53 | def disable_urandom(): |
| 54 | """ |
| 55 | Python os.urandom() calls C getentropy() which calls JS |
| 56 | crypto.getRandomValues() which throws at top level, fatally crashing the |
| 57 | interpreter. |
| 58 | |
| 59 | TODO: Patch Emscripten's getentropy() to return EIO if |
| 60 | `crypto.getRandomValues()` throws. Then we can remove this. |
| 61 | """ |
| 62 | os.urandom = patch_urandom |
| 63 | |
| 64 | |
| 65 | def restore_urandom(): |
| 66 | os.urandom = orig_urandom |
| 67 | |
| 68 | |
| 69 | def before_top_level(): |
| 70 | disable_urandom() |
| 71 | install_import_patch_manager() |
| 72 | |
| 73 | |
| 74 | def after_snapshot(): |
| 75 | remove_import_patch_manager() |
| 76 | for cb in after_snapshot_handlers: |
| 77 | cb() |
| 78 | |
| 79 | |
| 80 | def before_first_request(): |
| 81 | _set_in_request_context() |
| 82 | restore_urandom() |
| 83 | remove_import_patch_manager() |
| 84 | for cb in before_first_request_handlers: |
| 85 | cb() |