diff options
author | Ron Yorston <rmy@pobox.com> | 2022-05-12 08:11:27 +0100 |
---|---|---|
committer | Ron Yorston <rmy@pobox.com> | 2022-05-12 08:11:27 +0100 |
commit | 7c8c7681a9c8fac1fb8cf77f5950d32885ebb08c (patch) | |
tree | 4e21c0c676bc424ba10e616d9f97de76bfe4409c /miscutils | |
parent | f637f37e0bd2e295936a7b4836676846693219aa (diff) | |
parent | 1099a27696cd733041db97f99da4e22ecd2424e5 (diff) | |
download | busybox-w32-7c8c7681a9c8fac1fb8cf77f5950d32885ebb08c.tar.gz busybox-w32-7c8c7681a9c8fac1fb8cf77f5950d32885ebb08c.tar.bz2 busybox-w32-7c8c7681a9c8fac1fb8cf77f5950d32885ebb08c.zip |
Merge branch 'busybox' into merge
Diffstat (limited to 'miscutils')
-rw-r--r-- | miscutils/crond.c | 23 | ||||
-rw-r--r-- | miscutils/seedrng.c | 242 |
2 files changed, 258 insertions, 7 deletions
diff --git a/miscutils/crond.c b/miscutils/crond.c index 1965af656..bd43c6b68 100644 --- a/miscutils/crond.c +++ b/miscutils/crond.c | |||
@@ -125,6 +125,7 @@ typedef struct CronLine { | |||
125 | char *cl_mailto; /* whom to mail results, may be NULL */ | 125 | char *cl_mailto; /* whom to mail results, may be NULL */ |
126 | #endif | 126 | #endif |
127 | char *cl_shell; | 127 | char *cl_shell; |
128 | char *cl_path; | ||
128 | /* ordered by size, not in natural order. makes code smaller: */ | 129 | /* ordered by size, not in natural order. makes code smaller: */ |
129 | char cl_Dow[7]; /* 0-6, beginning sunday */ | 130 | char cl_Dow[7]; /* 0-6, beginning sunday */ |
130 | char cl_Mons[12]; /* 0-11 */ | 131 | char cl_Mons[12]; /* 0-11 */ |
@@ -421,6 +422,7 @@ static void load_crontab(const char *fileName) | |||
421 | char *mailTo = NULL; | 422 | char *mailTo = NULL; |
422 | #endif | 423 | #endif |
423 | char *shell = NULL; | 424 | char *shell = NULL; |
425 | char *path = NULL; | ||
424 | 426 | ||
425 | delete_cronfile(fileName); | 427 | delete_cronfile(fileName); |
426 | 428 | ||
@@ -470,7 +472,12 @@ static void load_crontab(const char *fileName) | |||
470 | shell = xstrdup(&tokens[0][6]); | 472 | shell = xstrdup(&tokens[0][6]); |
471 | continue; | 473 | continue; |
472 | } | 474 | } |
473 | //TODO: handle HOME= too? "man crontab" says: | 475 | if (is_prefixed_with(tokens[0], "PATH=")) { |
476 | free(path); | ||
477 | path = xstrdup(&tokens[0][5]); | ||
478 | continue; | ||
479 | } | ||
480 | //TODO: handle HOME= too? Better yet, handle arbitrary ENVVARs? "man crontab" says: | ||
474 | //name = value | 481 | //name = value |
475 | // | 482 | // |
476 | //where the spaces around the equal-sign (=) are optional, and any subsequent | 483 | //where the spaces around the equal-sign (=) are optional, and any subsequent |
@@ -480,8 +487,8 @@ static void load_crontab(const char *fileName) | |||
480 | // | 487 | // |
481 | //Several environment variables are set up automatically by the cron(8) daemon. | 488 | //Several environment variables are set up automatically by the cron(8) daemon. |
482 | //SHELL is set to /bin/sh, and LOGNAME and HOME are set from the /etc/passwd | 489 | //SHELL is set to /bin/sh, and LOGNAME and HOME are set from the /etc/passwd |
483 | //line of the crontab's owner. HOME and SHELL may be overridden by settings | 490 | //line of the crontab's owner. HOME, SHELL, and PATH may be overridden by |
484 | //in the crontab; LOGNAME may not. | 491 | //settings in the crontab; LOGNAME may not. |
485 | 492 | ||
486 | #if ENABLE_FEATURE_CROND_SPECIAL_TIMES | 493 | #if ENABLE_FEATURE_CROND_SPECIAL_TIMES |
487 | if (tokens[0][0] == '@') { | 494 | if (tokens[0][0] == '@') { |
@@ -567,6 +574,7 @@ static void load_crontab(const char *fileName) | |||
567 | line->cl_mailto = xstrdup(mailTo); | 574 | line->cl_mailto = xstrdup(mailTo); |
568 | #endif | 575 | #endif |
569 | line->cl_shell = xstrdup(shell); | 576 | line->cl_shell = xstrdup(shell); |
577 | line->cl_path = xstrdup(path); | ||
570 | /* copy command */ | 578 | /* copy command */ |
571 | line->cl_cmd = xstrdup(tokens[5]); | 579 | line->cl_cmd = xstrdup(tokens[5]); |
572 | pline = &line->cl_next; | 580 | pline = &line->cl_next; |
@@ -653,21 +661,22 @@ static void safe_setenv(char **pvar_val, const char *var, const char *val) | |||
653 | } | 661 | } |
654 | #endif | 662 | #endif |
655 | 663 | ||
656 | static void set_env_vars(struct passwd *pas, const char *shell) | 664 | static void set_env_vars(struct passwd *pas, const char *shell, const char *path) |
657 | { | 665 | { |
658 | /* POSIX requires crond to set up at least HOME, LOGNAME, PATH, SHELL. | 666 | /* POSIX requires crond to set up at least HOME, LOGNAME, PATH, SHELL. |
659 | * We assume crond inherited suitable PATH. | ||
660 | */ | 667 | */ |
661 | #if SETENV_LEAKS | 668 | #if SETENV_LEAKS |
662 | safe_setenv(&G.env_var_logname, "LOGNAME", pas->pw_name); | 669 | safe_setenv(&G.env_var_logname, "LOGNAME", pas->pw_name); |
663 | safe_setenv(&G.env_var_user, "USER", pas->pw_name); | 670 | safe_setenv(&G.env_var_user, "USER", pas->pw_name); |
664 | safe_setenv(&G.env_var_home, "HOME", pas->pw_dir); | 671 | safe_setenv(&G.env_var_home, "HOME", pas->pw_dir); |
665 | safe_setenv(&G.env_var_shell, "SHELL", shell); | 672 | safe_setenv(&G.env_var_shell, "SHELL", shell); |
673 | if (path) safe_setenv(&G.env_var_shell, "PATH", path); | ||
666 | #else | 674 | #else |
667 | xsetenv("LOGNAME", pas->pw_name); | 675 | xsetenv("LOGNAME", pas->pw_name); |
668 | xsetenv("USER", pas->pw_name); | 676 | xsetenv("USER", pas->pw_name); |
669 | xsetenv("HOME", pas->pw_dir); | 677 | xsetenv("HOME", pas->pw_dir); |
670 | xsetenv("SHELL", shell); | 678 | xsetenv("SHELL", shell); |
679 | if (path) xsetenv("PATH", path); | ||
671 | #endif | 680 | #endif |
672 | } | 681 | } |
673 | 682 | ||
@@ -701,7 +710,7 @@ fork_job(const char *user, int mailFd, CronLine *line, bool run_sendmail) | |||
701 | shell = line->cl_shell ? line->cl_shell : G.default_shell; | 710 | shell = line->cl_shell ? line->cl_shell : G.default_shell; |
702 | prog = run_sendmail ? SENDMAIL : shell; | 711 | prog = run_sendmail ? SENDMAIL : shell; |
703 | 712 | ||
704 | set_env_vars(pas, shell); | 713 | set_env_vars(pas, shell, NULL); /* don't use crontab's PATH for sendmail */ |
705 | 714 | ||
706 | sv_logmode = logmode; | 715 | sv_logmode = logmode; |
707 | pid = vfork(); | 716 | pid = vfork(); |
@@ -845,7 +854,7 @@ static pid_t start_one_job(const char *user, CronLine *line) | |||
845 | 854 | ||
846 | /* Prepare things before vfork */ | 855 | /* Prepare things before vfork */ |
847 | shell = line->cl_shell ? line->cl_shell : G.default_shell; | 856 | shell = line->cl_shell ? line->cl_shell : G.default_shell; |
848 | set_env_vars(pas, shell); | 857 | set_env_vars(pas, shell, line->cl_path); |
849 | 858 | ||
850 | /* Fork as the user in question and run program */ | 859 | /* Fork as the user in question and run program */ |
851 | pid = vfork(); | 860 | pid = vfork(); |
diff --git a/miscutils/seedrng.c b/miscutils/seedrng.c new file mode 100644 index 000000000..967741dc7 --- /dev/null +++ b/miscutils/seedrng.c | |||
@@ -0,0 +1,242 @@ | |||
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, ×tamp[0]); | ||
211 | clock_gettime(CLOCK_BOOTTIME, ×tamp[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 | } | ||