Repository navigation
Support x86_64-unknown-linux-none #424
Description
Activity
I think #401 is relevant here. We need a way of doing raw syscalls for this. Ideally, it would be handled by the
linux-raw-syscrate, but @sunfishcode is hesitant to add raw syscall functions to it.I think if we want to support this target, we should just write the inline assembly to invoke the
x86_64syscallinstruction, and hardcode the raw syscall number (318).I agre with @sunfishcode that supporting a general mechanism for invoking any syscall on any Linux CPU architecture is very complicated (vDSO, some syscalls may or may not return,
vfork, inline assembly isn't stable on all architectures supported by Rust, etc...).But the situation for
getrandom(2) + x86_64specifically is much simpler, Linux only usessyscallonx86_64, andx86_64inline assembly is stable. So the implementation can just be:const NR_GETRANDOM: u32 = 318; pub fn getrandom_syscall(buf: &mut [MaybeUninit<u8>], flags: u32) -> isize { unsafe { let ret; asm!( "syscall", inlateout("rax") NR_GETRANDOM as isize => ret, in("rdi") buf.as_mut_ptr(), in("rsi") buf.len(), in("rdx") flags, lateout("rcx") _, lateout("r11") _, options(nostack, preserves_flags) ); ret } }
@josephlr
Hm, you are right. If the raw target is limited to x86-64 it would be the easiest way to do this.We probably can do it for other target arches as well (on x86 we can just use
int 0x80for simplicity) and gate it behind a hypotheticallinux_raw_syscallfeature.In the PR, @morr0ne said that x86_64-unknown-linux-none won't have libc, so it's likely that many crates will use rustix for I/O on
*-linux-none, so rustix will often be in people's dependency trees already. Perhaps that makes it feasible for getrandom to just userustix::rand::getrandom_uniniton x86_64-unknown-linux-none.If it were just getrandom it may seem ok to hard-code a little bit of
asm, but if other crates start following this example and we end up with many crates doing their own syscallasms, I think that would be unfortunate. And, it's reasonable to anticipate morelinux-nonetargets than just x86_64.Reacted by MatiIn the PR, @morr0ne said that x86_64-unknown-linux-none won't have libc, so it's likely that many crates will use rustix for I/O on
*-linux-none, so rustix will often be in people's dependency trees already. Perhaps that makes it feasible for getrandom to just userustix::rand::getrandom_uniniton x86_64-unknown-linux-none.The *-linux-none targets heavily rely on something like rustix to work; at the moment its basically guaranteed that rustix will already be in the dependency tree.
If it were just getrandom it may seem ok to hard-code a little bit of
asm, but if other crates start following this example and we end up with many crates doing their own syscallasms, I think that would be unfortunate. And, it's reasonable to anticipate morelinux-nonetargets than just x86_64.The x86_64-unknown-linux-none target is just the first of other *-linux-none targets. I plan to implement at the very least aarch64 and riscv64gc targets. Using asm here would mean having to implement such syscall manually for each potential target.
rustixdoes a lot of things and it's a relatively big dependency (42k LoCs insrc/according totokei). As I wrote in the other issue, I likerustixand use it in my projects, but it's often prohibitively big for certain things.getrandomis just one of the examples. Another example is theio-uringcrate, it needs only 3 syscalls and it handles associated types by itself.This is why I would strongly prefer to have raw syscalls in the
linux-raw-syscrate. In my opinion, the safety arguments against it just don't hold water in the light of thelibc::sycallexistence. Yes, it's an extremely dangerousunsafetool and most people should userustix, but it does not mean it should not be exposed. Arguably, it's similar to how we often expose*-syscrates, while users are directed toward safe wrappers.If raw syscalls will not be added to
linux-raw-sys, then our next best option is to define raw syscalls by ourselves as proposed by @josephlr above. Outside of the x86 targets (if we do not want to useint 0x80), it's a relatively straightforward piece of code.@josephlr wrote:
I think if we want to support this target, we should just write the inline assembly to invoke the x86_64 syscall instruction, and hardcode the raw syscall number (318).
I agree with this suggestion, and this is what I'm planning to do in ring for all
x86_64-*-linux-*targets.getrandom shouldn't add rustix as a dependency for this.
At the most, we could have a feature flag to use rustix instead of the inline assembly support. I suspect there are reason why some rustix users would prefer this, e.g. if they are using rustix to port to a non-Linux OS. But there's a separate issue for rustix already, to track this.
If getrandom is going to have the inline assembly, it should use the inline assembly for all
x86_64-*-linux-*targets and eliminate the libc dependency for these targets, so that it gets tested continuously.This is why I would strongly prefer to have raw syscalls in the linux-raw-sys crate.
I would rather not have getrandom depend on
linux-raw-sys crate. Chances are high that there will be bugs inlinux-raw-sys cratethat will not affectgetrandombut which would trigger dependency management tools, causing a lot of churn and wasted effort.Chances are high that there will be bugs in linux-raw-sys crate that will not affect getrandom but which would trigger dependency management tools, causing a lot of churn and wasted effort.
I don't think it's likely. The crate is a largely automatic conversion of the Linux headers and has almost no issues in its tracker despite being used actively (mostly through
rustix). Comparatively,libcis a more problematic dependency and it has caused several breakages ingetrandomfor various reasons.I think that maintaining inline assembly for syscalls and syscall numbers (rant: I wish Linux had stable syscall numbers across targets...) for various targets should be outside of the
getrandom's area of responsibility. Ideally, we need THE crate for doing raw Linux syscall stuff, which would be used across the crates ecosystem.linux-raw-syslooks like the best candidate for this right now.What are the practical problems with using rustix for just *-linux-none, and leaving the rest of getrandom as-is?
@josephlr wrote:
But the situation for getrandom(2) + x86_64 specifically is much simpler, Linux only uses syscall on x86_64, and x86_64 inline assembly is stable. So the implementation can just be:
That would support the target when the
dev/urandomfallback is disabled. When they added this target, I asked them to commit to a minimum kernel version that would include thegetrandomsyscall, but they didn't want to. Thus, the minimum kernel version should be assumed to be the same as the Rust project's minimum in general for x86_64-linux; i.e. adev/urandomfallback would be needed for this target as well.FYI: In ring, for
*-*-linux-*, I am considering requiring the user to opt into a libstd dependency for thedev/urandomfallback, and implementing the syscall much like @josephlr proposed. I would be willing to helpgetrandommaintain a standalone syscall wrapper.What are the practical problems with using rustix for just *-linux-none, and leaving the rest of getrandom as-is?
At least for me (ring), I don't want to (in some sense, can't) have divergent codepaths for the
x86_64-*-linux-*targets (in configurations for which thedev/urandomfallback is disabled). I expect there will be many rustix-like things in the next few years, unless/until rustix becomes a rust-lang project.At least for me (ring), I don't want to (in some sense, can't) have divergent codepaths for the
x86_64-*-linux-*targets. I expect there will be many rustix-like things in the next few years, unless/until rustix becomes a rust-lang project.I'm not familiar with ring. What are the practical considerations around using different codepaths for the
x86_64-*-linux-*targets?I expect there will be many rustix-like things in the next few years, unless/until rustix becomes a rust-lang project.
If we only used rustix for
*-linux-none, which very few people are using today, how much of a practical concern is this?What are the practical problems with using rustix for just *-linux-none, and leaving the rest of getrandom as-is?
- Complexity and general size. It increases compilation times and makes it harder to review code in security-sensitive settings.
- It increases chances of accidentally pulling
libc(e.g. by enabling features). You couldcfgthis out for*-linux-nonetargets, but it will makerustixless consistent (e.g. it will be strange if theuse-libcfeature will have no effect). - It has a relatively high MSRV, which is likely to be bumped in future. (This point will be no longer relevant after wide adoption of the MSRV-aware resolver)
- It evolves faster than
linux-raw-sysand has a much bigger API surface, which increases downstream maintenance burden and probability of introducing new bugs.
I will reverse the question: what are the practical problems with exposing raw syscall functions in
linux-raw-sys? As I wrote above and in therustixissue, I don't consider the safety argument valid. The only stumbling block which I see is the vDSO stuff on x86 targets. One potential solution for this is to useint 0x80(or potentiallysysenter) inlinux-raw-sysand implement vDSO-based syscalls in a separate crate or leave it inrustix.If we only used rustix for
*-linux-none, which very few people are using today, how much of a practical concern is this?I am using
*-linux-nonewithout rustix for ring specifically for unspecified reasons.That would support the target when the
dev/urandomfallback is disabled. When they added this target, I asked them to commit to a minimum kernel version that would include thegetrandomsyscall, but they didn't want to. Thus, the minimum kernel version should be assumed to be the same as the Rust project's minimum in general for x86_64-linux; i.e. adev/urandomfallback would be needed for this target as well.When adding the target I specifically did not want to add a minimum version because I felt it was too soon considering I was the only user of the target. Seeing there is interest in adding support for the *-linux-none target(s) in getrandom then it is reasonable to bump the minimum kernel version to support the required syscall.
3 remaining items
The linux-raw-sys crate cannot provide a syscall function because such a function does not exist.
Such a wrapper would be outside the scope of a *-sys crate
I disagree. For most practical intents and purposes syscalls are functions, they just have a "weird" ABI, which can not be properly described in C headers (well, I guess technically you could do it? but historically people don't do it for various reasons). The items defined in the Linux headers are intended to by used with syscalls, so, arguably, it makes sense to have them in one crate.
That said, I am fine with having a separate syscall crate when compared to the current status quo. I guess such crate could be relatively stable, while
linux-raw-sysmay need new releases on future Linux kernel releases which introduce new stuff in public API.I disagree. For most practical intents and purposes syscalls are functions, they just have a "weird" ABI, which can not be properly described in C headers (well, I guess technically you could do it? but historically people don't do it for various reasons). The items defined in the Linux headers are intended to by used with syscalls, so, arguably, it makes sense to have them in one crate.
Perhaps I didn't explain myself properly. What I mean to say is that syscalls are not something we can automatically generate but need to be manually implemented. linux-raw-sys is fully generated code.
That said, I am fine with having a separate syscall crate when compared to the current status quo. I guess such crate could be relatively stable, while
linux-raw-sysmay need new releases on future Linux kernel releases which introduce new stuff in public API.That is somewhat the idea however we really need @sunfishcode input on this
Perhaps I didn't explain myself properly. What I mean to say is that syscalls are not something we can automatically generate but need to be manually implemented. linux-raw-sys is fully generated code.
I think it would be possible for the
linux-raw-syscrate to generate the syscall wrapper functions, as all their information is present in the various Linux headers, so they could (in theory) be auto-generated. For example:include/uapi/asm-generic/unistd.h(and arch-specific files) defines__NR_getrandomand referencessys_getrandom#define __NR_getrandom 278 __SYSCALL(__NR_getrandom, sys_getrandom)
include/linux/syscalls.hdefines the prototype forsys_getrandomasmlinkage long sys_getrandom(char __user *buf, size_t count, unsigned int flags);
linux_raw_sys::general::__NR_getrandomwould still exist as a constantlinux_raw_sys::syscalls::sys_getrandomwould be a function whose implementation could be generated based on the "ABI" value in the syscall table:#[inline] pub unsafe fn sys_getrandom(buf: *mut c_char, count: c_size_t, flags: c_uint) -> c_long
- There would not be a generic
syscall()function. I agree with the above points that a function which can invoke any syscall is impossible to implement reliably.
However, I definitely recognize that such auto-generation is quite complex, compared to what
linux-raw-syscurrently does.Zooming out, I think it would be fine for this crate to depend on
rustix/linux-raw-syson either all Linux targets or just thelinux-nonetargets, provided that adoption is sufficiently widespread. A good litmus test for this would belibstditself depending onrustix/linux-raw-syson some/all Linux targets.Until that point, having our own syscall wrapper seems like the least bad option. It will work with our without rustix, and we can always move to
rustix/linux-raw-syswhen there is consensus.Reacted by Artyom Pavlov and Brian SmithZooming out, I think it would be fine for this crate to depend on rustix/linux-raw-sys on either all Linux targets or just the linux-none targets, provided that adoption is sufficiently widespread. A good litmus test for this would be libstd itself depending on rustix/linux-raw-sys on some/all Linux targets.
Until that point, having our own syscall wrapper seems like the least bad option. It will work with our without rustix, and we can always move to rustix/linux-raw-sys when there is consensus.
I agree with this.
One question is, how will we test this? My understanding is that
libtestand the other things we use in our tests aren't available for this target (yet?). If that is true, then we may need to get a little creative.I propose the following:
- We use the exact same code paths in all cases for
all(target_os = "linux", any(target_env = "none", all(feature = "linux_disable_fallback", not(feature = "std"))). - We rely on the
cargo test --features=linux_disable_fallbackwithout--features=stdto approximate this target.
The implication here is we'd need to remove all the libc usage from the
all(target_os = "linux", feature = "linux_disable_fallback", not(feature = "std"))case.Ideally, we'd change Cargo.toml so that libc isn't a dependency for
target_os = "linux"at all, or at least when thelinux_disable_fallbackfeature is activated. But, I think that would require us to reverse in this sense:- linux_disable_fallback = [] + linux_enable_dev_urandom_fallback = ["libc"](The use_file implementation uses
util_libc::{open_readonly, sys_fill_exact}; libc_util uses, of course, many things fromlibcincludinglibc::open.)- We use the exact same code paths in all cases for
I propose the following:
-
We use the exact same code paths in all cases for
all(target_os = "linux", any(target_env = "none", all(feature = "linux_disable_fallback", not(feature = "std"))). -
We rely on the
cargo test --features=linux_disable_fallbackwithout--features=stdto approximate this target.
That testing strategy sounds good to me. I think that provided we don't explicitly check for
target_env = "none"in the code andx86_64-unknown-linux-nonebuilds, we can be reasonably sure the implementation is properly tested.The implication here is we'd need to remove all the libc usage from the
all(target_os = "linux", feature = "linux_disable_fallback", not(feature = "std"))case.The main other uses of
libcon Linux are:- The
errnoconstants (needed for checkinglibc::EINTR) - The use of
last_os_error()inside ofsys_fill_exact
The ideal approach might be to have the main looping functionality of
sys_fill_exactbe moved toutil.rsorlib.rs.-
My opinion is that we should add new
linux_rawfeature which would implylinux_disable_fallback. It would use the same backend as thenonetargets. It would make testing easier and would allows using rawgetrandomsyscall on the "classic" Linux targets.Does anybody want to take a stab at the inline assembly for invoking the syscall?
The errno constants (needed for checking libc::EINTR)
It looks like it doesn't vary by architecture; it's always 4; https://github.com/torvalds/linux/blob/2df0193e62cf887f373995fb8a91068562784adc/include/uapi/asm-generic/errno-base.h#L8. Though it would be good to find a definitive promise of that.
The use of last_os_error() inside of sys_fill_exact
We could change the signature of
sys_fill_exactlike so:- sys_fill: impl Fn(&mut [MaybeUninit<u8>]) -> libc::ssize_t, + sys_fill: impl Fn(&mut [MaybeUninit<u8>]) -> Result<usize, Error>
where any short read, including now
Ok(0), indicates that we should keep going. Then the raw syscall code can do something like:usize::try_from(res).map_err(|_| res.unsigned_abs())
Regardless, I think it is manageable.
My opinion is that we should add new linux_raw feature which would imply linux_disable_fallback. It would use the same backend as the none targets. It would make testing easier and would allows using raw getrandom syscall on the "classic" Linux targets.
Why would we want to keep the current code path for using
libc::syscallthough? I think we should use the same backend for all linux targets, since that will ensure that it gets the most testing and review.Does anybody want to take a stab at the inline assembly for invoking the syscall?
nvm. Giving it a go now.
OK, there is now a sketch of this, including building for
--target=x86_64-unknown-linux-none, and also ensuring thefeature="linux_disable_fallback"andtarget_env=""share a code path, in PR #46.Why would we want to keep the current code path for using libc::syscall though? I think we should use the same backend for all linux targets, since that will ensure that it gets the most testing and review.
Personally, I would be fine with unconditionally using raw syscalls for calling
getrandom, but it may be controversial with some users. Though we already uselibc::syscall, so it's probably less of an issue if we were usinglibc::getrandom.Out of curiosity, I was trying to figure out if it would work to only use either
libc::getrandomor directly invoke a syscall viaasm!, avoiding any use oflibc::syscall. This would make our logic simpler and consistent:if has libc::getrandom: call libc::getrandom else: raw syscallx86-64-unknown-linux-nonethen just becomes a target wherehas libc::getrandomis always false. This approch would only requireasm!support for targets that either lack alibcor have an old minimumlibcversion. Usinglibc::getrandomwould also allow us to avoid needingunpoisioncalls as part of #463 (by basically saying "use a not-ancient libc if you want to use sanitizers" which is fine for me).Looking at the supported Android/Linux targets:
- All MUSL targets are fine
libc::getrandomadded in MUSL 1.1.20 (2018)- Rust updated to MUSL 1.1.20 in Update musl to 1.1.20. rust-lang/rust#54430 for Rust 1.31
- uClibc has
libc::getrandomsince v1.0.2 (2015) (before glibc)- A minimum version isn't specified for the targets, but I'd be fine assuming we are good here.
- OHOS seems to have it in all publicly available releases (3.0 - 4.1)
- Added in Android API Level 28 (Android 9 / Android P / Andorid Pie)
- Rust supports NDK 25 (soon to be 26), so has a min API Level of 19 (soon to be 21)
- So we need direct syscalls on
aarch64, arm, x86, x86_64, all of which have stable inline asm. - It's not clear if Android Apps are permitted to make raw system calls (my guess is yes but I can check at work).
- Added in glibc 2.25 (see https://lwn.net/Articles/711013/)
- Rust supports a min glibc of 2.17 on
aarch64, arm, powerpc, powerpc64, powerpc64le, s390x, x86, x86_64 - Rust supports a min glibc of 2.23 on
armv5te, sparc64, thumbv7neon, mips, mipsel, mips64, mips64el - We would need direct syscalls on all these architectures.
- Rust supports a min glibc of 2.17 on
Given this, it's probably not possible to remove use of
libc::syscallfor quite some time.- All MUSL targets are fine
The target was added in rust-lang/rust#125023 and is available in the latest Nightly builds. Use
cargo +nightly build --target=x86_64-unknown-linux-none -Zbuild-std.The
libccrate does NOT support this target, solibc::syscallandlibc::getrandomcannot be used on this target.I am planning to support this target in ring soon.