diff --git a/CHANGELOG.md b/CHANGELOG.md index d55bec9be72..d219465fe82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use `memoffset` for computing PyCell offsets [#2450](https://github.com/PyO3/pyo3/pull/2450) - Fix incorrect enum names being returned by `repr` for enums renamed by `#[pyclass(name)]` [#2457](https://github.com/PyO3/pyo3/pull/2457) - Fix incorrect Python version cfg definition on `PyObject_CallNoArgs`[#2476](https://github.com/PyO3/pyo3/pull/2476) +- Fix use-after-free in `PyCapsule` type. [#2481](https://github.com/PyO3/pyo3/pull/2481) ## [0.16.5] - 2022-05-15 diff --git a/noxfile.py b/noxfile.py index ba81e1a0dd0..12fe0e4b293 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,4 +1,5 @@ import re +import subprocess import sys import time from glob import glob @@ -199,3 +200,32 @@ def test_emscripten(session: nox.Session): @nox.session(name="build-guide", venv_backend="none") def build_guide(session: nox.Session): session.run("mdbook", "build", "-d", "../target/guide", "guide", *session.posargs) + + +@nox.session(name="address-sanitizer", venv_backend="none") +def address_sanitizer(session: nox.Session): + session.run( + "cargo", + "+nightly", + "test", + f"--target={_get_rust_target()}", + "--", + "--test-threads=1", + env={ + "RUSTFLAGS": "-Zsanitizer=address", + "RUSTDOCFLAGS": "-Zsanitizer=address", + "ASAN_OPTIONS": "detect_leaks=0", + }, + external=True, + ) + + +def _get_rust_target() -> str: + output = subprocess.check_output(["rustc", "-vV"], text=True) + + for line in output.splitlines(): + if line.startswith(_HOST_LINE_START): + return line[len(_HOST_LINE_START) :].strip() + + +_HOST_LINE_START = "host: " diff --git a/src/ffi/tests.rs b/src/ffi/tests.rs index fcb93ee0792..a1789366133 100644 --- a/src/ffi/tests.rs +++ b/src/ffi/tests.rs @@ -48,9 +48,10 @@ fn test_utc_timezone() { Python::with_gil(|py| { let utc_timezone = unsafe { PyDateTime_IMPORT(); - &*(&PyDateTime_TimeZone_UTC() as *const *mut crate::ffi::PyObject - as *const crate::PyObject) + PyDateTime_TimeZone_UTC() }; + let utc_timezone = + unsafe { &*((&utc_timezone) as *const *mut PyObject as *const Py) }; let locals = PyDict::new(py); locals.set_item("utc_timezone", utc_timezone).unwrap(); py.run( diff --git a/src/types/capsule.rs b/src/types/capsule.rs index 8add77b4133..525b68ac2fe 100644 --- a/src/types/capsule.rs +++ b/src/types/capsule.rs @@ -2,7 +2,7 @@ use crate::Python; use crate::{ffi, AsPyPointer, PyAny}; use crate::{pyobject_native_type_core, PyErr, PyResult}; -use std::ffi::{c_void, CStr}; +use std::ffi::{c_void, CStr, CString}; use std::os::raw::c_int; /// Represents a Python Capsule @@ -100,12 +100,21 @@ impl PyCapsule { destructor: F, ) -> PyResult<&'py Self> { AssertNotZeroSized::assert_not_zero_sized(&value); - let val = Box::new(CapsuleContents { value, destructor }); + // Take ownership of a copy of `name` so that the string is guaranteed to live as long + // as the capsule. PyCapsule_new purely saves the pointer in the capsule so doesn't + // guarantee ownership itself. + let name = name.to_owned(); + let name_ptr = name.as_ptr(); + let val = Box::new(CapsuleContents { + value, + destructor, + name, + }); let cap_ptr = unsafe { ffi::PyCapsule_New( Box::into_raw(val) as *mut c_void, - name.as_ptr(), + name_ptr, Some(capsule_destructor::), ) }; @@ -221,6 +230,7 @@ impl PyCapsule { struct CapsuleContents { value: T, destructor: D, + name: CString, } // Wrapping ffi::PyCapsule_Destructor for a user supplied FnOnce(T) for capsule destructor @@ -229,7 +239,9 @@ unsafe extern "C" fn capsule_destructor); + let CapsuleContents { + value, destructor, .. + } = *Box::from_raw(ptr as *mut CapsuleContents); destructor(value, ctx) }