diff --git a/CHANGELOG.md b/CHANGELOG.md index aaed3f2a9..5d73e940a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ - Unreleased +- v0.17.2 + - Fix unsound aliasing into `Box<[T]>` when converting them into NumPy arrays. ([#351](https://github.com/PyO3/rust-numpy/pull/351)) + - v0.17.1 - Fix use-after-free in `PyArray::resize`, `PyArray::reshape` and `PyArray::reshape_with_order`. ([#341](https://github.com/PyO3/rust-numpy/pull/341)) - Fix UB in `ToNpyDims::as_dims_ptr` with dimensions of dynamic size (-1). ([#344](https://github.com/PyO3/rust-numpy/pull/344)) diff --git a/Cargo.toml b/Cargo.toml index 21e5d3cfa..9ae5c3e22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "numpy" -version = "0.17.1" +version = "0.17.2" authors = [ "The rust-numpy Project Developers", "PyO3 Project and Contributors " diff --git a/src/array.rs b/src/array.rs index f42b2e161..1128b4275 100644 --- a/src/array.rs +++ b/src/array.rs @@ -451,7 +451,7 @@ impl PyArray { where ID: IntoDimension, { - let flags = if is_fortran { 1 } else { 0 }; + let flags = c_int::from(is_fortran); Self::new_uninit(py, dims, ptr::null_mut(), flags) } @@ -512,18 +512,14 @@ impl PyArray { Self::from_owned_ptr(py, ptr) } - pub(crate) unsafe fn from_raw_parts<'py, ID, C>( + pub(crate) unsafe fn from_raw_parts<'py>( py: Python<'py>, - dims: ID, + dims: D, strides: *const npy_intp, data_ptr: *const T, - container: C, - ) -> &'py Self - where - ID: IntoDimension, - PySliceContainer: From, - { - let container = PyClassInitializer::from(PySliceContainer::from(container)) + container: PySliceContainer, + ) -> &'py Self { + let container = PyClassInitializer::from(container) .create_cell(py) .expect("Failed to create slice container"); @@ -676,10 +672,18 @@ impl PyArray { /// assert_eq!(pyarray.readonly().as_array(), array![[1, 2], [3, 4]]); /// }); /// ``` - pub fn from_owned_array<'py>(py: Python<'py>, arr: Array) -> &'py Self { + pub fn from_owned_array<'py>(py: Python<'py>, mut arr: Array) -> &'py Self { let (strides, dims) = (arr.npy_strides(), arr.raw_dim()); - let data_ptr = arr.as_ptr(); - unsafe { Self::from_raw_parts(py, dims, strides.as_ptr(), data_ptr, arr) } + let data_ptr = arr.as_mut_ptr(); + unsafe { + Self::from_raw_parts( + py, + dims, + strides.as_ptr(), + data_ptr, + PySliceContainer::from(arr), + ) + } } /// Get a reference of the specified element if the given index is valid. @@ -1071,10 +1075,18 @@ impl PyArray { /// assert!(pyarray.readonly().as_array().get(0).unwrap().as_ref(py).is_instance_of::().unwrap()); /// }); /// ``` - pub fn from_owned_object_array<'py, T>(py: Python<'py>, arr: Array, D>) -> &'py Self { + pub fn from_owned_object_array<'py, T>(py: Python<'py>, mut arr: Array, D>) -> &'py Self { let (strides, dims) = (arr.npy_strides(), arr.raw_dim()); - let data_ptr = arr.as_ptr() as *const PyObject; - unsafe { PyArray::from_raw_parts(py, dims, strides.as_ptr(), data_ptr, arr) } + let data_ptr = arr.as_mut_ptr() as *const PyObject; + unsafe { + Self::from_raw_parts( + py, + dims, + strides.as_ptr(), + data_ptr, + PySliceContainer::from(arr), + ) + } } } diff --git a/src/convert.rs b/src/convert.rs index ded0cf8b2..10d19693a 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -2,7 +2,7 @@ use std::{mem, os::raw::c_int, ptr}; -use ndarray::{ArrayBase, Data, Dimension, IntoDimension, Ix1, OwnedRepr}; +use ndarray::{ArrayBase, Data, Dim, Dimension, IntoDimension, Ix1, OwnedRepr}; use pyo3::Python; use crate::array::PyArray; @@ -10,6 +10,7 @@ use crate::dtype::Element; use crate::error::MAX_DIMENSIONALITY_ERR; use crate::npyffi::{self, npy_intp}; use crate::sealed::Sealed; +use crate::slice_container::PySliceContainer; /// Conversion trait from owning Rust types into [`PyArray`]. /// @@ -49,10 +50,14 @@ impl IntoPyArray for Box<[T]> { type Dim = Ix1; fn into_pyarray<'py>(self, py: Python<'py>) -> &'py PyArray { - let dims = [self.len()]; + let container = PySliceContainer::from(self); + let dims = Dim([container.len]); let strides = [mem::size_of::() as npy_intp]; - let data_ptr = self.as_ptr(); - unsafe { PyArray::from_raw_parts(py, dims, strides.as_ptr(), data_ptr, self) } + // The data pointer is derived only after dissolving `Box` into `PySliceContainer` + // to avoid unsound aliasing of Box<[T]> which is currently noalias, + // c.f. https://github.com/rust-lang/unsafe-code-guidelines/issues/326 + let data_ptr = container.ptr as *mut T; + unsafe { PyArray::from_raw_parts(py, dims, strides.as_ptr(), data_ptr, container) } } } @@ -60,11 +65,19 @@ impl IntoPyArray for Vec { type Item = T; type Dim = Ix1; - fn into_pyarray<'py>(self, py: Python<'py>) -> &'py PyArray { - let dims = [self.len()]; + fn into_pyarray<'py>(mut self, py: Python<'py>) -> &'py PyArray { + let dims = Dim([self.len()]); let strides = [mem::size_of::() as npy_intp]; - let data_ptr = self.as_ptr(); - unsafe { PyArray::from_raw_parts(py, dims, strides.as_ptr(), data_ptr, self) } + let data_ptr = self.as_mut_ptr(); + unsafe { + PyArray::from_raw_parts( + py, + dims, + strides.as_ptr(), + data_ptr, + PySliceContainer::from(self), + ) + } } } diff --git a/src/slice_container.rs b/src/slice_container.rs index 3cbfc6110..ba57e2e12 100644 --- a/src/slice_container.rs +++ b/src/slice_container.rs @@ -1,4 +1,4 @@ -use std::{mem, slice}; +use std::{mem, ptr}; use ndarray::{ArrayBase, Dimension, OwnedRepr}; use pyo3::pyclass; @@ -6,8 +6,8 @@ use pyo3::pyclass; /// Utility type to safely store `Box<[_]>` or `Vec<_>` on the Python heap #[pyclass] pub(crate) struct PySliceContainer { - ptr: *mut u8, - len: usize, + pub(crate) ptr: *mut u8, + pub(crate) len: usize, cap: usize, drop: unsafe fn(*mut u8, usize, usize), } @@ -17,18 +17,18 @@ unsafe impl Send for PySliceContainer {} impl From> for PySliceContainer { fn from(data: Box<[T]>) -> Self { unsafe fn drop_boxed_slice(ptr: *mut u8, len: usize, _cap: usize) { - let _ = Box::from_raw(slice::from_raw_parts_mut(ptr as *mut T, len) as *mut [T]); + let _ = Box::from_raw(ptr::slice_from_raw_parts_mut(ptr as *mut T, len)); } // FIXME(adamreichold): Use `Box::into_raw` when // `*mut [T]::{as_mut_ptr, len}` become stable and compatible with our MSRV. - let ptr = data.as_ptr() as *mut u8; + let mut data = mem::ManuallyDrop::new(data); + + let ptr = data.as_mut_ptr() as *mut u8; let len = data.len(); let cap = 0; let drop = drop_boxed_slice::; - mem::forget(data); - Self { ptr, len, @@ -46,13 +46,13 @@ impl From> for PySliceContainer { // FIXME(adamreichold): Use `Vec::into_raw_parts` // when it becomes stable and compatible with our MSRV. - let ptr = data.as_ptr() as *mut u8; + let mut data = mem::ManuallyDrop::new(data); + + let ptr = data.as_mut_ptr() as *mut u8; let len = data.len(); let cap = data.capacity(); let drop = drop_vec::; - mem::forget(data); - Self { ptr, len,