Skip to content

Commit 3be979b

Browse files
authored
Add linux_raw opt-in backend (#572)
This PR replaces the `linux_rustix` opt-in backend introduced in #520 with `linux_raw` opt-in backend based raw syscalls implemented using `asm!`. It noticeably reduces dependency footprint of the crate (see concerns raised in #433). Unfortunately, it means that we have to implement the raw syscalls ourselves. This PR implements `linux_raw` support only for target arches with stable `asm!`. Nightly-only support for other targets can be added in later PRs.
1 parent cf1f37e commit 3be979b

File tree

8 files changed

+195
-2
lines changed

8 files changed

+195
-2
lines changed

.github/workflows/build.yml

+26
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,32 @@ jobs:
132132
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand"
133133
run: cargo build --features=std
134134

135+
linux-raw:
136+
name: Build Raw Linux
137+
runs-on: ubuntu-24.04
138+
strategy:
139+
matrix:
140+
target: [
141+
arm-unknown-linux-gnueabihf,
142+
aarch64-unknown-linux-gnu,
143+
loongarch64-unknown-linux-gnu,
144+
riscv32gc-unknown-linux-gnu,
145+
riscv64gc-unknown-linux-gnu,
146+
s390x-unknown-linux-gnu,
147+
i686-unknown-linux-gnu,
148+
x86_64-unknown-linux-gnu,
149+
x86_64-unknown-linux-gnux32,
150+
]
151+
steps:
152+
- uses: actions/checkout@v4
153+
- uses: dtolnay/rust-toolchain@master
154+
with:
155+
toolchain: nightly-2025-02-15
156+
components: rust-src
157+
- env:
158+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw"
159+
run: cargo build -Zbuild-std=core --target=${{ matrix.target }}
160+
135161
web:
136162
name: ${{ matrix.target.description }} ${{ matrix.feature.description }} ${{ matrix.atomic.description }}
137163
runs-on: ubuntu-24.04

.github/workflows/nopanic.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ jobs:
4949
- name: Check (getrandom.rs)
5050
run: (exit $( grep -c panic target/release/libgetrandom_wrapper.so ))
5151

52+
- name: Build (linux_raw.rs)
53+
env:
54+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw"
55+
run: cargo build --release
56+
- name: Check (linux_raw.rs)
57+
run: (exit $( grep -c panic target/release/libgetrandom_wrapper.so ))
58+
5259
- name: Build (rdrand.rs)
5360
env:
5461
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand"

.github/workflows/tests.yml

+4
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ jobs:
5757
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom"
5858
RUSTDOCFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom"
5959
run: cargo test --target=${{ matrix.target }} --features=std
60+
- env:
61+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw"
62+
RUSTDOCFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw"
63+
run: cargo test --target=${{ matrix.target }} --features=std
6064
- env:
6165
RUSTFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback
6266
RUSTDOCFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback

.github/workflows/workspace.yml

+4
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ jobs:
5757
run: cargo clippy --target x86_64-unknown-linux-gnu
5858
- name: Linux (linux_android_with_fallback.rs)
5959
run: cargo clippy --target x86_64-unknown-linux-gnu
60+
- name: Linux (linux_raw.rs)
61+
env:
62+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw"
63+
run: cargo clippy --target x86_64-unknown-linux-gnu
6064
- name: NetBSD (netbsd.rs)
6165
run: cargo clippy -Zbuild-std=core --target x86_64-unknown-netbsd
6266
- name: Fortranix SGX (rdrand.rs)

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ compiler_builtins = { version = "0.1", optional = true }
3131
core = { version = "1.0", optional = true, package = "rustc-std-workspace-core" }
3232

3333
# getrandom / linux_android_with_fallback
34-
[target.'cfg(all(any(target_os = "linux", target_os = "android"), not(any(getrandom_backend = "custom", getrandom_backend = "rdrand", getrandom_backend = "rndr"))))'.dependencies]
34+
[target.'cfg(all(any(target_os = "linux", target_os = "android"), not(any(getrandom_backend = "custom", getrandom_backend = "linux_raw", getrandom_backend = "rdrand", getrandom_backend = "rndr"))))'.dependencies]
3535
libc = { version = "0.2.154", default-features = false }
3636

3737
# apple-other
@@ -81,7 +81,7 @@ wasm-bindgen-test = "0.3"
8181
[lints.rust.unexpected_cfgs]
8282
level = "warn"
8383
check-cfg = [
84-
'cfg(getrandom_backend, values("custom", "rdrand", "rndr", "linux_getrandom", "wasm_js"))',
84+
'cfg(getrandom_backend, values("custom", "rdrand", "rndr", "linux_getrandom", "linux_raw", "wasm_js"))',
8585
'cfg(getrandom_msan)',
8686
'cfg(getrandom_windows_legacy)',
8787
'cfg(getrandom_test_linux_fallback)',

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ of randomness based on their specific needs:
8080
| Backend name | Target | Target Triple | Implementation
8181
| ----------------- | -------------------- | ------------------------ | --------------
8282
| `linux_getrandom` | Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call (without `/dev/urandom` fallback). Bumps minimum supported Linux kernel version to 3.17 and Android API level to 23 (Marshmallow).
83+
| `linux_raw` | Linux, Android | `*‑linux‑*` | Same as `linux_getrandom`, but uses raw `asm!`-based syscalls instead of `libc`.
8384
| `rdrand` | x86, x86-64 | `x86_64-*`, `i686-*` | [`RDRAND`] instruction
8485
| `rndr` | AArch64 | `aarch64-*` | [`RNDR`] register
8586
| `wasm_js` | Web Browser, Node.js | `wasm32‑unknown‑unknown`, `wasm32v1-none` | [`Crypto.getRandomValues`]. Requires feature `wasm_js` ([see below](#webassembly-support)).
@@ -110,6 +111,15 @@ WILL NOT have any effect on its downstream users.
110111

111112
[`.cargo/config.toml`]: https://doc.rust-lang.org/cargo/reference/config.html
112113

114+
### Raw Linux syscall support
115+
116+
Currently the `linux_raw` backend supports only targets with stabilized `asm!` macro,
117+
i.e. `arm`, `aarch64`, `loongarch64`, `riscv32`, `riscv64`, `s390x`, `x86`, and `x86_64`.
118+
119+
Note that the raw syscall backend may be slower than backends based on `libc::getrandom`,
120+
e.g. it does not implement vDSO optimizations and on `x86` it uses the infamously slow
121+
`int 0x80` instruction to perform syscall.
122+
113123
### WebAssembly support
114124

115125
This crate fully supports the [WASI] and [Emscripten] targets. However,

src/backends.rs

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ cfg_if! {
1313
} else if #[cfg(getrandom_backend = "linux_getrandom")] {
1414
mod getrandom;
1515
pub use getrandom::*;
16+
} else if #[cfg(getrandom_backend = "linux_raw")] {
17+
mod linux_raw;
18+
pub use linux_raw::*;
1619
} else if #[cfg(getrandom_backend = "rdrand")] {
1720
mod rdrand;
1821
pub use rdrand::*;

src/backends/linux_raw.rs

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
//! Implementation for Linux / Android using `asm!`-based syscalls.
2+
use crate::{Error, MaybeUninit};
3+
4+
pub use crate::util::{inner_u32, inner_u64};
5+
6+
#[cfg(not(any(target_os = "android", target_os = "linux")))]
7+
compile_error!("`linux_raw` backend can be enabled only for Linux/Android targets!");
8+
9+
#[allow(non_upper_case_globals)]
10+
unsafe fn getrandom_syscall(buf: *mut u8, buflen: usize, flags: u32) -> isize {
11+
let r0;
12+
13+
// Based on `rustix` and `linux-raw-sys` code.
14+
cfg_if! {
15+
if #[cfg(target_arch = "arm")] {
16+
const __NR_getrandom: u32 = 384;
17+
// In thumb-mode, r7 is the frame pointer and is not permitted to be used in
18+
// an inline asm operand, so we have to use a different register and copy it
19+
// into r7 inside the inline asm.
20+
// Theoretically, we could detect thumb mode in the build script, but several
21+
// register moves are cheap enough compared to the syscall cost, so we do not
22+
// bother with it.
23+
core::arch::asm!(
24+
"mov {tmp}, r7",
25+
"mov r7, {nr}",
26+
"svc 0",
27+
"mov r7, {tmp}",
28+
nr = const __NR_getrandom,
29+
tmp = out(reg) _,
30+
inlateout("r0") buf => r0,
31+
in("r1") buflen,
32+
in("r2") flags,
33+
options(nostack, preserves_flags)
34+
);
35+
} else if #[cfg(target_arch = "aarch64")] {
36+
const __NR_getrandom: u32 = 278;
37+
core::arch::asm!(
38+
"svc 0",
39+
in("x8") __NR_getrandom,
40+
inlateout("x0") buf => r0,
41+
in("x1") buflen,
42+
in("x2") flags,
43+
options(nostack, preserves_flags)
44+
);
45+
} else if #[cfg(target_arch = "loongarch64")] {
46+
const __NR_getrandom: u32 = 278;
47+
core::arch::asm!(
48+
"syscall 0",
49+
in("$a7") __NR_getrandom,
50+
inlateout("$a0") buf => r0,
51+
in("$a1") buflen,
52+
in("$a2") flags,
53+
options(nostack, preserves_flags)
54+
);
55+
} else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] {
56+
const __NR_getrandom: u32 = 278;
57+
core::arch::asm!(
58+
"ecall",
59+
in("a7") __NR_getrandom,
60+
inlateout("a0") buf => r0,
61+
in("a1") buflen,
62+
in("a2") flags,
63+
options(nostack, preserves_flags)
64+
);
65+
} else if #[cfg(target_arch = "s390x")] {
66+
const __NR_getrandom: u32 = 349;
67+
core::arch::asm!(
68+
"svc 0",
69+
in("r1") __NR_getrandom,
70+
inlateout("r2") buf => r0,
71+
in("r3") buflen,
72+
in("r4") flags,
73+
options(nostack, preserves_flags)
74+
);
75+
} else if #[cfg(target_arch = "x86")] {
76+
const __NR_getrandom: u32 = 355;
77+
// `int 0x80` is famously slow, but implementing vDSO is too complex
78+
// and `sysenter`/`syscall` have their own portability issues,
79+
// so we use the simple "legacy" way of doing syscalls.
80+
core::arch::asm!(
81+
"int $$0x80",
82+
in("eax") __NR_getrandom,
83+
in("ebx") buf,
84+
in("ecx") buflen,
85+
in("edx") flags,
86+
lateout("eax") r0,
87+
options(nostack, preserves_flags)
88+
);
89+
} else if #[cfg(target_arch = "x86_64")] {
90+
#[cfg(target_pointer_width = "64")]
91+
const __NR_getrandom: u32 = 318;
92+
#[cfg(target_pointer_width = "32")]
93+
const __NR_getrandom: u32 = (1 << 30) + 318;
94+
95+
core::arch::asm!(
96+
"syscall",
97+
in("rax") __NR_getrandom,
98+
in("rdi") buf,
99+
in("rsi") buflen,
100+
in("rdx") flags,
101+
lateout("rax") r0,
102+
lateout("rcx") _,
103+
lateout("r11") _,
104+
options(nostack, preserves_flags)
105+
);
106+
} else {
107+
compile_error!("`linux_raw` backend does not support this target arch");
108+
}
109+
}
110+
111+
r0
112+
}
113+
114+
#[inline]
115+
pub fn fill_inner(mut dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
116+
// Value of this error code is stable across all target arches.
117+
const EINTR: isize = -4;
118+
119+
loop {
120+
let ret = unsafe { getrandom_syscall(dest.as_mut_ptr().cast(), dest.len(), 0) };
121+
match usize::try_from(ret) {
122+
Ok(0) => return Err(Error::UNEXPECTED),
123+
Ok(len) => {
124+
dest = dest.get_mut(len..).ok_or(Error::UNEXPECTED)?;
125+
if dest.is_empty() {
126+
return Ok(());
127+
}
128+
}
129+
Err(_) if ret == EINTR => continue,
130+
Err(_) => {
131+
let code: u32 = ret
132+
.wrapping_neg()
133+
.try_into()
134+
.map_err(|_| Error::UNEXPECTED)?;
135+
return Err(Error::from_os_error(code));
136+
}
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)