Home | History | Annotate | Download | only in Python
      1 #include "Python.h"
      2 #ifdef MS_WINDOWS
      3 #include <windows.h>
      4 #else
      5 #include <fcntl.h>
      6 #if defined(HAVE_GETRANDOM) || defined(HAVE_GETENTROPY)
      7 #include <sys/random.h>
      8 #endif
      9 #endif
     10 
     11 #ifdef Py_DEBUG
     12 int _Py_HashSecret_Initialized = 0;
     13 #else
     14 static int _Py_HashSecret_Initialized = 0;
     15 #endif
     16 
     17 #ifdef MS_WINDOWS
     18 typedef BOOL (WINAPI *CRYPTACQUIRECONTEXTA)(HCRYPTPROV *phProv,\
     19               LPCSTR pszContainer, LPCSTR pszProvider, DWORD dwProvType,\
     20               DWORD dwFlags );
     21 typedef BOOL (WINAPI *CRYPTGENRANDOM)(HCRYPTPROV hProv, DWORD dwLen,\
     22               BYTE *pbBuffer );
     23 
     24 static CRYPTGENRANDOM pCryptGenRandom = NULL;
     25 /* This handle is never explicitly released. Instead, the operating
     26    system will release it when the process terminates. */
     27 static HCRYPTPROV hCryptProv = 0;
     28 
     29 static int
     30 win32_urandom_init(int raise)
     31 {
     32     HINSTANCE hAdvAPI32 = NULL;
     33     CRYPTACQUIRECONTEXTA pCryptAcquireContext = NULL;
     34 
     35     /* Obtain handle to the DLL containing CryptoAPI. This should not fail. */
     36     hAdvAPI32 = GetModuleHandle("advapi32.dll");
     37     if(hAdvAPI32 == NULL)
     38         goto error;
     39 
     40     /* Obtain pointers to the CryptoAPI functions. This will fail on some early
     41        versions of Win95. */
     42     pCryptAcquireContext = (CRYPTACQUIRECONTEXTA)GetProcAddress(
     43                                hAdvAPI32, "CryptAcquireContextA");
     44     if (pCryptAcquireContext == NULL)
     45         goto error;
     46 
     47     pCryptGenRandom = (CRYPTGENRANDOM)GetProcAddress(hAdvAPI32,
     48                                                      "CryptGenRandom");
     49     if (pCryptGenRandom == NULL)
     50         goto error;
     51 
     52     /* Acquire context */
     53     if (! pCryptAcquireContext(&hCryptProv, NULL, NULL,
     54                                PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
     55         goto error;
     56 
     57     return 0;
     58 
     59 error:
     60     if (raise)
     61         PyErr_SetFromWindowsErr(0);
     62     else
     63         Py_FatalError("Failed to initialize Windows random API (CryptoGen)");
     64     return -1;
     65 }
     66 
     67 /* Fill buffer with size pseudo-random bytes generated by the Windows CryptoGen
     68    API. Return 0 on success, or -1 on error. */
     69 static int
     70 win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
     71 {
     72     Py_ssize_t chunk;
     73 
     74     if (hCryptProv == 0)
     75     {
     76         if (win32_urandom_init(raise) == -1)
     77             return -1;
     78     }
     79 
     80     while (size > 0)
     81     {
     82         chunk = size > INT_MAX ? INT_MAX : size;
     83         if (!pCryptGenRandom(hCryptProv, chunk, buffer))
     84         {
     85             /* CryptGenRandom() failed */
     86             if (raise)
     87                 PyErr_SetFromWindowsErr(0);
     88             else
     89                 Py_FatalError("Failed to initialized the randomized hash "
     90                         "secret using CryptoGen)");
     91             return -1;
     92         }
     93         buffer += chunk;
     94         size -= chunk;
     95     }
     96     return 0;
     97 }
     98 
     99 /* Issue #25003: Don't use getentropy() on Solaris (available since
    100  * Solaris 11.3), it is blocking whereas os.urandom() should not block. */
    101 #elif defined(HAVE_GETENTROPY) && !defined(sun)
    102 #define PY_GETENTROPY 1
    103 
    104 /* Fill buffer with size pseudo-random bytes generated by getentropy().
    105    Return 0 on success, or raise an exception and return -1 on error.
    106    If fatal is nonzero, call Py_FatalError() instead of raising an exception
    107    on error. */
    108 static int
    109 py_getentropy(unsigned char *buffer, Py_ssize_t size, int fatal)
    110 {
    111     while (size > 0) {
    112         Py_ssize_t len = size < 256 ? size : 256;
    113         int res;
    114 
    115         if (!fatal) {
    116             Py_BEGIN_ALLOW_THREADS
    117             res = getentropy(buffer, len);
    118             Py_END_ALLOW_THREADS
    119 
    120             if (res < 0) {
    121                 PyErr_SetFromErrno(PyExc_OSError);
    122                 return -1;
    123             }
    124         }
    125         else {
    126             res = getentropy(buffer, len);
    127             if (res < 0)
    128                 Py_FatalError("getentropy() failed");
    129         }
    130 
    131         buffer += len;
    132         size -= len;
    133     }
    134     return 0;
    135 }
    136 #endif
    137 
    138 #ifdef __VMS
    139 /* Use openssl random routine */
    140 #include <openssl/rand.h>
    141 static int
    142 vms_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
    143 {
    144     if (RAND_pseudo_bytes(buffer, size) < 0) {
    145         if (raise) {
    146             PyErr_Format(PyExc_ValueError,
    147                          "RAND_pseudo_bytes");
    148         } else {
    149             Py_FatalError("Failed to initialize the randomized hash "
    150                           "secret using RAND_pseudo_bytes");
    151         }
    152         return -1;
    153     }
    154     return 0;
    155 }
    156 #endif /* __VMS */
    157 
    158 
    159 #if !defined(MS_WINDOWS) && !defined(__VMS)
    160 
    161 static struct {
    162     int fd;
    163     dev_t st_dev;
    164     ino_t st_ino;
    165 } urandom_cache = { -1 };
    166 
    167 /* Read size bytes from /dev/urandom into buffer.
    168    Call Py_FatalError() on error. */
    169 static void
    170 dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size)
    171 {
    172     int fd;
    173     Py_ssize_t n;
    174 
    175     assert (0 < size);
    176 
    177     fd = open("/dev/urandom", O_RDONLY);
    178     if (fd < 0)
    179         Py_FatalError("Failed to open /dev/urandom");
    180 
    181     while (0 < size)
    182     {
    183         do {
    184             n = read(fd, buffer, (size_t)size);
    185         } while (n < 0 && errno == EINTR);
    186         if (n <= 0)
    187         {
    188             /* stop on error or if read(size) returned 0 */
    189             Py_FatalError("Failed to read bytes from /dev/urandom");
    190             break;
    191         }
    192         buffer += n;
    193         size -= (Py_ssize_t)n;
    194     }
    195     close(fd);
    196 }
    197 
    198 /* Read size bytes from /dev/urandom into buffer.
    199    Return 0 on success, raise an exception and return -1 on error. */
    200 static int
    201 dev_urandom_python(char *buffer, Py_ssize_t size)
    202 {
    203     int fd;
    204     Py_ssize_t n;
    205     struct stat st;
    206     int attr;
    207 
    208     if (size <= 0)
    209         return 0;
    210 
    211     if (urandom_cache.fd >= 0) {
    212         /* Does the fd point to the same thing as before? (issue #21207) */
    213         if (fstat(urandom_cache.fd, &st)
    214             || st.st_dev != urandom_cache.st_dev
    215             || st.st_ino != urandom_cache.st_ino) {
    216             /* Something changed: forget the cached fd (but don't close it,
    217                since it probably points to something important for some
    218                third-party code). */
    219             urandom_cache.fd = -1;
    220         }
    221     }
    222     if (urandom_cache.fd >= 0)
    223         fd = urandom_cache.fd;
    224     else {
    225         Py_BEGIN_ALLOW_THREADS
    226         fd = open("/dev/urandom", O_RDONLY);
    227         Py_END_ALLOW_THREADS
    228         if (fd < 0)
    229         {
    230             if (errno == ENOENT || errno == ENXIO ||
    231                 errno == ENODEV || errno == EACCES)
    232                 PyErr_SetString(PyExc_NotImplementedError,
    233                                 "/dev/urandom (or equivalent) not found");
    234             else
    235                 PyErr_SetFromErrno(PyExc_OSError);
    236             return -1;
    237         }
    238 
    239         /* try to make the file descriptor non-inheritable, ignore errors */
    240         attr = fcntl(fd, F_GETFD);
    241         if (attr >= 0) {
    242             attr |= FD_CLOEXEC;
    243             (void)fcntl(fd, F_SETFD, attr);
    244         }
    245 
    246         if (urandom_cache.fd >= 0) {
    247             /* urandom_fd was initialized by another thread while we were
    248                not holding the GIL, keep it. */
    249             close(fd);
    250             fd = urandom_cache.fd;
    251         }
    252         else {
    253             if (fstat(fd, &st)) {
    254                 PyErr_SetFromErrno(PyExc_OSError);
    255                 close(fd);
    256                 return -1;
    257             }
    258             else {
    259                 urandom_cache.fd = fd;
    260                 urandom_cache.st_dev = st.st_dev;
    261                 urandom_cache.st_ino = st.st_ino;
    262             }
    263         }
    264     }
    265 
    266     Py_BEGIN_ALLOW_THREADS
    267     do {
    268         do {
    269             n = read(fd, buffer, (size_t)size);
    270         } while (n < 0 && errno == EINTR);
    271         if (n <= 0)
    272             break;
    273         buffer += n;
    274         size -= (Py_ssize_t)n;
    275     } while (0 < size);
    276     Py_END_ALLOW_THREADS
    277 
    278     if (n <= 0)
    279     {
    280         /* stop on error or if read(size) returned 0 */
    281         if (n < 0)
    282             PyErr_SetFromErrno(PyExc_OSError);
    283         else
    284             PyErr_Format(PyExc_RuntimeError,
    285                          "Failed to read %zi bytes from /dev/urandom",
    286                          size);
    287         return -1;
    288     }
    289     return 0;
    290 }
    291 
    292 static void
    293 dev_urandom_close(void)
    294 {
    295     if (urandom_cache.fd >= 0) {
    296         close(urandom_cache.fd);
    297         urandom_cache.fd = -1;
    298     }
    299 }
    300 
    301 
    302 #endif /* !defined(MS_WINDOWS) && !defined(__VMS) */
    303 
    304 /* Fill buffer with pseudo-random bytes generated by a linear congruent
    305    generator (LCG):
    306 
    307        x(n+1) = (x(n) * 214013 + 2531011) % 2^32
    308 
    309    Use bits 23..16 of x(n) to generate a byte. */
    310 static void
    311 lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
    312 {
    313     size_t index;
    314     unsigned int x;
    315 
    316     x = x0;
    317     for (index=0; index < size; index++) {
    318         x *= 214013;
    319         x += 2531011;
    320         /* modulo 2 ^ (8 * sizeof(int)) */
    321         buffer[index] = (x >> 16) & 0xff;
    322     }
    323 }
    324 
    325 /* Fill buffer with size pseudo-random bytes from the operating system random
    326    number generator (RNG). It is suitable for most cryptographic purposes
    327    except long living private keys for asymmetric encryption.
    328 
    329    Return 0 on success, raise an exception and return -1 on error. */
    330 int
    331 _PyOS_URandom(void *buffer, Py_ssize_t size)
    332 {
    333     if (size < 0) {
    334         PyErr_Format(PyExc_ValueError,
    335                      "negative argument not allowed");
    336         return -1;
    337     }
    338     if (size == 0)
    339         return 0;
    340 
    341 #ifdef MS_WINDOWS
    342     return win32_urandom((unsigned char *)buffer, size, 1);
    343 #elif defined(PY_GETENTROPY)
    344     return py_getentropy(buffer, size, 0);
    345 #else
    346 # ifdef __VMS
    347     return vms_urandom((unsigned char *)buffer, size, 1);
    348 # else
    349     return dev_urandom_python((char*)buffer, size);
    350 # endif
    351 #endif
    352 }
    353 
    354 void
    355 _PyRandom_Init(void)
    356 {
    357     char *env;
    358     void *secret = &_Py_HashSecret;
    359     Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
    360 
    361     if (_Py_HashSecret_Initialized)
    362         return;
    363     _Py_HashSecret_Initialized = 1;
    364 
    365     /*
    366       By default, hash randomization is disabled, and only
    367       enabled if PYTHONHASHSEED is set to non-empty or if
    368       "-R" is provided at the command line:
    369     */
    370     if (!Py_HashRandomizationFlag) {
    371         /* Disable the randomized hash: */
    372         memset(secret, 0, secret_size);
    373         return;
    374     }
    375 
    376     /*
    377       Hash randomization is enabled.  Generate a per-process secret,
    378       using PYTHONHASHSEED if provided.
    379     */
    380 
    381     env = Py_GETENV("PYTHONHASHSEED");
    382     if (env && *env != '\0' && strcmp(env, "random") != 0) {
    383         char *endptr = env;
    384         unsigned long seed;
    385         seed = strtoul(env, &endptr, 10);
    386         if (*endptr != '\0'
    387             || seed > 4294967295UL
    388             || (errno == ERANGE && seed == ULONG_MAX))
    389         {
    390             Py_FatalError("PYTHONHASHSEED must be \"random\" or an integer "
    391                           "in range [0; 4294967295]");
    392         }
    393         if (seed == 0) {
    394             /* disable the randomized hash */
    395             memset(secret, 0, secret_size);
    396         }
    397         else {
    398             lcg_urandom(seed, (unsigned char*)secret, secret_size);
    399         }
    400     }
    401     else {
    402 #ifdef MS_WINDOWS
    403         (void)win32_urandom((unsigned char *)secret, secret_size, 0);
    404 #elif __VMS
    405         vms_urandom((unsigned char *)secret, secret_size, 0);
    406 #elif defined(PY_GETENTROPY)
    407         (void)py_getentropy(secret, secret_size, 1);
    408 #else
    409         dev_urandom_noraise(secret, secret_size);
    410 #endif
    411     }
    412 }
    413 
    414 void
    415 _PyRandom_Fini(void)
    416 {
    417 #ifdef MS_WINDOWS
    418     if (hCryptProv) {
    419         CryptReleaseContext(hCryptProv, 0);
    420         hCryptProv = 0;
    421     }
    422 #elif defined(PY_GETENTROPY)
    423     /* nothing to clean */
    424 #else
    425     dev_urandom_close();
    426 #endif
    427 }
    428