Skip to content

[P2] Unreleased Py_buffer exports lock write buffers and leak returned status objects #242

Description

@drebbe-intrepid

Bug

uart_write and generic_api_send_command acquire a buffer with y* but never release it. Several result-producing wrappers also omit PyBuffer_Release on success: get_device_status, get_hw_firmware_info, and get_dll_firmware_info. flash_accessory_firmware likewise leaves its parameter buffer exported.

Reviewed commit: 45167e09de6b886cd451a4245f842c8221349711 (current master on 2026-09-15).
Source: src/methods.cpp:5019; src/methods.cpp:5201; src/methods.cpp:3954

Reproduction

This reproducer uses a hardware-free DLL. Save this as mock.c:

#define API __declspec(dllexport)
typedef unsigned long long size_t;
API int icsneoUartWrite(void* h, int port, const void* data, size_t len, size_t* sent, unsigned char* flags) { *sent = len; return 1; }
API int icsneoGenericAPISendCommand(void* h, unsigned char a, unsigned char i, unsigned char f, void* data, unsigned int len, unsigned char* error) { *error = 0; return 1; }
API int icsneoGetDeviceStatus(void* h, void* status, size_t* size) { return 1; }

Build with LLVM on Windows x64:

clang-cl /nologo /c /GS- /Zl mock.c /Fomock.obj
lld-link /dll /noentry /nodefaultlib /out:mock.dll mock.obj

Then run:

import ics
import ctypes
from pathlib import Path
ics.override_library_name(str(Path('mock.dll').resolve()))
d = ics.PyNeoDeviceEx()
d._auto_handle_close = False
for func, args in [(ics.uart_write, (d, 0)), (ics.generic_api_send_command, (d, 0, 0, 0))]:
    data = bytearray(b'abc')
    func(*args, data)
    try: data.extend(b'd')
    except BufferError as e: print(func.__name__, e)
import weakref, gc
status = ics.get_device_status(d)
ref = weakref.ref(status)
del status
gc.collect()
print('status remains alive:', ref() is not None)

Observed / expected

Both successful writes leave BufferError: Existing exports of data: object cannot be re-sized. The returned status remains alive after deletion. The firmware-info getters were independently checked and also retain their results. Expected the write buffer to be resizable after the synchronous call and returned objects to be collectable.

Suggested fix and regression coverage

Use scoped Py_buffer ownership and release on every success/error/exception path. Check PyObject_GetBuffer return values before using the buffer. Add bytearray-resize and weak-reference collection tests for the affected wrappers.

Validation environment

Windows x64, CPython 3.14.5, extension rebuilt from the commit above. Hardware-dependent entry points below were exercised with a mock DLL, not a connected device. The existing 30 tests pass despite these findings.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions