python / python/cpython

CPython UaF during index callbacks

Ouverte
#144,128 2 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

3.13 3.14 3.15 extension-modules pending type-crash
Langage dominant
Python
Étoiles
77.2k
Forks
35.9k
Métriques de merge des PR
Métriques de PR en attente

Description

Bug report

Bug description:

The _PyNumber_Index, PyFloat_AsDouble and PyNumber_Long functions use a borrowed reference for error formatting after calling Python slots (index/float), but the slot's frame cleanup can free the object before the error is formatted.

A use-after-free vulnerability exists in CPython's type conversion functions (_PyNumber_Index, PyFloat_AsDouble and PyNumber_Long) where a borrowed reference is used for error message formatting after a Python callback that can free the referenced object.
For example, the vulnerable code pattern could be observed in _PyNumber_Index implementation (Objects/abstract.c):

  PyObject *
  _PyNumber_Index(PyObject *item)    // item is a BORROWED reference
  {
      if (item == NULL) {
          return null_error();
      }

      if (PyLong_Check(item)) {
          return Py_NewRef(item);
      }
      if (!_PyIndex_Check(item)) {
          PyErr_Format(PyExc_TypeError,
                       "'%.200s' object cannot be interpreted "
                       "as an integer", Py_TYPE(item)->tp_name);
          return NULL;
      }

      // === PYTHON CODE EXECUTES HERE ===
      PyObject *result = Py_TYPE(item)->tp_as_number->nb_index(item);
      // === item MAY NOW BE FREED ===

      if (!result || PyLong_CheckExact(result)) {
          return result;
      }

      if (!PyLong_Check(result)) {
          PyErr_Format(PyExc_TypeError,
                       "%T.__index__() must return an int, not %T",
                       item, result);  // <== UAF: item may be freed
          Py_DECREF(result);
          return NULL;
      }
      // ... similar pattern continues
  }

Similar patterns can be found in the PyFloat_AsDouble and PyNumber_Long functions.

The Python array.fromlist() method provides an ideal attack vector because it:

Iterates over a list using PyList_GET_ITEM() (returns borrowed reference)
Passes the borrowed reference directly to type-specific setitem functions
These functions call _PyNumber_Index() or PyFloat_AsDouble() for conversion.

Running with a python interpreter compiled with ASAN (./configure --with-address-sanitizer):

import array

class Evil:
    def __init__(self, lst):
        self.lst = lst

    def __index__(self):
        self.lst.clear()
        return "not an int"

lst = []
e = Evil(lst)
lst.append(e)
del e

arr = array.array('I')
arr.fromlist(lst)

You'll get the following:

==73525==ERROR: AddressSanitizer: heap-use-after-free on address 0x6130000300b8 at pc 0x0001013458a4 bp 0x00016ee396f0 sp 0x00016ee396e8
READ of size 8 at 0x6130000300b8 thread T0
    #0 0x0001013458a0 in unicode_from_format unicodeobject.c:3075
    #1 0x0001013436a8 in PyUnicode_FromFormatV unicodeobject.c:3109
    #2 0x00010154d5c8 in PyErr_Format errors.c:1243
    #3 0x0001010fe4e8 in _PyNumber_Index abstract.c:1433
    #4 0x000109338334 in II_setitem arraymodule.c:411
    #5 0x0001093427d4 in array_array_fromlist arraymodule.c.h:495
    #6 0x00010114aeb0 in _PyObject_VectorcallTstate pycore_call.h:136
    #7 0x0001014828ac in _Py_VectorCallInstrumentation_StackRefSteal ceval.c:1094
    #8 0x0001014b4718 in _PyEval_EvalFrameDefault generated_cases.c.h:1785
    #9 0x00010148180c in _PyEval_Vector ceval.c:2516
    #10 0x00010148105c in PyEval_EvalCode ceval.c:1005
    #11 0x000101646118 in run_mod pythonrun.c:1469
    #12 0x00010163e6a0 in _PyRun_SimpleFileObject pythonrun.c:518
    #13 0x00010163da30 in _PyRun_AnyFileObject pythonrun.c:81
    #14 0x0001016ac948 in pymain_run_file main.c:429
    #15 0x0001016aabcc in Py_RunMain main.c:772
    #16 0x0001016ab9fc in pymain_main main.c:802
    #17 0x0001016abba8 in Py_BytesMain main.c:826
    #18 0x00019e4edd50  (<unknown module>)
CPython versions tested on:

3.15

Operating systems tested on:

macOS

Linked PRs
  • gh-144138

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez dans Objects/abstract.c, au niveau de _PyNumber_Index, PyFloat_AsDouble et PyNumber_Long, puis examinez le chemin d’appel de arraymodule.c et reproduisez le problème avec l’exemple ASAN. Comparez le traitement correspondant et les tests dans l’arborescence actuelle ; le travail est terminé lorsque le use-after-free signalé est corrigé dans toutes les fonctions de conversion nommées et que le reproducteur ASAN n’échoue plus.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
c, python
Domaine
security
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
À l'abandon
Clarté
Clairement spécifiée
Accessibilité débutants
25/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.