homepage

This issue tracker has been migrated to GitHub , and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Incomplete tuple created by PyTuple_New() and accessed via the GC can trigged a crash
Type: behavior Stage: resolved
Components: Extension Modules Versions: Python 3.10, Python 3.9, Python 3.8
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: amaury.forgeotdarc, arigo, corona10, erlendaasland, ghaering, iritkatriel, jcea, lkraav, pablogsal, pitrou, pxd, rhettinger, vstinner
Priority: normal Keywords: patch

Created on 2012年06月19日 21:52 by pxd, last changed 2022年04月11日 14:57 by admin. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 24510 closed pablogsal, 2021年02月12日 00:58
Messages (22)
msg163224 - (view) Author: Pankaj D (pxd) Date: 2012年06月19日 21:52
Hi,
Sporadically, while running an sqlite3 query, the above error is seen.
In order to debug, I modified Objects/tupleobject.c, PyTuple_SetItem() as follows:
	if (!PyTuple_Check(op) || op->ob_refcnt != 1) {
		Py_XDECREF(newitem);
		/*
		 * Temp: Bug XYZ Generate core so that we can debug
		 * 
		 * PyErr_BadInternalCall();
		 * return -1;
		*/
		char errmsg[200];
		sprintf(errmsg, "Bug XYZ: PyTuple_Check(op) = %d "
			"op->ob_refcnt = %d; see core\n", PyTuple_Check(op),
			op->ob_refcnt);
		Py_FatalError(errmsg); 
	}
This generates a core with the following bt. Showing the top few frames only:
(gdb) bt
#0 0x0000000800acd3fc in thr_kill () at thr_kill.S:3
#1 0x0000000800b5e283 in abort () at /build/mnt/src/lib/libc/stdlib/abort.c:65
#2 0x0000000000494acf in Py_FatalError (msg=Variable "msg" is not available.
) at ./../Python/pythonrun.c:1646
#3 0x000000000044e740 in PyTuple_SetItem (op=0x80c6e6308, i=16, newitem=0x80a80d780) at ./../Objects/tupleobject.c:128
#4 0x0000000807298866 in _pysqlite_fetch_one_row (self=0x80b846e48) at _sqlite/cursor.c:402
#5 0x0000000807298bf5 in pysqlite_cursor_iternext (self=0x80b846e48) at _sqlite/cursor.c:898
#6 0x0000000000476943 in PyEval_EvalFrameEx (f=0x80a94d420, throwflag=Variable "throwflag" is not available.
) at ./../Python/ceval.c:2237
#7 0x000000000047acbf in PyEval_EvalFrameEx (f=0x80a94b820, throwflag=Variable "throwflag" is not available.
) at ./../Python/ceval.c:3765
#8 0x000000000047bf09 in PyEval_EvalCodeEx (co=0x808d2ac60, globals=Variable "globals" is not available.
) at ./../Python/ceval.c:2942
...
(gdb) fr 4
#4 0x0000000807298866 in _pysqlite_fetch_one_row (self=0x80b846e48) at _sqlite/cursor.c:402
402	 PyTuple_SetItem(row, i, converted);
Current language: auto; currently c
(gdb) l
397	 converted = buffer;
398	 }
(gdb) p *(PyTupleObject *)row
11ドル = {ob_refcnt = 2, ob_type = 0x60fee0, ob_size = 22, ob_item = {0x80a534030}}
'row' was allocated via PyTuple_New()
but, somehow its refcount has become 2 while setting the 16th item!!!
Is this a known issue? If not, what can I do to debug.
Thanks,
Pankaj
msg163225 - (view) Author: Jesús Cea Avión (jcea) * (Python committer) Date: 2012年06月19日 21:57
Could you possibly reproduce this in 2.7, 3.2 and/or "default" (future 3.3)?.
Python 2.6 support is over.
msg163226 - (view) Author: Pankaj D (pxd) Date: 2012年06月19日 22:11
sorry, 2.7, 3.2 is not an option currently but I am hoping someone can
provide enough info to help probe this more efficiently. There seem to be references to this issue on the web but no root-cause.
msg164257 - (view) Author: Pankaj D (pxd) Date: 2012年06月28日 14:20
I believe I have found the root-cause for this issue. 
It is occurring due to the use of the garbage collector in another "memMonitor" thread (we run it periodically to get stats on objects, track mem leaks, etc). Since _pysqlite_fetch_one_row() releases the GIL before calling PyTuple_SetItem(), if the memMonitor is scheduled to run and, say, calls gc.get_objects(), it increments the refcount on all tracked objects (via append_objects()->PyList_Append()->app1()->PY_INCREF()). I have stack traces to confirm. This seems to rule out the use of gc methods (such as get_objects(), get_referrers/referents()) in multi-threaded programs or have them handle SystemError arising from such usage. Agree?
msg164263 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2012年06月28日 15:13
Thanks for the analysis!
This is quite similar to issue793822: gc.get_referrers() can access unfinished tuples. The difference here is that what breaks is not the monitoring tool, but the "main" program!
Here is a simple script inspired from the original bug; PySequence_Tuple() uses PyTuple_SET_ITEM() which is a macro without the ob_refcnt check, but we can make it call _PyTuple_Resize() and fail there. All versions of CPython are affected:
import gc
TAG = object()
def monitor():
 lst = [x for x in gc.get_referrers(TAG)
 if isinstance(x, tuple)]
 t = lst[0] # this *is* the result tuple
 print(t) # full of nulls !
 return t # Keep it alive for some time
def my_iter():
 yield TAG # 'tag' gets stored in the result tuple
 t = monitor()
 for x in range(10):
 yield x # SystemError when the tuple needs to be resized
tuple(my_iter())
msg164273 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012年06月28日 16:43
I wonder why _pysqlite_fetch_one_row() releases the GIL around sqlite3_column_type(). By its name, it doesn't sound like an expensive function.
Another workaround would be to call PyTuple_SET_ITEM instead of PyTuple_SetItem.
msg164282 - (view) Author: Pankaj D (pxd) Date: 2012年06月28日 17:36
Wondering the same thing myself, and yes sqlite3_column_type() by itself doesn't seem expensive. I assumed in general it was to allow more responsiveness for apps with huge number of columns (i.e. large tuple size). But we have about 20-25 columns and so I was going to try removing it and seeing the
results. In any case, it seems, fewer GIL acquire/releases will help with throughput. 
Are there any guidelines on when GIL should be released?
Re PyTuple_SET_ITEM...yes that's also a possibility but it would then hide genuine bugs.
msg164285 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012年06月28日 17:40
> Are there any guidelines on when GIL should be released?
The GIL should be released:
- for CPU-heavy external functions (e.g. compression, cryptography)
- for external functions which wait for I/O
> Re PyTuple_SET_ITEM...yes that's also a possibility but it would then
> hide genuine bugs.
Well, as long as your monitor only increfs the tuple and doesn't mutate
it, there shouldn't be any problem. We use PyTuple_SET_ITEM in many
other places.
msg228344 - (view) Author: (lkraav) Date: 2014年10月03日 17:28
I may be seeing this running tracd-1.1.2b1 on python 2.7.7
http://trac.edgewall.org/ticket/11772 
msg228357 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2014年10月03日 17:52
What is tracd doing? This issue should only appear when calling one of the debugging functions in the gc module, AFAICT.
msg386841 - (view) Author: Irit Katriel (iritkatriel) * (Python committer) Date: 2021年02月11日 23:12
Still happening in 3.10:
Python 3.10.0a5+ (heads/master:bf2e7e55d7, Feb 11 2021, 23:09:25) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import gc
>>> TAG = object()
>>>
>>> def monitor():
... lst = [x for x in gc.get_referrers(TAG)
... if isinstance(x, tuple)]
... t = lst[0] # this *is* the result tuple
... print(t) # full of nulls !
... return t # Keep it alive for some time
...
>>> def my_iter():
... yield TAG # 'tag' gets stored in the result tuple
... t = monitor()
... for x in range(10):
... yield x # SystemError when the tuple needs to be resized
...
>>> tuple(my_iter())
(<object object at 0x00000217225091B0>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>)
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
SystemError: C:\Users\User\src\cpython-dev\Objects\tupleobject.c:963: bad argument to internal function
>>>
msg387029 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2021年02月15日 17:21
The general issue here is a the PyTuple_New() is unsafe: it immediately tracks the newly created tuple in the GC, whereas the tuple is not initialized yet. If the GIL is released before the tuple is fully populated and something access to this tuple via the GC (ex: gc.get_objects()), accessing the tuple can crash, especially in the Python land (for example, repr(the_tuple) is likely to crash).
IMO the unsafe PyTuple_New() API should be avoided. For example, allocate an array of PyObject* on the stack memory, and then call _PyTuple_FromArray(). This API is safe because it only tracks the tuple once it's fully initialized, and it calls INCREF on items. Problem: this safe and efficient API is currently private.
There are other safe alternatives like Py_BuildValue("(OOO)", item1, item2, item3).
_pysqlite_fetch_one_row() calls PyTuple_New() and releases the GIL at each sqlite3_column_type() call, so yeah, it has this exact bug. By the way, it doesn't check for PyTuple_SetItem() failure, whereas it's currently possible that there is more than one strong reference to the tuple which is being populated (because of the GC issue).
PyTuple_New() is ok-ish if there is no way to trigger a GC collection and if the GIL cannot be released until the tuple is fully initialized.
Maybe we need a private _PyTuple_NewUntracked() API to create a tuple which is not tracked by the GC, and also a _PyTuple_ResizeUntracked() API. By the way, _PyTuple_Resize() sounds like a nonsense since a tuple is supposed to be immutable ;-)
msg387033 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021年02月15日 17:58
> IMO the unsafe PyTuple_New() API should be avoided.
Is not that simple, there are other APIs that track the tuple as _PyTuple_Resize. This problem also is not unique to tuples, although is mainly prominent in them.
We have this warning in the docs:
>> Care must be taken when using objects returned by get_referrers() because some of them could still be under construction and hence in a temporarily invalid state. Avoid using get_referrers() for any purpose other than debugging.
That's because *by thesign* these APIs can potentially access half-initialized objects. 
I don't know if is worth to add a new API just for tuples, given that this problem happens with many other objects
msg387034 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021年02月15日 18:00
> There are other safe alternatives like Py_BuildValue("(OOO)", item1, item2, item3).
That's a lot slower unfortunately
msg387036 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021年02月15日 18:02
> If the GIL is released before the tuple is fully populated and something access to this tuple via the GC (ex: gc.get_objects()), accessing the tuple can crash, especially in the Python land (for example, repr(the_tuple) is likely to crash).
It can happen even without releasing the GIL: A new tuple is created, then some other object is created using the CAPI, the gc runs, the callback triggers (or the tuplevisit method is invoked) and then kaboom
msg387068 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2021年02月15日 22:40
> That's a lot slower unfortunately
Ah sorry, I forgot PyTuple_Pack(3, item1, item2, item3) which should be very efficient. This function is also safe: only track the tuple when it is fully initialized.
> This problem also is not unique to tuples, although is mainly prominent in them.
PyList_New() is also affected. Do you think about other types?
PyDict_New() and PySet_New() create empty containers and so are ok.
msg387070 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021年02月15日 23:01
> PyList_New() is also affected. Do you think about other types?
Any C extension class that implements a new_whatever() method that leaves the class tracked and not ready.
msg387079 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2021年02月16日 00:21
> Any C extension class that implements a new_whatever() method that leaves the class tracked and not ready.
I'm not aware of such C extension but they likely exists. If we have such extensions in the stdlib, we can try to fix them.
We cannot fix such GC bugs in third party code, and I don't think that we can hack the GC to work around this issue neither.
msg387204 - (view) Author: Erlend E. Aasland (erlendaasland) * (Python triager) Date: 2021年02月18日 09:34
Should we still fix sqlite3, or wait for an agreement on GH-24510?
msg387224 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021年02月18日 11:50
> Should we still fix sqlite3, or wait for an agreement on GH-24510?
I suggest to let's all agree on how to fix this on the bigger scale first.
msg387492 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2021年02月22日 03:33
Unless there is a simple, reliable, cheap, and universal solution at hand, consider closing this. Given how long PyTuple_New() has exist, it doesn't seem to be much of a problem in the real world.
Historically, we punted on "crashers" involving either gc.get_referrers or byte code hacks. Both of those reach inside Python's black box, allowing disruption of otherwise sensible invariants.
msg387506 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2021年02月22日 09:15
I mainly agree on closing this issue as we already have a warning about this behaviour in gc.get_referrers and other friends.
On the other hand I am still a bit afraid of a crash that could happen if the GC does a pass and one of these tuples is in an inconsistent state. Is possible that this crashes Python without the user calling ever any function from the GC module. I need to do some investigation around this, but I agree that this is a separate issue, so I will open a new one if it happens to be relevant.
History
Date User Action Args
2022年04月11日 14:57:31adminsetgithub: 59313
2021年02月22日 09:15:10pablogsalsetstatus: open -> closed
resolution: not a bug
messages: + msg387506

stage: patch review -> resolved
2021年02月22日 03:33:01rhettingersetnosy: + rhettinger
messages: + msg387492
2021年02月22日 00:51:16corona10setnosy: + corona10
2021年02月18日 11:50:01pablogsalsetmessages: + msg387224
2021年02月18日 09:34:57erlendaaslandsetmessages: + msg387204
2021年02月16日 00:21:19vstinnersetmessages: + msg387079
2021年02月15日 23:26:54erlendaaslandsetnosy: + erlendaasland
2021年02月15日 23:01:32pablogsalsetmessages: + msg387070
2021年02月15日 22:40:58vstinnersetmessages: + msg387068
2021年02月15日 18:02:33pablogsalsetmessages: + msg387036
2021年02月15日 18:00:14pablogsalsetmessages: + msg387034
2021年02月15日 17:58:06pablogsalsetmessages: + msg387033
2021年02月15日 17:21:37vstinnersetnosy: + vstinner

messages: + msg387029
title: ERROR: SystemError: ./../Objects/tupleobject.c:118: bad argument to internal function -> Incomplete tuple created by PyTuple_New() and accessed via the GC can trigged a crash
2021年02月12日 00:58:25pablogsalsetkeywords: + patch
nosy: + pablogsal

pull_requests: + pull_request23297
stage: patch review
2021年02月11日 23:12:57iritkatrielsetmessages: - msg386839
2021年02月11日 23:12:55iritkatrielsetmessages: - msg386840
2021年02月11日 23:12:45iritkatrielsetmessages: + msg386841
versions: + Python 3.8, Python 3.9, Python 3.10, - Python 2.6, Python 2.7
2021年02月11日 23:06:45iritkatrielsetstatus: pending -> open

messages: + msg386840
2021年02月11日 22:59:34iritkatrielsetstatus: open -> pending
nosy: + iritkatriel
messages: + msg386839

2014年10月03日 17:52:13pitrousetmessages: + msg228357
2014年10月03日 17:28:03lkraavsetmessages: + msg228344
versions: + Python 2.7
2014年10月03日 17:21:50lkraavsetnosy: + lkraav
2012年06月28日 17:40:06pitrousetmessages: + msg164285
2012年06月28日 17:36:09pxdsetmessages: + msg164282
versions: + Python 2.6, - Python 2.7, Python 3.2, Python 3.3
2012年06月28日 16:43:49pitrousetversions: + Python 2.7, Python 3.2, Python 3.3, - Python 2.6
nosy: + pitrou, ghaering

messages: + msg164273

components: + Extension Modules
2012年06月28日 15:13:30amaury.forgeotdarcsetnosy: + amaury.forgeotdarc, arigo
messages: + msg164263
2012年06月28日 14:20:05pxdsetmessages: + msg164257
2012年06月19日 22:11:02pxdsetmessages: + msg163226
2012年06月19日 21:57:55jceasetcomponents: - Cross-Build
2012年06月19日 21:57:24jceasetnosy: + jcea
messages: + msg163225
components: + Cross-Build
2012年06月19日 21:52:13pxdcreate

AltStyle によって変換されたページ (->オリジナル) /