Coverage Report

Created: 2026-10-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Objects/mimalloc/random.c
Line
Count
Source
1
/* ----------------------------------------------------------------------------
2
Copyright (c) 2019-2021, Microsoft Research, Daan Leijen
3
This is free software; you can redistribute it and/or modify it under the
4
terms of the MIT license. A copy of the license can be found in the file
5
"LICENSE" at the root of this distribution.
6
-----------------------------------------------------------------------------*/
7
#include "mimalloc.h"
8
#include "mimalloc/internal.h"
9
#include "mimalloc/prim.h"    // _mi_prim_random_buf
10
#include <string.h>       // memset
11
12
/* ----------------------------------------------------------------------------
13
We use our own PRNG to keep predictable performance of random number generation
14
and to avoid implementations that use a lock. We only use the OS provided
15
random source to initialize the initial seeds. Since we do not need ultimate
16
performance but we do rely on the security (for secret cookies in secure mode)
17
we use a cryptographically secure generator (chacha20).
18
-----------------------------------------------------------------------------*/
19
20
407
#define MI_CHACHA_ROUNDS (20)   // perhaps use 12 for better performance?
21
22
23
/* ----------------------------------------------------------------------------
24
Chacha20 implementation as the original algorithm with a 64-bit nonce
25
and counter: https://en.wikipedia.org/wiki/Salsa20
26
The input matrix has sixteen 32-bit values:
27
Position  0 to  3: constant key
28
Position  4 to 11: the key
29
Position 12 to 13: the counter.
30
Position 14 to 15: the nonce.
31
32
The implementation uses regular C code which compiles very well on modern compilers.
33
(gcc x64 has no register spills, and clang 6+ uses SSE instructions)
34
-----------------------------------------------------------------------------*/
35
36
11.8k
static inline uint32_t rotl(uint32_t x, uint32_t shift) {
37
11.8k
  return (x << shift) | (x >> (32 - shift));
38
11.8k
}
39
40
2.96k
static inline void qround(uint32_t x[16], size_t a, size_t b, size_t c, size_t d) {
41
2.96k
  x[a] += x[b]; x[d] = rotl(x[d] ^ x[a], 16);
42
2.96k
  x[c] += x[d]; x[b] = rotl(x[b] ^ x[c], 12);
43
2.96k
  x[a] += x[b]; x[d] = rotl(x[d] ^ x[a], 8);
44
2.96k
  x[c] += x[d]; x[b] = rotl(x[b] ^ x[c], 7);
45
2.96k
}
46
47
static void chacha_block(mi_random_ctx_t* ctx)
48
37
{
49
  // scramble into `x`
50
37
  uint32_t x[16];
51
629
  for (size_t i = 0; i < 16; i++) {
52
592
    x[i] = ctx->input[i];
53
592
  }
54
407
  for (size_t i = 0; i < MI_CHACHA_ROUNDS; i += 2) {
55
370
    qround(x, 0, 4,  8, 12);
56
370
    qround(x, 1, 5,  9, 13);
57
370
    qround(x, 2, 6, 10, 14);
58
370
    qround(x, 3, 7, 11, 15);
59
370
    qround(x, 0, 5, 10, 15);
60
370
    qround(x, 1, 6, 11, 12);
61
370
    qround(x, 2, 7,  8, 13);
62
370
    qround(x, 3, 4,  9, 14);
63
370
  }
64
65
  // add scrambled data to the initial state
66
629
  for (size_t i = 0; i < 16; i++) {
67
592
    ctx->output[i] = x[i] + ctx->input[i];
68
592
  }
69
37
  ctx->output_available = 16;
70
71
  // increment the counter for the next round
72
37
  ctx->input[12] += 1;
73
37
  if (ctx->input[12] == 0) {
74
0
    ctx->input[13] += 1;
75
0
    if (ctx->input[13] == 0) {  // and keep increasing into the nonce
76
0
      ctx->input[14] += 1;
77
0
    }
78
0
  }
79
37
}
80
81
222
static uint32_t chacha_next32(mi_random_ctx_t* ctx) {
82
222
  if (ctx->output_available <= 0) {
83
37
    chacha_block(ctx);
84
37
    ctx->output_available = 16; // (assign again to suppress static analysis warning)
85
37
  }
86
222
  const uint32_t x = ctx->output[16 - ctx->output_available];
87
222
  ctx->output[16 - ctx->output_available] = 0; // reset once the data is handed out
88
222
  ctx->output_available--;
89
222
  return x;
90
222
}
91
92
444
static inline uint32_t read32(const uint8_t* p, size_t idx32) {
93
444
  const size_t i = 4*idx32;
94
444
  return ((uint32_t)p[i+0] | (uint32_t)p[i+1] << 8 | (uint32_t)p[i+2] << 16 | (uint32_t)p[i+3] << 24);
95
444
}
96
97
static void chacha_init(mi_random_ctx_t* ctx, const uint8_t key[32], uint64_t nonce)
98
37
{
99
  // since we only use chacha for randomness (and not encryption) we
100
  // do not _need_ to read 32-bit values as little endian but we do anyways
101
  // just for being compatible :-)
102
37
  ctx->output_available = 0;
103
37
  _mi_memzero(ctx->output,sizeof(ctx->output));
104
185
  for (size_t i = 0; i < 4; i++) {
105
148
    const uint8_t* sigma = (uint8_t*)"expand 32-byte k";
106
148
    ctx->input[i] = read32(sigma,i);
107
148
  }
108
333
  for (size_t i = 0; i < 8; i++) {
109
296
    ctx->input[i + 4] = read32(key,i);
110
296
  }
111
37
  ctx->input[12] = 0;
112
37
  ctx->input[13] = 0;
113
37
  ctx->input[14] = (uint32_t)nonce;
114
37
  ctx->input[15] = (uint32_t)(nonce >> 32);
115
37
}
116
117
0
static void chacha_split(mi_random_ctx_t* ctx, uint64_t nonce, mi_random_ctx_t* ctx_new) {
118
0
  _mi_memzero(ctx_new, sizeof(*ctx_new));
119
0
  ctx_new->weak = ctx->weak;
120
0
  _mi_memcpy(ctx_new->input, ctx->input, sizeof(ctx_new->input));
121
0
  ctx_new->input[12] = 0;
122
0
  ctx_new->input[13] = 0;
123
0
  ctx_new->input[14] = (uint32_t)nonce;
124
0
  ctx_new->input[15] = (uint32_t)(nonce >> 32);
125
0
  mi_assert_internal(ctx->input[14] != ctx_new->input[14] || ctx->input[15] != ctx_new->input[15]); // do not reuse nonces!
126
0
  chacha_block(ctx_new);
127
0
}
128
129
130
/* ----------------------------------------------------------------------------
131
Random interface
132
-----------------------------------------------------------------------------*/
133
134
#if MI_DEBUG>1
135
static bool mi_random_is_initialized(mi_random_ctx_t* ctx) {
136
  return (ctx != NULL && ctx->input[0] != 0);
137
}
138
#endif
139
140
0
void _mi_random_split(mi_random_ctx_t* ctx, mi_random_ctx_t* ctx_new) {
141
0
  mi_assert_internal(mi_random_is_initialized(ctx));
142
0
  mi_assert_internal(ctx != ctx_new);
143
0
  const uintptr_t nonce_rnd = _mi_random_next(ctx);
144
0
  chacha_split(ctx, (uintptr_t)ctx_new ^ nonce_rnd /*nonce*/, ctx_new);
145
0
}
146
147
111
uintptr_t _mi_random_next(mi_random_ctx_t* ctx) {
148
111
  mi_assert_internal(mi_random_is_initialized(ctx));
149
111
  uintptr_t r;
150
111
  do {
151
    #if MI_INTPTR_SIZE <= 4
152
    r = chacha_next32(ctx);
153
    #elif MI_INTPTR_SIZE == 8
154
    r = (((uintptr_t)chacha_next32(ctx) << 32) | chacha_next32(ctx));
155
    #else
156
    # error "define mi_random_next for this platform"
157
    #endif
158
111
  } while (r==0);
159
111
  return r;
160
111
}
161
162
163
/* ----------------------------------------------------------------------------
164
To initialize a fresh random context.
165
If we cannot get good randomness, we fall back to weak randomness based on a timer and ASLR.
166
-----------------------------------------------------------------------------*/
167
168
0
uintptr_t _mi_os_random_weak(uintptr_t extra_seed) {
169
0
  uintptr_t x = (uintptr_t)&_mi_os_random_weak ^ extra_seed; // ASLR makes the address random
170
0
  x ^= _mi_prim_clock_now();
171
  // and do a few randomization steps
172
0
  uintptr_t max = ((x ^ (x >> 17)) & 0x0F) + 1;
173
0
  for (uintptr_t i = 0; i < max || x==0; i++, x++) {
174
0
    x = _mi_random_shuffle(x);
175
0
  }
176
0
  mi_assert_internal(x != 0);
177
0
  return x;
178
0
}
179
180
37
static void mi_random_init_ex(mi_random_ctx_t* ctx, bool use_weak) {
181
37
  uint8_t key[32] = {0};  // CPython: zero-initialize to avoid an MSAN report
182
37
  if (use_weak || !_mi_prim_random_buf(key, sizeof(key))) {
183
    // if we fail to get random data from the OS, we fall back to a
184
    // weak random source based on the current time
185
0
    #if !defined(__wasi__)
186
0
    if (!use_weak) { _mi_warning_message("unable to use secure randomness\n"); }
187
0
    #endif
188
0
    uintptr_t x = _mi_os_random_weak(0);
189
0
    for (size_t i = 0; i < 32; i+=4, x++) {
190
0
      x = _mi_random_shuffle(x);
191
0
      key[i]   = (uint8_t)(x);
192
0
      key[i+1] = (uint8_t)(x>>8);
193
0
      key[i+2] = (uint8_t)(x>>16);
194
0
      key[i+3] = (uint8_t)(x>>24);
195
0
    }
196
0
    ctx->weak = true;
197
0
  }
198
37
  else {
199
37
    ctx->weak = false;
200
37
  }
201
37
  chacha_init(ctx, key, (uintptr_t)ctx /*nonce*/ );
202
37
}
203
204
37
void _mi_random_init(mi_random_ctx_t* ctx) {
205
37
  mi_random_init_ex(ctx, false);
206
37
}
207
208
0
void _mi_random_init_weak(mi_random_ctx_t * ctx) {
209
0
  mi_random_init_ex(ctx, true);
210
0
}
211
212
37
void _mi_random_reinit_if_weak(mi_random_ctx_t * ctx) {
213
37
  if (ctx->weak) {
214
0
    _mi_random_init(ctx);
215
0
  }
216
37
}
217
218
/* --------------------------------------------------------
219
test vectors from <https://tools.ietf.org/html/rfc8439>
220
----------------------------------------------------------- */
221
/*
222
static bool array_equals(uint32_t* x, uint32_t* y, size_t n) {
223
  for (size_t i = 0; i < n; i++) {
224
    if (x[i] != y[i]) return false;
225
  }
226
  return true;
227
}
228
static void chacha_test(void)
229
{
230
  uint32_t x[4] = { 0x11111111, 0x01020304, 0x9b8d6f43, 0x01234567 };
231
  uint32_t x_out[4] = { 0xea2a92f4, 0xcb1cf8ce, 0x4581472e, 0x5881c4bb };
232
  qround(x, 0, 1, 2, 3);
233
  mi_assert_internal(array_equals(x, x_out, 4));
234
235
  uint32_t y[16] = {
236
       0x879531e0,  0xc5ecf37d,  0x516461b1,  0xc9a62f8a,
237
       0x44c20ef3,  0x3390af7f,  0xd9fc690b,  0x2a5f714c,
238
       0x53372767,  0xb00a5631,  0x974c541a,  0x359e9963,
239
       0x5c971061,  0x3d631689,  0x2098d9d6,  0x91dbd320 };
240
  uint32_t y_out[16] = {
241
       0x879531e0,  0xc5ecf37d,  0xbdb886dc,  0xc9a62f8a,
242
       0x44c20ef3,  0x3390af7f,  0xd9fc690b,  0xcfacafd2,
243
       0xe46bea80,  0xb00a5631,  0x974c541a,  0x359e9963,
244
       0x5c971061,  0xccc07c79,  0x2098d9d6,  0x91dbd320 };
245
  qround(y, 2, 7, 8, 13);
246
  mi_assert_internal(array_equals(y, y_out, 16));
247
248
  mi_random_ctx_t r = {
249
    { 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574,
250
      0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c,
251
      0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c,
252
      0x00000001, 0x09000000, 0x4a000000, 0x00000000 },
253
    {0},
254
    0
255
  };
256
  uint32_t r_out[16] = {
257
       0xe4e7f110, 0x15593bd1, 0x1fdd0f50, 0xc47120a3,
258
       0xc7f4d1c7, 0x0368c033, 0x9aaa2204, 0x4e6cd4c3,
259
       0x466482d2, 0x09aa9f07, 0x05d7c214, 0xa2028bd9,
260
       0xd19c12b5, 0xb94e16de, 0xe883d0cb, 0x4e3c50a2 };
261
  chacha_block(&r);
262
  mi_assert_internal(array_equals(r.output, r_out, 16));
263
}
264
*/