diff options
| author | Ben Noordhuis <info@bnoordhuis.nl> | 2018-02-04 00:51:21 +0100 |
|---|---|---|
| committer | Ben Noordhuis <info@bnoordhuis.nl> | 2018-02-04 18:58:36 +0100 |
| commit | 73ee434c8c81c373c8dc723c3aa6677978352642 (patch) | |
| tree | 76c7319d14cc89645b4a1abb5539fa16c9abe8f6 /std/os/linux_random.zig | |
| parent | 15eb28efafd0c454e8302cbff0f5c90041d0b17d (diff) | |
| download | zig-73ee434c8c81c373c8dc723c3aa6677978352642.tar.gz zig-73ee434c8c81c373c8dc723c3aa6677978352642.zip | |
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.
Diffstat (limited to 'std/os/linux_random.zig')
| -rw-r--r-- | std/os/linux_random.zig | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/std/os/linux_random.zig b/std/os/linux_random.zig new file mode 100644 index 0000000000..0fb10d1ee9 --- /dev/null +++ b/std/os/linux_random.zig @@ -0,0 +1,246 @@ +const std = @import("../index.zig"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const linux = std.os.linux; +const math = std.math; +const mem = std.mem; +const os = std.os; + +use @import("linux_errno.zig"); + +const arch = switch (builtin.arch) { + builtin.Arch.x86_64 => @import("linux_x86_64.zig"), + builtin.Arch.i386 => @import("linux_i386.zig"), + else => @compileError("unsupported arch"), +}; + +const Method = enum { + Syscall, + Sysctl, + Urandom, +}; + +const Callback = fn(&i32, []u8) usize; + +const Context = struct { + syscall: Callback, + sysctl: Callback, + urandom: Callback, +}; + +pub fn getRandomBytes(buf: []u8) usize { + const ctx = Context { + .syscall = syscall, + .sysctl = sysctl, + .urandom = urandom, + }; + return withContext(ctx, buf); +} + +fn withContext(comptime ctx: Context, buf: []u8) usize { + if (buf.len == 0) return 0; + + var fd: i32 = -1; + defer if (fd != -1) { + const _ = linux.close(fd); // Ignore errors, can't do anything sensible. + }; + + // TODO(bnoordhuis) Remember the method across invocations so we don't make + // unnecessary system calls that are going to fail with ENOSYS anyway. + var method = Method.Syscall; + var i: usize = 0; + while (i < buf.len) { + const rc = switch (method) { + Method.Syscall => ctx.syscall(&fd, buf[i..]), + Method.Sysctl => ctx.sysctl(&fd, buf[i..]), + Method.Urandom => ctx.urandom(&fd, buf[i..]), + }; + if (rc == 0) return usize(-EIO); // Can't really happen. + if (!isErr(rc)) { + i += rc; + continue; + } + if (rc == usize(-EINTR)) continue; + if (rc == usize(-ENOSYS) and method == Method.Syscall) { + method = Method.Urandom; + continue; + } + if (method == Method.Urandom) { + method = Method.Sysctl; + continue; + } + return rc; // Unexpected error. + } + + return i; +} + +fn syscall(_: &i32, buf: []u8) usize { + return arch.syscall3(arch.SYS_getrandom, @ptrToInt(&buf[0]), buf.len, 0); +} + +// Note: reads only 14 bytes at a time. +fn sysctl(_: &i32, buf: []u8) usize { + const __sysctl_args = extern struct { + name: &c_int, + nlen: c_int, + oldval: &u8, + oldlenp: &usize, + newval: ?&u8, + newlen: usize, + unused: [4]usize, + }; + + var name = [3]c_int { 1, 40, 6 }; // { CTL_KERN, KERN_RANDOM, RANDOM_UUID } + var uuid: [16]u8 = undefined; + + const expected: usize = @sizeOf(@typeOf(uuid)); + var len = expected; + + var args = __sysctl_args { + .name = &name[0], + .nlen = c_int(name.len), + .oldval = &uuid[0], + .oldlenp = &len, + .newval = null, + .newlen = 0, + .unused = []usize {0} ** 4, + }; + + const rc = arch.syscall1(arch.SYS__sysctl, @ptrToInt(&args)); + if (rc != 0) return rc; + if (len != expected) return 0; // Can't happen. + + // uuid[] is now a type 4 UUID; bytes 6 and 8 (counting from zero) + // contain 4 and 5 bits of entropy, respectively. For ease of use, + // we skip those and only use 14 of the 16 bytes. + uuid[6] = uuid[14]; + uuid[8] = uuid[15]; + + const n = math.min(buf.len, usize(14)); + @memcpy(&buf[0], &uuid[0], n); + return n; +} + +fn urandom(fd: &i32, buf: []u8) usize { + if (*fd == -1) { + const flags = linux.O_CLOEXEC|linux.O_RDONLY; + const rc = linux.open(c"/dev/urandom", flags, 0); + if (isErr(rc)) return rc; + *fd = i32(rc); + } + // read() doesn't like reads > INT_MAX. + const n = math.min(buf.len, usize(0x7FFFFFFF)); + return linux.read(*fd, &buf[0], n); +} + +fn isErr(rc: usize) bool { + return rc > usize(-4096); +} + +test "os.linux.getRandomBytes" { + try check(42, getRandomBytesTrampoline); +} + +test "os.linux.getRandomBytes syscall" { + try check(42, syscall); +} + +test "os.linux.getRandomBytes sysctl" { + try check(14, sysctl); +} + +test "os.linux.getRandomBytes /dev/urandom" { + try check(42, urandom); +} + +test "os.linux.getRandomBytes state machine" { + const ctx = Context { + .syscall = fortytwo, + .urandom = fail, + .sysctl = fail, + }; + var buf = []u8 {0}; + assert(1 == withContext(ctx, buf[0..])); + assert(42 == buf[0]); +} + +test "os.linux.getRandomBytes no-syscall state machine" { + const ctx = Context { + .syscall = enosys, + .urandom = fortytwo, + .sysctl = fail, + }; + var buf = []u8 {0}; + assert(1 == withContext(ctx, buf[0..])); + assert(42 == buf[0]); +} + +test "os.linux.getRandomBytes no-urandom state machine" { + const ctx = Context { + .syscall = enosys, + .urandom = einval, + .sysctl = fortytwo, + }; + var buf = []u8 {0}; + assert(1 == withContext(ctx, buf[0..])); + assert(42 == buf[0]); +} + +test "os.linux.getRandomBytes no-sysctl state machine" { + const ctx = Context { + .syscall = enosys, + .urandom = einval, + .sysctl = einval, + }; + var buf = []u8 {0}; + assert(usize(-EINVAL) == withContext(ctx, buf[0..])); + assert(0 == buf[0]); +} + +fn einval(_: &i32, buf: []u8) usize { + return usize(-EINVAL); +} + +fn enosys(_: &i32, buf: []u8) usize { + return usize(-ENOSYS); +} + +fn fail(_: &i32, buf: []u8) usize { + os.abort(); +} + +fn fortytwo(_: &i32, buf: []u8) usize { + assert(buf.len == 1); + buf[0] = 42; + return 1; +} + +fn check(comptime N: usize, cb: Callback) %void { + if (builtin.os == builtin.Os.linux) { + var fd: i32 = -1; + defer if (fd != -1) { + const _ = linux.close(fd); // Ignore errors, can't do anything sensible. + }; + + var bufs = [3][N]u8 { + []u8 {0} ** N, + []u8 {0} ** N, + []u8 {0} ** N, + }; + + for (bufs) |*buf| { + const err = cb(&fd, (*buf)[0..]); + assert(err == N); + } + + for (bufs) |*a| + for (bufs) |*b| + if (a != b) + assert(!mem.eql(u8, *a, *b)); + } +} + +fn getRandomBytesTrampoline(_: &i32, buf: []u8) usize { + return getRandomBytes(buf); +} |
