prng change feedback

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/OpenBSC@lists.osmocom.org/.

ringsignature at riseup.net ringsignature at riseup.net
Fri Oct 6 08:19:17 UTC 2017


On 2017-10-06 01:23, Harald Welte wrote:
> Hi RS,
> 
>> After running the above tests and reading the related documentation, my
>> conclusion is that it would be reasonable to use syscall(SYS_getrandom,
>> &buf, bufsize, 0) as a suitable replacement for rand() in all cases
>> without any concrete security or performance concerns. The overhead is
>> also significantly less than importing or otherwise depending on
>> OpenSSL, GnuTLS,  NaCL, or probably even glibc.
> 
> Thanks again for your investigation.

You're welcome. Thanks for writing Free Software! I really appreciate
that your project is open to feedback from complete strangers!

My program is not optimized in any sense and I re-ran my program for 0
to -1 iterations with the following outcome:

 time ./getrandom-exhaust
 getrandom status - byte size requested: 512 iteration: -1
 Current entropy available: 2575
 Original entropy estimate: 3338
 
 real	170m44.814s
 user	3m50.836s
 sys	166m52.948s

I think it would be interesting to run this on any supported system and
see if the output is similar. All of the conclusions that I've reached
are based on the notion that any decreases to the entropy pool are
negligible. Furthermore that the entropy pool might increase during the
attempted exhaustion process which is probably an unrelated systems side
effect. In either case, it will never underflow and it will always
return the requested number of bytes.

> So the conclusion is probably, for now:
> * use getrandom() with zero flags for TMSIs and other random identifiers
> * use getrandom() with zero flags for RAND challenges
> * use getrandom() with GRND_RANDOM flag for K/OP/OPc/Ki generation
> 

I don't entirely disagree, however the third option creates a more
complex case that I'm not sure is worth the trouble. I would very
strongly resist the urge to believe that the raw device bits are
_better_ than the output of the PRNG. The GRND_RANDOM flag will result
in underflows of the entire systems entropy pool, which especially on an
embedded system is a halt-and-catch-fire situation. The complexity
around that failure mode might cause failures that are otherwise avoided
entirely.

>> It may make sense to use the platform's libc interface if it is available.
> 
> I would go for that, and I believe the current patch under discussion is doing
> exactly that: use getrandom() if available, and fall back to the raw syscall,
> if not.
> 

I'd suggest someone examine the failure modes for glibc before making
this decision. I expect it to be a completely sane interface which fails
nicely - though, if it doesn't, we need to consider how it fails.
Apparently it was a "long road" [0] [1] to supporting getrandom() in
glibc. The 2.25 appears to be the first release to support getrandom().
Probably the most interesting thing of note for that version is "* The
getentropy and getrandom functions, and the <sys/random.h> header file
have been added." This almost suggests to me that getentropy() and a
maximum buffer of 256 bytes is a reasonable interface to settle on.

As further reading - the people implementing Rust's random [3] have the
following view of the matter:

    Unix-like systems (Linux, Android, Mac OSX): read directly from
/dev/urandom, or from getrandom(2) system call if available.
    OpenBSD: calls getentropy(2)
    FreeBSD: uses the kern.arandom sysctl(2) mib
    Windows: calls RtlGenRandom, exported from advapi32.dll as
SystemFunction036.
    iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed.
    PNaCl: calls into the nacl-irt-random-0.1 IRT interface.

> Further falling back on rand() is probably a bad idea, we could simply
> make this a fatal runtime error ending the program and see if anyone
> ever reports that error to the mailing list.  If at all, the fall-back
> should not happen automatically, but should be explicitly requested at
> compile-time ("--enable-unsafe-rand"), or depend on an environment
> variable, or the like.  Basically to make sure the default configuration
> will be safe, and any unsafe operation requires explicit user
> intervention.
> 
>> It may also be worthwhile to try to ensure that buffer is indeed
>> changed.
> 
> good point, I like that.

I'm not entirely sure of the best method for checking but I would expect
something like this to at least fail safely in the event of a kernel
failure:

 - allocate a fixed size buffer of 256 bytes which seems to be the most
portable size among all the OS provided interfaces
 - memset the buffer with a known value such as #define
THEMEASUREOFABYTE 0x47
 - memcmp each byte of the buffer with THEMEASUREOFABYTE to ensure that
each byte compared is the same
 - request that the OS or glibc or libc fills that same buffer with
random bytes for the size of the buffer
 - ensure that the number of bytes written is equal to the requested
size
 - memcmp each byte of the buffer with THEMEASUREOFABYTE to ensure that
each byte compared is *not* the same
 - if anything has gone wrong, return -1 and 0 (is this correct?)
 - in one possible future, hash the buffer in place to conceal the raw
outputs from the PRNG
 - return the number of bytes written to the buffer, return the buffer
full of random bytes
 
That may make a better unit test than an actual wrapper function. It
seems that for portability, it's worth considering such a wrapper as it
doesn't appear that every API has the same failure mode. There is of
course the unfortunate side effect of removing all 0x47s from your
buffer with such an approach, which leads to a suggestion of generating
a single random byte, which well, it's 🐢🐢🐢 all the way down... :-)

Such an implementation should be constant time and take into
consideration other things that might leak the random data. I'm not sure
if memcmp is ideal for that reason. I'll do some additional
research/experimentation and share the results.

>> The small program below could also easily be modified to test that the
>> buffer is indeed completely filled with some new data and to
>> additionally hash the buffer before use in any cryptographic
>> application.
> 
> Yes, we could improve on that by using some hashing function on the
> result, rather than using the (cs)prng output directly.  But let's keep
> that as a possible future improvement for now.  Out of curiosity: What
> would you recommend?

For a 512 *bit* buffer, I think hashing with SHA512 would be a fine
transformation. If the attacker can guess the input for an output of
SHA512, there is a serious problem. In which case, the hashing might
mitigate practical, casual exploitation of the problem. It might not, of
course.

For buffers that are 256 or 512 bytes, I think it could be hashed in two
or four chunks but someone smarter than me should think about that
strategy before drawing a final conclusion. There are strategies using
block ciphers rather than hashing functions that might make sense for
larger buffers. Such a block cipher would essentially become a user
space RNG of a similar design to the one from djb that I shared in
earlier in the thread. Once we're in block cipher territory, we're using
additional memory, we increase complexity, we may also have concerns
about internal key schedules, side channels, and so on. I suppose this
is another open research question for me. I'm sure someone else knows an
answer off hand but I'm not confident that I know how to do it
correctly.

Happy Hacking,
RS

[0] https://lwn.net/Articles/711013/
[1] https://sourceware.org/bugzilla/show_bug.cgi?id=17252
[2] https://doc.rust-lang.org/rand/rand/os/struct.OsRng.html



More information about the OpenBSC mailing list