authorgravatar for info@bnoordhuis.nlBen Noordhuis <info@bnoordhuis.nl> 2018-02-04 00:51:21+01:00
committergravatar for info@bnoordhuis.nlBen Noordhuis <info@bnoordhuis.nl> 2018-02-04 18:58:36+01:00
log73ee434c8c81c373c8dc723c3aa6677978352642
tree76c7319d14cc89645b4a1abb5539fa16c9abe8f6
parent15eb28efafd0c454e8302cbff0f5c90041d0b17d

Use /dev/urandom and sysctl(RANDOM_UUID) on Linux.

Add fallback paths for when the getrandom(2) system call is not available. Try /dev/urandom first and sysctl(RANDOM_UUID) second. The sysctl issues a warning in the system logs with some kernels but that seems like an acceptable tradeoff for the fallback of a fallback.

4 files changed, 250 insertions(+), 11 deletions(-)

CMakeLists.txt+1
......@@ -441,6 +441,7 @@ set(ZIG_STD_FILES
441441 "os/index.zig"
442442 "os/linux.zig"
443443 "os/linux_errno.zig"
444 "os/linux_random.zig"
444445 "os/linux_i386.zig"
445446 "os/linux_x86_64.zig"
446447 "os/path.zig"
std/os/index.zig+2-11
......@@ -78,17 +78,8 @@ error WouldBlock;
7878pub fn getRandomBytes(buf: []u8) %void {
7979 switch (builtin.os) {
8080 Os.linux => while (true) {
81 // TODO check libc version and potentially call c.getrandom.
82 // See #397
83 const err = posix.getErrno(posix.getrandom(buf.ptr, buf.len, 0));
84 if (err > 0) {
85 return switch (err) {
86 posix.EINVAL => unreachable,
87 posix.EFAULT => unreachable,
88 posix.EINTR => continue,
89 else => unexpectedErrorPosix(err),
90 };
91 }
81 const err = posix.getErrno(posix.getRandomBytes(buf));
82 if (err > 0) return unexpectedErrorPosix(err);
9283 return;
9384 },
9485 Os.macosx, Os.ios => {
std/os/linux.zig+1
......@@ -7,6 +7,7 @@ const arch = switch (builtin.arch) {
77 else => @compileError("unsupported arch"),
88};
99pub use @import("linux_errno.zig");
10pub use @import("linux_random.zig");
1011
1112pub const PATH_MAX = 4096;
1213
std/os/linux_random.zig created+246
......@@ -0,0 +1,246 @@
1const std = @import("../index.zig");
2const builtin = @import("builtin");
3const assert = std.debug.assert;
4const linux = std.os.linux;
5const math = std.math;
6const mem = std.mem;
7const os = std.os;
8
9use @import("linux_errno.zig");
10
11const arch = switch (builtin.arch) {
12 builtin.Arch.x86_64 => @import("linux_x86_64.zig"),
13 builtin.Arch.i386 => @import("linux_i386.zig"),
14 else => @compileError("unsupported arch"),
15};
16
17const Method = enum {
18 Syscall,
19 Sysctl,
20 Urandom,
21};
22
23const Callback = fn(&i32, []u8) usize;
24
25const Context = struct {
26 syscall: Callback,
27 sysctl: Callback,
28 urandom: Callback,
29};
30
31pub fn getRandomBytes(buf: []u8) usize {
32 const ctx = Context {
33 .syscall = syscall,
34 .sysctl = sysctl,
35 .urandom = urandom,
36 };
37 return withContext(ctx, buf);
38}
39
40fn withContext(comptime ctx: Context, buf: []u8) usize {
41 if (buf.len == 0) return 0;
42
43 var fd: i32 = -1;
44 defer if (fd != -1) {
45 const _ = linux.close(fd); // Ignore errors, can't do anything sensible.
46 };
47
48 // TODO(bnoordhuis) Remember the method across invocations so we don't make
49 // unnecessary system calls that are going to fail with ENOSYS anyway.
50 var method = Method.Syscall;
51 var i: usize = 0;
52 while (i < buf.len) {
53 const rc = switch (method) {
54 Method.Syscall => ctx.syscall(&fd, buf[i..]),
55 Method.Sysctl => ctx.sysctl(&fd, buf[i..]),
56 Method.Urandom => ctx.urandom(&fd, buf[i..]),
57 };
58 if (rc == 0) return usize(-EIO); // Can't really happen.
59 if (!isErr(rc)) {
60 i += rc;
61 continue;
62 }
63 if (rc == usize(-EINTR)) continue;
64 if (rc == usize(-ENOSYS) and method == Method.Syscall) {
65 method = Method.Urandom;
66 continue;
67 }
68 if (method == Method.Urandom) {
69 method = Method.Sysctl;
70 continue;
71 }
72 return rc; // Unexpected error.
73 }
74
75 return i;
76}
77
78fn syscall(_: &i32, buf: []u8) usize {
79 return arch.syscall3(arch.SYS_getrandom, @ptrToInt(&buf[0]), buf.len, 0);
80}
81
82// Note: reads only 14 bytes at a time.
83fn sysctl(_: &i32, buf: []u8) usize {
84 const __sysctl_args = extern struct {
85 name: &c_int,
86 nlen: c_int,
87 oldval: &u8,
88 oldlenp: &usize,
89 newval: ?&u8,
90 newlen: usize,
91 unused: [4]usize,
92 };
93
94 var name = [3]c_int { 1, 40, 6 }; // { CTL_KERN, KERN_RANDOM, RANDOM_UUID }
95 var uuid: [16]u8 = undefined;
96
97 const expected: usize = @sizeOf(@typeOf(uuid));
98 var len = expected;
99
100 var args = __sysctl_args {
101 .name = &name[0],
102 .nlen = c_int(name.len),
103 .oldval = &uuid[0],
104 .oldlenp = &len,
105 .newval = null,
106 .newlen = 0,
107 .unused = []usize {0} ** 4,
108 };
109
110 const rc = arch.syscall1(arch.SYS__sysctl, @ptrToInt(&args));
111 if (rc != 0) return rc;
112 if (len != expected) return 0; // Can't happen.
113
114 // uuid[] is now a type 4 UUID; bytes 6 and 8 (counting from zero)
115 // contain 4 and 5 bits of entropy, respectively. For ease of use,
116 // we skip those and only use 14 of the 16 bytes.
117 uuid[6] = uuid[14];
118 uuid[8] = uuid[15];
119
120 const n = math.min(buf.len, usize(14));
121 @memcpy(&buf[0], &uuid[0], n);
122 return n;
123}
124
125fn urandom(fd: &i32, buf: []u8) usize {
126 if (*fd == -1) {
127 const flags = linux.O_CLOEXEC|linux.O_RDONLY;
128 const rc = linux.open(c"/dev/urandom", flags, 0);
129 if (isErr(rc)) return rc;
130 *fd = i32(rc);
131 }
132 // read() doesn't like reads > INT_MAX.
133 const n = math.min(buf.len, usize(0x7FFFFFFF));
134 return linux.read(*fd, &buf[0], n);
135}
136
137fn isErr(rc: usize) bool {
138 return rc > usize(-4096);
139}
140
141test "os.linux.getRandomBytes" {
142 try check(42, getRandomBytesTrampoline);
143}
144
145test "os.linux.getRandomBytes syscall" {
146 try check(42, syscall);
147}
148
149test "os.linux.getRandomBytes sysctl" {
150 try check(14, sysctl);
151}
152
153test "os.linux.getRandomBytes /dev/urandom" {
154 try check(42, urandom);
155}
156
157test "os.linux.getRandomBytes state machine" {
158 const ctx = Context {
159 .syscall = fortytwo,
160 .urandom = fail,
161 .sysctl = fail,
162 };
163 var buf = []u8 {0};
164 assert(1 == withContext(ctx, buf[0..]));
165 assert(42 == buf[0]);
166}
167
168test "os.linux.getRandomBytes no-syscall state machine" {
169 const ctx = Context {
170 .syscall = enosys,
171 .urandom = fortytwo,
172 .sysctl = fail,
173 };
174 var buf = []u8 {0};
175 assert(1 == withContext(ctx, buf[0..]));
176 assert(42 == buf[0]);
177}
178
179test "os.linux.getRandomBytes no-urandom state machine" {
180 const ctx = Context {
181 .syscall = enosys,
182 .urandom = einval,
183 .sysctl = fortytwo,
184 };
185 var buf = []u8 {0};
186 assert(1 == withContext(ctx, buf[0..]));
187 assert(42 == buf[0]);
188}
189
190test "os.linux.getRandomBytes no-sysctl state machine" {
191 const ctx = Context {
192 .syscall = enosys,
193 .urandom = einval,
194 .sysctl = einval,
195 };
196 var buf = []u8 {0};
197 assert(usize(-EINVAL) == withContext(ctx, buf[0..]));
198 assert(0 == buf[0]);
199}
200
201fn einval(_: &i32, buf: []u8) usize {
202 return usize(-EINVAL);
203}
204
205fn enosys(_: &i32, buf: []u8) usize {
206 return usize(-ENOSYS);
207}
208
209fn fail(_: &i32, buf: []u8) usize {
210 os.abort();
211}
212
213fn fortytwo(_: &i32, buf: []u8) usize {
214 assert(buf.len == 1);
215 buf[0] = 42;
216 return 1;
217}
218
219fn check(comptime N: usize, cb: Callback) %void {
220 if (builtin.os == builtin.Os.linux) {
221 var fd: i32 = -1;
222 defer if (fd != -1) {
223 const _ = linux.close(fd); // Ignore errors, can't do anything sensible.
224 };
225
226 var bufs = [3][N]u8 {
227 []u8 {0} ** N,
228 []u8 {0} ** N,
229 []u8 {0} ** N,
230 };
231
232 for (bufs) |*buf| {
233 const err = cb(&fd, (*buf)[0..]);
234 assert(err == N);
235 }
236
237 for (bufs) |*a|
238 for (bufs) |*b|
239 if (a != b)
240 assert(!mem.eql(u8, *a, *b));
241 }
242}
243
244fn getRandomBytesTrampoline(_: &i32, buf: []u8) usize {
245 return getRandomBytes(buf);
246}