Skip to content

ctypes ignores when a DLL function is called with too many arguments #82439

Closed

Description

@s-m-e
mannequin
BPO 38258
Nosy @pfmoore, @tjguk, @zware, @eryksun, @zooba, @s-m-e

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = None
created_at = <Date 2019-09-23.14:35:01.156>
labels = ['type-bug', '3.8', '3.9', 'ctypes', 'OS-windows', 'docs']
title = 'ctypes ignores when a DLL function is called with too many arguments'
updated_at = <Date 2019-09-24.16:56:54.631>
user = 'https://github.com/s-m-e'

bugs.python.org fields:

activity = <Date 2019-09-24.16:56:54.631>
actor = 'eryksun'
assignee = 'docs@python'
closed = False
closed_date = None
closer = None
components = ['Documentation', 'Windows', 'ctypes']
creation = <Date 2019-09-23.14:35:01.156>
creator = 'smernst'
dependencies = []
files = []
hgrepos = []
issue_num = 38258
keywords = []
message_count = 4.0
messages = ['353021', '353036', '353069', '353100']
nosy_count = 7.0
nosy_names = ['paul.moore', 'tim.golden', 'docs@python', 'zach.ware', 'eryksun', 'steve.dower', 'smernst']
pr_nums = []
priority = 'normal'
resolution = None
stage = 'needs patch'
status = 'open'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue38258'
versions = ['Python 3.8', 'Python 3.9']

Linked PRs

Activity

  1. s-m-e commented on Sep 23, 2019

    s-m-emannequin
    MannequinAuthor

    A c-function with the following signature ...

    int16_t __stdcall __declspec(dllimport) square_int(
    	int16_t a
    	);

    ... is being called with ctypes:

    def test_error_callargs_unconfigured_too_many_args():
    
    	dll = ctypes.windll.LoadLibrary('tests/demo_dll.dll')
    	square_int = dll.square_int
    
    	with pytest.raises(ValueError):
    		a = square_int(1, 2, 3)

    Expected result: If the function is called with too many (positional) arguments (in the example 3 instead of 1), a ValueError should be raised. This is the case for at least CPython 3.4 to 3.7.

    Actual result: "Nothing", i.e. no exception. The described test "fails". The function is called without an error - with CPython 3.8.0b4.

    If this behavior is intended, is has not been (as far as I can tell) documented.

  2. eryksun commented on Sep 23, 2019

    @eryksun
    Contributor

    According to the docs, raising ValueError in this case has been a deprecated feature since 3.6.2, and the ability to do so no longer exists in 3.8. The documentation needs to be updated to reflect the new behavior.

    https://docs.python.org/3.8/library/ctypes.html#calling-functions

    In bpo-35947, the customized libffi_msvc was replaced by standard libffi. ffi_call() in libffi_msvc returned a stack cleanup delta. This allowed raising ValueError in _call_function_pointer if the delta value was non-zero for an x86 32-bit stdcall function (callee cleanup). A positive delta implied too many arguments, and a negative delta implied too few arguments. The standard libffi ffi_call() has no return value, so the code for raising ValueError was removed.

    ---

    On a related note, PyCFuncPtr_call can be relaxed to allow the argument count to exceed the length of argtypes for x64 stdcall (but not x86). The 64-bit calling convention is the same for cdecl, stdcall, fastcall, and thiscall. It uses caller cleanup, like x86 cdecl, so we can skip raising TypeError in this case, just like we already do for cdecl.

  3. s-m-e commented on Sep 24, 2019

    s-m-emannequin
    MannequinAuthor

    Thanks a lot for the clarification, Eryk. I did not notice that it was deprecated behavior.

    For the "too many arguments" case I guess this is not an issue. However, just for the symmetry of things, I also looked at calling a function with TOO FEW arguments.

    int16_t __stdcall __declspec(dllimport) mul_ints(
    	int16_t a,
    	int16_t b
    	)
    {
    	return a * b;
    }
    def test_error_callargs_unconfigured_too_few_args():
    
    	dll = ctypes.windll.LoadLibrary('tests/demo_dll.dll')
    	mul_ints = dll.mul_ints
    
    	with pytest.raises(ValueError):
    		a = mul_ints(7)

    As expected after your explanation, also no error in CPython 3.8 (i.e. the test fails, while is passes on CPython <= 3.7). If I run this manually, I even get a "result":

    >>> dll = ctypes.windll.LoadLibrary('tests/demo_dll.dll')
    >>> mul_ints = dll.mul_ints
    >>> a = mul_ints(7)
    >>> a
    445564 # !
    >>> 445564/7 # Just looking at where this result is coming from ...
    63652.0

    Re-starting Python (3.8) and (intentionally) misconfiguring the function interestingly also does not change the result:

    >>> dll = ctypes.windll.LoadLibrary('tests/demo_dll.dll')
    >>> mul_ints = dll.mul_ints
    >>> mul_ints.argtypes = (ctypes.c_int16,)
    >>> a = mul_ints(7)                                      
    >>> a
    445564 # Apparently, this is deterministic?!?

    Just out of curiosity, where is this data coming from?

    This ONLY way to prevent things like this to happen is to actually correctly configure the function:

    >>> dll = ctypes.windll.LoadLibrary('tests/demo_dll.dll')
    >>> mul_ints = dll.mul_ints
    >>> mul_ints.argtypes = (ctypes.c_int16, ctypes.c_int16)
    >>> mul_ints(7)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: this function takes 2 arguments (1 given)

    This should very CLEARLY be mentioned in the documentation ...

  4. eryksun commented on Sep 24, 2019

    @eryksun
    Contributor

    Just out of curiosity, where is this data coming from?

    In general it's just random data on the stack. It's not worth investigating where this particular value comes from. It could be the low word of the address of a stack-allocated buffer, such as the buffer where ffi_call writes the return value.

    This ONLY way to prevent things like this to happen is to actually
    correctly configure the function:
    ...
    >>> mul_ints.argtypes = (ctypes.c_int16, ctypes.c_int16)

    Yes, the docs should explain that TypeError is raised for the wrong number of arguments when argtypes is assigned, and it should be noted that cdecl (CDLL) allows passing more (but not fewer) arguments than what argtypes defines. As I mentioned previously, we could extend this cdecl behavior to stdcall (WinDLL) for the x64 architecture, since it's caller cleanup like cdecl.

  5. transferred this issue fromon Apr 10, 2022
  6. added
    3.14bugs and security fixes
    3.15bugs and security fixes
    3.16new features, bugs and security fixes
    and removed on Oct 21, 2026
  7. added a commit that references this issue on Oct 21, 2026
  8. added 2 commits that reference this issue on Oct 21, 2026
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

    3.14bugs and security fixes3.15bugs and security fixes3.16new features, bugs and security fixesOS-windowsdocsDocumentation in the Doc dirtopic-ctypes

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions