diff --git a/SECURITY.md b/SECURITY.md index 4283541556..63ef6e4950 100755 --- a/SECURITY.md +++ b/SECURITY.md @@ -29,6 +29,17 @@ disclosure policy. Please visit our [Product Security Incident Response Team (PSIRT)](https://www.nvidia.com/en-us/security/psirt-policies/) policies page for more information. +## CUDA IPC and Python serialization + +`cuda.core.Buffer` objects allocated from IPC-enabled memory resources can be +pickled for transfer between same-host processes. Unpickling performs an IPC +memory import using the embedded `IPCBufferDescriptor`. Only unpickle buffers +(and call `Buffer.from_ipc_descriptor`) with descriptors from trusted peers; +malicious descriptors can trigger invalid memory operations. + +When sharing CUDA objects across processes, use `multiprocessing` with the +`spawn` start method. + ## NVIDIA Product Security For all security-related concerns, please visit NVIDIA's Product Security portal at diff --git a/cuda_core/cuda/core/_memory/_buffer.pyi b/cuda_core/cuda/core/_memory/_buffer.pyi index 728853c4bc..99b73c98bc 100644 --- a/cuda_core/cuda/core/_memory/_buffer.pyi +++ b/cuda_core/cuda/core/_memory/_buffer.pyi @@ -19,6 +19,13 @@ class Buffer: allocations. Support for data interchange mechanisms are provided by DLPack. + + Note + ---- + Pickling an IPC-enabled :class:`Buffer` embeds an + :class:`~_memory.IPCBufferDescriptor`. Unpickling reconstructs the buffer + by calling :meth:`from_ipc_descriptor` and therefore performs an IPC + import. Do not unpickle buffers from untrusted sources. """ def __cinit__(self) -> None: @@ -85,6 +92,12 @@ class Buffer: stream : :obj:`~_stream.Stream` Keyword-only. The stream used for asynchronous deallocation when the buffer is closed or garbage collected. + + Note + ---- + The descriptor payload and ``size`` are supplied by the exporting peer + and must be treated as untrusted input unless the peer is known to be + cooperating. """ @property diff --git a/cuda_core/cuda/core/_memory/_buffer.pyx b/cuda_core/cuda/core/_memory/_buffer.pyx index 88f9054385..a7bb096ce7 100644 --- a/cuda_core/cuda/core/_memory/_buffer.pyx +++ b/cuda_core/cuda/core/_memory/_buffer.pyx @@ -86,6 +86,13 @@ cdef class Buffer: allocations. Support for data interchange mechanisms are provided by DLPack. + + Note + ---- + Pickling an IPC-enabled :class:`Buffer` embeds an + :class:`~_memory.IPCBufferDescriptor`. Unpickling reconstructs the buffer + by calling :meth:`from_ipc_descriptor` and therefore performs an IPC + import. Do not unpickle buffers from untrusted sources. """ def __cinit__(self) -> None: self._clear() @@ -138,6 +145,8 @@ cdef class Buffer: return Buffer.from_ipc_descriptor(mr, ipc_descriptor, stream=default_stream()) def __reduce__(self) -> tuple[object, ...]: + # Unpickling performs a live CUDA IPC import from descriptor bytes in the + # pickle stream. Only deserialize Buffers from a trusted principal. # Must not serialize the parent's stream! return Buffer._reduce_helper, (self.memory_resource, self.ipc_descriptor) @@ -187,6 +196,12 @@ cdef class Buffer: stream : :obj:`~_stream.Stream` Keyword-only. The stream used for asynchronous deallocation when the buffer is closed or garbage collected. + + Note + ---- + The descriptor payload and ``size`` are supplied by the exporting peer + and must be treated as untrusted input unless the peer is known to be + cooperating. """ return _ipc.Buffer_from_ipc_descriptor(cls, mr, ipc_descriptor, stream) diff --git a/cuda_core/cuda/core/_memory/_device_memory_resource.pyi b/cuda_core/cuda/core/_memory/_device_memory_resource.pyi index 033ff7c533..21862b32b7 100644 --- a/cuda_core/cuda/core/_memory/_device_memory_resource.pyi +++ b/cuda_core/cuda/core/_memory/_device_memory_resource.pyi @@ -105,7 +105,16 @@ class DeviceMemoryResource(_MemPool): an MMR is created and registered in the receiving process. Subsequently, buffers may be serialized and transferred using ordinary :mod:`pickle` methods. The reconstruction procedure uses the registry to find the - associated MMR. + associated MMR. Unpickling a :class:`Buffer` performs an IPC import from + the embedded descriptor; only unpickle buffers received from trusted peers. + + Warning + ------- + IPC descriptors and pickled buffers cross a trust boundary between + cooperating same-host processes. A malicious peer can supply crafted + descriptor fields. Use :meth:`Buffer.from_ipc_descriptor` only with + descriptors from trusted peers, and do not unpickle buffers from + untrusted sources. """ def __cinit__(self, *args, **kwargs) -> None: diff --git a/cuda_core/cuda/core/_memory/_device_memory_resource.pyx b/cuda_core/cuda/core/_memory/_device_memory_resource.pyx index 13f654e305..45d8d543ac 100644 --- a/cuda_core/cuda/core/_memory/_device_memory_resource.pyx +++ b/cuda_core/cuda/core/_memory/_device_memory_resource.pyx @@ -130,7 +130,16 @@ cdef class DeviceMemoryResource(_MemPool): an MMR is created and registered in the receiving process. Subsequently, buffers may be serialized and transferred using ordinary :mod:`pickle` methods. The reconstruction procedure uses the registry to find the - associated MMR. + associated MMR. Unpickling a :class:`Buffer` performs an IPC import from + the embedded descriptor; only unpickle buffers received from trusted peers. + + Warning + ------- + IPC descriptors and pickled buffers cross a trust boundary between + cooperating same-host processes. A malicious peer can supply crafted + descriptor fields. Use :meth:`Buffer.from_ipc_descriptor` only with + descriptors from trusted peers, and do not unpickle buffers from + untrusted sources. """ def __cinit__(self, *args, **kwargs) -> None: diff --git a/cuda_core/cuda/core/_memory/_ipc.pyi b/cuda_core/cuda/core/_memory/_ipc.pyi index 9e5be61af7..0c912a567b 100644 --- a/cuda_core/cuda/core/_memory/_ipc.pyi +++ b/cuda_core/cuda/core/_memory/_ipc.pyi @@ -38,7 +38,14 @@ class IPCDataForMR: ... class IPCBufferDescriptor: - """Serializable object describing a buffer that can be shared between processes.""" + """Serializable object describing a buffer that can be shared between processes. + + Note + ---- + The payload and ``size`` fields are controlled by the exporting peer. + Receivers must treat them as untrusted and import only through + :meth:`Buffer.from_ipc_descriptor`. + """ def __init__(self, *arg, **kwargs) -> None: ... diff --git a/cuda_core/cuda/core/_memory/_ipc.pyx b/cuda_core/cuda/core/_memory/_ipc.pyx index 61fd8b086e..7baf6e94bb 100644 --- a/cuda_core/cuda/core/_memory/_ipc.pyx +++ b/cuda_core/cuda/core/_memory/_ipc.pyx @@ -78,7 +78,14 @@ cdef class IPCDataForMR: cdef class IPCBufferDescriptor: - """Serializable object describing a buffer that can be shared between processes.""" + """Serializable object describing a buffer that can be shared between processes. + + Note + ---- + The payload and ``size`` fields are controlled by the exporting peer. + Receivers must treat them as untrusted and import only through + :meth:`Buffer.from_ipc_descriptor`. + """ def __init__(self, *arg, **kwargs) -> None: raise RuntimeError("IPCBufferDescriptor objects cannot be instantiated directly. Please use MemoryResource APIs.")