~mmach/netext73/busybox

« back to all changes in this revision

Viewing changes to miscutils/seedrng.c

  • Committer: mmach
  • Date: 2023-07-06 04:40:25 UTC
  • Revision ID: netbit73@gmail.com-20230706044025-2ia9985i8wzdn2a7
1.36.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// SPDX-License-Identifier: GPL-2.0 OR MIT
 
2
/*
 
3
 * Copyright (C) 2022 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
 
4
 *
 
5
 * SeedRNG is a simple program made for seeding the Linux kernel random number
 
6
 * generator from seed files. It is is useful in light of the fact that the
 
7
 * Linux kernel RNG cannot be initialized from shell scripts, and new seeds
 
8
 * cannot be safely generated from boot time shell scripts either. It should
 
9
 * be run once at init time and once at shutdown time. It can be run at other
 
10
 * times on a timer as well. Whenever it is run, it writes existing seed files
 
11
 * into the RNG pool, and then creates a new seed file. If the RNG is
 
12
 * initialized at the time of creating a new seed file, then that new seed file
 
13
 * is marked as "creditable", which means it can be used to initialize the RNG.
 
14
 * Otherwise, it is marked as "non-creditable", in which case it is still used
 
15
 * to seed the RNG's pool, but will not initialize the RNG. In order to ensure
 
16
 * that entropy only ever stays the same or increases from one seed file to the
 
17
 * next, old seed values are hashed together with new seed values when writing
 
18
 * new seed files.
 
19
 *
 
20
 * This is based on code from <https://git.zx2c4.com/seedrng/about/>.
 
21
 */
 
22
//config:config SEEDRNG
 
23
//config:       bool "seedrng (1.3 kb)"
 
24
//config:       default y
 
25
//config:       help
 
26
//config:       Seed the kernel RNG from seed files, meant to be called
 
27
//config:       once during startup, once during shutdown, and optionally
 
28
//config:       at some periodic interval in between.
 
29
 
 
30
//applet:IF_SEEDRNG(APPLET(seedrng, BB_DIR_USR_SBIN, BB_SUID_DROP))
 
31
 
 
32
//kbuild:lib-$(CONFIG_SEEDRNG) += seedrng.o
 
33
 
 
34
//usage:#define seedrng_trivial_usage
 
35
//usage:        "[-d DIR] [-n]"
 
36
//usage:#define seedrng_full_usage "\n\n"
 
37
//usage:        "Seed the kernel RNG from seed files"
 
38
//usage:        "\n"
 
39
//usage:        "\n     -d DIR  Use seed files in DIR (default: /var/lib/seedrng)"
 
40
//usage:        "\n     -n      Do not credit randomness, even if creditable"
 
41
 
 
42
#include "libbb.h"
 
43
 
 
44
#include <linux/random.h>
 
45
#include <sys/random.h>
 
46
#include <sys/file.h>
 
47
 
 
48
#ifndef GRND_INSECURE
 
49
#define GRND_INSECURE 0x0004 /* Apparently some headers don't ship with this yet. */
 
50
#endif
 
51
 
 
52
#define DEFAULT_SEED_DIR         "/var/lib/seedrng"
 
53
#define CREDITABLE_SEED_NAME     "seed.credit"
 
54
#define NON_CREDITABLE_SEED_NAME "seed.no-credit"
 
55
 
 
56
enum {
 
57
        MIN_SEED_LEN = SHA256_OUTSIZE,
 
58
        /* kernels < 5.18 could return short reads from getrandom()
 
59
         * if signal is pending and length is > 256.
 
60
         * Let's limit our reads to 256 bytes.
 
61
         */
 
62
        MAX_SEED_LEN = 256,
 
63
};
 
64
 
 
65
static size_t determine_optimal_seed_len(void)
 
66
{
 
67
        char poolsize_str[12];
 
68
        unsigned poolsize;
 
69
        int n;
 
70
 
 
71
        n = open_read_close("/proc/sys/kernel/random/poolsize", poolsize_str, sizeof(poolsize_str) - 1);
 
72
        if (n < 0) {
 
73
                bb_perror_msg("can't determine pool size, assuming %u bits", MIN_SEED_LEN * 8);
 
74
                return MIN_SEED_LEN;
 
75
        }
 
76
        poolsize_str[n] = '\0';
 
77
        poolsize = (bb_strtou(poolsize_str, NULL, 10) + 7) / 8;
 
78
        return MAX(MIN(poolsize, MAX_SEED_LEN), MIN_SEED_LEN);
 
79
}
 
80
 
 
81
static bool read_new_seed(uint8_t *seed, size_t len)
 
82
{
 
83
        bool is_creditable;
 
84
        ssize_t ret;
 
85
 
 
86
        ret = getrandom(seed, len, GRND_NONBLOCK);
 
87
        if (ret == (ssize_t)len) {
 
88
                return true;
 
89
        }
 
90
        if (ret < 0 && errno == ENOSYS) {
 
91
                int fd = xopen("/dev/random", O_RDONLY);
 
92
                struct pollfd random_fd;
 
93
                random_fd.fd = fd;
 
94
                random_fd.events = POLLIN;
 
95
                is_creditable = poll(&random_fd, 1, 0) == 1;
 
96
//This is racy. is_creditable can be set to true here, but other process
 
97
//can consume "good" random data from /dev/urandom before we do it below.
 
98
                close(fd);
 
99
        } else {
 
100
                if (getrandom(seed, len, GRND_INSECURE) == (ssize_t)len)
 
101
                        return false;
 
102
                is_creditable = false;
 
103
        }
 
104
 
 
105
        /* Either getrandom() is not implemented, or
 
106
         * getrandom(GRND_INSECURE) did not give us LEN bytes.
 
107
         * Fallback to reading /dev/urandom.
 
108
         */
 
109
        errno = 0;
 
110
        if (open_read_close("/dev/urandom", seed, len) != (ssize_t)len)
 
111
                bb_perror_msg_and_die("can't read '%s'", "/dev/urandom");
 
112
        return is_creditable;
 
113
}
 
114
 
 
115
static void seed_from_file_if_exists(const char *filename, int dfd, bool credit, sha256_ctx_t *hash)
 
116
{
 
117
        struct {
 
118
                int entropy_count;
 
119
                int buf_size;
 
120
                uint8_t buf[MAX_SEED_LEN];
 
121
        } req;
 
122
        ssize_t seed_len;
 
123
 
 
124
        seed_len = open_read_close(filename, req.buf, sizeof(req.buf));
 
125
        if (seed_len < 0) {
 
126
                if (errno != ENOENT)
 
127
                        bb_perror_msg_and_die("can't read '%s'", filename);
 
128
                return;
 
129
        }
 
130
        xunlink(filename);
 
131
        if (seed_len != 0) {
 
132
                int fd;
 
133
 
 
134
                /* We are going to use this data to seed the RNG:
 
135
                 * we believe it to genuinely containing entropy.
 
136
                 * If this just-unlinked file survives
 
137
                 * (if machine crashes before deletion is recorded on disk)
 
138
                 * and we reuse it after reboot, this assumption
 
139
                 * would be violated, and RNG may end up generating
 
140
                 * the same data. fsync the directory
 
141
                 * to make sure file is gone:
 
142
                 */
 
143
                if (fsync(dfd) != 0)
 
144
                        bb_simple_perror_msg_and_die("I/O error");
 
145
 
 
146
//Length is not random, and taking its address spills variable to stack
 
147
//              sha256_hash(hash, &seed_len, sizeof(seed_len));
 
148
                sha256_hash(hash, req.buf, seed_len);
 
149
 
 
150
                req.buf_size = seed_len;
 
151
                seed_len *= 8;
 
152
                req.entropy_count = credit ? seed_len : 0;
 
153
                printf("Seeding %u bits %s crediting\n",
 
154
                                (unsigned)seed_len, credit ? "and" : "without");
 
155
                fd = xopen("/dev/urandom", O_RDONLY);
 
156
                xioctl(fd, RNDADDENTROPY, &req);
 
157
                if (ENABLE_FEATURE_CLEAN_UP)
 
158
                        close(fd);
 
159
        }
 
160
}
 
161
 
 
162
int seedrng_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 
163
int seedrng_main(int argc UNUSED_PARAM, char **argv)
 
164
{
 
165
        const char *seed_dir;
 
166
        int fd, dfd;
 
167
        int i;
 
168
        unsigned opts;
 
169
        uint8_t new_seed[MAX_SEED_LEN];
 
170
        size_t new_seed_len;
 
171
        bool new_seed_creditable;
 
172
        struct timespec timestamp[2];
 
173
        sha256_ctx_t hash;
 
174
 
 
175
        enum {
 
176
                OPT_n = (1 << 0), /* must be 1 */
 
177
                OPT_d = (1 << 1),
 
178
        };
 
179
#if ENABLE_LONG_OPTS
 
180
        static const char longopts[] ALIGN1 =
 
181
                "skip-credit\0" No_argument       "n"
 
182
                "seed-dir\0"    Required_argument "d"
 
183
                ;
 
184
#endif
 
185
 
 
186
        seed_dir = DEFAULT_SEED_DIR;
 
187
        opts = getopt32long(argv, "nd:", longopts, &seed_dir);
 
188
        umask(0077);
 
189
        if (getuid() != 0)
 
190
                bb_simple_error_msg_and_die(bb_msg_you_must_be_root);
 
191
 
 
192
        if (mkdir(seed_dir, 0700) < 0 && errno != EEXIST)
 
193
                bb_perror_msg_and_die("can't create directory '%s'", seed_dir);
 
194
        dfd = xopen(seed_dir, O_DIRECTORY | O_RDONLY);
 
195
        xfchdir(dfd);
 
196
        /* Concurrent runs of this tool might feed the same data to RNG twice.
 
197
         * Avoid concurrent runs by taking a blocking lock on the directory.
 
198
         * Not checking for errors. Looking at manpage,
 
199
         * ENOLCK "The kernel ran out of memory for allocating lock records"
 
200
         * seems to be the only one which is possible - and if that happens,
 
201
         * machine is OOMing (much worse problem than inability to lock...).
 
202
         * Also, typically configured Linux machines do not fail GFP_KERNEL
 
203
         * allocations (they trigger memory reclaim instead).
 
204
         */
 
205
        flock(dfd, LOCK_EX); /* blocks while another instance runs */
 
206
 
 
207
        sha256_begin(&hash);
 
208
//Hashing in a constant string doesn't add any entropy
 
209
//      sha256_hash(&hash, "SeedRNG v1 Old+New Prefix", 25);
 
210
        clock_gettime(CLOCK_REALTIME, &timestamp[0]);
 
211
        clock_gettime(CLOCK_BOOTTIME, &timestamp[1]);
 
212
        sha256_hash(&hash, timestamp, sizeof(timestamp));
 
213
 
 
214
        for (i = 0; i <= 1; i++) {
 
215
                seed_from_file_if_exists(
 
216
                        i == 0 ? NON_CREDITABLE_SEED_NAME : CREDITABLE_SEED_NAME,
 
217
                        dfd,
 
218
                        /*credit?*/ (opts ^ OPT_n) & i, /* 0, then 1 unless -n */
 
219
                        &hash);
 
220
        }
 
221
 
 
222
        new_seed_len = determine_optimal_seed_len();
 
223
        new_seed_creditable = read_new_seed(new_seed, new_seed_len);
 
224
//Length is not random, and taking its address spills variable to stack
 
225
//      sha256_hash(&hash, &new_seed_len, sizeof(new_seed_len));
 
226
        sha256_hash(&hash, new_seed, new_seed_len);
 
227
        sha256_end(&hash, new_seed + new_seed_len - SHA256_OUTSIZE);
 
228
 
 
229
        printf("Saving %u bits of %screditable seed for next boot\n",
 
230
                (unsigned)new_seed_len * 8, new_seed_creditable ? "" : "non-");
 
231
        fd = xopen3(NON_CREDITABLE_SEED_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0400);
 
232
        xwrite(fd, new_seed, new_seed_len);
 
233
        if (new_seed_creditable) {
 
234
                /* More paranoia when we create a file which we believe contains
 
235
                 * genuine entropy: make sure disk is not full, quota isn't exceeded, etc:
 
236
                 */
 
237
                if (fsync(fd) < 0)
 
238
                        bb_perror_msg_and_die("can't write '%s'", NON_CREDITABLE_SEED_NAME);
 
239
                xrename(NON_CREDITABLE_SEED_NAME, CREDITABLE_SEED_NAME);
 
240
        }
 
241
        return EXIT_SUCCESS;
 
242
}