Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assume getrandom on Linux #15040

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/crystal/system/random.cr
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ end
{% if flag?(:wasi) %}
require "./wasi/random"
{% elsif flag?(:linux) %}
require "./unix/getrandom"
require "c/sys/random"
\{% if LibC.has_method?(:getrandom) %}
require "./unix/getrandom"
\{% else %}
require "./unix/urandom"
\{% end %}
{% elsif flag?(:bsd) || flag?(:darwin) %}
require "./unix/arc4random"
{% elsif flag?(:unix) %}
Expand Down
118 changes: 19 additions & 99 deletions src/crystal/system/unix/getrandom.cr
Original file line number Diff line number Diff line change
@@ -1,119 +1,39 @@
{% skip_file unless flag?(:linux) %}

require "c/unistd"
require "./syscall"

{% if flag?(:interpreted) %}
lib LibC
fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : LibC::SSizeT
end

module Crystal::System::Syscall
GRND_NONBLOCK = 1u32

# TODO: Implement syscall for interpreter
def self.getrandom(buf : UInt8*, buflen : LibC::SizeT, flags : UInt32) : LibC::SSizeT
# the syscall returns the negative of errno directly, the C function
# doesn't, so we mimic the syscall behavior
read_bytes = LibC.getrandom(buf, buflen, flags)
read_bytes >= 0 ? read_bytes : LibC::SSizeT.new(-Errno.value.value)
end
end
{% end %}
require "c/sys/random"

module Crystal::System::Random
@@initialized = false
@@getrandom_available = false
@@urandom : ::File?

private def self.init
@@initialized = true

if has_sys_getrandom
@@getrandom_available = true
else
urandom = ::File.open("/dev/urandom", "r")
return unless urandom.info.type.character_device?

urandom.close_on_exec = true
urandom.read_buffering = false # don't buffer bytes
@@urandom = urandom
end
end

private def self.has_sys_getrandom
sys_getrandom(Bytes.new(16))
true
rescue
false
end

# Reads n random bytes using the Linux `getrandom(2)` syscall.
def self.random_bytes(buf : Bytes) : Nil
init unless @@initialized

if @@getrandom_available
getrandom(buf)
elsif urandom = @@urandom
urandom.read_fully(buf)
else
raise "Failed to access secure source to generate random bytes!"
end
def self.random_bytes(buffer : Bytes) : Nil
getrandom(buffer)
end

def self.next_u : UInt8
init unless @@initialized

if @@getrandom_available
buf = uninitialized UInt8
getrandom(pointerof(buf).to_slice(1))
buf
elsif urandom = @@urandom
urandom.read_byte.not_nil!
else
raise "Failed to access secure source to generate random bytes!"
end
buffer = uninitialized UInt8
getrandom(pointerof(buffer).to_slice(1))
buffer
end

# Reads n random bytes using the Linux `getrandom(2)` syscall.
private def self.getrandom(buf)
private def self.getrandom(buffer)
# getrandom(2) may only read up to 256 bytes at once without being
# interrupted or returning early
chunk_size = 256

while buf.size > 0
if buf.size < chunk_size
chunk_size = buf.size
end
while buffer.size > 0
read_bytes = 0

read_bytes = sys_getrandom(buf[0, chunk_size])
loop do
# pass GRND_NONBLOCK flag so that it fails with EAGAIN if the requested
# entropy was not available
read_bytes = LibC.getrandom(buffer, buffer.size.clamp(..chunk_size), LibC::GRND_NONBLOCK)
break unless read_bytes == -1

buf += read_bytes
end
end
err = Errno.value
raise RuntimeError.from_os_error("getrandom", err) unless err.in?(Errno::EINTR, Errno::EAGAIN)

# Low-level wrapper for the `getrandom(2)` syscall, returns the number of
# bytes read or the errno as a negative number if an error occurred (or the
# syscall isn't available). The GRND_NONBLOCK=1 flag is passed as last argument,
# so that it returns -EAGAIN if the requested entropy was not available.
#
# We use the kernel syscall instead of the `getrandom` C function so any
# binary compiled for Linux will always use getrandom if the kernel is 3.17+
# and silently fallback to read from /dev/urandom if not (so it's more
# portable).
private def self.sys_getrandom(buf : Bytes)
loop do
read_bytes = Syscall.getrandom(buf.to_unsafe, LibC::SizeT.new(buf.size), Syscall::GRND_NONBLOCK)
if read_bytes < 0
err = Errno.new(-read_bytes.to_i)
if err.in?(Errno::EINTR, Errno::EAGAIN)
::Fiber.yield
else
raise RuntimeError.from_os_error("getrandom", err)
end
else
return read_bytes
::Fiber.yield
end

buffer += read_bytes
end
end
end
2 changes: 0 additions & 2 deletions src/crystal/system/unix/urandom.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
{% skip_file unless flag?(:unix) && !flag?(:netbsd) && !flag?(:openbsd) && !flag?(:linux) %}

module Crystal::System::Random
@@initialized = false
@@urandom : ::File?
Expand Down
7 changes: 7 additions & 0 deletions src/lib_c/aarch64-android/c/sys/random.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
lib LibC
{% if ANDROID_API >= 28 %}
GRND_NONBLOCK = 1_u32

fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
{% end %}
end
5 changes: 5 additions & 0 deletions src/lib_c/aarch64-linux-gnu/c/sys/random.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
lib LibC
GRND_NONBLOCK = 1_u32

fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
end
5 changes: 5 additions & 0 deletions src/lib_c/aarch64-linux-musl/c/sys/random.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
lib LibC
GRND_NONBLOCK = 1_u32

fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
end
5 changes: 5 additions & 0 deletions src/lib_c/arm-linux-gnueabihf/c/sys/random.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
lib LibC
GRND_NONBLOCK = 1_u32

fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
end
5 changes: 5 additions & 0 deletions src/lib_c/i386-linux-gnu/c/sys/random.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
lib LibC
GRND_NONBLOCK = 1_u32

fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
end
5 changes: 5 additions & 0 deletions src/lib_c/i386-linux-musl/c/sys/random.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
lib LibC
GRND_NONBLOCK = 1_u32

fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
end
5 changes: 5 additions & 0 deletions src/lib_c/x86_64-linux-gnu/c/sys/random.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
lib LibC
GRND_NONBLOCK = 1_u32

fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
end
5 changes: 5 additions & 0 deletions src/lib_c/x86_64-linux-musl/c/sys/random.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
lib LibC
GRND_NONBLOCK = 1_u32

fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
end
2 changes: 1 addition & 1 deletion src/random/secure.cr
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ require "crystal/system/random"
# ```
#
# On BSD-based systems and macOS/Darwin, it uses [`arc4random`](https://man.openbsd.org/arc4random),
# on Linux [`getrandom`](http://man7.org/linux/man-pages/man2/getrandom.2.html) (if the kernel supports it),
# on Linux [`getrandom`](http://man7.org/linux/man-pages/man2/getrandom.2.html),
# on Windows [`RtlGenRandom`](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom),
# and falls back to reading from `/dev/urandom` on UNIX systems.
module Random::Secure
Expand Down
Loading