After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 693121 - Integer overflow on Gdk.ModifierType.RELEASE_MASK
Integer overflow on Gdk.ModifierType.RELEASE_MASK
Status: RESOLVED FIXED
Product: pygobject
Classification: Bindings
Component: introspection
3.4.x
Other Linux
: Normal normal
: GNOME 3.8
Assigned To: Martin Pitt
Python bindings maintainers
Depends on:
Blocks:
 
 
Reported: 2013-02-04 05:33 UTC by Mathieu Bridon
Modified: 2013-04-03 03:24 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
current diff for debugging (BROKEN!) (11.81 KB, text/plain)
2013-02-26 14:39 UTC, Martin Pitt
  Details
Fix signedness, overflow checking, and 32 bit overflow of GFlags (15.02 KB, patch)
2013-02-27 07:38 UTC, Martin Pitt
committed Details | Review

Description Mathieu Bridon 2013-02-04 05:33:36 UTC
I'm using pygobject on Fedora 18.

On a 32 bits machine:
  $ rpm -q python3-gobject pygobject3
  python3-gobject-3.4.2-6.fc18.i686
  pygobject3-3.4.2-6.fc18.i686

On a 64 bits machine:
  $ rpm -q python3-gobject pygobject3
  python3-gobject-3.4.2-6.fc18.x86_64
  pygobject3-3.4.2-6.fc18.x86_64

On the 64 bits machine, everything works fine, both Python 2 and Python 3:
  >>> from gi.repository import Gdk
  >>> Gdk.ModifierType.RELEASE_MASK
  <flags GDK_RELEASE_MASK of type GdkModifierType>
  >>> 0 | Gdk.ModifierType.RELEASE_MASK
  1073741824

On the 32 bits machine, Python 2 works:
  >>> from gi.repository import Gdk
  >>> Gdk.ModifierType.RELEASE_MASK
  <flags GDK_RELEASE_MASK of type GdkModifierType>
  >>> 0 | Gdk.ModifierType.RELEASE_MASK
  1073741824

On the 32 bits machine, Python 3 is having troubles:
  >>> from gi.repository import Gdk
  >>> Gdk.ModifierType.RELEASE_MASK
  <flags GDK_SHIFT_MASK | GDK_LOCK_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_MOD2_MASK | GDK_MOD3_MASK | GDK_MOD4_MASK | GDK_MOD5_MASK | GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK | GDK_BUTTON4_MASK | GDK_BUTTON5_MASK | GDK_MODIFIER_RESERVED_13_MASK | GDK_MODIFIER_RESERVED_14_MASK | GDK_MODIFIER_RESERVED_15_MASK | GDK_MODIFIER_RESERVED_16_MASK | GDK_MODIFIER_RESERVED_17_MASK | GDK_MODIFIER_RESERVED_18_MASK | GDK_MODIFIER_RESERVED_19_MASK | GDK_MODIFIER_RESERVED_20_MASK | GDK_MODIFIER_RESERVED_21_MASK | GDK_MODIFIER_RESERVED_22_MASK | GDK_MODIFIER_RESERVED_23_MASK | GDK_MODIFIER_RESERVED_24_MASK | GDK_MODIFIER_RESERVED_25_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK | GDK_MODIFIER_RESERVED_29_MASK | GDK_RELEASE_MASK | GDK_MODIFIER_MASK of type GdkModifierType>Traceback (most recent call last):
  • File "<stdin>", line 1 in <module>
    OverflowError: Python int too large to convert to C long   >>> 0 | Gdk.ModifierType.RELEASE_MASK   3393024163840

Comment 1 Mathieu Bridon 2013-02-04 09:16:02 UTC
I can reproduce the bug with Rawhide:
  $ rpm -q python3-gobject pygobject3
  python3-gobject-3.7.4-1.fc19.i686
  pygobject3-3.7.4-1.fc19.i686

As before, on a 32 bits machine, Python 2 works but Python 3 doesn't with the same error message (but the integer overflows to a different value though)
Comment 2 Tomeu Vizoso 2013-02-07 09:55:52 UTC
I'm not sure of this, but as help was requested on #python, here is my best guess:

Values are assigned to the enum Python object here:

http://git.gnome.org/browse/pygobject/tree/gi/module.py#n166

That get_value method is implemented here (assuming get_values returns ValueInfos):

http://git.gnome.org/browse/pygobject/tree/gi/pygi-info.c#n1279

So I would check why that PYGLIB_PyLong_FromLong call isn't doing what it should.
Comment 3 Simon Feltman 2013-02-07 10:37:21 UTC
Thanks Tomeu, I was actually just wondering how the enum vars were stuck on the the class as the static bindings didn't do it.

I don't have a 32 bit machine to test this on but running the following might give some hints:

Gdk.ModifierType.mro()

for value in Gdk.ModifierType.__info__.get_values():
    if value.get_name_unescaped() == 'release_mask':
        print('%s = 0x%02x' % (value.get_name_unescaped(), value.get_value()))

hex(Gdk.ModifierType.RELEASE_MASK)

hex(GObject.G_MAXLONG)


As Tomeu pointed out, it is most likely a problem in PYGLIB_PyLong_FromLong. The output of loop printing will at least verify the "get_value" method is returning a bad value and it is not caused by wrapping it with the Flags object. The correct answer is that they should both read "0x40000000".
Comment 4 Mathieu Bridon 2013-02-07 11:11:47 UTC
Hah!

I think you're onto something Simon.

Here's what I get on Python 3:

>>> Gdk.ModifierType.mro()
[<class 'gi.repository.Gdk.GdkModifierType'>, <class 'gobject.GFlags'>, <class 'int'>, <class 'object'>]
>>> for value in Gdk.ModifierType.__info__.get_values():
...     if value.get_name_unescaped() == 'release_mask':
...         print('%s = 0x%02x' % (value.get_name_unescaped(), value.get_value()))
... 
release_mask = 0x40000000
>>> hex(Gdk.ModifierType.RELEASE_MASK)
0x37f200000000
>>> hex(GObject.G_MAXLONG)
0x7fffffff

Notice how it's different from what I get on Python 2:
>>> Gdk.ModifierType.mro()
[<class 'gi.repository.Gdk.GdkModifierType'>, <type 'gobject.GFlags'>, <type 'int'>, <type 'object'>]
>>> for value in Gdk.ModifierType.__info__.get_values():
...     if value.get_name_unescaped() == 'release_mask':
...         print('%s = 0x%02x' % (value.get_name_unescaped(), value.get_value()))
... 
release_mask = 0x40000000
>>> hex(Gdk.ModifierType.RELEASE_MASK)
0x40000000
>>> hex(GObject.G_MAXLONG)
0x7fffffff

That first hex value on Python 3 looks very wrong. Also, I get a different value every time I run it. (on Python 3 only, it is stable on Python 2)
Comment 5 Mathieu Bridon 2013-02-07 11:13:32 UTC
By the way, the results from commt 4 were obtained with the latest pygobject:
  $ rpm -q python3-gobject pygobject3
  python3-gobject-3.7.5.1-1.fc19.i686
  pygobject3-3.7.5.1-1.fc19.i686
Comment 6 Martin Pitt 2013-02-26 09:53:11 UTC
I built an i386 schroot and can reproduce.
Comment 7 Martin Pitt 2013-02-26 14:39:56 UTC
Created attachment 237444 [details]
current diff for debugging (BROKEN!)

This initially seemed very easy, but it seems I opened a can of worms here.

First of all, GFlagsValue.value is a guint, so everything that handles flags right now is currently doing it wrong. It happens to work on 64 bit because long just happens to be 64 bit, and we don't have that large flag values. So I defined two new aliases PYGLIB_PyLong_FromUnsignedLong() and PYGLIB_PyLong_AsUnsignedLong() (for Python 3 for now, I'll worry about Python 2 once this works at all), and fixed pygflags.c and some other places to use unsigned longs.

However, this is still not enough. The crux of the problem is in pyg_flags_val_new(). When this is being called for Gdk.ModifierType.RELEASE_MASK (0x40000000, first value to exceed MAX_INT), stepping through gives:

43	    item =  (&PYGLIB_PyLong_Type)->tp_new((PyTypeObject*)subclass, args, NULL);
(gdb) call PyLong_AsUnsignedLong(item)
$18 = 1073741824
(gdb) call PyLong_AsUnsignedLongMask(item)
$19 = 1073741824
(gdb) call PyErr_Occurred()
$20 = (PyObject *) 0x0

which is the correct value. Things go haywire as soon as gtype is assigned a bit further down:

47	    ((PyGEnum*)item)->gtype = gtype;

Right before this, PyLong_AsUnsignedLong{,Mask}() both returned correct values. But as soon as ->gtype gets assigned, they both fail:

(gdb) n
49	    return item;
(gdb) call PyLong_AsUnsignedLongMask(item)
$31 = 0
(gdb) call PyErr_Occurred()
$32 = (PyObject *) 0x0
(gdb) call PyLong_AsUnsignedLong(item)
$33 = 4294967295
(gdb) call PyErr_Print()
OverflowError: python int too large to convert to C unsigned long

I. e. assigning the ->gtype field somehow breaks integer conversion.

I was thinking that this would require implementing __int__/__long__, so I extended pyg_flags_as_number to point to a new pyg_flags_int() method. Note that this is totally unclean, but as the function is currently not called at all (I ensured this by adding an abort() at the top), that's not the problem either.

So we need to debug what's breaking conversion of a PyGFlags to an int.
Comment 8 Martin Pitt 2013-02-26 14:43:05 UTC
For the record, I removed nb_coerce for Python 3 from the patch (http://docs.python.org/3.3/c-api/typeobj.html#number-structs), pyg_flags_int() is still not being called.
Comment 9 Martin Pitt 2013-02-26 15:33:48 UTC
Simon, as author of http://git.gnome.org/browse/pygobject/commit/?id=3e3525e93d8 you can perhaps explain this to me:

+ item = (&PYGLIB_PyLong_Type)->tp_new((PyTypeObject*)subclass, args, NULL);

item later gets cast into PyGFlags, although it's not really clear what type subclass is. For example, when being called from pyg_flags_add() it is this weird "stub" type, which looks like PyGFlags_Type:

    stub = PyObject_CallFunction((PyObject *)&PyType_Type, "s(O)O",
                                 typename, (PyObject *)&PyGFlags_Type,
                                 instance_dict);

I don't know what this does.

However, I experimented with various types in pyg_flags_val_new() including hardcoding PyGFlags_Type, and none of that made the bug disappear, so I guess it's not due to mis-casting a different object into a PyGFlags and partially overwriting its data with the gtype.
Comment 10 Simon Feltman 2013-02-26 22:23:44 UTC
I know it is confusing, hence bug 682405.

(In reply to comment #9)
> Simon, as author of
> http://git.gnome.org/browse/pygobject/commit/?id=3e3525e93d8 you can perhaps
> explain this to me:
> 
> + item = (&PYGLIB_PyLong_Type)->tp_new((PyTypeObject*)subclass, args, NULL);
>
> item later gets cast into PyGFlags, although it's not really clear what type
> subclass is. For example, when being called from pyg_flags_add() it is this
> weird "stub" type, which looks like PyGFlags_Type:
> 
>     stub = PyObject_CallFunction((PyObject *)&PyType_Type, "s(O)O",
>                                  typename, (PyObject *)&PyGFlags_Type,
>                                  instance_dict);
> 
> I don't know what this does.

Crazy right? it is dynamically generating a subclass, pure Python would look something like this:

stub = type('Name', (GObject.GFlags,), {})

If you look at my commit, I added had actually added a comment to pyg_enum_add stating this (but not pyg_flags_add). I recall it took some time to decode what the that was :)

A side note, there is a copy paste but in my commit, although it should have no affect:
http://git.gnome.org/browse/pygobject/tree/gi/_gobject/pygflags.c?h=pygobject-3-4#n47

Shows a cast of the "item" into a PyGEnum (should be PyGFlags). That probably shouldn't matter so much because they are basically the same thing.

I had a quick read through: http://www.python.org/dev/peps/pep-0253/
Specifically the section "Creating a subtype of a built-in type in C"

Specifically I noticed we do not set the "tp_basicsize" slot of the the PyGFlags or PyGEnum types, this needs to be set to sizeof(PyGFlags) and sizeof(PyGEnum) which might be the problem here.

Another note is we probably don't even need to store the GType per instance of these items because
1) we should be able to store it on the class
2) we probably don't need such strict type checking or could just use python type checking (issubclass, etc)

but that can be saved for another time.
Comment 11 Simon Feltman 2013-02-27 04:36:32 UTC
(In reply to comment #9)
> Simon, as author of
> http://git.gnome.org/browse/pygobject/commit/?id=3e3525e93d8 you can perhaps
> explain this to me:
> 
> + item = (&PYGLIB_PyLong_Type)->tp_new((PyTypeObject*)subclass, args, NULL);
> 
> item later gets cast into PyGFlags, although it's not really clear what type
> subclass is.

It should always be a sub-type of PyGFlags_Type. Specifying this explicitly as the argument type or asserting about it would not be a bad thing.


(In reply to comment #10)
> Specifically I noticed we do not set the "tp_basicsize" slot of the the
> PyGFlags or PyGEnum types, this needs to be set to sizeof(PyGFlags) and
> sizeof(PyGEnum) which might be the problem here.

Turns out the tp_basicsize is already setup by PYGLIB_DEFINE_TYPE.

Not sure if any of this is helpful but here are some additional details about size attributes on my machine:

Python 3.3 64 bit:
sizeof(PyLongObject)         int         32	
PyLong_Type.tp_basicsize     Py_ssize_t  24	
PyLong_Type.tp_itemsize      Py_ssize_t  4	
sizeof(PyGFlags)	     int         40	
sizeof(GType)                int         8	
PyGFlags_Type.tp_basicsize   Py_ssize_t  40	
PyGFlags_Type.tp_itemsize    Py_ssize_t  0	


Python 2.7 64 bit:
sizeof(PyIntObject)          int         24	
PyInt_Type.tp_basicsize      Py_ssize_t  24	
PyInt_Type.tp_itemsize       Py_ssize_t  0	
sizeof(PyGFlags)             int         32	
sizeof(GType)                int         8	
PyGFlags_Type.tp_basicsize   Py_ssize_t  32	
PyGFlags_Type.tp_itemsize    Py_ssize_t  0	


PyIntObject and PyLongObject have a different overall size due to PyIntObject using PyObject_HEAD and PyLongObject using PyObject_VAR_HEAD. It would be interesting to see what this looks like for 32 bits but it might not be relevant.
Comment 12 Martin Pitt 2013-02-27 07:38:00 UTC
Created attachment 237496 [details] [review]
Fix signedness, overflow checking, and 32 bit overflow of GFlags

I got it working now, tested on 32 and 64 bit architectures with Python 2.7 and 3.3.

The bogus numbers were due to PyLong_FromUnsignedLong() taking the adjacent GType value into account when interpreting a PyLong subclass such as PyGFlags. I don't fully understand why this happens, as I would expect it to have an internal representation how many digits the struct has. My hunch is that it's actually taking the size of the data type into account, according to /usr/include/python3.3m/longintrepr.h:

/* Long integer representation.
   The absolute value of a number is equal to
        SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i)

So I added an extra zero padding to the struct, which helps:

 typedef struct {
     PYGLIB_PyLongObject parent;
+    int zero_pad; /* must always be 0 */
     GType gtype;
 } PyGFlags;

I'm not sure whether that's guaranteed to succeed for all possible corner cases, but at least the test cases now cover representation and arithmetic with flags whose values exceed MAX_INT.
Comment 13 Simon Feltman 2013-02-27 07:56:42 UTC
Review of attachment 237496 [details] [review]:

Looks good to me. Thanks for tackling this!
Comment 14 Dave Malcolm 2013-03-29 20:03:11 UTC
This patch appears to break pygobject on big-endian 64-bit architectures (seen on ppc64 box with 3.7.92).

[I think for any python class subclassing GObject.GObject, due to 
PARAM_READABLE, PARAM_WRITABLE and PARAM_READWRITE all erroneously being 0 and thus
(process:39448): GLib-GObject-CRITICAL **: g_object_class_install_property: assertion `pspec->flags & (G_PARAM_READABLE | G_PARAM_WRITABLE)' failed
]

See https://bugzilla.redhat.com/show_bug.cgi?id=924425 for more details (the hard failure goes away if I undo caeeeb7e4282e183eefc3c53b2d53c8c2bb7de89)
Comment 15 Simon Feltman 2013-03-31 09:00:52 UTC
Hi Dave,
I just fixed a potentially related stack corruption bug (bug 696892). Please try the latest pygobject-3-8 or master branch to see if it helps.
Comment 16 Simon Feltman 2013-04-03 03:24:18 UTC
Closing as the patch mentioned seemed to have fixed the problem:
https://bugzilla.redhat.com/show_bug.cgi?id=924425