sunfishcode's blog
A blog by sunfishcode

Eyra does the impossible

Posted on

Eyra is challenging ideas about what it means to be a libc. In doing so, it's doing a few things often considered to be... impossible 😉.

Fixing Rust's set_var unsoundness

rust-lang/rust#27970 is a soundness bug in Rust. It is a way that Rust programs can segfault without using any unsafe code. The bug was opened 8 years ago, and it's widely believed to be impossible to fully fix.

One of the reasons it's so difficult is that it isn't enough to do locking, even inside libc, because getenv returns pointers to memory that need to stay valid after the getenv call returns. And, getenv/setenv/etc. can be called by arbitrary C code, so any scheme that relies on all callers of getenv following a particular protocol isn't reliable.

Eyra solves this by having setenv etc. just leak the old memory. That ensures that it stays valid for as long as any thread needs it. Granted, leaking isn't great, and Eyra makes it configurable with the "threadsafe-setenv" cargo feature, so it can be disabled in favor of the thread-unsafe implementation. However that said, leaking is not unsafe, and for some use cases, it'll be less bad than the possibility of undefined behavior from dangling pointers.

Eyra currently only supports Linux, and has other limitations, so it isn't a full solution for all of Rust, but it does solve the problem within its range.

Full host NSS and DNS support without dynamic linking

In glibc, a statically-linked binary, despite being statically linked, heroically includes the ability to dlopen NSS libraries as needed in order to implement the lookup rules defined in "/etc/nsswitch.conf", and this means that statically-linked binaries end up depending on the versions of the NSS libraries that match the glibc version they were built with.

In musl, a statically-linked binary just hard-codes the basic NSS and DNS resolution strategies. It knows how to read "/etc/passwd", "/etc/resolv.conf", and other files, and do the main things that one does with those files, so it doesn't respect "/etc/nsswitch.conf" at all, and doesn't use the same name lookup logic as other programs on a glibc-based distribution.

These have long been the only options, but Eyra does something different.

In Eyra, NSS functions are implemented by executing the external getent program, and parsing its output. getent is present on both glibc-based and musl-based Linux distributions, and has a stable command-line interface, so Eyra programs do not depend on a specific version of libc being installed, and it follows the system NSS and DNS configuration.

(And to be sure, using getent like this won't work for all use cases, so Eyra may add other options in the future.)

Compiling whole programs with a single cargo build.

Eyra is not the only system capable of whole-program optimization, however to my knowledge, it is the only system that can compile a whole program, entirely from source, in a single cargo build invocation.

This is achieved by using cargo's -Z build-std option, which builds libcore, liballoc, libstd, and other Rust libraries from source, by using Eyra which builds the libc implementation from source, and, for completeness, by disabling Eyra's "use-compiler-builtins" feature, which tells it to use its own implementations of memcpy etc. instead of linking to Rust's prebuilt compiler_builtins library. Thay way, everything down to the OS boundary is just a cargo dependency built from source. See the all-from-source example for more details.

This may be useful for doing whole-program static analysis, for using non-standard calling conventions, or anything else that requires that the whole program be compiled together.

What's Eyra?

Eyra is currently a side project that I'm building for fun. If you think it sounds interesting, please reach out!