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 694448 - Segmentation fault when calling SecretValue.get() in Python
Segmentation fault when calling SecretValue.get() in Python
Status: RESOLVED FIXED
Product: libsecret
Classification: Other
Component: General
unspecified
Other Linux
: Normal normal
: ---
Assigned To: libsecret maintainer(s)
libsecret maintainer(s)
Depends on:
Blocks:
 
 
Reported: 2013-02-22 13:57 UTC by Dmitry Shachnev
Modified: 2013-02-26 14:57 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
The test case (784 bytes, text/plain)
2013-02-24 10:10 UTC, Stef Walter
  Details
Fix introspection for secret_value_get() to return a uint8 (3.11 KB, patch)
2013-02-25 12:59 UTC, Stef Walter
committed Details | Review

Description Dmitry Shachnev 2013-02-22 13:57:30 UTC
When trying to run this example script: http://paste.debian.net/236870/,

I get a segmentation fault.

  • #0 PyUnicodeUCS4_FromString
    at ../Objects/unicodeobject.c line 563
  • #1 ??
    from /usr/lib/python3/dist-packages/gi/_gi.cpython-32mu.so
  • #2 ??
    from /usr/lib/python3/dist-packages/gi/_gi.cpython-32mu.so
  • #3 ??
    from /usr/lib/python3/dist-packages/gi/_gi.cpython-32mu.so
  • #4 ext_do_call
    at ../Python/ceval.c line 4233
  • #5 PyEval_EvalFrameEx
  • #6 PyEval_EvalCodeEx
  • #7 fast_function
    at ../Python/ceval.c line 4019
  • #8 call_function
    at ../Python/ceval.c line 3942
  • #9 PyEval_EvalFrameEx
  • #10 PyEval_EvalCodeEx
  • #11 PyEval_EvalCode
  • #12 run_mod.19852
  • #13 PyRun_FileExFlags
  • #14 PyRun_SimpleFileExFlags
    at ../Python/pythonrun.c line 1299
  • #15 PyRun_AnyFileExFlags
    at ../Python/pythonrun.c line 1068
  • #16 run_file
    at ../Modules/main.c line 307
  • #17 Py_Main
    at ../Modules/main.c line 733
  • #18 main
    at ../Modules/python.c line 65

Comment 1 Dmitry Shachnev 2013-02-22 14:02:45 UTC
Here is a better stacktrace (with pygobject dbg symbols installed).

  • #0 __strlen_sse2
    at ../sysdeps/i386/i686/multiarch/strlen-sse2.S line 46
  • #1 PyUnicodeUCS4_FromString
    at ../Objects/unicodeobject.c line 563
  • #2 _pygi_marshal_to_py_utf8
    at /build/buildd-pygobject_3.2.2-1-i386-pG4vUD/pygobject-3.2.2/gi/pygi-marshal-to-py.c line 224
  • #3 _pygi_marshal_to_py_array
    at /build/buildd-pygobject_3.2.2-1-i386-pG4vUD/pygobject-3.2.2/gi/pygi-marshal-to-py.c line 371
  • #4 _invoke_marshal_out_args
    at /build/buildd-pygobject_3.2.2-1-i386-pG4vUD/pygobject-3.2.2/gi/pygi-invoke.c line 521
  • #5 _wrap_g_callable_info_invoke
  • #6 PyCFunction_Call
  • #7 ext_do_call
    at ../Python/ceval.c line 4233
  • #8 PyEval_EvalFrameEx
  • #9 PyEval_EvalCodeEx
  • #10 fast_function
    at ../Python/ceval.c line 4019
  • #11 call_function
    at ../Python/ceval.c line 3942
  • #12 PyEval_EvalFrameEx
  • #13 PyEval_EvalCodeEx
  • #14 PyEval_EvalCode
  • #15 run_mod
  • #16 PyRun_FileExFlags
  • #17 PyRun_SimpleFileExFlags
    at ../Python/pythonrun.c line 1299
  • #18 PyRun_AnyFileExFlags
    at ../Python/pythonrun.c line 1068
  • #19 run_file
    at ../Modules/main.c line 307
  • #20 Py_Main
    at ../Modules/main.c line 733
  • #21 main
    at ../Modules/python.c line 65

I've also forgot to mention that I'm running Debian sid with the latest updates.
Comment 2 Stef Walter 2013-02-22 14:06:17 UTC
Colud you attach your test.py? Or a small test case which reproduces this problem? It's hard to tell from the stack trace on how search_sync() is being called.
Comment 3 Dmitry Shachnev 2013-02-22 14:37:20 UTC
My test.py is exactly what I linked above, here is a raw/downloadable view if you prefer: http://paste.debian.net/plain/236870.
Comment 4 Stef Walter 2013-02-22 17:17:23 UTC
Ooops, sorry for missing the pastebin link :S

This looks like a crash in pygobject. Can reproduce here too. I checked and the value that libsecret is returning from secret_value_get() is kosher. The length is 12 which is correct for "the password".
Comment 5 Stef Walter 2013-02-22 17:18:56 UTC
For reference and debugging, here is the implementation of secret_value_get() and its introspection. I tried removing the (out) from the length parameter, and this didn't fix the crash. Any ideas from the pygobject guys are welcome.

http://git.gnome.org/browse/libsecret/tree/libsecret/secret-value.c#n151
Comment 6 Tomeu Vizoso 2013-02-22 18:44:31 UTC
(In reply to comment #4)
> Ooops, sorry for missing the pastebin link :S
> 
> This looks like a crash in pygobject. Can reproduce here too. I checked and the
> value that libsecret is returning from secret_value_get() is kosher. The length
> is 12 which is correct for "the password".

Can you check what's the value that gets passed to PYGLIB_PyUnicode_FromString?

http://git.gnome.org/browse/pygobject/tree/gi/pygi-marshal-to-py.c#n301
Comment 7 Simon Feltman 2013-02-22 23:14:10 UTC
The problem here is PyGObject is assuming the array is null terminated (PYGLIB_PyUnicode_FromString) but in this case it is given an explicit length.
Comment 8 Simon Feltman 2013-02-22 23:55:15 UTC
Actually the problem is slightly different than this. It seems we are trying to marshal to a unicode string per-item in the array. Besides being wrong, this should be optimized out to just using PyUnicode_FromStringAndSize for the whole array early on, similar to what we do for an array of GI_TYPE_TAG_UINT8.

http://git.gnome.org/browse/pygobject/tree/gi/pygi-marshal-to-py.c#n392
Comment 9 Stef Walter 2013-02-24 10:10:32 UTC
Created attachment 237267 [details]
The test case

Since this was requested on IRC by Tomeu: attached test case downloaded from pastebin.
Comment 10 Simon Feltman 2013-02-24 10:23:13 UTC
It turns out PyGObject is attempting the correct behaviour as far as I can tell. GI_TYPE_TAG_UTF8 specifies "a UTF-8 encoded string" and the return annotation from secret_value_get shows the following in the gir:

        <return-value transfer-ownership="none">
          <doc xml:whitespace="preserve">the secret data</doc>
          <array length="0" zero-terminated="0" c:type="gchar*">
            <type name="utf8" c:type="gchar"/>
          </array>
        </return-value>

To me the basic type information here means an array of utf8 encoded strings (the c:type obviously shows otherwise). The special case mentioned in comment #8 about GI_TYPE_TAG_UINT8 seems to be for this use case (length terminated string). A brief look at Gjs marshaling and this problem would show up there as well.

Changing the return annotation for secret_get_value to the following fixes the problem:

Returns: (array length=length) (element-type guint8): the secret data

This produces the following gir which PyGObject interprets correctly as a PyString/PyBytes:

        <return-value transfer-ownership="none">
          <doc xml:whitespace="preserve">the secret data</doc>
          <array length="0" zero-terminated="0" c:type="gchar*">
            <type name="guint8"/>
          </array>
        </return-value>

Moving back to libsecret but this could also be considered a GI bug. GI should give an error about this instead of producing a bad gir or it should give a better default for char pointers with length params.
Comment 11 Simon Feltman 2013-02-25 06:19:03 UTC
Note this bug is also related to bug 622123 and it seems there has been some historic problems trying to use a utf8 strings with a length parameter.
Comment 12 Stef Walter 2013-02-25 12:59:42 UTC
Created attachment 237353 [details] [review]
Fix introspection for secret_value_get() to return a uint8

Does the attached patch work for you?

This works around a crash in pygobject. It seems to me this should be fixed elsewhere as well. Either with a giscanner warning or not crashing in pygobject. But that's up to the pygobject maintainers.
Comment 13 Simon Feltman 2013-02-26 12:01:50 UTC
Stef,

I logged bug 694735 to see if we can get better scanner/binding support. An issue with the uint8 workaround is it will always return a raw PyString/PyBytes without attempting to decode it from utf8. If this is a problem, another workaround would be to have a "binding friendly" function which uses a "Rename to" annotation. This new function would just wrap the old one but not return the length and instead guarantee a zero terminated string is returned.

As for pygobject crashing I don't think there is anything we can do because the only way to interpret the annotations we are given is as an array of utf8 strings, essentially like a GStrv with a specific length.
Comment 14 Stef Walter 2013-02-26 13:31:55 UTC
I think it's appropriate to SecretValue.get() be represented by 'bytes' in python 3 and a non-unicode string in python 2.x. A secret value can be binary data. It's up to the caller to either:

 * Use SecretValue.get_content_type() to determine whether it's text and
   convert.
 * Or use the high level Secret.password_store() functions and friends
   which operate only on text passwords.

Dmitry or Simon, could you review the attached patch before I apply it to libsecret?
Comment 15 Dmitry Shachnev 2013-02-26 14:52:58 UTC
The patch fixes the crash for me. However, if SecretValue.get() returns bytes, why doesn't password_store() accept bytes?

>>> Secret.password_store_sync(EXAMPLE_SCHEMA, attributes,
... Secret.COLLECTION_DEFAULT, 'the label', b'the password', None)
Traceback (most recent call last):
  • File "<stdin>", line 2 in <module>
  • File "/usr/lib/python3/dist-packages/gi/types.py", line 43 in function
    return info.invoke(*args, **kwargs)
TypeError: Must be string, not bytes

Comment 16 Stef Walter 2013-02-26 14:55:14 UTC
(In reply to comment #15)
> The patch fixes the crash for me. However, if SecretValue.get() returns bytes,
> why doesn't password_store() accept bytes?

The password_xxx() functions operate on text passwords. That's the simple API. The lower level API operates on raw binary data.
Comment 17 Stef Walter 2013-02-26 14:57:04 UTC
Attachment 237353 [details] pushed as ddd9bdd - Fix introspection for secret_value_get() to return a uint8
Comment 18 Dmitry Shachnev 2013-02-26 14:57:26 UTC
(In reply to comment #16)

Makes sense then, thanks.