python / python/cpython

`pickle.Unpickler`: add hook to intercept `REDUCE` calls

未關閉
#153,347 3 則留言 0 個 reaction 已指派 1 人 在 GitHub 檢視

@serhiy-storchaka 已經在處理了。

開始於 2026年7月9日。

extension-modules pending type-feature
主要語言
Python
星號
77.2k
分支
36k
PR 合併指標
PR 指標待擷取

描述

Feature or enhancement

Proposal:

I want to pickle and unpickle data that may validly contain types.FunctionType instances, but I want to prevent them from being used in REDUCE opcodes, to protect me from RCEs like REDUCE os.system('firefox https://www.youtube.com/watch?v=dQw4w9WgXcQ').

The traditional conservative approach is to use an allowlist, e.g.:

class SafeUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        obj = super().find_class(module, name)
        if not known_to_be_safe(obj):
            raise pickle.UnpicklingError(f"unsupported symbol '{module}.{name}'")
        return obj

However, find_class is applied when a GLOBAL/STACK_GLOBAL opcode is executed, before the interpreter knows whether the symbol will be used as a value or passed to REDUCE. I want to permit pickling of any function, including os.system; I just don't want to allow passing it to REDUCE.

So, what I really want is some sort of hook called at REDUCE time. One concrete proposal:

Ts = TypeVarTuple('Ts')
R  = TypeVar('R')

def reconstruct(self, constructor: Callable[[*Ts], R], args: tuple[*Ts]) -> R:
    # default impl; overrides may raise UnpicklingError
    return constructor(*args)

The safe idiom would be:

known_safe_classes = (SomeClass, OtherClass)
def reconstruct(self, constructor: Callable[[*Ts], R], args: tuple[*Ts]) -> R:
    if not isinstance(constructor, type):
        # prevent e.g. os.system RCE
        raise UnpicklingError(...)
    if not issubclass(constructor, known_safe_classes):
        # prevent e.g. subprocess.Popen RCE
        raise UnpicklingError(...)
    return constructor(*args)

Dismissed alternatives:

  • I can solve my problem by prohibiting functions in find_class, and instead providing a custom serialization for FunctionType through persistent_id/persistent_load, but this is unfortunate since I need to re-implement serialization from scratch of a type that is already supported natively.
  • I can solve my problem by subclassing the undocumented pure-python pickle._Unpickler instead of pickle.Unpickler, and overriding the undocumented load_reduce (which cPickle's Unpickler does not honor). This is undocumented and slow.
Has this already been discussed elsewhere?

No response given

Links to previous discussion of this feature:

No response

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

評估

這個 Issue 還沒有評估資料。

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。