]> mj.ucw.cz Git - libucw.git/blob - ucw/random-strong.c
Random: Implemented strong random source.
[libucw.git] / ucw / random-strong.c
1 /*
2  *      UCW Library -- Strong random generator
3  *
4  *      (c) 2020--2022 Pavel Charvat <pchar@ucw.cz>
5  *
6  *      This software may be freely distributed and used according to the terms
7  *      of the GNU Lesser General Public License.
8  */
9
10 #undef LOCAL_DEBUG
11
12 #include <ucw/lib.h>
13 #include <ucw/random.h>
14 #include <ucw/threads.h>
15
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <string.h>
19 #include <unistd.h>
20 #include <sys/types.h>
21
22 #ifdef CONFIG_UCW_GETRANDOM
23 #include <sys/random.h>
24 #endif
25
26 /*** getrandom() helpers ***/
27
28 #ifdef CONFIG_UCW_GETRANDOM
29 static bool strongrand_getrandom_detect(void)
30 {
31   static int static_detected;
32   int detected;
33   ucwlib_lock();
34   detected = static_detected;
35   if (detected)
36     {
37       ucwlib_unlock();
38       return detected > 0;
39     }
40   byte buf[1];
41   int err;
42   while (1)
43     {
44       int n = getrandom(buf, 1, GRND_NONBLOCK);
45       if (n >= 0)
46         {
47           ASSERT(n == 1);
48           detected = 1;
49           break;
50         }
51       else if (errno != EINTR)
52         {
53           err = errno;
54           detected = -1;
55           break;
56         }
57     }
58   static_detected = detected;
59   ucwlib_unlock();
60   if (detected > 0)
61     {
62       DBG("RANDOM: Kernel supports getrandom()");
63       return true;
64     }
65   else if (err == ENOSYS)
66     {
67       DBG("RANDOM: Kernel does not support getrandom()");
68       return false;
69     }
70   else
71     {
72       // We print an error also for EAGAIN -- for urandom it should be possible only during early boot.
73       msg(L_ERROR, "RANDOM: Failed to call getrandom(): %s -> assuming no support", strerror(err));
74       return false;
75     }
76 }
77
78 bool strongrand_getrandom_mem_try(void *buf, size_t size)
79 {
80   if (!strongrand_getrandom_detect())
81     return false;
82   while (size)
83     {
84       ssize_t n = getrandom(buf, size, 0);
85       if (n < 0)
86         {
87           if (errno == EINTR)
88             continue;
89           die("RANDOM: Failed to call getrandom(): %m");
90         }
91       buf = (byte *)buf + n;
92       size -= n;
93     }
94   return true;
95 }
96 #else
97 bool strongrand_getrandom_mem_try(void *buf UNUSED, size_t size UNUSED)
98 {
99   return false;
100 }
101 #endif
102
103 /*** Generic interface ***/
104
105 void strongrand_close(struct strongrand *c)
106 {
107   if (c && c->close)
108     c->close(c);
109 }
110
111 void strongrand_reset(struct strongrand *c)
112 {
113   if (c->reset)
114     c->reset(c);
115 }
116
117 void strongrand_mem(struct strongrand *c, void *ptr, size_t size)
118 {
119   ASSERT(c->read);
120   while (size)
121     {
122       int n = MIN(size, 0x7fffffff);
123       c->read(c, ptr, n);
124       size -= n;
125       ptr = (byte *)ptr + n;
126     }
127 }
128
129 /*** Kernel random source ***/
130
131 struct strongrand_std {
132   struct strongrand sr;
133   int fd;               // Non-negative descriptor or special STRONGRAND_FD_x value
134   uint flags;           // STRONGRAND_STD_x
135   uint buf_size;        // Size of @buf or 0
136   uint buf_avail;       // Remaining buffered bytes, <= @buf_size
137   pid_t last_pid;       // Last known process id; maintained with STRONGRAND_STD_x_RESET
138   byte buf[];           // Buffered randomness
139 };
140
141 // Use getrandom()
142 #define STRONGRAND_FD_KERNEL_GETRANDOM -1
143
144 // Not yet open /dev/[u]random
145 #define STRONGRAND_FD_KERNEL_DEVICE -2
146
147 static void strongrand_std_close(struct strongrand *sr)
148 {
149   struct strongrand_std *srs = (struct strongrand_std *)sr;
150   if (srs->fd >= 0)
151     close(srs->fd);
152   DBG("RANDOM[%s]: Closed", sr->name);
153   xfree(srs);
154 }
155
156 static void strongrand_std_reset(struct strongrand *sr)
157 {
158   struct strongrand_std *srs = (struct strongrand_std *)sr;
159   srs->buf_avail = 0;
160 }
161
162 static void strongrand_std_open_device(struct strongrand_std *srs)
163 {
164   ASSERT(srs->fd == STRONGRAND_FD_KERNEL_DEVICE);
165   ASSERT(srs->flags & (STRONGRAND_STD_URANDOM | STRONGRAND_STD_RANDOM));
166   const char *path = (srs->flags & STRONGRAND_STD_URANDOM) ? "/dev/urandom" : "/dev/random";
167   int fd = open(path, O_RDONLY);
168   if (fd < 0)
169     die("RANDOM[%s]: Cannot open %s: %m", srs->sr.name, path);
170   srs->fd = fd;
171   DBG("RANDOM[%s]: Opened device, path=%s, fd=%d", srs->sr.name, path, fd);
172 }
173
174 static void strongrand_std_read_unbuffered(struct strongrand *sr, byte *ptr, int size)
175 {
176   struct strongrand_std *srs = (struct strongrand_std *)sr;
177 #ifdef CONFIG_UCW_GETRANDOM
178   if (srs->fd == STRONGRAND_FD_KERNEL_GETRANDOM)
179     {
180       while (size)
181         {
182           int n = getrandom(ptr, size, (srs->flags & STRONGRAND_STD_RANDOM) ? GRND_RANDOM : 0);
183           if (n < 0)
184             {
185               if (errno == EINTR || errno == EAGAIN)
186                 continue;
187               die("RANDOM[%s]: Failed to read %u bytes with getrandom(): %m", srs->sr.name, size);
188             }
189           DBG("RANDOM[%s]: Read %u bytes with getrandom()", srs->sr.name, n);
190           ASSERT(n <= size);
191           ptr += n;
192           size -= n;
193         }
194       return;
195     }
196 #endif
197   if (srs->fd < 0)
198     {
199       ASSERT(srs->flags & STRONGRAND_STD_DELAYED);
200       strongrand_std_open_device(srs);
201     }
202   while (size)
203     {
204       int n = read(srs->fd, ptr, size);
205       if (n < 0)
206         {
207           if (errno == EINTR || errno == EAGAIN)
208             continue;
209           die("RANDOM[%s]: Failed to read %u bytes: %m", srs->sr.name, size);
210         }
211       ASSERT(n <= size);
212       DBG("RANDOM[%s]: Read %u bytes", srs->sr.name, n);
213       ptr += n;
214       size -= n;
215     }
216 }
217
218 static void strongrand_std_read_buffered(struct strongrand *sr, byte *ptr, int size)
219 {
220   struct strongrand_std *srs = (struct strongrand_std *)sr;
221   if (srs->flags & (STRONGRAND_STD_AUTO_RESET | STRONGRAND_STD_ASSERT_RESET))
222     {
223       pid_t pid = getpid();
224       if (pid != srs->last_pid)
225         {
226           DBG("RANDOM[%s]: Detected changed pid from %u to %u", sr->name, (uint)srs->last_pid, (uint)pid);
227           srs->last_pid = pid;
228           if (srs->flags & STRONGRAND_STD_ASSERT_RESET)
229             ASSERT(!srs->buf_avail);
230           srs->buf_avail = 0;
231         }
232     }
233   while (size)
234     {
235       if (!srs->buf_avail)
236         {
237           DBG("RANDOM[%s]: Refilling buffer", srs->sr.name);
238           strongrand_std_read_unbuffered(sr, srs->buf, srs->buf_size);
239           srs->buf_avail = srs->buf_size;
240         }
241       uint n = MIN(srs->buf_avail, (uint)size);
242       memcpy(ptr, srs->buf + (srs->buf_size - srs->buf_avail), n);
243       srs->buf_avail -= n;
244       size -= n;
245       ptr += n;
246     }
247 }
248
249 static struct strongrand *strongrand_std_open_internal(int fd, const char *name, uint buf_size, uint flags)
250 {
251   struct strongrand_std *srs = xmalloc(sizeof(*srs) + buf_size);
252   bzero(srs, sizeof(*srs));
253   srs->sr.name = name;
254   srs->sr.close = strongrand_std_close;
255   srs->sr.reset = buf_size ? strongrand_std_reset : NULL;
256   srs->sr.read = buf_size ? strongrand_std_read_buffered : strongrand_std_read_unbuffered;
257   srs->fd = fd;
258   srs->flags = flags;
259   srs->buf_size = buf_size;
260   if (flags & (STRONGRAND_STD_AUTO_RESET | STRONGRAND_STD_ASSERT_RESET))
261     srs->last_pid = getpid();
262   return &srs->sr;
263 }
264
265 struct strongrand *strongrand_std_open(uint buf_size, uint flags)
266 {
267   const char *name;
268   if (flags & STRONGRAND_STD_URANDOM)
269     {
270       ASSERT(!(flags & STRONGRAND_STD_RANDOM));
271       name = "urandom";
272     }
273   else
274     {
275       ASSERT(flags & STRONGRAND_STD_RANDOM);
276       name = "random";
277     }
278 #ifdef CONFIG_UCW_GETRANDOM
279   if (strongrand_getrandom_detect())
280     return strongrand_std_open_internal(STRONGRAND_FD_KERNEL_GETRANDOM, name, buf_size, flags);
281 #endif
282   struct strongrand *sr = strongrand_std_open_internal(STRONGRAND_FD_KERNEL_DEVICE, name, buf_size, flags);
283   if (!(flags & STRONGRAND_STD_DELAYED))
284     strongrand_std_open_device((struct strongrand_std *)sr);
285   return sr;
286 }