Skip to content

Commit bff082c

Browse files
committed
Add linux_raw opt-in backend
1 parent ce3b017 commit bff082c

File tree

8 files changed

+179
-1
lines changed

8 files changed

+179
-1
lines changed

.github/workflows/build.yml

+25
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,31 @@ 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+
i686-unknown-linux-gnu,
147+
x86_64-unknown-linux-gnu,
148+
x86_64-unknown-linux-gnux32,
149+
]
150+
steps:
151+
- uses: actions/checkout@v4
152+
- uses: dtolnay/rust-toolchain@master
153+
with:
154+
toolchain: nightly-2024-10-24
155+
components: rust-src
156+
- env:
157+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw"
158+
run: cargo build -Zbuild-std=core --target=${{ matrix.target }}
159+
135160
web:
136161
name: ${{ matrix.target.description }} ${{ matrix.feature.description }} ${{ matrix.atomic.description }}
137162
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 (linux_android.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
@@ -59,6 +59,10 @@ jobs:
5959
run: cargo clippy --target x86_64-unknown-linux-gnu
6060
- name: Linux (linux_android_with_fallback.rs)
6161
run: cargo clippy --target x86_64-unknown-linux-gnu
62+
- name: Linux (linux_raw.rs)
63+
env:
64+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw"
65+
run: cargo clippy --target x86_64-unknown-linux-gnu
6266
- name: NetBSD (netbsd.rs)
6367
run: cargo clippy -Zbuild-std=core --target x86_64-unknown-netbsd
6468
- name: Fortranix SGX (rdrand.rs)

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -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_test_linux_fallback)',
8787
'cfg(getrandom_test_netbsd_fallback)',

README.md

+9
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,14 @@ 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`, `x86`, and `x86_64`.
118+
119+
Note that on `x86` we use the famously slow `int 0x80` to perform syscall.
120+
We recommend to avoid `linux_raw` on this target arch.
121+
113122
### WebAssembly support
114123

115124
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 linux_android;
1515
pub use linux_android::*;
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

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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 = "x86")] {
66+
const __NR_getrandom: isize = 355;
67+
// `int 0x80` is famously slow, but implementing vDSO is too complex
68+
// and `sysenter`/`syscall` have their own portability issues,
69+
// so we use the simple "legacy" way of doing syscalls.
70+
core::arch::asm!(
71+
"int $$0x80",
72+
inlateout("eax") __NR_getrandom => r0,
73+
in("ebx") buf,
74+
in("ecx") buflen,
75+
in("edx") flags,
76+
options(nostack, preserves_flags)
77+
);
78+
} else if #[cfg(target_arch = "x86_64")] {
79+
#[cfg(target_pointer_width = "64")]
80+
const __NR_getrandom: isize = 318;
81+
#[cfg(target_pointer_width = "32")]
82+
const __NR_getrandom: isize = 1073742142;
83+
84+
core::arch::asm!(
85+
"syscall",
86+
inlateout("rax") __NR_getrandom => r0,
87+
in("rdi") buf,
88+
in("rsi") buflen,
89+
in("rdx") flags,
90+
lateout("rcx") _,
91+
lateout("r11") _,
92+
options(nostack, preserves_flags)
93+
);
94+
} else {
95+
compile_error!("`linux_raw` backend does not support this target arch");
96+
}
97+
}
98+
99+
r0
100+
}
101+
102+
pub fn fill_inner(mut dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
103+
// Value of this error code is stable across all target arches.
104+
const EINTR: isize = -4;
105+
106+
loop {
107+
let ret = unsafe { getrandom_syscall(dest.as_mut_ptr().cast(), dest.len(), 0) };
108+
match usize::try_from(ret) {
109+
Ok(0) => return Err(Error::UNEXPECTED),
110+
Ok(len) => {
111+
dest = dest.get_mut(len..).ok_or(Error::UNEXPECTED)?;
112+
if dest.is_empty() {
113+
return Ok(());
114+
}
115+
}
116+
Err(_) if ret == EINTR => continue,
117+
Err(_) => {
118+
let code: u32 = ret
119+
.wrapping_neg()
120+
.try_into()
121+
.map_err(|_| Error::UNEXPECTED)?;
122+
return Err(Error::from_os_error(code));
123+
}
124+
}
125+
}
126+
}

0 commit comments

Comments
 (0)