Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditionally disable file fallback for Android and Linux #396

Merged
merged 2 commits into from Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Expand Up @@ -61,6 +61,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
- run: cargo test
- run: cargo test --features=std
- run: cargo test --features=linux_disable_fallback
- run: cargo test --features=custom # custom should do nothing here
- if: ${{ matrix.toolchain == 'nightly' }}
run: cargo test --benches
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased
### Added
- `linux_disable_fallback` crate feature to disable `/dev/urandom`-based fallback on Linux and
Android targets. Enabling this feature bumps minimum supported Linux kernel version to 4.17 and
Android API level to 23 (Marshmallow). [#396]

### Changed
- Disable `/dev/urandom` fallback for Linux targets outside of the following `target_arch`es:
`aarch64`, `arm`, `powerpc`, `powerpc64`, `s390x`, `x86`, `x86_64` [#396]
- Do not catch `EPERM` error code on Android while checking availability of
the `getrandom` syscall [#396]

[#396]: https://github.com/rust-random/getrandom/pull/396

## [0.2.12] - 2024-01-09
### Fixed
- Custom backend for targets without atomics [#385]
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Expand Up @@ -32,6 +32,9 @@ wasm-bindgen-test = "0.3.18"
[features]
# Implement std-only traits for getrandom::Error
std = []
# Disable `/dev/urandom` fallback for Linux and Android targets.
# Bumps minimum supported Linux kernel version to 3.17 and Android API level to 23 (Marshmallow).
linux_disable_fallback = []
# Feature to enable fallback RDRAND-based implementation on x86/x86_64
rdrand = []
# Feature to enable JavaScript bindings on wasm*-unknown-unknown
Expand Down
51 changes: 46 additions & 5 deletions src/lib.rs
Expand Up @@ -99,11 +99,14 @@
//! This crate will then use the provided `webcrypto` implementation.
//!
//! ### Platform Support
//! This crate generally supports the same operating system and platform versions that the Rust standard library does.
//! Additional targets may be supported using pluggable custom implementations.
//! This crate generally supports the same operating system and platform versions
//! that the Rust standard library does. Additional targets may be supported using
//! pluggable custom implementations.
//!
//! This means that as Rust drops support for old versions of operating systems (such as old Linux kernel versions, Android API levels, etc)
//! in stable releases, `getrandom` may create new patch releases (`0.N.x`) that remove support for outdated platform versions.
//! This means that as Rust drops support for old versions of operating systems
//! (such as old Linux kernel versions, Android API levels, etc) in stable releases,
//! `getrandom` may create new patch releases (`0.N.x`) that remove support for
//! outdated platform versions.
//!
//! ### Custom implementations
//!
Expand Down Expand Up @@ -220,10 +223,48 @@ cfg_if! {
if #[cfg(any(target_os = "haiku", target_os = "redox", target_os = "nto", target_os = "aix"))] {
mod util_libc;
#[path = "use_file.rs"] mod imp;
} else if #[cfg(any(target_os = "android", target_os = "linux"))] {
} else if #[cfg(all(
not(feature = "linux_disable_fallback"),
any(
// Rust supports Android API level 19 (KitKat) [0] and the next upgrade targets
// level 21 (Lollipop) [1], while `getrandom(2)` was added only in
// level 23 (Marshmallow). Note that it applies only to the "old" `target_arch`es,
// RISC-V Android targets sufficiently new API level, same will apply for potential
// new Android `target_arch`es.
// [0]: https://blog.rust-lang.org/2023/01/09/android-ndk-update-r25.html
newpavlov marked this conversation as resolved.
Show resolved Hide resolved
// [1]: https://github.com/rust-lang/rust/pull/120593
all(
target_os = "android",
any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "x86",
target_arch = "x86_64",
),
),
// Only on these `target_arch`es Rust supports Linux kernel versions (3.2+)
// that precede the version (3.17) in which `getrandom(2)` was added:
// https://doc.rust-lang.org/stable/rustc/platform-support.html
all(
target_os = "linux",
any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "s390x",
target_arch = "x86",
target_arch = "x86_64",
),
)
),
))] {
newpavlov marked this conversation as resolved.
Show resolved Hide resolved
mod util_libc;
mod use_file;
mod lazy;
#[path = "linux_android_with_fallback.rs"] mod imp;
} else if #[cfg(any(target_os = "android", target_os = "linux"))] {
mod util_libc;
#[path = "linux_android.rs"] mod imp;
} else if #[cfg(any(target_os = "illumos", target_os = "solaris"))] {
mod util_libc;
Expand Down
39 changes: 3 additions & 36 deletions src/linux_android.rs
@@ -1,40 +1,7 @@
//! Implementation for Linux / Android
use crate::{
lazy::LazyBool,
util_libc::{last_os_error, sys_fill_exact},
{use_file, Error},
};
//! Implementation for Linux / Android without `/dev/urandom` fallback
use crate::{util_libc, Error};
use core::mem::MaybeUninit;

pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
// getrandom(2) was introduced in Linux 3.17
static HAS_GETRANDOM: LazyBool = LazyBool::new();
if HAS_GETRANDOM.unsync_init(is_getrandom_available) {
sys_fill_exact(dest, |buf| unsafe {
getrandom(buf.as_mut_ptr() as *mut libc::c_void, buf.len(), 0)
})
} else {
use_file::getrandom_inner(dest)
}
}

fn is_getrandom_available() -> bool {
let res = unsafe { getrandom(core::ptr::null_mut(), 0, libc::GRND_NONBLOCK) };
if res < 0 {
match last_os_error().raw_os_error() {
Some(libc::ENOSYS) => false, // No kernel support
Some(libc::EPERM) => false, // Blocked by seccomp
_ => true,
}
} else {
true
}
}

unsafe fn getrandom(
buf: *mut libc::c_void,
newpavlov marked this conversation as resolved.
Show resolved Hide resolved
buflen: libc::size_t,
flags: libc::c_uint,
) -> libc::ssize_t {
libc::syscall(libc::SYS_getrandom, buf, buflen, flags) as libc::ssize_t
util_libc::sys_fill_exact(dest, util_libc::getrandom_syscall)
}
33 changes: 33 additions & 0 deletions src/linux_android_with_fallback.rs
@@ -0,0 +1,33 @@
//! Implementation for Linux / Android with `/dev/urandom` fallback
use crate::{
lazy::LazyBool,
util_libc::{getrandom_syscall, last_os_error, sys_fill_exact},
{use_file, Error},
};
use core::mem::MaybeUninit;

pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
// getrandom(2) was introduced in Linux 3.17
static HAS_GETRANDOM: LazyBool = LazyBool::new();
if HAS_GETRANDOM.unsync_init(is_getrandom_available) {
sys_fill_exact(dest, getrandom_syscall)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to contradict my earlier suggestion, but I think I have a better one: Let's change this line to linux_android::getrandom_inner(dest). This way, it will crystal clear that the logic is EXACTLY the same as the no-fallback case. This would also allow you to keep getrandom_syscall in linux_android, which I think would be better than putting it in libc_util.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need getrandom_syscall for checking getrandom availability, so I think the current solution is fine.

} else {
use_file::getrandom_inner(dest)
}
}

fn is_getrandom_available() -> bool {
if getrandom_syscall(&mut []) < 0 {
match last_os_error().raw_os_error() {
Some(libc::ENOSYS) => false, // No kernel support
// The fallback on EPERM is intentionally not done on Android since this workaround
// seems to be needed only for specific Linux-based products that aren't based
// on Android. See https://github.com/rust-random/getrandom/issues/229.
#[cfg(target_os = "linux")]
Some(libc::EPERM) => false, // Blocked by seccomp
_ => true,
}
} else {
true
}
}
13 changes: 13 additions & 0 deletions src/util_libc.rs
Expand Up @@ -151,3 +151,16 @@ pub unsafe fn open_readonly(path: &str) -> Result<libc::c_int, Error> {
}
}
}

/// Thin wrapper around the `getrandom()` Linux system call
#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn getrandom_syscall(buf: &mut [MaybeUninit<u8>]) -> libc::ssize_t {
unsafe {
libc::syscall(
libc::SYS_getrandom,
buf.as_mut_ptr() as *mut libc::c_void,
buf.len(),
0,
) as libc::ssize_t
}
}