├── .editorconfig ├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── randombytes.c ├── randombytes.h └── randombytes_test.c /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{c,h}] 2 | indent_style = tab 3 | indent_size = 8 4 | 5 | [Makefile] 6 | indent_style = tab 7 | indent_size = 8 8 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Tests 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | Test-Randombytes: 7 | runs-on: "${{ matrix.os }}" 8 | strategy: 9 | matrix: 10 | cc: 11 | - gcc 12 | - clang 13 | os: 14 | - ubuntu-latest 15 | - macos-latest 16 | env: 17 | CC: "${{ matrix.cc }}" 18 | steps: 19 | - uses: actions/checkout@v3 20 | - run: | 21 | make check 22 | 23 | Test-Randombytes-Musl: 24 | runs-on: ubuntu-latest 25 | env: 26 | CC: musl-gcc 27 | steps: 28 | - uses: actions/checkout@v3 29 | - name: Install musl-tools 30 | run: sudo apt-get install -y musl-tools 31 | - run: make check 32 | 33 | Test-Randombytes-Windows: 34 | runs-on: windows-latest 35 | strategy: 36 | matrix: 37 | arch: 38 | - x64 39 | - x86 40 | steps: 41 | - uses: actions/checkout@v3 42 | - uses: ilammy/msvc-dev-cmd@v1 43 | with: 44 | arch: ${{ matrix.arch }} 45 | - run: cl /c /nologo /W3 /WX randombytes.c 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # test binary 2 | /randombytes_test 3 | 4 | # Prerequisites 5 | *.d 6 | 7 | # Object files 8 | *.o 9 | *.ko 10 | *.obj 11 | *.elf 12 | 13 | # Linker output 14 | *.ilk 15 | *.map 16 | *.exp 17 | 18 | # Precompiled Headers 19 | *.gch 20 | *.pch 21 | 22 | # Libraries 23 | *.lib 24 | *.a 25 | *.la 26 | *.lo 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | # Debug files 43 | *.dSYM/ 44 | *.su 45 | *.idb 46 | *.pdb 47 | 48 | # Kernel Module Compile Results 49 | *.mod* 50 | *.cmd 51 | .tmp_versions/ 52 | modules.order 53 | Module.symvers 54 | Mkfile.old 55 | dkms.conf 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2017 Daan Sprenkels 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS ?= -g -O2 -std=gnu99 \ 2 | -Wall -Wextra -Wshadow -Wpointer-arith -Wcast-qual -Wformat \ 3 | -Wformat-security -Werror=format-security -Wstrict-prototypes \ 4 | -D_FORTIFY_SOURCE=2 -fPIC -fno-strict-overflow 5 | 6 | TEST_WRAPS := -Wl,-wrap=ioctl,-wrap=getrandom,-wrap=syscall 7 | 8 | ifeq ($(shell uname -o), GNU/Linux) 9 | TEST_LDFLAGS := $(TEST_WRAPS) 10 | endif 11 | 12 | all: librandombytes.a 13 | 14 | librandombytes.a: randombytes.o 15 | $(AR) -rcs librandombytes.a randombytes.o 16 | 17 | randombytes.js: CC := ${EMSCRIPTEN}/emcc 18 | randombytes.js: CFLAGS += -Wno-dollar-in-identifier-extension 19 | randombytes.js: randombytes.o 20 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LDLIBS) 21 | 22 | randombytes_test: CFLAGS+=-Wno-implicit-function-declaration 23 | randombytes_test: LDFLAGS:=$(TEST_LDFLAGS) 24 | randombytes_test: randombytes_test.c 25 | 26 | randombytes_test.js: CC := ${EMSCRIPTEN}/emcc 27 | randombytes_test.js: CFLAGS += -Wno-dollar-in-identifier-extension 28 | randombytes_test.js: randombytes_test.c 29 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LDLIBS) 30 | 31 | .PHONY: check 32 | check: randombytes_test 33 | ./randombytes_test 34 | 35 | .PHONY: check 36 | check-js: randombytes_test.js 37 | node randombytes_test.js 38 | 39 | .PHONY: clean 40 | clean: 41 | $(RM) librandombytes.a randombytes.o randombytes.js randombytes_test randombytes_test.o randombytes_test.js 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pluggable randombytes function 2 | 3 | [![GitHub Actions](https://github.com/dsprenkels/randombytes/workflows/Tests/badge.svg)](https://github.com/dsprenkels/randombytes/actions/workflows/test.yml) 4 | 5 | `randombytes` is a library that exposes a single function for retrieving 6 | _crypto-secure_ random bytes. It is loosely based on [Libsodium's random bytes 7 | API][libsodium_randombytes]. If you can, you should use that one. Otherwise, you 8 | can use this library. 9 | 10 | ## Usage 11 | 12 | `randombytes` takes two arguments: 13 | 1. A pointer to the buffer 14 | 2. The length of the buffer in bytes 15 | 16 | The function will always return an `int` which will be `0` on success. The 17 | caller _must_ check this. If some kind of error occured, `errno` MAY contain a 18 | hint to what the error was, and a subsequent call to the `randombytes` function 19 | MAY succeed. An example of when the function may fail is when `/dev/urandom` 20 | could not be opened, because there were no file descriptors left to use for the 21 | process. 22 | 23 | On sensible systems (like the ones with `arc4random`) the latency is very low. 24 | However, this is totally not guaranteed. Do not expect this function to be very 25 | fast. Benchmark for your specific setup, and use a fast CSPRNG if you need. 26 | 27 | Example code: 28 | 29 | ```c 30 | #include "randombytes.h" 31 | #include 32 | #include 33 | 34 | int main() 35 | { 36 | // Generate some random bytes and print them in hex 37 | int ret; 38 | uint8_t buf[20]; 39 | size_t i; 40 | 41 | ret = randombytes(&buf[0], sizeof(buf)); 42 | if (ret != 0) { 43 | printf("Error in `randombytes`"); 44 | return 1; 45 | } 46 | for (i = 0; i < sizeof(buf); ++i) { 47 | printf("%02hhx", buf[i]); 48 | } 49 | printf("\n"); 50 | return 0; 51 | } 52 | ``` 53 | 54 | ## How secure is it really? 55 | 56 | While building this I keep one rule of thumb which is: **Trust the OS**. 57 | Most OS'es implement a secure random generator, which is seeded by a good 58 | entropy source. We will always use this random source. This essentially means 59 | that the implementation is highly platform-dependent. For example we use 60 | `getrandom` on Linux and `arc4random` on BSD systems. 61 | 62 | ### What if the OS's random generator is bad? 63 | 64 | If you are dealing with an OS that has a compromised random generator you are 65 | out of luck. The reason why you cannot generate high quality random data from 66 | userspace is that userspace is made so that everything is too deterministic. 67 | A secure random generator needs a good source of entropy, such as 2.4 GHz noise 68 | or the user's mouse movements. Collecting these kinds of events only works well 69 | when working on the lowest level. 70 | 71 | ## Questions 72 | 73 | ### It does not compile on my platform! 74 | 75 | [Please open an issue.](https://github.com/dsprenkels/randombytes/issues/new) 76 | If possible I will try to make a `randombytes` implementation for your platform. 77 | 78 | ### Do you have bindings for language _x_? 79 | 80 | No, your language probably already has a random source. Use that one. 81 | 82 | ### Other 83 | 84 | Feel free to send me an email on my Github associated e-mail address. 85 | 86 | 87 | [libsodium_randombytes]: https://github.com/jedisct1/libsodium/blob/master/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c 88 | -------------------------------------------------------------------------------- /randombytes.c: -------------------------------------------------------------------------------- 1 | // In the case that are compiling on linux, we need to define _GNU_SOURCE 2 | // *before* randombytes.h is included. Otherwise SYS_getrandom will not be 3 | // declared. 4 | #if defined(__linux__) || defined(__GNU__) 5 | # define _GNU_SOURCE 6 | #endif /* defined(__linux__) || defined(__GNU__) */ 7 | 8 | #include "randombytes.h" 9 | 10 | #if defined(_WIN32) 11 | /* Windows */ 12 | # include 13 | # include /* CryptAcquireContext, CryptGenRandom */ 14 | #endif /* defined(_WIN32) */ 15 | 16 | /* wasi */ 17 | #if defined(__wasi__) 18 | #include 19 | #endif 20 | 21 | /* kFreeBSD */ 22 | #if defined(__FreeBSD_kernel__) && defined(__GLIBC__) 23 | # define GNU_KFREEBSD 24 | #endif 25 | 26 | #if defined(__linux__) || defined(__GNU__) || defined(GNU_KFREEBSD) 27 | /* Linux */ 28 | // We would need to include , but not every target has access 29 | // to the linux headers. We only need RNDGETENTCNT, so we instead inline it. 30 | // RNDGETENTCNT is originally defined in `include/uapi/linux/random.h` in the 31 | // linux repo. 32 | # define RNDGETENTCNT 0x80045200 33 | 34 | # include 35 | # include 36 | # include 37 | # include 38 | # include 39 | # include 40 | # include 41 | # if (defined(__linux__) || defined(__GNU__)) && defined(__GLIBC__) && ((__GLIBC__ > 2) || (__GLIBC_MINOR__ > 24)) 42 | # define USE_GLIBC 43 | # include 44 | # endif /* (defined(__linux__) || defined(__GNU__)) && defined(__GLIBC__) && ((__GLIBC__ > 2) || (__GLIBC_MINOR__ > 24)) */ 45 | # include 46 | # include 47 | # include 48 | # include 49 | 50 | // We need SSIZE_MAX as the maximum read len from /dev/urandom 51 | # if !defined(SSIZE_MAX) 52 | # define SSIZE_MAX (SIZE_MAX / 2 - 1) 53 | # endif /* defined(SSIZE_MAX) */ 54 | 55 | #endif /* defined(__linux__) || defined(__GNU__) || defined(GNU_KFREEBSD) */ 56 | 57 | 58 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 59 | /* Dragonfly, FreeBSD, NetBSD, OpenBSD (has arc4random) */ 60 | # include 61 | # if defined(BSD) 62 | # include 63 | # endif 64 | /* GNU/Hurd defines BSD in sys/param.h which causes problems later */ 65 | # if defined(__GNU__) 66 | # undef BSD 67 | # endif 68 | #endif 69 | 70 | #if defined(__EMSCRIPTEN__) 71 | # include 72 | # include 73 | # include 74 | # include 75 | #endif /* defined(__EMSCRIPTEN__) */ 76 | 77 | 78 | #if defined(_WIN32) 79 | static int randombytes_win32_randombytes(void* buf, size_t n) 80 | { 81 | HCRYPTPROV ctx; 82 | BOOL tmp; 83 | DWORD to_read = 0; 84 | const size_t MAX_DWORD = 0xFFFFFFFF; 85 | 86 | tmp = CryptAcquireContext(&ctx, NULL, NULL, PROV_RSA_FULL, 87 | CRYPT_VERIFYCONTEXT); 88 | if (tmp == FALSE) return -1; 89 | 90 | while (n > 0) { 91 | to_read = (DWORD)(n < MAX_DWORD ? n : MAX_DWORD); 92 | tmp = CryptGenRandom(ctx, to_read, (BYTE*) buf); 93 | if (tmp == FALSE) return -1; 94 | buf = ((char*)buf) + to_read; 95 | n -= to_read; 96 | } 97 | 98 | tmp = CryptReleaseContext(ctx, 0); 99 | if (tmp == FALSE) return -1; 100 | 101 | return 0; 102 | } 103 | #endif /* defined(_WIN32) */ 104 | 105 | #if defined(__wasi__) 106 | static int randombytes_wasi_randombytes(void *buf, size_t n) { 107 | arc4random_buf(buf, n); 108 | return 0; 109 | } 110 | #endif /* defined(__wasi__) */ 111 | 112 | #if (defined(__linux__) || defined(__GNU__)) && (defined(USE_GLIBC) || defined(SYS_getrandom)) 113 | # if defined(USE_GLIBC) 114 | // getrandom is declared in glibc. 115 | # elif defined(SYS_getrandom) 116 | static ssize_t getrandom(void *buf, size_t buflen, unsigned int flags) { 117 | return syscall(SYS_getrandom, buf, buflen, flags); 118 | } 119 | # endif 120 | 121 | static int randombytes_linux_randombytes_getrandom(void *buf, size_t n) 122 | { 123 | /* I have thought about using a separate PRF, seeded by getrandom, but 124 | * it turns out that the performance of getrandom is good enough 125 | * (250 MB/s on my laptop). 126 | */ 127 | size_t offset = 0, chunk; 128 | int ret; 129 | while (n > 0) { 130 | /* getrandom does not allow chunks larger than 33554431 */ 131 | chunk = n <= 33554431 ? n : 33554431; 132 | do { 133 | ret = getrandom((char *)buf + offset, chunk, 0); 134 | } while (ret == -1 && errno == EINTR); 135 | if (ret < 0) return ret; 136 | offset += ret; 137 | n -= ret; 138 | } 139 | assert(n == 0); 140 | return 0; 141 | } 142 | #endif /* (defined(__linux__) || defined(__GNU__)) && (defined(USE_GLIBC) || defined(SYS_getrandom)) */ 143 | 144 | #if (defined(__linux__) || defined(GNU_KFREEBSD)) && !defined(SYS_getrandom) 145 | 146 | # if defined(__linux__) 147 | static int randombytes_linux_read_entropy_ioctl(int device, int *entropy) 148 | { 149 | return ioctl(device, RNDGETENTCNT, entropy); 150 | } 151 | 152 | static int randombytes_linux_read_entropy_proc(FILE *stream, int *entropy) 153 | { 154 | int retcode; 155 | do { 156 | rewind(stream); 157 | retcode = fscanf(stream, "%d", entropy); 158 | } while (retcode != 1 && errno == EINTR); 159 | if (retcode != 1) { 160 | return -1; 161 | } 162 | return 0; 163 | } 164 | 165 | static int randombytes_linux_wait_for_entropy(int device) 166 | { 167 | /* We will block on /dev/random, because any increase in the OS' entropy 168 | * level will unblock the request. I use poll here (as does libsodium), 169 | * because we don't *actually* want to read from the device. */ 170 | enum { IOCTL, PROC } strategy = IOCTL; 171 | const int bits = 128; 172 | struct pollfd pfd; 173 | int fd; 174 | FILE *proc_file; 175 | int retcode, retcode_error = 0; // Used as return codes throughout this function 176 | int entropy = 0; 177 | 178 | /* If the device has enough entropy already, we will want to return early */ 179 | retcode = randombytes_linux_read_entropy_ioctl(device, &entropy); 180 | // printf("errno: %d (%s)\n", errno, strerror(errno)); 181 | if (retcode != 0 && (errno == ENOTTY || errno == ENOSYS)) { 182 | // The ioctl call on /dev/urandom has failed due to a 183 | // - ENOTTY (unsupported action), or 184 | // - ENOSYS (invalid ioctl; this happens on MIPS, see #22). 185 | // 186 | // We will fall back to reading from 187 | // `/proc/sys/kernel/random/entropy_avail`. This less ideal, 188 | // because it allocates a file descriptor, and it may not work 189 | // in a chroot. But at this point it seems we have no better 190 | // options left. 191 | strategy = PROC; 192 | // Open the entropy count file 193 | proc_file = fopen("/proc/sys/kernel/random/entropy_avail", "r"); 194 | if (proc_file == NULL) { 195 | return -1; 196 | } 197 | } else if (retcode != 0) { 198 | // Unrecoverable ioctl error 199 | return -1; 200 | } 201 | if (entropy >= bits) { 202 | return 0; 203 | } 204 | 205 | do { 206 | fd = open("/dev/random", O_RDONLY); 207 | } while (fd == -1 && errno == EINTR); /* EAGAIN will not occur */ 208 | if (fd == -1) { 209 | /* Unrecoverable IO error */ 210 | return -1; 211 | } 212 | 213 | pfd.fd = fd; 214 | pfd.events = POLLIN; 215 | for (;;) { 216 | retcode = poll(&pfd, 1, -1); 217 | if (retcode == -1 && (errno == EINTR || errno == EAGAIN)) { 218 | continue; 219 | } else if (retcode == 1) { 220 | if (strategy == IOCTL) { 221 | retcode = randombytes_linux_read_entropy_ioctl(device, &entropy); 222 | } else if (strategy == PROC) { 223 | retcode = randombytes_linux_read_entropy_proc(proc_file, &entropy); 224 | } else { 225 | return -1; // Unreachable 226 | } 227 | 228 | if (retcode != 0) { 229 | // Unrecoverable I/O error 230 | retcode_error = retcode; 231 | break; 232 | } 233 | if (entropy >= bits) { 234 | break; 235 | } 236 | } else { 237 | // Unreachable: poll() should only return -1 or 1 238 | retcode_error = -1; 239 | break; 240 | } 241 | } 242 | do { 243 | retcode = close(fd); 244 | } while (retcode == -1 && errno == EINTR); 245 | if (strategy == PROC) { 246 | do { 247 | retcode = fclose(proc_file); 248 | } while (retcode == -1 && errno == EINTR); 249 | } 250 | if (retcode_error != 0) { 251 | return retcode_error; 252 | } 253 | return retcode; 254 | } 255 | # endif /* defined(__linux__) */ 256 | 257 | 258 | static int randombytes_linux_randombytes_urandom(void *buf, size_t n) 259 | { 260 | int fd; 261 | size_t offset = 0, count; 262 | ssize_t tmp; 263 | do { 264 | fd = open("/dev/urandom", O_RDONLY); 265 | } while (fd == -1 && errno == EINTR); 266 | if (fd == -1) return -1; 267 | # if defined(__linux__) 268 | if (randombytes_linux_wait_for_entropy(fd) == -1) return -1; 269 | # endif 270 | 271 | while (n > 0) { 272 | count = n <= SSIZE_MAX ? n : SSIZE_MAX; 273 | tmp = read(fd, (char *)buf + offset, count); 274 | if (tmp == -1 && (errno == EAGAIN || errno == EINTR)) { 275 | continue; 276 | } 277 | if (tmp == -1) return -1; /* Unrecoverable IO error */ 278 | offset += tmp; 279 | n -= tmp; 280 | } 281 | close(fd); 282 | assert(n == 0); 283 | return 0; 284 | } 285 | #endif /* defined(__linux__) && !defined(SYS_getrandom) */ 286 | 287 | 288 | #if defined(BSD) 289 | static int randombytes_bsd_randombytes(void *buf, size_t n) 290 | { 291 | arc4random_buf(buf, n); 292 | return 0; 293 | } 294 | #endif /* defined(BSD) */ 295 | 296 | 297 | #if defined(__EMSCRIPTEN__) 298 | static int randombytes_js_randombytes_nodejs(void *buf, size_t n) { 299 | const int ret = EM_ASM_INT({ 300 | var crypto; 301 | try { 302 | crypto = require('crypto'); 303 | } catch (error) { 304 | return -2; 305 | } 306 | try { 307 | writeArrayToMemory(crypto.randomBytes($1), $0); 308 | return 0; 309 | } catch (error) { 310 | return -1; 311 | } 312 | }, buf, n); 313 | switch (ret) { 314 | case 0: 315 | return 0; 316 | case -1: 317 | errno = EINVAL; 318 | return -1; 319 | case -2: 320 | errno = ENOSYS; 321 | return -1; 322 | } 323 | assert(false); // Unreachable 324 | } 325 | #endif /* defined(__EMSCRIPTEN__) */ 326 | 327 | 328 | int randombytes(void *buf, size_t n) 329 | { 330 | #if defined(__EMSCRIPTEN__) 331 | # pragma message("Using crypto api from NodeJS") 332 | return randombytes_js_randombytes_nodejs(buf, n); 333 | #elif defined(__linux__) || defined(__GNU__) || defined(GNU_KFREEBSD) 334 | # if defined(USE_GLIBC) 335 | # pragma message("Using getrandom function call") 336 | /* Use getrandom system call */ 337 | return randombytes_linux_randombytes_getrandom(buf, n); 338 | # elif defined(SYS_getrandom) 339 | # pragma message("Using getrandom system call") 340 | /* Use getrandom system call */ 341 | return randombytes_linux_randombytes_getrandom(buf, n); 342 | # else 343 | # pragma message("Using /dev/urandom device") 344 | /* When we have enough entropy, we can read from /dev/urandom */ 345 | return randombytes_linux_randombytes_urandom(buf, n); 346 | # endif 347 | #elif defined(BSD) 348 | # pragma message("Using arc4random system call") 349 | /* Use arc4random system call */ 350 | return randombytes_bsd_randombytes(buf, n); 351 | #elif defined(_WIN32) 352 | # pragma message("Using Windows cryptographic API") 353 | /* Use windows API */ 354 | return randombytes_win32_randombytes(buf, n); 355 | #elif defined(__wasi__) 356 | # pragma message("Using WASI arc4random_buf system call") 357 | /* Use WASI */ 358 | return randombytes_wasi_randombytes(buf, n); 359 | #else 360 | # error "randombytes(...) is not supported on this platform" 361 | #endif 362 | } 363 | -------------------------------------------------------------------------------- /randombytes.h: -------------------------------------------------------------------------------- 1 | #ifndef sss_RANDOMBYTES_H 2 | #define sss_RANDOMBYTES_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #ifdef _WIN32 9 | /* Load size_t on windows */ 10 | #include 11 | #else 12 | #include 13 | #endif /* _WIN32 */ 14 | 15 | 16 | /* 17 | * Write `n` bytes of high quality random bytes to `buf` 18 | */ 19 | int randombytes(void *buf, size_t n); 20 | 21 | #ifdef __cplusplus 22 | } 23 | #endif 24 | 25 | #endif /* sss_RANDOMBYTES_H */ 26 | -------------------------------------------------------------------------------- /randombytes_test.c: -------------------------------------------------------------------------------- 1 | #include "randombytes.c" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static void *current_test = NULL; 9 | static int syscall_called = 0; 10 | static int glib_getrandom_called = 0; 11 | 12 | // ======== Helper macros and functions ======== 13 | 14 | #define RUN_TEST(name) \ 15 | printf("%s ... ", #name); \ 16 | padto(' ', sizeof(#name) + sizeof(" ... "), 32); \ 17 | current_test = name; \ 18 | name(); \ 19 | printf("ok\n"); 20 | #define SKIP_TEST(name) \ 21 | printf("%s ... ", #name); \ 22 | padto(' ', sizeof(#name) + sizeof(" ... "), 32); \ 23 | printf("skipped\n"); 24 | 25 | static void padto(const char c, const size_t curlen, const size_t len) { 26 | for (size_t i = curlen; i < len; i++) { 27 | putchar(c); 28 | } 29 | } 30 | 31 | // ======== Forward declarations needed for mocked functions ======== 32 | 33 | #if defined(__linux__) && defined(SYS_getrandom) 34 | int __wrap_syscall(int n, char *buf, size_t buflen, int flags); 35 | int __real_syscall(int n, char *buf, size_t buflen, int flags); 36 | #endif /* defined(__linux__) && defined(SYS_getrandom) */ 37 | 38 | #if defined(__linux__) && !defined(SYS_getrandom) 39 | int __wrap_ioctl(int fd, int code, int* ret); 40 | int __real_ioctl(int fd, int code, int* ret); 41 | #endif /* defined(__linux__) && !defined(SYS_getrandom) */ 42 | 43 | // ======== Test definitions ======== 44 | 45 | static void test_functional(void) { 46 | uint8_t buf1[20] = {}, buf2[sizeof(buf1)] = {}; 47 | const int ret1 = randombytes(buf1, sizeof(buf1)); 48 | const int ret2 = randombytes(buf2, sizeof(buf2)); 49 | if (ret1 != 0 || ret2 != 0) { 50 | printf("error: %s\n", strerror(errno)); 51 | } 52 | assert(ret1 == 0); 53 | assert(ret2 == 0); 54 | assert(memcmp(buf1, buf2, sizeof(buf1)) != 0); 55 | } 56 | 57 | static void test_empty(void) { 58 | const uint8_t zero[20] = {}; 59 | uint8_t buf[sizeof(zero)] = {}; 60 | const int ret = randombytes(buf, 0); 61 | assert(ret == 0); 62 | assert(memcmp(buf, zero, sizeof(zero)) == 0); 63 | } 64 | 65 | static void test_getrandom_syscall_partial(void) { 66 | syscall_called = 0; 67 | uint8_t buf[100] = {}; 68 | const int ret = randombytes(buf, sizeof(buf)); 69 | assert(ret == 0); 70 | assert(syscall_called >= 5); 71 | for (int i = 1; i < 5; i++) { 72 | assert(memcmp(&buf[0], &buf[20*i], 20) != 0); 73 | } 74 | } 75 | 76 | static void test_getrandom_syscall_interrupted(void) { 77 | syscall_called = 0; 78 | uint8_t zero[20] = {}; 79 | uint8_t buf[sizeof(zero)] = {}; 80 | const int ret = randombytes(buf, sizeof(buf)); 81 | assert(ret == 0); 82 | assert(memcmp(buf, zero, 20) != 0); 83 | } 84 | 85 | static void test_getrandom_glib_partial(void) { 86 | glib_getrandom_called = 0; 87 | uint8_t buf[100] = {}; 88 | const int ret = randombytes(buf, sizeof(buf)); 89 | assert(ret == 0); 90 | assert(glib_getrandom_called >= 5); 91 | for (int i = 1; i < 5; i++) { 92 | assert(memcmp(&buf[0], &buf[20*i], 20) != 0); 93 | } 94 | } 95 | 96 | static void test_getrandom_glib_interrupted(void) { 97 | glib_getrandom_called = 0; 98 | uint8_t zero[20] = {}; 99 | uint8_t buf[sizeof(zero)] = {}; 100 | const int ret = randombytes(buf, sizeof(buf)); 101 | assert(ret == 0); 102 | assert(memcmp(buf, zero, 20) != 0); 103 | } 104 | 105 | static void test_issue_17(void) { 106 | uint8_t buf1[20] = {}, buf2[sizeof(buf1)] = {}; 107 | const int ret1 = randombytes(buf1, sizeof(buf1)); 108 | const int ret2 = randombytes(buf2, sizeof(buf2)); 109 | assert(ret1 == 0); 110 | assert(ret2 == 0); 111 | assert(memcmp(buf1, buf2, sizeof(buf1)) != 0); 112 | } 113 | 114 | static void test_issue_22(void) { 115 | uint8_t buf1[20] = {}, buf2[sizeof(buf1)] = {}; 116 | const int ret1 = randombytes(buf1, sizeof(buf1)); 117 | const int ret2 = randombytes(buf2, sizeof(buf2)); 118 | assert(ret1 == 0); 119 | assert(ret2 == 0); 120 | assert(memcmp(buf1, buf2, sizeof(buf1)) != 0); 121 | } 122 | 123 | static void test_issue_33(void) { 124 | for (size_t idx = 0; idx < 100000; idx++) { 125 | uint8_t buf[20] = {}; 126 | const int ret = randombytes(&buf, sizeof(buf)); 127 | if (ret != 0) { 128 | printf("error: %s\n", strerror(errno)); 129 | } 130 | assert(ret == 0); 131 | } 132 | } 133 | 134 | // ======== Mock OS functions to simulate uncommon behavior ======== 135 | 136 | #if defined(__linux__) && defined(__GLIBC__) && ((__GLIBC__ > 2) || (__GLIBC_MINOR__ > 24)) 137 | int __wrap_getrandom(char *buf, size_t buflen, int flags) { 138 | glib_getrandom_called++; 139 | if (current_test == test_getrandom_glib_partial) { 140 | // Fill only 16 bytes, the caller should retry 141 | const size_t current_buflen = buflen <= 16 ? buflen : 16; 142 | return __real_getrandom(buf, current_buflen, flags); 143 | } else if (current_test == test_getrandom_glib_interrupted) { 144 | if (glib_getrandom_called < 5) { 145 | errno = EINTR; 146 | return -1; 147 | } 148 | } 149 | return __real_getrandom(buf, buflen, flags); 150 | } 151 | 152 | #elif defined(__linux__) && defined(SYS_getrandom) 153 | int __wrap_syscall(int n, char *buf, size_t buflen, int flags) { 154 | syscall_called++; 155 | if (current_test == test_getrandom_syscall_partial) { 156 | // Fill only 16 bytes, the caller should retry 157 | const size_t current_buflen = buflen <= 16 ? buflen : 16; 158 | return __real_syscall(n, buf, current_buflen, flags); 159 | } else if (current_test == test_getrandom_syscall_interrupted) { 160 | if (syscall_called < 5) { 161 | errno = EINTR; 162 | return -1; 163 | } 164 | } 165 | return __real_syscall(n, buf, buflen, flags); 166 | } 167 | #endif /* defined(__linux__) && (defined(SYS_getrandom) or glibc version > 2.24) */ 168 | 169 | #if defined(__linux__) && !defined(SYS_getrandom) 170 | int __wrap_ioctl(int fd, int code, int* ret) { 171 | if (current_test == test_issue_17) { 172 | errno = ENOTTY; 173 | return -1; 174 | } 175 | if (current_test == test_issue_17) { 176 | errno = ENOSYS; 177 | return -1; 178 | } 179 | return __real_ioctl(fd, code, ret); 180 | } 181 | #endif /* defined(__linux__) && !defined(SYS_getrandom) */ 182 | 183 | // ======== Main function ======== 184 | 185 | int main(void) { 186 | // Use `#if defined()` to enable/disable tests on a platform. If disabled, 187 | // please still call `SKIP_TEST` to make sure no tests are skipped silently. 188 | 189 | RUN_TEST(test_functional) 190 | RUN_TEST(test_empty) 191 | #if defined(__linux__) && defined(USE_GLIBC) 192 | RUN_TEST(test_getrandom_glib_partial) 193 | RUN_TEST(test_getrandom_glib_interrupted) 194 | SKIP_TEST(test_getrandom_syscall_partial) 195 | SKIP_TEST(test_getrandom_syscall_interrupted) 196 | #elif defined(__linux__) && defined(SYS_getrandom) 197 | SKIP_TEST(test_getrandom_glib_partial) 198 | SKIP_TEST(test_getrandom_glib_interrupted) 199 | RUN_TEST(test_getrandom_syscall_partial) 200 | RUN_TEST(test_getrandom_syscall_interrupted) 201 | #else 202 | SKIP_TEST(test_getrandom_glib_partial) 203 | SKIP_TEST(test_getrandom_glib_interrupted) 204 | SKIP_TEST(test_getrandom_syscall_partial) 205 | SKIP_TEST(test_getrandom_syscall_interrupted) 206 | #endif /* defined(__linux__) && (defined(SYS_getrandom) or glibc version > 2.24) */ 207 | #if defined(__linux__) && !defined(SYS_getrandom) 208 | RUN_TEST(test_issue_17) 209 | RUN_TEST(test_issue_22) 210 | RUN_TEST(test_issue_33) 211 | #else 212 | SKIP_TEST(test_issue_17) 213 | SKIP_TEST(test_issue_22) 214 | SKIP_TEST(test_issue_33) 215 | #endif /* defined(__linux__) && !defined(SYS_getrandom) */ 216 | return 0; 217 | } 218 | --------------------------------------------------------------------------------