Skip to content
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

Segfault in Python when an object returns this and multiple Python objects wrap one C object #3086

Open
hroncok opened this issue Dec 9, 2024 · 2 comments

Comments

@hroncok
Copy link

hroncok commented Dec 9, 2024

Hello. I am the Python maintainer in Fedora and I have very little experience with SWIG.

When we updated Python from 3.13.0 to 3.13.1, we discovered a segfault in SWIG-generated Python bindings for libdnf.

The segfault pre-existed before Python 3.13.1, but it was discovered due to python/cpython#127682

tl;dr this is in the SWIG .i file:

%inline %{
template<class T>
class Iterator {
public:
    Iterator(typename T::iterator _cur, typename T::iterator _end) : cur(_cur), end(_end) {}
    Iterator* __iter__()
    {
        return this;
    }

    typename T::iterator cur;
    typename T::iterator end;
  };
%}
...
%extend libdnf::OptionBinds {
    ...
    Iterator<libdnf::OptionBinds> __iter__()
    {
        return Iterator<libdnf::OptionBinds>($self->begin(), $self->end());
    }
}

And in Python:

>>> it1 = iter(...OptionBinds instance...)
>>> it2 = iter(it1)
>>> next(it1)
'one'
>>> next(it2)
'two'
>>> next(it1)
'three'
>>> next(it2)
'four'
>>> next(it1)
...
StopIteration: End of iterator
>>> next(it2)
...
StopIteration: End of iterator
>>> next(it1)
...
StopIteration: End of iterator

However, it1 and it2 are different Python objects and accessing the second one after deleting the first leads to segafult:

>>> del it1
>>> next(it2)
Segmentation fault (core dumped)

The easiest way to get the segfault is next(iter(iter(...OptionBinds instance...))).

I believe the problem is that both Python objects wrap a signed C object and deleting one of the Python objects frees some memory that the other Python object still references.

Some of the backtraces are available at https://bugzilla.redhat.com/show_bug.cgi?id=2330562#c13
The code lives at https://github.com/rpm-software-management/libdnf/blob/79ed383cd5a822e6d8d9d549835383f5c5106204/bindings/swig/conf.i#L70

I avoided the problem by replacing the __iter__ implementation with pury Python in rpm-software-management/libdnf#1682

@wsfulton
Copy link
Member

I don't know which container the iterators are iterating through, but many iterators pointing to an STL container are invalidated if you delete an object in the container. The details are missing/I couldn't figure them out, so this seems expected to me. It's a case of don't do that as it's documented as undefined behaviour.

@hroncok
Copy link
Author

hroncok commented Dec 12, 2024

My thought was this: if a C object returns this deleting the "original" Python object should not free any memory of the shared underlying structures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants