Closed jadonk closed 1 month ago
I'm assuming the same code using crystal run
would work just fine? If so this is specific to the interpreter.
This failure only affects the interpreter. Compiled Crystal works fine.
The interpreter is an experimental feature and missing support for musl libc. This is tracked in the meta issue #11555 (although at this point I'm not aware of further details).
Perhaps we should explicitly deactivate the interpreter on *-linux-musl
targets to make this clear. But interpreter support is an opt-in build anyway, so I don't think this is necessary.
It's certainly an error that the Alpine Linux packages a compiler build with interpreter support on a target platform that it does not support.
AFAIR we don't call the libc getrandom
but make a direct syscall. Maybe this the the issue.
We specifically use LibC.getrandom
in interpreted code, because inline assembly used by Syscall
is unavailable in the interpreter:
The real problem is that whereas the syscall returns the negative of the Errno
value on failure, the LibC
function returns -1 and then sets Errno.value
. Crystal always assumes the former:
As EPERM
equals 1 on Linux, all failures are treated like EPERM
in interpreted code, even though EPERM
itself is not listed as an error for getrandom(2)
, hence the "Operation not permitted". The same can probably happen on other Linux distros if you run out of entropy.
I am in the process of testing #15035 and suspect it should fix the issue for me, but I wonder why not use the musl libc function that imitates the standard libc function? Either way, I like making the Crystal function match the syscall itself is better than matching the libc functions. The less libc dependencies, the better.
When the feature was implemented getrandom(2)
wasn't available in glibc while the syscall was generally available. We also wanted backward compatibility of binaries with kernels that didn't have the syscall.
That was in 2017. The libc symbol was released in glibc 2.25, and even the EOL Ubuntu 18.04 for example had glibc 2.27. I think it's fine to assume that the symbol exists nowadays, and we can simplify the implementation to always use the libc call. That would avoid the init check and the urandom fallback.
Hm I can still reproduce this issue even with #15035 🤔
$ docker run --rm -it alpine:edge
$ apk add crystal git
$ git clone https://github.com/crystal-lang/crystal
$ cd crystal
$ bin/crystal i
Crystal interpreter 1.13.2 (2024-08-29).
EXPERIMENTAL SOFTWARE: if you find a bug, please consider opening an issue in
https://github.com/crystal-lang/crystal/issues/new/
Unhandled exception: getrandom: Bad address (RuntimeError)
from raise|219|1|/crystal/src/raise.cr
from sys_getrandom|112|11|/crystal/src/crystal/system/unix/getrandom.cr
from loop|143|5|/crystal/src/kernel.cr
from sys_getrandom|105|5|/crystal/src/crystal/system/unix/getrandom.cr
from getrandom|89|20|/crystal/src/crystal/system/unix/getrandom.cr
from random_bytes|56|7|/crystal/src/crystal/system/unix/getrandom.cr
from /crystal/src/crystal/hasher.cr|83|3|/crystal/src/crystal/hasher.cr
There is something wrong with Slice#[]
in this line:
It returns a slice whose pointer is the same as the size, hence the getrandom(0x10, 16, GRND_NONBLOCK) = -1 EFAULT (Bad address)
line. If you manually patch that line with read_bytes = sys_getrandom(Slice.new(buf.to_unsafe, chunk_size))
then the interpreter works until you call Slice#[]
yourself:
Bytes.new(16)[0, 4].to_unsafe # => Pointer(UInt8)@0x4
Bytes.new(16)[4, 8]?.try &.to_unsafe # => Pointer(UInt8)@0x8
Dockerfile:
I did test and confirm that running docker with --privileged does not impact the failure.
I found a duplicate at https://github.com/crystal-lang/crystal/issues/12967 and didn't understand why it was marked "completed".
It seems the error should not be musl-specific as I don't see how musl would change the arguements to the syscall. It looks instead that
crystal
might be depending on only certain errors and a failure mode of providing a bad address isn't properly handled.strace:
https://git.musl-libc.org/cgit/musl/tree/src/linux/getrandom.c:
This seems identical to the way libc handles it: