1 | /* Hurdish implementation of getrandom |
2 | Copyright (C) 2016-2024 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | #include <hurd.h> |
20 | #include <sys/random.h> |
21 | #include <fcntl.h> |
22 | |
23 | __libc_rwlock_define_initialized (static, lock); |
24 | static file_t random_server, random_server_nonblock, |
25 | urandom_server, urandom_server_nonblock; |
26 | |
27 | extern char *__trivfs_server_name __attribute__((weak)); |
28 | |
29 | /* Write up to LENGTH bytes of randomness starting at BUFFER. |
30 | Return the number of bytes written, or -1 on error. */ |
31 | ssize_t |
32 | __getrandom (void *buffer, size_t length, unsigned int flags) |
33 | { |
34 | const char *random_source = "/dev/urandom" ; |
35 | int open_flags = O_RDONLY; |
36 | file_t server, *cached_server; |
37 | error_t err; |
38 | char *data = buffer; |
39 | mach_msg_type_number_t nread = length; |
40 | |
41 | switch (flags) |
42 | { |
43 | case 0: |
44 | cached_server = &urandom_server; |
45 | break; |
46 | case GRND_RANDOM: |
47 | cached_server = &random_server; |
48 | break; |
49 | case GRND_NONBLOCK: |
50 | cached_server = &urandom_server_nonblock; |
51 | break; |
52 | case GRND_RANDOM | GRND_NONBLOCK: |
53 | cached_server = &random_server_nonblock; |
54 | break; |
55 | default: |
56 | return __hurd_fail (EINVAL); |
57 | } |
58 | |
59 | if (flags & GRND_RANDOM) |
60 | random_source = "/dev/random" ; |
61 | if (flags & GRND_NONBLOCK) |
62 | open_flags |= O_NONBLOCK; |
63 | /* No point in passing either O_NOCTTY, O_IGNORE_CTTY, or O_CLOEXEC |
64 | to file_name_lookup, since we're not making an fd. */ |
65 | |
66 | if (&__trivfs_server_name && __trivfs_server_name |
67 | && __trivfs_server_name[0] == 'r' |
68 | && __trivfs_server_name[1] == 'a' |
69 | && __trivfs_server_name[2] == 'n' |
70 | && __trivfs_server_name[3] == 'd' |
71 | && __trivfs_server_name[4] == 'o' |
72 | && __trivfs_server_name[5] == 'm' |
73 | && __trivfs_server_name[6] == '\0') |
74 | /* We are random, don't try to read ourselves! */ |
75 | return length; |
76 | |
77 | again: |
78 | __libc_rwlock_rdlock (lock); |
79 | server = *cached_server; |
80 | if (MACH_PORT_VALID (server)) |
81 | /* Attempt to read some random data using this port. */ |
82 | err = __io_read (server, &data, &nread, -1, length); |
83 | else |
84 | err = MACH_SEND_INVALID_DEST; |
85 | __libc_rwlock_unlock (lock); |
86 | |
87 | if (err == MACH_SEND_INVALID_DEST || err == MIG_SERVER_DIED) |
88 | { |
89 | file_t oldserver = server; |
90 | mach_port_urefs_t urefs; |
91 | |
92 | /* Slow path: the cached port didn't work, or there was no |
93 | cached port in the first place. */ |
94 | |
95 | __libc_rwlock_wrlock (lock); |
96 | server = *cached_server; |
97 | if (server != oldserver) |
98 | { |
99 | /* Someone else must have refetched the port while we were |
100 | waiting for the lock. */ |
101 | __libc_rwlock_unlock (lock); |
102 | goto again; |
103 | } |
104 | |
105 | if (MACH_PORT_VALID (server)) |
106 | { |
107 | /* It could be that someone else has refetched the port and |
108 | it got the very same name. So check whether it is a send |
109 | right (and not a dead name). */ |
110 | err = __mach_port_get_refs (__mach_task_self (), server, |
111 | MACH_PORT_RIGHT_SEND, &urefs); |
112 | if (!err && urefs > 0) |
113 | { |
114 | __libc_rwlock_unlock (lock); |
115 | goto again; |
116 | } |
117 | |
118 | /* Now we're sure that it's dead. */ |
119 | __mach_port_deallocate (__mach_task_self (), server); |
120 | } |
121 | |
122 | server = *cached_server = __file_name_lookup (random_source, |
123 | open_flags, 0); |
124 | __libc_rwlock_unlock (lock); |
125 | if (!MACH_PORT_VALID (server)) |
126 | { |
127 | if (errno == ENOENT) |
128 | /* No translator set up, we won't have support for it. */ |
129 | errno = ENOSYS; |
130 | /* No luck. */ |
131 | return -1; |
132 | } |
133 | |
134 | goto again; |
135 | } |
136 | |
137 | if (err) |
138 | return __hurd_fail (err); |
139 | |
140 | if (data != buffer) |
141 | { |
142 | if (nread > length) |
143 | { |
144 | __vm_deallocate (__mach_task_self (), (vm_address_t) data, nread); |
145 | return __hurd_fail (EGRATUITOUS); |
146 | } |
147 | memcpy (buffer, data, nread); |
148 | __vm_deallocate (__mach_task_self (), (vm_address_t) data, nread); |
149 | } |
150 | |
151 | return nread; |
152 | } |
153 | |
154 | libc_hidden_def (__getrandom) |
155 | weak_alias (__getrandom, getrandom) |
156 | |