Skip to content

gh-118814: Fix crash in _PyArg_UnpackKeywordsWithVararg #122558

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from

Conversation

picnixz
Copy link
Member

@picnixz picnixz commented Aug 1, 2024

The cause of the crash, if I'm not mistaken, is that we currently have:

if (nkwargs) {
	current_arg = ...
}
else {
    current_arg = NULL;
}

if (nargs < vararg && i != vararg) { 
	buf[i] = current_arg;
}
else {
    buf[i + 1] = current_arg;
}

if (current_arg) {
    Py_DECREF(current_arg);
    --nkwargs;
}

If we have the following unrolling:

  • first iteration: current_arg != NULL and use of buf[i + 1] = current_arg
  • second iteration: current_arg == NULL -> buf[i] = NULL so we smash what we just stored, hence the subsequent segfaults. The patch consists of
if (nargs < vararg && i < vararg) {
    if (current_arg != NULL) {
        buf[i] = current_arg;
    }
}

so that we are sure that we don't overwrite something. I don't think it's also correct to overwrite buf[i] if the latter is not NULL but I may be wrong. If I'm wrong, the patch should simply check that we did not set buf[i + 1] to a non-NULL current_arg in the previous iteration.

picnixz added 5 commits August 1, 2024 14:01
This fixes an issue where passing positional arguments
as explicit keyword arguments to functions accepting
positional and variadic arguments.
@picnixz
Copy link
Member Author

picnixz commented Aug 1, 2024

Ok, it's not fixed. I found another bug and the fix doesn't work. I need to think a bit more.

@picnixz picnixz marked this pull request as draft August 1, 2024 15:42
@picnixz picnixz marked this pull request as ready for review August 2, 2024 11:01
@picnixz
Copy link
Member Author

picnixz commented Aug 2, 2024

Hopefully it's now fixed (the issue, if I'm correct, was about the check i != vararg instead of i < vararg (if i comes after vararg, then it should also be offsetted).

@erlend-aasland
Copy link
Contributor

This looks correct; I'll be able to review it in a day or two.

erlend-aasland
erlend-aasland previously approved these changes Aug 3, 2024
Copy link
Contributor

@erlend-aasland erlend-aasland left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me, but I'd like @serhiy-storchaka's eyes on this, as he's more knowledgable about the arg parsing corners of the code base.

@serhiy-storchaka serhiy-storchaka self-requested a review August 4, 2024 08:25
Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The design of this function is questionable:

  • It seems that vararg is always equal maxpos. So this parameter is redundant.
  • Placing the tuple of var-positional arguments between keyword-or-positional and keyword-only arguments complicates the code.
  • This is the only buf item that contains non-borrowed reference. This is error prone.
  • Not always we need a tuple. Creating a tuple can add an overhead. This also makes impossible a fast path.

I will address this in other issue.

// copy the first arguments until the 'vararg' index
memcpy(buf, args, vararg * sizeof(PyObject *));
// remaining arguments are all considered as variadic ones
buf[vararg] = _PyTuple_FromArray(&args[vararg], varargssize);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check the result for NULL.

}
}
// copy the first arguments until the 'vararg' index
memcpy(buf, args, vararg * sizeof(PyObject *));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There may be less than vararg items in args.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right and this is something I didn't check. I need to add tests for that (I would like to suggest a way to generate more clinic tests because there is a lack of coverage).

* buf = [1, 2, NULL, 3, 4].
*/
if (nargs < vararg && i < vararg) {
if (current_arg != NULL) {
Copy link
Member

@serhiy-storchaka serhiy-storchaka Aug 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This leaves buf[i] not initialized, isn't?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Urgh... yes. I should probably change the way it's being checked... like a boolean checking whether I replaced something before or not. But depending on whether you want to change the logic of this function, I may delay the commit.

@picnixz
Copy link
Member Author

picnixz commented Aug 4, 2024

Urgh, actually there's another issue:

vararg_with_many_args

    a: object
    b: object
    c: object
    d: object
    *args: object
    e: bool
    f: bool = False
    g: bool = False
  • Calling ac_tester.vararg_with_many_args(1, 2, 3, 4, e=5) -> says that 'f' is required
  • Calling ac_tester.vararg_with_many_args(1, 2, 3, 4, e=5, f=6) -> segfault

I wanted to avoid rewriting the entire function but I think it'll just be easier to just rewrite entirely... I just need to understand the internals a bit more (the variable naming doesn't help IMO...)

@picnixz
Copy link
Member Author

picnixz commented Aug 4, 2024

Placing the tuple of var-positional arguments between keyword-or-positional and keyword-only arguments complicates the code.

And yes... I'm pretty sure that my issue is about indices... (I like coding and so, but the topic that I always disliked whether it's in maths or in dev is about indices!!! (the irony being I'm doing stuff that requires indices a lot...)))

@picnixz picnixz marked this pull request as draft August 4, 2024 10:25
@picnixz
Copy link
Member Author

picnixz commented Aug 4, 2024

@serhiy-storchaka I think it's better to re-address the design of the function itself. Do you want to create a separate issue and work on that? (I'm only available today and will be leaving on holidays for roughly 10 days)

I should also confess that I don't have the courage to think of it today...

@serhiy-storchaka
Copy link
Member

The solution for this issue can be simpler: #122664.

@erlend-aasland erlend-aasland dismissed their stale review August 4, 2024 22:41

There are unresolved issues

@picnixz
Copy link
Member Author

picnixz commented Aug 7, 2024

Closing in favor of #122664.

If more test coverage is needed, we'll just extract my tests from this one in another PR.

@picnixz picnixz closed this Aug 7, 2024
@picnixz picnixz deleted the ac-typevar-crash branch August 19, 2024 09:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants