Page MenuHome GnuPG

python gpg segfaults when first parameter to gpg.Context().op_genkey() is None or otherwise malformed
Closed, ResolvedPublic

Description

here are two different examples of a failed op_genkey that crashes the python interpreter:

$ python3 -c 'import gpg; c = gpg.Context(); c.op_genkey(None,None,None)'
Segmentation fault
$ python3 -c 'import gpg; c = gpg.Context(); parms="<GnupgKeyParms format=\"internal\">\nKey-Type: default\nName-Real: monkeyman\n%commit\n"; c.op_genkey(parms,None,None)'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/lib/python3/dist-packages/gpg/core.py", line 158, in wrapper
    return _funcwrap(self, *args)
  File "/usr/lib/python3/dist-packages/gpg/core.py", line 141, in _funcwrap
    return errorcheck(result, name)
  File "/usr/lib/python3/dist-packages/gpg/errors.py", line 129, in errorcheck
    raise GPGMEError(retval, extradata)
gpg.errors.GPGMEError: gpgme_op_genkey: GPGME: Cannot allocate memory
Segmentation fault

python should never segfault -- errors should raise exceptions.

Event Timeline

dkg renamed this task from python gpg segfaults when first parameter to gpg.Context().op_genkey() is None to python gpg segfaults when first parameter to gpg.Context().op_genkey() is None or otherwise malformed.Oct 18 2018, 8:14 AM
dkg updated the task description. (Show Details)

Is this new in gpgme 1.12 or might it also be in older versions?

It the first error (first param = None) is a segfault in versions 1.11.1-2 (debian unstable i386) and 1.8.0-3+b2 (debian stretch amd64).

the second error (malformed(?) first param) is a segfault in 1.11.1-2 (debian unstable i386), but on 1.8.0-3+b2 (debian stretch amd64) results in *** Error in python3: double free or corruption (top): 0x00005601e17f0260 *** with the following backtrace+abort:

$ python3 -c 'import gpg; c = gpg.Context(); parms="<GnupgKeyParms format=\"internal\">\nKey-Type: default\nName-Real: monkeyman\n%commit\n"; c.op_genkey(parms,None,None)'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/lib/python3/dist-packages/gpg/core.py", line 151, in wrapper
    return _funcwrap(self, *args)
  File "/usr/lib/python3/dist-packages/gpg/core.py", line 135, in _funcwrap
    return errorcheck(result, "Invocation of " + name)
  File "/usr/lib/python3/dist-packages/gpg/errors.py", line 62, in errorcheck
    raise GPGMEError(retval, extradata)
gpg.errors.GPGMEError: Invocation of gpgme_op_genkey: GPGME: Cannot allocate memory
*** Error in `python3': double free or corruption (top): 0x00005601e17f0260 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x70bfb)[0x7f815d02abfb]
/lib/x86_64-linux-gnu/libc.so.6(+0x76fc6)[0x7f815d030fc6]
/lib/x86_64-linux-gnu/libc.so.6(+0x7780e)[0x7f815d03180e]
/usr/lib/x86_64-linux-gnu/libgpgme.so.11(gpgme_result_unref+0x4d)[0x7f815c8c2d2d]
/usr/lib/x86_64-linux-gnu/libgpgme.so.11(+0x2fdad)[0x7f815c8c2dad]
/usr/lib/x86_64-linux-gnu/libgpgme.so.11(gpgme_release+0x60)[0x7f815c8c4690]
/usr/lib/python3/dist-packages/gpg/_gpgme.cpython-35m-x86_64-linux-gnu.so(+0x211b3)[0x7f815caf91b3]
python3(PyCFunction_Call+0x4f)[0x5601df43025f]
python3(PyEval_EvalFrameEx+0x48ef)[0x5601df3fad0f]
python3(PyEval_EvalCodeEx+0x12f)[0x5601df3e816f]
python3(+0x1c20e3)[0x5601df4320e3]
python3(PyObject_Call+0x47)[0x5601df47be17]
python3(+0x12934e)[0x5601df39934e]
python3(PyObject_Call+0x47)[0x5601df47be17]
python3(PyEval_CallObjectWithKeywords+0x7d)[0x5601df3e6f7d]
python3(+0x1b7f4f)[0x5601df427f4f]
python3(PyObject_CallFinalizer+0x85)[0x5601df4428a5]
python3(PyObject_CallFinalizerFromDealloc+0x1a)[0x5601df44296a]
python3(+0x1d9527)[0x5601df449527]
python3(+0x1f6ee2)[0x5601df466ee2]
python3(+0x1d2a63)[0x5601df442a63]
python3(PyDict_SetItem+0x279)[0x5601df465ff9]
python3(PyImport_Cleanup+0x1ea)[0x5601df3cdbaa]
python3(Py_Finalize+0x61)[0x5601df4c71b1]
python3(Py_Main+0x641)[0x5601df4f54d1]
python3(main+0xe1)[0x5601df384d71]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf1)[0x7f815cfda2e1]
python3(_start+0x2a)[0x5601df48ba7a]
======= Memory map: ========
5601df270000-5601df660000 r-xp 00000000 fe:01 20229                      /usr/bin/python3.5
5601df85f000-5601df861000 r--p 003ef000 fe:01 20229                      /usr/bin/python3.5
5601df861000-5601df8f8000 rw-p 003f1000 fe:01 20229                      /usr/bin/python3.5
5601df8f8000-5601df929000 rw-p 00000000 00:00 0 
5601e166f000-5601e1807000 rw-p 00000000 00:00 0                          [heap]
7f8157de9000-7f8157dff000 r-xp 00000000 fe:01 16523                      /lib/x86_64-linux-gnu/libgcc_s.so.1
7f8157dff000-7f8157ffe000 ---p 00016000 fe:01 16523                      /lib/x86_64-linux-gnu/libgcc_s.so.1
7f8157ffe000-7f8157fff000 r--p 00015000 fe:01 16523                      /lib/x86_64-linux-gnu/libgcc_s.so.1
7f8157fff000-7f8158000000 rw-p 00016000 fe:01 16523                      /lib/x86_64-linux-gnu/libgcc_s.so.1
7f8158000000-7f8158021000 rw-p 00000000 00:00 0 
7f8158021000-7f815c000000 ---p 00000000 00:00 0 
7f815c1c5000-7f815c1c9000 r-xp 00000000 fe:01 30232                      /usr/lib/python3.5/lib-dynload/termios.cpython-35m-x86_64-linux-gnu.so
7f815c1c9000-7f815c3c8000 ---p 00004000 fe:01 30232                      /usr/lib/python3.5/lib-dynload/termios.cpython-35m-x86_64-linux-gnu.so
7f815c3c8000-7f815c3c9000 r--p 00003000 fe:01 30232                      /usr/lib/python3.5/lib-dynload/termios.cpython-35m-x86_64-linux-gnu.so
7f815c3c9000-7f815c3cb000 rw-p 00004000 fe:01 30232                      /usr/lib/python3.5/lib-dynload/termios.cpython-35m-x86_64-linux-gnu.so
7f815c3cb000-7f815c46c000 rw-p 00000000 00:00 0 
7f815c46c000-7f815c47f000 r-xp 00000000 fe:01 1865                       /lib/x86_64-linux-gnu/libgpg-error.so.0.21.0
7f815c47f000-7f815c67e000 ---p 00013000 fe:01 1865                       /lib/x86_64-linux-gnu/libgpg-error.so.0.21.0
7f815c67e000-7f815c67f000 r--p 00012000 fe:01 1865                       /lib/x86_64-linux-gnu/libgpg-error.so.0.21.0
7f815c67f000-7f815c680000 rw-p 00013000 fe:01 1865                       /lib/x86_64-linux-gnu/libgpg-error.so.0.21.0
7f815c680000-7f815c692000 r-xp 00000000 fe:01 8497                       /usr/lib/x86_64-linux-gnu/libassuan.so.0.7.3
7f815c692000-7f815c891000 ---p 00012000 fe:01 8497                       /usr/lib/x86_64-linux-gnu/libassuan.so.0.7.3
7f815c891000-7f815c892000 r--p 00011000 fe:01 8497                       /usr/lib/x86_64-linux-gnu/libassuan.so.0.7.3
7f815c892000-7f815c893000 rw-p 00012000 fe:01 8497                       /usr/lib/x86_64-linux-gnu/libassuan.so.0.7.3
7f815c893000-7f815c8d6000 r-xp 00000000 fe:01 27593                      /usr/lib/x86_64-linux-gnu/libgpgme.so.11.17.0
7f815c8d6000-7f815cad5000 ---p 00043000 fe:01 27593                      /usr/lib/x86_64-linux-gnu/libgpgme.so.11.17.0
7f815cad5000-7f815cad6000 r--p 00042000 fe:01 27593                      /usr/lib/x86_64-linux-gnu/libgpgme.so.11.17.0
7f815cad6000-7f815cad8000 rw-p 00043000 fe:01 27593                      /usr/lib/x86_64-linux-gnu/libgpgme.so.11.17.0
7f815cad8000-7f815cb77000 r-xp 00000000 fe:01 263148                     /usr/lib/python3/dist-packages/gpg/_gpgme.cpython-35m-x86_64-linux-gnu.so
7f815cb77000-7f815cd77000 ---p 0009f000 fe:01 263148                     /usr/lib/python3/dist-packages/gpg/_gpgme.cpython-35m-x86_64-linux-gnu.so
7f815cd77000-7f815cd78000 r--p 0009f000 fe:01 263148                     /usr/lib/python3/dist-packages/gpg/_gpgme.cpython-35m-x86_64-linux-gnu.so
7f815cd78000-7f815cd88000 rw-p 000a0000 fe:01 263148                     /usr/lib/python3/dist-packages/gpg/_gpgme.cpython-35m-x86_64-linux-gnu.so
7f815cd88000-7f815cd89000 rw-p 00000000 00:00 0 
7f815cd89000-7f815ce49000 rw-p 00000000 00:00 0 
7f815ce7a000-7f815cfba000 rw-p 00000000 00:00 0 
7f815cfba000-7f815d14f000 r-xp 00000000 fe:01 24652                      /lib/x86_64-linux-gnu/libc-2.24.so
7f815d14f000-7f815d34f000 ---p 00195000 fe:01 24652                      /lib/x86_64-linux-gnu/libc-2.24.so
7f815d34f000-7f815d353000 r--p 00195000 fe:01 24652                      /lib/x86_64-linux-gnu/libc-2.24.so
7f815d353000-7f815d355000 rw-p 00199000 fe:01 24652                      /lib/x86_64-linux-gnu/libc-2.24.so
7f815d355000-7f815d359000 rw-p 00000000 00:00 0 
7f815d359000-7f815d45c000 r-xp 00000000 fe:01 24656                      /lib/x86_64-linux-gnu/libm-2.24.so
7f815d45c000-7f815d65b000 ---p 00103000 fe:01 24656                      /lib/x86_64-linux-gnu/libm-2.24.so
7f815d65b000-7f815d65c000 r--p 00102000 fe:01 24656                      /lib/x86_64-linux-gnu/libm-2.24.so
7f815d65c000-7f815d65d000 rw-p 00103000 fe:01 24656                      /lib/x86_64-linux-gnu/libm-2.24.so
7f815d65d000-7f815d676000 r-xp 00000000 fe:01 4243                       /lib/x86_64-linux-gnu/libz.so.1.2.8
7f815d676000-7f815d875000 ---p 00019000 fe:01 4243                       /lib/x86_64-linux-gnu/libz.so.1.2.8
7f815d875000-7f815d876000 r--p 00018000 fe:01 4243                       /lib/x86_64-linux-gnu/libz.so.1.2.8
7f815d876000-7f815d877000 rw-p 00019000 fe:01 4243                       /lib/x86_64-linux-gnu/libz.so.1.2.8
7f815d877000-7f815d89e000 r-xp 00000000 fe:01 12212                      /lib/x86_64-linux-gnu/libexpat.so.1.6.2
7f815d89e000-7f815da9e000 ---p 00027000 fe:01 12212                      /lib/x86_64-linux-gnu/libexpat.so.1.6.2
7f815da9e000-7f815daa0000 r--p 00027000 fe:01 12212                      /lib/x86_64-linux-gnu/libexpat.so.1.6.2
7f815daa0000-7f815daa1000 rw-p 00029000 fe:01 12212                      /lib/x86_64-linux-gnu/libexpat.so.1.6.2
7f815daa1000-7f815daa3000 r-xp 00000000 fe:01 24671                      /lib/x86_64-linux-gnu/libutil-2.24.so
7f815daa3000-7f815dca2000 ---p 00002000 fe:01 24671                      /lib/x86_64-linux-gnu/libutil-2.24.so
7f815dca2000-7f815dca3000 r--p 00001000 fe:01 24671                      /lib/x86_64-linux-gnu/libutil-2.24.so
7f815dca3000-7f815dca4000 rw-p 00002000 fe:01 24671                      /lib/x86_64-linux-gnu/libutil-2.24.so
7f815dca4000-7f815dca7000 r-xp 00000000 fe:01 24655                      /lib/x86_64-linux-gnu/libdl-2.24.so
7f815dca7000-7f815dea6000 ---p 00003000 fe:01 24655                      /lib/x86_64-linux-gnu/libdl-2.24.so
7f815dea6000-7f815dea7000 r--p 00002000 fe:01 24655                      /lib/x86_64-linux-gnu/libdl-2.24.so
7f815dea7000-7f815dea8000 rw-p 00003000 fe:01 24655                      /lib/x86_64-linux-gnu/libdl-2.24.so
7f815dea8000-7f815dec0000 r-xp 00000000 fe:01 24667                      /lib/x86_64-linux-gnu/libpthread-2.24.so
7f815dec0000-7f815e0bf000 ---p 00018000 fe:01 24667                      /lib/x86_64-linux-gnu/libpthread-2.24.so
7f815e0bf000-7f815e0c0000 r--p 00017000 fe:01 24667                      /lib/x86_64-linux-gnu/libpthread-2.24.so
7f815e0c0000-7f815e0c1000 rw-p 00018000 fe:01 24667                      /lib/x86_64-linux-gnu/libpthread-2.24.so
7f815e0c1000-7f815e0c5000 rw-p 00000000 00:00 0 
7f815e0c5000-7f815e0e8000 r-xp 00000000 fe:01 24648                      /lib/x86_64-linux-gnu/ld-2.24.so
7f815e0fb000-7f815e13b000 rw-p 00000000 00:00 0 
7f815e13b000-7f815e2d6000 r--p 00000000 fe:01 10293                      /usr/lib/locale/locale-archive
7f815e2d6000-7f815e2da000 rw-p 00000000 00:00 0 
7f815e2dd000-7f815e2de000 rw-p 00000000 00:00 0 
7f815e2de000-7f815e2e5000 r--s 00000000 fe:01 24923                      /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
7f815e2e5000-7f815e2e8000 rw-p 00000000 00:00 0 
7f815e2e8000-7f815e2e9000 r--p 00023000 fe:01 24648                      /lib/x86_64-linux-gnu/ld-2.24.so
7f815e2e9000-7f815e2ea000 rw-p 00024000 fe:01 24648                      /lib/x86_64-linux-gnu/ld-2.24.so
7f815e2ea000-7f815e2eb000 rw-p 00000000 00:00 0 
7ffd28a99000-7ffd28aba000 rw-p 00000000 00:00 0                          [stack]
7ffd28b7c000-7ffd28b7e000 r--p 00000000 00:00 0                          [vvar]
7ffd28b7e000-7ffd28b80000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
Aborted

To deal with passing None correctly, it looks to me like the problem is inside get_parameter() in src/genkey.c -- there ought to be a check for parms being NULL, and then returning either GPG_ERR_INV_VALUE or something else. otherwise, the segfault happens inside strstr.

That swig based interface is not really robust and it can't be because it does not known about API requirements. I bet there are other places where mandatory parameters are not checked.

Good catch , it would also fail earlier if you enable GPGME_DEBUG.

When parms is malformed but not NULL, then the error appears to be a bug in the python bindings in _wrap_gpgme_release. maybe something is going wrong because of the "cannot allocate memory" error? in particular:

(gdb) run -c 'import gpg; c = gpg.Context(); parms="<GnupgKeyParms format=\"internal\">\nKey-Type: default\nName-Real: monkeyman\n%commit\n\n"; c.op_genkey(parms,None,None)'
Starting program: /usr/bin/python3 -c 'import gpg; c = gpg.Context(); parms="<GnupgKeyParms format=\"internal\">\nKey-Type: default\nName-Real: monkeyman\n%commit\n\n"; c.op_genkey(parms,None,None)'
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/lib/python3/dist-packages/gpg/core.py", line 158, in wrapper
    return _funcwrap(self, *args)
  File "/usr/lib/python3/dist-packages/gpg/core.py", line 141, in _funcwrap
    return errorcheck(result, name)
  File "/usr/lib/python3/dist-packages/gpg/errors.py", line 129, in errorcheck
    raise GPGMEError(retval, extradata)
gpg.errors.GPGMEError: gpgme_op_genkey: GPGME: Cannot allocate memory

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff720f21a in gpgme_data_release (dh=0xbc5a10) at ../../src/data.c:467
467	../../src/data.c: No such file or directory.
(gdb) bt
#0  0x00007ffff720f21a in gpgme_data_release (dh=0xbc5a10) at ../../src/data.c:467
#1  0x00007ffff7238f3b in gpgme_result_unref (result=0xb2e110) at ../../src/gpgme.c:299
#2  0x00007ffff7238f9d in _gpgme_release_result (ctx=ctx@entry=0xb2d890) at ../../src/gpgme.c:313
#3  0x00007ffff723abb0 in gpgme_release (ctx=0xb2d890) at ../../src/gpgme.c:243
#4  0x00007ffff726f993 in _wrap_gpgme_release (self=<optimized out>, args=<optimized out>) at python3-gpg/gpgme_wrap.c:11443
#5  0x000000000050c07f in ?? ()
#6  0x000000000050fad9 in _PyEval_EvalFrameDefault ()
#7  0x000000000050b519 in ?? ()
#8  0x000000000050c24d in ?? ()
#9  0x000000000050fad9 in _PyEval_EvalFrameDefault ()
#10 0x000000000050ac55 in _PyFunction_FastCallDict ()
#11 0x0000000000599b21 in ?? ()
#12 0x00000000005ab51c in _PyObject_FastCallDict ()
#13 0x000000000061adb3 in ?? ()
#14 0x0000000000567515 in PyObject_CallFinalizerFromDealloc ()
#15 0x000000000055d1a7 in ?? ()
#16 0x000000000057b4b0 in ?? ()
#17 0x000000000056db08 in ?? ()
#18 0x0000000000576e16 in PyDict_SetItem ()
#19 0x00000000004f6e49 in PyImport_Cleanup ()
#20 0x0000000000638bce in Py_FinalizeEx ()
#21 0x0000000000639bee in Py_Main ()
#22 0x00000000004b2760 in main ()
(gdb)

If the swig interface isn't robust, can we replace it with something that will be more robust? Or do we need to wrap it with hand-crafted error checks that describe the API's constraints? It's pretty bad form to segfault python.

@BenM thinks that swig is still the best option. Actually this case helped to find a bug in gpgme. See my next commit.

werner changed the task status from Open to Testing.Oct 18 2018, 5:41 PM

Interesting, I'll look into it, but is there a reason for using op_genkey instead of create_key (optionally with create_subkey and/or key_add_uid)? The latter should be easier and more pythonic.

As for the SWIG vs. CFFI or other non-SWIG binding method of preference, I addressed some of the issues in the updated documentation.

I know many people want the bindings to switch to CFFI and if it were capable of doing so then perhaps a very long term project to port over to it would be warranted, but it's just not feasible at the present time.

Anyway, there's a little challenge issued in those docs, specifically to bind gpgme-tool.c with CFFI (or anything else). If anyone can manage that, then I'll take another look, but right now there's still work to be done in the upstream pycparser package (a dependency of CFFI). Then, even if that is completed, it still won't have the automatic binding generation which SWIG provides, so everything which SWIG currently generates would need to be reimplemented by hand. Well, unless SWIG were to be coaxed to generate CFFI code instead of its own (which is theoretically possible, but I don't know if anyone has ever done it).