iosmacossecurityprngsecurity-framework

Is OS X's SecRandomCopyBytes fork safe?


Many userspace CSPRNG's have an issue where after fork(2), it's possible for the two different processes to return the same stream of random bytes.

From looking at dtruss, it's clear that SecRandomCopyBytes is, at a minimum, seeding from /dev/random, but is it doing so in a way that's safe for use after fork()?

With the following source code:

#include <Security/Security.h>


int main() {
    uint8_t data[8];
    SecRandomCopyBytes(kSecRandomDefault, 8, data);
    SecRandomCopyBytes(kSecRandomDefault, 8, data);
    printf("%llu\n", *(uint64_t *)data);
}

I get the following from dtruss (with irrelevant stuff removed):

open("/dev/random\0", 0x0, 0x7FFF900D76F5)       = 3 0
read(0x3, "\b\2029a6\020+\254\356\256\017\3171\222\376T\300\212\017\213\002\034w\3608\203-\214\373\244\177K\177Y\371\033\243Y\020\030*M\3264\265\027\216r\220\002\361\006\262\326\234\336\357F\035\036o\306\216\227\0", 0x40)        = 64 0
read(0x3, "\223??3\263\324\3604\314:+\362c\311\274\326\a_Ga\331\261\022\023\265C\na\211]\356)\0", 0x20)      = 32 0

Solution

  • The implementation is actually CCRandomCopyBytes():

    http://www.opensource.apple.com/source/Security/Security-55471/libsecurity_keychain/lib/SecRandom.c

    int SecRandomCopyBytes(SecRandomRef rnd, size_t count, uint8_t *bytes) {
        if (rnd != kSecRandomDefault)
            return errSecParam;
        return CCRandomCopyBytes(kCCRandomDefault, bytes, count);
    }
    

    So the actual code is here:

    http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-60049/lib/CommonRandom.c

    The comments in the include for CCRandomCopyBytes state that it is fork() safe:

    It is inconvenient to call system random number generators directly. In the simple case of calling /dev/random, the caller has to open the device and close it in addition to managing it while it's open. This module has as its immediate raison d'ĂȘtre the inconvenience of doing this. It manages a file descriptor to /dev/random including the exception processing of what happens in a fork() and exec(). Call CCRandomCopyBytes() and all the fiddly bits are managed for you. Just get on with whatever you were really trying to do. [...]

    In my own quick test, the child gets killed when it invokes SecRandomCopyBytes()