[Security-sig] RFC: PEP: Make os.urandom() blocking on Linux

Nick Coghlan ncoghlan at gmail.com
Thu Jun 23 20:56:21 EDT 2016


On 23 June 2016 at 11:38, Nick Coghlan <ncoghlan at gmail.com> wrote:
> Although now I'm wondering whether it might be worth proposing a
> "secrets.wait_for_system_rng()" API as part of PEP 522, with the
> following implementation:
>
>     def wait_for_system_rng():
>         # Avoid the below busy loop if possible
>         try:
>             block_on_system_rng = open("/dev/random", "rb")
>         except FileNotFoundError:
>             pass
>         else:
>             with block_on_system_rng:
>                 block_on_system_rng.read(1)
>         # Busy loop until the system RNG is ready
>         while True:
>             try:
>                 os.urandom(1)
>                 break
>             except BlockingIOError:
>                 pass

I realised even this more complex variant still has a subtle bug: due
to the way /dev/random works, it can block inappropriately if Python
is started after the system RNG has already been seeded. That means a
completely correct implementation (assuming the rest of PEP 522 was in
place) would look more like this:

    def wait_for_system_rng():
        # If the system RNG is already seeded, don't wait at all
        try:
            os.urandom(1)
            return
        except BlockingIOError:
            pass
        # Avoid the below busy loop if possible
        try:
            block_on_system_rng = open("/dev/random", "rb")
        except FileNotFoundError:
            pass
        else:
            with block_on_system_rng:
                block_on_system_rng.read(1)
        # Busy loop until the system RNG is ready
        while True:
            try:
                os.urandom(1)
                break
            except BlockingIOError:
                # Only check once per millisecond
                time.sleep(0.001)

So I'll update PEP 522 to include this as part of the proposal - it's
trickier to get right than I thought, and it provides an additional
hook to help explain that the system RNG is something that once
initialized, stays initialized, so waiting for it is best handled as
an application level and system configuration concern rather than on
each call to os.urandom().

It also enables a pretty neat ExecStartPre [1] trick in systemd unit files:

    ExecStartPre=/usr/bin/python3 -c "import secrets;
secrets.wait_for_system_rng()"

to make an arbitrary service wait until the system RNG is ready before it runs.

Cheers,
Nick.

[1] https://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStartPre=

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia


More information about the Security-SIG mailing list