From ca5c98114dfb8c1eb1d06d12ebbb5d50e0fe47fe 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, 23 Dec 2024 20:25:02 +0300 Subject: [PATCH 1/4] Replace `linux_rustix` opt-in backend with `linux_raw` --- .github/workflows/build.yml | 26 ++++++- .github/workflows/nopanic.yaml | 6 +- .github/workflows/tests.yml | 2 +- .github/workflows/workspace.yml | 4 +- Cargo.toml | 6 +- README.md | 9 ++- src/backends.rs | 6 +- src/backends/linux_raw.rs | 125 ++++++++++++++++++++++++++++++++ src/backends/linux_rustix.rs | 32 -------- 9 files changed, 168 insertions(+), 48 deletions(-) create mode 100644 src/backends/linux_raw.rs delete mode 100644 src/backends/linux_rustix.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4a251776..0e1db130 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -122,7 +122,7 @@ jobs: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" run: cargo build --target=${{ matrix.target }} --features=std - env: - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_rustix" + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw" run: cargo build --target=${{ matrix.target }} --features=std - env: RUSTFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback @@ -131,6 +131,30 @@ 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@nightly + with: + targets: ${{ matrix.target }} + - 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 f9821a1d..50fd9492 100644 --- a/.github/workflows/nopanic.yaml +++ b/.github/workflows/nopanic.yaml @@ -49,11 +49,11 @@ jobs: - name: Check (linux_android.rs) run: (exit $( grep -c panic target/release/libgetrandom_wrapper.so )) - - name: Build (linux_rustix.rs) + - name: Build (linux_raw.rs) env: - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_rustix" + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw" run: cargo build --release - - name: Check (linux_rustix.rs) + - name: Check (linux_raw.rs) run: (exit $( grep -c panic target/release/libgetrandom_wrapper.so )) - name: Build (rdrand.rs) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 673fd370..51dab33c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -57,7 +57,7 @@ jobs: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" run: cargo test --target=${{ matrix.target }} --features=std - env: - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_rustix" + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw" run: cargo test --target=${{ matrix.target }} --features=std - env: RUSTFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index 7f62034a..f7d2cfa0 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -57,9 +57,9 @@ jobs: env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" run: cargo clippy --target x86_64-unknown-linux-gnu - - name: Linux (linux_rustix.rs) + - name: Linux (linux_raw.rs) env: - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_rustix" + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_raw" 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 diff --git a/Cargo.toml b/Cargo.toml index 57fe2274..4ab81c32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,10 +22,6 @@ core = { version = "1.0", optional = true, package = "rustc-std-workspace-core" [target.'cfg(all(any(target_os = "linux", target_os = "android"), not(any(target_env = "", getrandom_backend = "linux_rustix", getrandom_backend = "custom"))))'.dependencies] libc = { version = "0.2.154", default-features = false } -# linux_rustix -[target.'cfg(all(any(target_os = "linux", target_os = "android"), any(target_env = "", getrandom_backend = "linux_rustix")))'.dependencies] -rustix = { version = "0.38.38", default-features = false, features = ["rand"] } - # apple-other [target.'cfg(any(target_os = "ios", target_os = "visionos", target_os = "watchos", target_os = "tvos"))'.dependencies] libc = { version = "0.2.154", default-features = false } @@ -80,7 +76,7 @@ rustc-dep-of-std = ["dep:compiler_builtins", "dep:core"] [lints.rust.unexpected_cfgs] level = "warn" check-cfg = [ - 'cfg(getrandom_backend, values("custom", "rdrand", "rndr", "linux_getrandom", "linux_rustix", "wasm_js", "esp_idf"))', + 'cfg(getrandom_backend, values("custom", "rdrand", "rndr", "linux_getrandom", "linux_raw", "wasm_js", "esp_idf"))', 'cfg(getrandom_msan)', 'cfg(getrandom_test_linux_fallback)', 'cfg(getrandom_test_netbsd_fallback)', diff --git a/README.md b/README.md index 7ece9ecf..facf1a2a 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,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_rustix` | Linux, Android | `*‑linux‑*` | Same as `linux_getrandom`, but uses [`rustix`] instead of `libc`. +| `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 | `esp_idf` | ESP-IDF | `*‑espidf` | [`esp_fill_random`]. WARNING: can return low-quality entropy without proper hardware configuration! @@ -106,6 +106,13 @@ 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 f7b720f5..24f550b8 100644 --- a/src/backends.rs +++ b/src/backends.rs @@ -13,9 +13,9 @@ cfg_if! { } else if #[cfg(getrandom_backend = "linux_getrandom")] { mod linux_android; pub use linux_android::*; - } else if #[cfg(getrandom_backend = "linux_rustix")] { - mod linux_rustix; - pub use linux_rustix::*; + } 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..09a572dd --- /dev/null +++ b/src/backends/linux_raw.rs @@ -0,0 +1,125 @@ +//! Implementation for Linux / Android without `/dev/urandom` fallback +use crate::{Error, MaybeUninit}; +// use rustix::rand::{getrandom_uninit, GetRandomFlags}; + +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")] { + // 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, #384", // __NR_getrandom = 384 + "svc 0", + "mov r7, {tmp}", + 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)); + } + } + } +} diff --git a/src/backends/linux_rustix.rs b/src/backends/linux_rustix.rs deleted file mode 100644 index d3bcce3e..00000000 --- a/src/backends/linux_rustix.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Implementation for Linux / Android without `/dev/urandom` fallback -use crate::{Error, MaybeUninit}; -use rustix::rand::{getrandom_uninit, GetRandomFlags}; - -pub use crate::util::{inner_u32, inner_u64}; - -#[cfg(not(any(target_os = "android", target_os = "linux")))] -compile_error!("`linux_rustix` backend can be enabled only for Linux/Android targets!"); - -pub fn fill_inner(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { - loop { - let res = getrandom_uninit(dest, GetRandomFlags::empty()).map(|(res, _)| res.len()); - match res { - Ok(0) => return Err(Error::UNEXPECTED), - Ok(res_len) => { - dest = dest.get_mut(res_len..).ok_or(Error::UNEXPECTED)?; - if dest.is_empty() { - return Ok(()); - } - } - Err(rustix::io::Errno::INTR) => continue, - Err(err) => { - let code = err - .raw_os_error() - .wrapping_neg() - .try_into() - .map_err(|_| Error::UNEXPECTED)?; - return Err(Error::from_os_error(code)); - } - } - } -} From 87d7d679cb7bec28810e5f02758329800ae2f855 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, 23 Dec 2024 20:32:08 +0300 Subject: [PATCH 2/4] Minor backend tweak --- src/backends/linux_raw.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/backends/linux_raw.rs b/src/backends/linux_raw.rs index 09a572dd..25db23ad 100644 --- a/src/backends/linux_raw.rs +++ b/src/backends/linux_raw.rs @@ -1,6 +1,5 @@ -//! Implementation for Linux / Android without `/dev/urandom` fallback +//! Implementation for Linux / Android using `asm!`-based syscalls. use crate::{Error, MaybeUninit}; -// use rustix::rand::{getrandom_uninit, GetRandomFlags}; pub use crate::util::{inner_u32, inner_u64}; From e054110573bb7b24da729af7a69b818d2199caac 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, 23 Dec 2024 20:32:57 +0300 Subject: [PATCH 3/4] Fix CI --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0e1db130..476dc9ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -148,9 +148,10 @@ jobs: ] steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly + - uses: dtolnay/rust-toolchain@master with: - targets: ${{ matrix.target }} + 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 }} From 49a0c3fb51171365e23f4342b4cd47b1f185f5b8 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, 23 Dec 2024 20:39:07 +0300 Subject: [PATCH 4/4] Update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afdefce2..fa54a8e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,9 +36,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `getrandom_backend` configuration flag for selection of opt-in backends [#504] - `Error::new_custom` method [#507] - `rndr` opt-in backend [#512] -- `linux_rustix` opt-in backend [#520] - Automatic MemorySanitizer support [#521] [#571] - `u32` and `u64` functions for generating random values of the respective type [#544] +- `linux_raw` opt-in backend [#572] ### Fixed - NetBSD fallback code based on `KERN_ARND` [#555] @@ -52,7 +52,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#504]: https://github.com/rust-random/getrandom/pull/504 [#507]: https://github.com/rust-random/getrandom/pull/507 [#512]: https://github.com/rust-random/getrandom/pull/512 -[#520]: https://github.com/rust-random/getrandom/pull/520 [#521]: https://github.com/rust-random/getrandom/pull/521 [#522]: https://github.com/rust-random/getrandom/pull/522 [#532]: https://github.com/rust-random/getrandom/pull/532 @@ -62,6 +61,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#555]: https://github.com/rust-random/getrandom/pull/555 [#557]: https://github.com/rust-random/getrandom/pull/557 [#571]: https://github.com/rust-random/getrandom/pull/571 +[#572]: https://github.com/rust-random/getrandom/pull/572 ## [0.2.15] - 2024-05-06 ### Added