From bff082c5636288c6e727639ef43d09a8a6ff382d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Mon, 3 Feb 2025 13:44:26 +0300 Subject: [PATCH 1/9] Add `linux_raw` opt-in backend --- .github/workflows/build.yml | 25 +++++++ .github/workflows/nopanic.yaml | 7 ++ .github/workflows/tests.yml | 4 + .github/workflows/workspace.yml | 4 + Cargo.toml | 2 +- README.md | 9 +++ src/backends.rs | 3 + src/backends/linux_raw.rs | 126 ++++++++++++++++++++++++++++++++ 8 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 src/backends/linux_raw.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 204da807..69008979 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -132,6 +132,31 @@ jobs: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand" run: cargo build --features=std + linux-raw: + name: Build Raw Linux + runs-on: ubuntu-24.04 + strategy: + matrix: + target: [ + arm-unknown-linux-gnueabihf, + aarch64-unknown-linux-gnu, + loongarch64-unknown-linux-gnu, + riscv32gc-unknown-linux-gnu, + riscv64gc-unknown-linux-gnu, + i686-unknown-linux-gnu, + x86_64-unknown-linux-gnu, + x86_64-unknown-linux-gnux32, + ] + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-10-24 + components: rust-src + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw" + run: cargo build -Zbuild-std=core --target=${{ matrix.target }} + web: name: ${{ matrix.target.description }} ${{ matrix.feature.description }} ${{ matrix.atomic.description }} runs-on: ubuntu-24.04 diff --git a/.github/workflows/nopanic.yaml b/.github/workflows/nopanic.yaml index 5b5734a0..50fd9492 100644 --- a/.github/workflows/nopanic.yaml +++ b/.github/workflows/nopanic.yaml @@ -49,6 +49,13 @@ jobs: - name: Check (linux_android.rs) run: (exit $( grep -c panic target/release/libgetrandom_wrapper.so )) + - name: Build (linux_raw.rs) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw" + run: cargo build --release + - name: Check (linux_raw.rs) + run: (exit $( grep -c panic target/release/libgetrandom_wrapper.so )) + - name: Build (rdrand.rs) env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4ba01e80..9dc3540b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -57,6 +57,10 @@ jobs: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" RUSTDOCFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" run: cargo test --target=${{ matrix.target }} --features=std + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw" + RUSTDOCFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw" + run: cargo test --target=${{ matrix.target }} --features=std - env: RUSTFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback RUSTDOCFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index 14b37dd9..ad91b40e 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -59,6 +59,10 @@ jobs: run: cargo clippy --target x86_64-unknown-linux-gnu - name: Linux (linux_android_with_fallback.rs) run: cargo clippy --target x86_64-unknown-linux-gnu + - name: Linux (linux_raw.rs) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw" + run: cargo clippy --target x86_64-unknown-linux-gnu - name: NetBSD (netbsd.rs) run: cargo clippy -Zbuild-std=core --target x86_64-unknown-netbsd - name: Fortranix SGX (rdrand.rs) diff --git a/Cargo.toml b/Cargo.toml index ee22ce15..472e540e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,7 @@ wasm-bindgen-test = "0.3" [lints.rust.unexpected_cfgs] level = "warn" check-cfg = [ - 'cfg(getrandom_backend, values("custom", "rdrand", "rndr", "linux_getrandom", "wasm_js"))', + 'cfg(getrandom_backend, values("custom", "rdrand", "rndr", "linux_getrandom", "linux_raw", "wasm_js"))', 'cfg(getrandom_msan)', 'cfg(getrandom_test_linux_fallback)', 'cfg(getrandom_test_netbsd_fallback)', diff --git a/README.md b/README.md index 79082409..30d76cf3 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ of randomness based on their specific needs: | Backend name | Target | Target Triple | Implementation | ----------------- | -------------------- | ------------------------ | -------------- | `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). +| `linux_raw` | Linux, Android | `*‑linux‑*` | Same as `linux_getrandom`, but uses raw `asm!`-based syscalls instead of `libc`. | `rdrand` | x86, x86-64 | `x86_64-*`, `i686-*` | [`RDRAND`] instruction | `rndr` | AArch64 | `aarch64-*` | [`RNDR`] register | `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. [`.cargo/config.toml`]: https://doc.rust-lang.org/cargo/reference/config.html +### Raw Linux syscall support + +Currently the `linux_raw` backend supports only targets with stabilized `asm!` macro, +i.e. `arm`, `aarch64`, `loongarch64`, `riscv32`, `riscv64`, `x86`, and `x86_64`. + +Note that on `x86` we use the famously slow `int 0x80` to perform syscall. +We recommend to avoid `linux_raw` on this target arch. + ### WebAssembly support This crate fully supports the [WASI] and [Emscripten] targets. However, diff --git a/src/backends.rs b/src/backends.rs index 1509af11..8bc97744 100644 --- a/src/backends.rs +++ b/src/backends.rs @@ -13,6 +13,9 @@ cfg_if! { } else if #[cfg(getrandom_backend = "linux_getrandom")] { mod linux_android; pub use linux_android::*; + } else if #[cfg(getrandom_backend = "linux_raw")] { + mod linux_raw; + pub use linux_raw::*; } else if #[cfg(getrandom_backend = "rdrand")] { mod rdrand; pub use rdrand::*; diff --git a/src/backends/linux_raw.rs b/src/backends/linux_raw.rs new file mode 100644 index 00000000..df8bf082 --- /dev/null +++ b/src/backends/linux_raw.rs @@ -0,0 +1,126 @@ +//! Implementation for Linux / Android using `asm!`-based syscalls. +use crate::{Error, MaybeUninit}; + +pub use crate::util::{inner_u32, inner_u64}; + +#[cfg(not(any(target_os = "android", target_os = "linux")))] +compile_error!("`linux_raw` backend can be enabled only for Linux/Android targets!"); + +#[allow(non_upper_case_globals)] +unsafe fn getrandom_syscall(buf: *mut u8, buflen: usize, flags: u32) -> isize { + let r0; + + // Based on `rustix` and `linux-raw-sys` code. + cfg_if! { + if #[cfg(target_arch = "arm")] { + const __NR_getrandom: u32 = 384; + // In thumb-mode, r7 is the frame pointer and is not permitted to be used in + // an inline asm operand, so we have to use a different register and copy it + // into r7 inside the inline asm. + // Theoretically, we could detect thumb mode in the build script, but several + // register moves are cheap enough compared to the syscall cost, so we do not + // bother with it. + core::arch::asm!( + "mov {tmp}, r7", + "mov r7, {nr}", + "svc 0", + "mov r7, {tmp}", + nr = const __NR_getrandom, + tmp = out(reg) _, + inlateout("r0") buf => r0, + in("r1") buflen, + in("r2") flags, + options(nostack, preserves_flags) + ); + } else if #[cfg(target_arch = "aarch64")] { + const __NR_getrandom: u32 = 278; + core::arch::asm!( + "svc 0", + in("x8") __NR_getrandom, + inlateout("x0") buf => r0, + in("x1") buflen, + in("x2") flags, + options(nostack, preserves_flags) + ); + } else if #[cfg(target_arch = "loongarch64")] { + const __NR_getrandom: u32 = 278; + core::arch::asm!( + "syscall 0", + in("$a7") __NR_getrandom, + inlateout("$a0") buf => r0, + in("$a1") buflen, + in("$a2") flags, + options(nostack, preserves_flags) + ); + } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] { + const __NR_getrandom: u32 = 278; + core::arch::asm!( + "ecall", + in("a7") __NR_getrandom, + inlateout("a0") buf => r0, + in("a1") buflen, + in("a2") flags, + options(nostack, preserves_flags) + ); + } else if #[cfg(target_arch = "x86")] { + const __NR_getrandom: isize = 355; + // `int 0x80` is famously slow, but implementing vDSO is too complex + // and `sysenter`/`syscall` have their own portability issues, + // so we use the simple "legacy" way of doing syscalls. + core::arch::asm!( + "int $$0x80", + inlateout("eax") __NR_getrandom => r0, + in("ebx") buf, + in("ecx") buflen, + in("edx") flags, + options(nostack, preserves_flags) + ); + } else if #[cfg(target_arch = "x86_64")] { + #[cfg(target_pointer_width = "64")] + const __NR_getrandom: isize = 318; + #[cfg(target_pointer_width = "32")] + const __NR_getrandom: isize = 1073742142; + + core::arch::asm!( + "syscall", + inlateout("rax") __NR_getrandom => r0, + in("rdi") buf, + in("rsi") buflen, + in("rdx") flags, + lateout("rcx") _, + lateout("r11") _, + options(nostack, preserves_flags) + ); + } else { + compile_error!("`linux_raw` backend does not support this target arch"); + } + } + + r0 +} + +pub fn fill_inner(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Value of this error code is stable across all target arches. + const EINTR: isize = -4; + + loop { + let ret = unsafe { getrandom_syscall(dest.as_mut_ptr().cast(), dest.len(), 0) }; + match usize::try_from(ret) { + Ok(0) => return Err(Error::UNEXPECTED), + Ok(len) => { + dest = dest.get_mut(len..).ok_or(Error::UNEXPECTED)?; + if dest.is_empty() { + return Ok(()); + } + } + Err(_) if ret == EINTR => continue, + Err(_) => { + let code: u32 = ret + .wrapping_neg() + .try_into() + .map_err(|_| Error::UNEXPECTED)?; + return Err(Error::from_os_error(code)); + } + } + } +} From c5c743bb4e3b87fa5dc296834ede6e4b65e792f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Mon, 3 Feb 2025 14:02:56 +0300 Subject: [PATCH 2/9] Disable libc dep for linux_raw --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 472e540e..f2e1b663 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ compiler_builtins = { version = "0.1", optional = true } core = { version = "1.0", optional = true, package = "rustc-std-workspace-core" } # linux_android / linux_android_with_fallback -[target.'cfg(all(any(target_os = "linux", target_os = "android"), not(any(getrandom_backend = "custom", getrandom_backend = "rdrand", getrandom_backend = "rndr"))))'.dependencies] +[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] libc = { version = "0.2.154", default-features = false } # apple-other From 2edbfcf09c82d8498147f92c8f390f0e3af78688 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Wed, 5 Feb 2025 16:17:23 +0300 Subject: [PATCH 3/9] Add `#[inline]` attribute --- src/backends/linux_raw.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backends/linux_raw.rs b/src/backends/linux_raw.rs index df8bf082..8c46592b 100644 --- a/src/backends/linux_raw.rs +++ b/src/backends/linux_raw.rs @@ -99,6 +99,7 @@ unsafe fn getrandom_syscall(buf: *mut u8, buflen: usize, flags: u32) -> isize { r0 } +#[inline] pub fn fill_inner(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { // Value of this error code is stable across all target arches. const EINTR: isize = -4; From fd78be91ba946812e34698b38623006bebc89432 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Sat, 15 Feb 2025 02:49:52 +0300 Subject: [PATCH 4/9] tweak constant --- src/backends/linux_raw.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backends/linux_raw.rs b/src/backends/linux_raw.rs index 8c46592b..d0fa6a2c 100644 --- a/src/backends/linux_raw.rs +++ b/src/backends/linux_raw.rs @@ -79,7 +79,7 @@ unsafe fn getrandom_syscall(buf: *mut u8, buflen: usize, flags: u32) -> isize { #[cfg(target_pointer_width = "64")] const __NR_getrandom: isize = 318; #[cfg(target_pointer_width = "32")] - const __NR_getrandom: isize = 1073742142; + const __NR_getrandom: isize = (1 << 30) + 318; core::arch::asm!( "syscall", From e558c4cfccf3e702e6ce7493b712dc5a4c8d092c Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Sat, 15 Feb 2025 18:47:32 +0300 Subject: [PATCH 5/9] Use `u32` for all `__NR_getrandom` constants --- src/backends/linux_raw.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/backends/linux_raw.rs b/src/backends/linux_raw.rs index d0fa6a2c..30752a44 100644 --- a/src/backends/linux_raw.rs +++ b/src/backends/linux_raw.rs @@ -63,30 +63,32 @@ unsafe fn getrandom_syscall(buf: *mut u8, buflen: usize, flags: u32) -> isize { options(nostack, preserves_flags) ); } else if #[cfg(target_arch = "x86")] { - const __NR_getrandom: isize = 355; + const __NR_getrandom: u32 = 355; // `int 0x80` is famously slow, but implementing vDSO is too complex // and `sysenter`/`syscall` have their own portability issues, // so we use the simple "legacy" way of doing syscalls. core::arch::asm!( "int $$0x80", - inlateout("eax") __NR_getrandom => r0, + in("eax") __NR_getrandom, in("ebx") buf, in("ecx") buflen, in("edx") flags, + lateout("eax") r0, options(nostack, preserves_flags) ); } else if #[cfg(target_arch = "x86_64")] { #[cfg(target_pointer_width = "64")] - const __NR_getrandom: isize = 318; + const __NR_getrandom: u32 = 318; #[cfg(target_pointer_width = "32")] - const __NR_getrandom: isize = (1 << 30) + 318; + const __NR_getrandom: u32 = (1 << 30) + 318; core::arch::asm!( "syscall", - inlateout("rax") __NR_getrandom => r0, + in("rax") __NR_getrandom, in("rdi") buf, in("rsi") buflen, in("rdx") flags, + lateout("rax") r0, lateout("rcx") _, lateout("r11") _, options(nostack, preserves_flags) From fbd8acb5cdead5c8237ae02b10380dd4a0183339 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Sat, 15 Feb 2025 19:15:31 +0300 Subject: [PATCH 6/9] Tweak doc warning --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 30d76cf3..a70877ce 100644 --- a/README.md +++ b/README.md @@ -116,8 +116,9 @@ WILL NOT have any effect on its downstream users. Currently the `linux_raw` backend supports only targets with stabilized `asm!` macro, i.e. `arm`, `aarch64`, `loongarch64`, `riscv32`, `riscv64`, `x86`, and `x86_64`. -Note that on `x86` we use the famously slow `int 0x80` to perform syscall. -We recommend to avoid `linux_raw` on this target arch. +Note that the raw syscall backend may be slower than backends based on `libc::getrandom`, +e.g. it does not implement vDSO optimizations and on `x86` it uses the infamously slow +`int 0x80` instruction to perform syscall. ### WebAssembly support From 53c4ca8e26605e402bffb82f89ae572d8e0f8335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Sat, 15 Feb 2025 19:34:53 +0300 Subject: [PATCH 7/9] Add s390x target arch support --- .github/workflows/build.yml | 1 + src/backends/linux_raw.rs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 69008979..380376ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -143,6 +143,7 @@ jobs: loongarch64-unknown-linux-gnu, riscv32gc-unknown-linux-gnu, riscv64gc-unknown-linux-gnu, + s390x-unknown-linux-gnu, i686-unknown-linux-gnu, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnux32, diff --git a/src/backends/linux_raw.rs b/src/backends/linux_raw.rs index 30752a44..b37ef6d9 100644 --- a/src/backends/linux_raw.rs +++ b/src/backends/linux_raw.rs @@ -62,6 +62,16 @@ unsafe fn getrandom_syscall(buf: *mut u8, buflen: usize, flags: u32) -> isize { in("a2") flags, options(nostack, preserves_flags) ); + } else if #[cfg(target_arch = "s390x")] { + const __NR_getrandom: u32 = 349; + asm!( + "svc 0", + in("r1") __NR_getrandom, + inlateout("r2") buf => r0, + in("r3") buflen, + in("r4") flags, + options(nostack, preserves_flags) + ); } else if #[cfg(target_arch = "x86")] { const __NR_getrandom: u32 = 355; // `int 0x80` is famously slow, but implementing vDSO is too complex From f626152ca5f453227d0e1050d5747825b78ea2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Sat, 15 Feb 2025 19:39:08 +0300 Subject: [PATCH 8/9] Bump nightly version, fix s390x --- .github/workflows/build.yml | 2 +- src/backends/linux_raw.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 380376ad..6ad2c4c2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -152,7 +152,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: nightly-2024-10-24 + toolchain: nightly-2025-02-15 components: rust-src - env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw" diff --git a/src/backends/linux_raw.rs b/src/backends/linux_raw.rs index b37ef6d9..a601f07b 100644 --- a/src/backends/linux_raw.rs +++ b/src/backends/linux_raw.rs @@ -64,7 +64,7 @@ unsafe fn getrandom_syscall(buf: *mut u8, buflen: usize, flags: u32) -> isize { ); } else if #[cfg(target_arch = "s390x")] { const __NR_getrandom: u32 = 349; - asm!( + core::arch::asm!( "svc 0", in("r1") __NR_getrandom, inlateout("r2") buf => r0, From 28503c23bf3abda7b0113bc7e7e3935978128732 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Sat, 15 Feb 2025 23:40:50 +0300 Subject: [PATCH 9/9] Tweak readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a70877ce..0c0a348f 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ WILL NOT have any effect on its downstream users. ### Raw Linux syscall support Currently the `linux_raw` backend supports only targets with stabilized `asm!` macro, -i.e. `arm`, `aarch64`, `loongarch64`, `riscv32`, `riscv64`, `x86`, and `x86_64`. +i.e. `arm`, `aarch64`, `loongarch64`, `riscv32`, `riscv64`, `s390x`, `x86`, and `x86_64`. Note that the raw syscall backend may be slower than backends based on `libc::getrandom`, e.g. it does not implement vDSO optimizations and on `x86` it uses the infamously slow