GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — `hashlib` BLAKE2 `.copy()` crashes with `Py_UNREACHABLE` in `py_blake2_clear` when the copy's allocation fails · Issue #152851 · python/cpython

hashlib BLAKE2 .copy() crashes with Py_UNREACHABLE in py_blake2_clear when the copy's allocation fails #152851

New issue
Copy link
New issue
Copy link
Closed
Closed
hashlib BLAKE2 .copy() crashes with Py_UNREACHABLE in py_blake2_clear when the copy's allocation fails#152851
Copy link
Labels
extension-modulesC modules in the Modules dirC modules in the Modules dirtype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump

Description

@devdanzin
devdanzin
opened on Jul 2, 2026
Issue body actions

Crash report

What happened?

_blake2.blake2b/_blake2.blake2s carry an impl enum that py_blake2_clear (the tp_clear/dealloc path) switches on, with a Py_UNREACHABLE() default. The .copy() method allocates a new object whose impl is left uninitialized, then performs the (fallible) HACL* state allocation; if that allocation fails, the error path deallocates the half-built object without ever setting impl, so py_blake2_clear reads garbage and hits Py_UNREACHABLE():

Fatal Python error: py_blake2_clear: We've reached an unreachable state. ...

On a debug build this is a deterministic fatal error; on a release build Py_UNREACHABLE() is __builtin_unreachable(), so a garbage impl is undefined behaviour (I observed a SIGSEGV on a free-threaded release build; and if the garbage value happens to match a valid enum case, py_blake2_clear calls Hacl_Hash_*_free() on an uninitialized state pointer — a wild free).

Reproducer

The failing allocation is HACL*'s raw malloc() for the copy's hash state, which _testcapi.set_nomemory (a PyMem hook) cannot reach — so the failure is injected at the C malloc layer with a tiny LD_PRELOAD shim. Needs a non-ASan build (ASan owns malloc, bypassing the shim).

/* oomshim.c:  cc -shared -fPIC -O2 -o oomshim.so oomshim.c -ldl
 * arm_next_malloc() makes the very next malloc() return NULL. */
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>
static void *(*real_malloc)(size_t); static void *(*real_calloc)(size_t,size_t);
static void (*real_free)(void*); static int armed = 0;
static char boot[16384]; static size_t boot_off = 0; static int initing = 0;
static int is_boot(const void*p){return (const char*)p>=boot&&(const char*)p<boot+sizeof boot;}
static void init_reals(void){ initing=1; real_malloc=dlsym(RTLD_NEXT,"malloc");
    real_calloc=dlsym(RTLD_NEXT,"calloc"); real_free=dlsym(RTLD_NEXT,"free"); initing=0; }
void arm_next_malloc(void){ armed = 1; }
void *malloc(size_t n){ if(!real_malloc){ if(initing){void*p=boot+boot_off;boot_off+=(n+15)&~(size_t)15;return p;} init_reals(); }
    if(armed){ armed=0; errno=ENOMEM; return NULL; } return real_malloc(n); }
void *calloc(size_t a,size_t b){ if(!real_calloc){ if(initing){void*p=boot+boot_off;boot_off+=((a*b)+15)&~(size_t)15;memset(p,0,a*b);return p;} init_reals(); }
    return real_calloc(a,b); }
void free(void*p){ if(is_boot(p))return; if(!real_free)init_reals(); if(real_free)real_free(p); }
# repro.py  —  LD_PRELOAD=./oomshim.so ./python repro.py
import ctypes, faulthandler
faulthandler.enable()
import _blake2

lib = ctypes.CDLL(None)
h = _blake2.blake2s(b"data")   # (blake2b behaves identically)
lib.arm_next_malloc()          # next malloc() returns NULL
h.copy()                       # -> Fatal Python error: py_blake2_clear ...

Traceback (debug build, gdb)

#8  py_blake2_clear            Modules/blake2module.c:997   # default: Py_UNREACHABLE() — reads uninitialized self->impl
#9  py_blake2_dealloc          Modules/blake2module.c:1008
#10 _Py_Dealloc               Objects/object.c:3319
#11 Py_DECREF                 Include/refcount.h:359
#12 _blake2_blake2b_copy_impl Modules/blake2module.c:812    # Py_DECREF(cpy) after the copy failed

Root cause

new_Blake2Object (blake2module.c:387) uses PyObject_GC_New (memory not zeroed) and immediately PyObject_GC_Tracks the object, leaving impl and the state pointers uninitialized; each caller is responsible for initializing them. py_blake2_new does so immediately, before any fallible allocation. But blake2_blake2b_copy_unlocked (blake2module.c:749) does the fallible HACL allocation first and only sets cpy->impl = self->impl on success (line 781); its error: path returns -1 with impl still uninitialized, and _blake2_blake2b_copy_impl then Py_DECREFs that object (line 812) → py_blake2_clear reads the garbage discriminant.

Suggested fix

Initialize the discriminant (and NULL the state pointer) before the fallible allocation, mirroring py_blake2_new — e.g. at the top of blake2_blake2b_copy_unlocked:

cpy->impl = self->impl;
/* leave the state pointer NULL until the copy succeeds, so py_blake2_clear is a no-op on error */

(Alternatively, have new_Blake2Object zero-initialize the struct so no caller can leave impl uninitialized.)

Environment

  • CPython main (3.16.0a0), commit 1b9fe5c; reproduced on debug free-threaded and GIL builds (deterministic fatal) and a release free-threaded build (SIGSEGV). Linux x86-64.
  • The copy path's structure dates to the 2024 HACL BLAKE2 rewrite (325e9b8); most recently reworked in Rewrite & cleanup HACL*-based extension modules #135532 (gh-135532: use defining_class for copying BLAKE-2 and SHA-3 objects #135838).

Found via allocation-failure fuzzing with fusil, created by @vstinner.

The investigation was conducted and this report was drafted with the help of Claude Code (Opus 4.8).

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Output from running 'python -VV' on the command line:

Python 3.16.0a0 (heads/main:1b9fe5c7226, Jun 20 2026, 23:56:12) [Clang 21.1.8 (6ubuntu1)]

Linked PRs

  • gh-152851: fix a crash when copying a BLAKE-2s/2b object #153008
  • [3.15] gh-152851: fix a crash when copying a BLAKE-2s/2b object (GH-153008) #153011
  • [3.14] gh-152851: fix a crash when copying a BLAKE-2s/2b object (GH-153008) #153012

Activity

  1. devdanzin
    added
    extension-modulesC modules in the Modules dirC modules in the Modules dir
    type-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
    on Jul 2, 2026
  2. devdanzin
    added a commit that references this issue on Jul 2, 2026

    OOM-0043: filed upstream as python/cpython#152851

    ec4828b
  3. picnixz
    assigned
    picnixz
    and unassigned
    picnixz
    on Jul 2, 2026
  4. picnixz
    added 3 commits that reference this issue on Jul 4, 2026

    gh-152851: fix a crash when copying a BLAKE-2s/2b object (#153008)

    5a400ce

    [3.15] gh-152851: fix a crash when copying a BLAKE-2s/2b object (GH-1…

    e1320ae

    [3.14] gh-152851: fix a crash when copying a BLAKE-2s/2b object (GH-1…

    d786fbd
  5. picnixz
    closed this as completedon Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirC modules in the Modules dirtype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402520 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.