You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
if (entry->hash == hash) {
startkey = entry->key;
Py_INCREF(startkey);
cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);
Py_DECREF(startkey);
At this point, object pointed to by startkey could be deallocated, and then new object may be allocated at the same address.
if (cmp < 0)
return NULL;
if (table == so->table && entry->key == startkey) {
At this point, the table may be reallocated at the same address but with different (possibly smaller) size, so entry->key may be in deallocated memory. Also, entry->key may be equal to startkey but still point to an object other than one key was compared with.
if (cmp > 0)
return entry;
}
else {
/* The compare did major nasty stuff to the
* set: start over.
*/
return set_lookkey(so, key, hash);
This can lead to infinite recursion.
}
Attaching a patch for set_lookkey() that does a DECREF only when the refcnt > 1 so that the DECREF won't trigger any state changes.
The merge crasher still needs a patch.
abacabadabacaba commented on Apr 18, 2010
abacabadabacabamannequin
MannequinAuthor
More actions
This patch still assumes that if so->table didn't change then the table wasn't reallocated (see http://en.wikipedia.org/wiki/ABA_problem). One solution is to check that so->mask didn't change as well. Also, checking that refcnt > 1 is redundant because if entry->key == startkey then there are at least two references: one from entry->key and another from startkey.
These functions have a bug that may cause them to refer to deallocated memory when both arguments are sets: set_intersection, set_isdisjoint, set_difference_update_internal, set_difference, set_symmetric_difference_update, set_issubset.
These functions may also do the same if the first argument is a set and the second argument is a dict: set_difference, set_symmetric_difference_update.
Bugs in set_repr:
keys = PySequence_List((PyObject *)so);
if (keys == NULL)
goto done;
listrepr = PyObject_Repr(keys);
Py_DECREF(keys);
List pointed to by keys is already deallocated at this point.
if (listrepr == NULL) {
Py_DECREF(keys);
But this code tries to DECREF it.
goto done;
}
newsize = PyUnicode_GET_SIZE(listrepr);
result = PyUnicode_FromUnicode(NULL, newsize);
if (result) {
u = PyUnicode_AS_UNICODE(result);
*u++ = '{';
/* Omit the brackets from the listrepr */
Py_UNICODE_COPY(u, PyUnicode_AS_UNICODE(listrepr)+1,
PyUnicode_GET_SIZE(listrepr)-2);
u += newsize-2;
*u++ = '}';
}
Py_DECREF(listrepr);
if (Py_TYPE(so) != &PySet_Type) {
result may be NULL here.
PyObject *tmp = PyUnicode_FromFormat("%s(%U)",
Py_TYPE(so)->tp_name,
result);
I think PyUnicode_FromFormat won't like it.
Py_DECREF(result);
result = tmp;
}
One solution is to check that so->mask didn't
change as well.
I saw that and agree it would make a tighter check, but haven't convinced myself that it is necessary.
Also, checking that refcnt > 1 is redundant
because if entry->key == startkey then there
are at least two references: one from entry->key
and another from startkey.
It is a meaningful check. We have our own INCREF
and one for the key being in the table. If the
count is 1, then it means that the comparison
check deleted the key from the table or replaced
its value (see bpo-1517).
Bugs in set_repr:
> keys = PySequence_List((PyObject *)so);
> if (keys == NULL)
> goto done;
>
> listrepr = PyObject_Repr(keys);
> Py_DECREF(keys);
List pointed to by keys is already deallocated at this point.
> if (listrepr == NULL) {
> Py_DECREF(keys);
>But this code tries to DECREF it.
> goto done;
> }
I don't follow why you think keys is already deallocated.
When assigned by PySequence_List() without a NULL return, the refcnt is one. The call to PyObject_Repr(keys) does not change the refcnt of keys,
so the Py_DECREF(keys) is correct.
abacabadabacaba commented on Apr 18, 2010
abacabadabacabamannequin
MannequinAuthor
More actions
> One solution is to check that so->mask didn't
> change as well.
I saw that and agree it would make a tighter check, but haven't convinced myself that it is necessary.
Without this check, it is possible that the comparison shrinks the table, so entry becomes out of bounds. However, if both so->table and so->mask didn't change then entry is still a pointer to one of table elements so it can be used safely.
> Also, checking that refcnt > 1 is redundant
> because if entry->key == startkey then there
> are at least two references: one from entry->key
> and another from startkey.
It is a meaningful check. We have our own INCREF
and one for the key being in the table. If the
count is 1, then it means that the comparison
check deleted the key from the table or replaced
its value (see bpo-1517).
If the comparison deleted or changed the key then the check entry->key == startkey would fail so refcnt check won't be reached. Checking refcounts is also bad because someone else may have references to the key.
I don't follow why you think keys is already deallocated.
When assigned by PySequence_List() without a NULL return, the refcnt is one. The call to PyObject_Repr(keys) does not change the refcnt of keys,
so the Py_DECREF(keys) is correct.
Look at the code again. If listrepr happens to be NULL, you do Py_DECREF(keys) twice (this bug is only present in py3k branch).
Fixed set_repr() issue in r80197 and r80196. Looks like someone futzed the unicode updates for 3.x.
abacabadabacaba commented on Apr 18, 2010
abacabadabacabamannequin
MannequinAuthor
More actions
This code crashes python by using another bug in set_repr. This only affects py3k. This code relies on out-of-memory condition, so run it like:
$ (ulimit -v 65536 && python3 test.py)
Otherwise, it will eat all your free memory before crashing.
+ PyObject *key = entry->key;
+ long hash = entry->hash;
assert(so->fill <= so->mask); /* at least one empty slot */
n_used = so->used;
- Py_INCREF(entry->key);- if (set_insert_key(so, entry->key, (long) entry->hash) == -1) {- Py_DECREF(entry->key);+ Py_INCREF(key);+ if (set_insert_key(so, key, hash) == -1) {+ Py_DECREF(key);
You’ll notice that I declare hash as long, but I don’t need to cast it to long to make it compile and run. However, the previous code did cast; was that done on purpose, maybe for 32-bit platforms, or is it okay to not have the cast?
Activity
abacabadabacaba commentedon Apr 16, 2010
I've noticed that set_lookkey (in Objects/setobject.c) does some unsafe things:
Objects/setobject.c:
abacabadabacaba commentedon Apr 17, 2010
I've found more unsafe code in Objects/setobject.c.
This code makes Python 3.1.2 segfault by using a bug in function set_merge:
Function set_symmetric_difference_update has a similar bug.
Another bug in set_symmetric_difference_update:
[-]set_lookkey is unsafe[/-][+]Objects/setobject.c contains unsafe code[/+]rhettinger commentedon Apr 18, 2010
Attaching a patch for set_lookkey() that does a DECREF only when the refcnt > 1 so that the DECREF won't trigger any state changes.
The merge crasher still needs a patch.
abacabadabacaba commentedon Apr 18, 2010
This patch still assumes that if so->table didn't change then the table wasn't reallocated (see http://en.wikipedia.org/wiki/ABA_problem). One solution is to check that so->mask didn't change as well. Also, checking that refcnt > 1 is redundant because if entry->key == startkey then there are at least two references: one from entry->key and another from startkey.
These functions have a bug that may cause them to refer to deallocated memory when both arguments are sets: set_intersection, set_isdisjoint, set_difference_update_internal, set_difference, set_symmetric_difference_update, set_issubset.
These functions may also do the same if the first argument is a set and the second argument is a dict: set_difference, set_symmetric_difference_update.
Bugs in set_repr:
rhettinger commentedon Apr 18, 2010
I saw that and agree it would make a tighter check, but haven't convinced myself that it is necessary.
It is a meaningful check. We have our own INCREF
and one for the key being in the table. If the
count is 1, then it means that the comparison
check deleted the key from the table or replaced
its value (see bpo-1517).
rhettinger commentedon Apr 18, 2010
I don't follow why you think keys is already deallocated.
When assigned by PySequence_List() without a NULL return, the refcnt is one. The call to PyObject_Repr(keys) does not change the refcnt of keys,
so the Py_DECREF(keys) is correct.
abacabadabacaba commentedon Apr 18, 2010
Without this check, it is possible that the comparison shrinks the table, so entry becomes out of bounds. However, if both so->table and so->mask didn't change then entry is still a pointer to one of table elements so it can be used safely.
If the comparison deleted or changed the key then the check entry->key == startkey would fail so refcnt check won't be reached. Checking refcounts is also bad because someone else may have references to the key.
rhettinger commentedon Apr 18, 2010
Fixed set_repr() issue in r80197 and r80196. Looks like someone futzed the unicode updates for 3.x.
abacabadabacaba commentedon Apr 18, 2010
This code crashes python by using another bug in set_repr. This only affects py3k. This code relies on out-of-memory condition, so run it like:
$ (ulimit -v 65536 && python3 test.py)
Otherwise, it will eat all your free memory before crashing.
rhettinger commentedon Apr 18, 2010
Patch please.
rhettinger commentedon Sep 3, 2010
Fixed in r84447
8 remaining items
rhettinger commentedon Mar 22, 2011
This looks okay.
On the first lines for set_merge() where there is:
key = entry->key;
also do:
hash = entry->key;
so that the two get handled in a parallel fashion.
Thanks for doing the backporting.
merwok commentedon Mar 22, 2011
I assume you meant “hash = entry->hash”, not “entry->key”. Updated patch attached.
merwok commentedon Mar 22, 2011
BTW, set_add_entry still uses entry->hash.
rhettinger commentedon Mar 22, 2011
The revised patch looks good.
Please make the same change for set_add_entry.
If the tests run, go ahead and apply.
merwok commentedon Mar 22, 2011
Is it a candidate for 3.1 too?
rhettinger commentedon Mar 22, 2011
Sure, go ahead and apply to 3.1
merwok commentedon Mar 23, 2011
Here is a part of my 3.1 patch:
+ PyObject *key = entry->key;
+ long hash = entry->hash;
assert(so->fill <= so->mask); /* at least one empty slot */ n_used = so->used; - Py_INCREF(entry->key); - if (set_insert_key(so, entry->key, (long) entry->hash) == -1) { - Py_DECREF(entry->key); + Py_INCREF(key); + if (set_insert_key(so, key, hash) == -1) { + Py_DECREF(key);You’ll notice that I declare hash as long, but I don’t need to cast it to long to make it compile and run. However, the previous code did cast; was that done on purpose, maybe for 32-bit platforms, or is it okay to not have the cast?
rhettinger commentedon Mar 23, 2011
Looks good.
Can this be closed now?
merwok commentedon Mar 23, 2011
Used wrong number in the commit message :/ Fixed in 24179f82b7de for 2.7.
python-dev commentedon Mar 23, 2011
New changeset 57657393ceaf by Éric Araujo in branch '3.1':
Fix obscure set crashers (bpo-8420). Backport of d56b3cafb1e6, reviewed by Raymond.
http://hg.python.org/cpython/rev/57657393ceaf
merwok commentedon Mar 23, 2011
All done.