classification
Title: assertion failure in random.seed() in case the seed argument has a bad __abs__() method
Type: crash Stage: resolved
Components: Extension Modules Versions: Python 3.7, Python 3.6, Python 2.7
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: Oren Milman, rhettinger, serhiy.storchaka, veky
Priority: normal Keywords: patch

Created on 2017-09-14 20:33 by Oren Milman, last changed 2017-10-02 21:35 by serhiy.storchaka. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 3596 merged Oren Milman, 2017-09-15 09:34
PR 3794 merged serhiy.storchaka, 2017-09-28 07:52
PR 3845 merged Oren Milman, 2017-10-01 13:51
Messages (13)
msg302208 - (view) Author: Oren Milman (Oren Milman) * Date: 2017-09-14 20:33
The following code causes an assertion failure:
class BadInt(int):
    def __abs__(self):
        return None

import random

random.seed(BadInt())


this is because random_seed() (in Modules/_randommodule.c) assumes that
PyNumber_Absolute() returned an int, and so it passes it to _PyLong_NumBits(),
which asserts it received an int.


what should we do in such a case?
should we raise an exception? (the docs don't mention abs() in case the seed is
an int - https://docs.python.org/3.7/library/random.html#random.seed)
msg302228 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2017-09-15 02:01
It would make sense to verify that an actual int was returned and to raise a TypeError if it wasn't.  

Do you want to submit a PR (with a test case)?
msg302232 - (view) Author: Oren Milman (Oren Milman) * Date: 2017-09-15 05:02
sure.
but what about the TypeError message? should it complain about
the return value of abs(seed)? (the docs of random.seed don't mention abs().)
msg302234 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-09-15 06:08
There are several ways of solving this issue:

1. Verify that an actual int was returned and raise a TypeError if it wasn't.

2. Verify that an actual int was returned and fall back to the hash if it wasn't.

3. Use int.__abs__() instead of abs(), the result will be guaranteed int. The drawback is that this makes a copy of positive integers for int subclasses.

4. Check the sign of the seed and call int.__neg__() for negative values.
msg302241 - (view) Author: Oren Milman (Oren Milman) * Date: 2017-09-15 09:35
i opened a PR that implements the first option, but of course I wouldn't
mind if you decide another option is better.
msg302317 - (view) Author: Vedran Čačić (veky) * Date: 2017-09-16 03:40
So floats (and complexes) cannot be seeds anymore? :-o Or this pertains only to ints? In this case, I think the easiest doc fix is to change

If a is an int, it is used directly.

to

If a is an int, its absolute value is used.
msg302740 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-09-22 08:43
The more I think about this the more I like the idea of using int.__abs__() directly (PyLong_Type.tp_as_number->nb_absolute() in C). The C code doesn't call potentially overridable methods bit_length() and to_bytes(), it uses concrete implementations directly. I don't see reasons why it should obey overriding the __abs__() method.

This will save us from the problem with the wording of the error message.

I mentioned a drawback, but the current implementation has the same drawback. We can avoid copying positive integer subtypes by using more complex code, but I think it isn't worth.
msg302782 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2017-09-23 04:57
>  I don't see reasons why it should obey overriding the __abs__() method.

I concur.
msg303196 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-09-28 07:50
New changeset d780b2d588e68bd7047ef5d1f04e36da38b7a350 by Serhiy Storchaka (Oren Milman) in branch 'master':
bpo-31478: Fix an assertion failure in random.seed() in case a seed has a bad __abs__() method. (#3596)
https://github.com/python/cpython/commit/d780b2d588e68bd7047ef5d1f04e36da38b7a350
msg303201 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-09-28 09:17
New changeset befc956acf8ddeb94f000ed081ddec51315429e5 by Serhiy Storchaka in branch '3.6':
[3.6] bpo-31478: Fix an assertion failure in random.seed() in case a seed has a bad __abs__() method. (GH-3596) (#3794)
https://github.com/python/cpython/commit/befc956acf8ddeb94f000ed081ddec51315429e5
msg303207 - (view) Author: Oren Milman (Oren Milman) * Date: 2017-09-28 10:22
With regard to backporting to 2.7:

In 2.7 also, PyNumber_Absolute() is called, and its return value is stored in
the variable n.
However, there is no _PyLong_NumBits(n), so there is no assertion failure.
If n isn't an integer:
- if !PyObject_IsTrue(n), then the seed is zero (e.g. if n is None, [], () or {})
- otherwise, PyNumber_And() and PyNumber_Rshift() are used in a loop on n, so
  probably a TypeError would be raised.


So I think a backport is still desirable, but i am not sure about the test.

Maybe we should use @cpython_only, and make sure that no error is raised?
We can also make sure that random() returns a different value than when the
seed is zero.

What do you think?
msg303451 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-10-01 06:46
I agree that a backport is still desirable.

Your plan LGTM. Implement __abs__ raising an exception (test both int ant long subclasses). Make sure that no error is raised. Make sure that random() returns the same value as when seeding with an exact int and long.
msg303566 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-10-02 21:31
New changeset 13da1a60f13e173f65bb0da5ab325641d5bb99ec by Serhiy Storchaka (Oren Milman) in branch '2.7':
[2.7] bpo-31478: Prevent unwanted behavior in _random.Random.seed() in case the arg has a bad __abs__() method (GH-3596) (#3845)
https://github.com/python/cpython/commit/13da1a60f13e173f65bb0da5ab325641d5bb99ec
History
Date User Action Args
2017-10-02 21:35:15serhiy.storchakasetstatus: open -> closed
resolution: fixed
stage: patch review -> resolved
2017-10-02 21:31:47serhiy.storchakasetmessages: + msg303566
2017-10-01 13:51:06Oren Milmansetpull_requests: + pull_request3826
2017-10-01 06:47:00serhiy.storchakasetmessages: + msg303451
2017-09-28 10:22:44Oren Milmansetmessages: + msg303207
2017-09-28 09:17:56serhiy.storchakasetmessages: + msg303201
2017-09-28 07:52:09serhiy.storchakasetpull_requests: + pull_request3778
2017-09-28 07:50:06serhiy.storchakasetmessages: + msg303196
2017-09-23 04:57:42rhettingersetmessages: + msg302782
2017-09-22 08:43:32serhiy.storchakasetmessages: + msg302740
versions: + Python 2.7, Python 3.6
2017-09-16 03:40:36vekysetnosy: + veky
messages: + msg302317
2017-09-15 09:35:33Oren Milmansetmessages: + msg302241
2017-09-15 09:34:19Oren Milmansetkeywords: + patch
stage: patch review
pull_requests: + pull_request3587
2017-09-15 06:08:56serhiy.storchakasetmessages: + msg302234
2017-09-15 05:02:02Oren Milmansetmessages: + msg302232
2017-09-15 02:01:36rhettingersetnosy: + serhiy.storchaka, rhettinger
messages: + msg302228
2017-09-14 20:33:46Oren Milmancreate