diff --git a/.cargo/config.toml b/.cargo/config.toml index be3061aba..567208e3d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,7 +1,9 @@ # Allow normal use of "cargo run" and "cargo test" on these wasm32 platforms. [target.wasm32-unknown-unknown] runner = 'wasm-bindgen-test-runner' -[target.wasm32-wasi] +[target.wasm32-wasip1] +runner = 'wasmtime' +[target.wasm32-wasip2] runner = 'wasmtime' # Just run on node by default (that's where emscripten is tested) diff --git a/.clippy.toml b/.clippy.toml deleted file mode 100644 index 992016c29..000000000 --- a/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -msrv = "1.36" diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..b42e381cf --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + versioning-strategy: lockfile-only + allow: + - dependency-type: "all" + schedule: + interval: weekly + groups: + all-deps: + patterns: + - "*" +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: weekly diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..2d65f5827 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,288 @@ +name: Build + +on: + push: + branches: master + pull_request: + branches: master + schedule: + - cron: "0 12 * * 1" + +permissions: + contents: read + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + macos: + name: Apple Other + # visionOS requires Xcode 15.2+, which is only available on the arm64 runners. + runs-on: macos-14 + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@nightly + with: + targets: aarch64-apple-darwin, aarch64-apple-ios + components: rust-src + - uses: Swatinem/rust-cache@v2 + - run: cargo test --no-run --target=aarch64-apple-darwin --features=std + - run: cargo test --no-run --target=aarch64-apple-ios --features=std + - run: cargo test --no-run --target=aarch64-apple-tvos -Zbuild-std --features=std + - run: cargo test --no-run --target=aarch64-apple-watchos -Zbuild-std --features=std + # visionOS requires Xcode 15.2+, GitHub Actions defaults to an older version. + - run: sudo xcode-select -switch /Applications/Xcode_15.2.app + # std is broken on visionOS right now + #- run: cargo test --no-run --target=aarch64-apple-visionos -Zbuild-std --features=std + + cross: + name: Cross + runs-on: ubuntu-24.04 + strategy: + matrix: + target: [ + # This target is currently broken: + # https://github.com/rust-random/getrandom/actions/runs/12949235456/job/36119544880 + # sparcv9-sun-solaris, + x86_64-unknown-illumos, + ] + steps: + - uses: actions/checkout@v5 + - name: Install precompiled cross + run: | + VERSION=v0.2.5 + URL=https://github.com/cross-rs/cross/releases/download/${VERSION}/cross-x86_64-unknown-linux-gnu.tar.gz + wget -O - $URL | tar -xz -C ~/.cargo/bin + cross --version + - name: Build Tests + run: cross test --no-run --target=${{ matrix.target }} --features=std + + tier2: + name: Tier 2 + runs-on: ubuntu-24.04 + strategy: + matrix: + target: [ + aarch64-linux-android, + x86_64-unknown-fuchsia, + x86_64-unknown-redox, + x86_64-fortanix-unknown-sgx, + ] + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + - name: Build + run: cargo build --target=${{ matrix.target }} --features=std + + tier3: + name: Tier 3 + runs-on: ubuntu-24.04 + strategy: + matrix: + target: [ + aarch64-kmc-solid_asp3, + aarch64-unknown-nto-qnx710, + armv6k-nintendo-3ds, + armv7-sony-vita-newlibeabihf, + i686-unknown-hurd-gnu, + riscv32imc-esp-espidf, + x86_64-unknown-hermit, + x86_64-wrs-vxworks, + x86_64-unknown-dragonfly, + x86_64-unknown-haiku, + x86_64-unknown-linux-none, + # TODO: once libc support for cygwin is released + # https://github.com/rust-lang/libc/pull/4308 + # x86_64-pc-cygwin, + ] + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@nightly # Required to build libcore + with: + components: rust-src + - uses: Swatinem/rust-cache@v2 + - run: cargo build -Z build-std=core --target=${{ matrix.target }} + + # Ubuntu does not support running x32 binaries: + # https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1994516/comments/21 + linux-x32: + name: Linux x32 + runs-on: ubuntu-24.04 + strategy: + matrix: + target: [x86_64-unknown-linux-gnux32] + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + - name: Install libc and libgcc + run: | + sudo apt-get update + sudo apt-get install --no-install-recommends libc6-dev-x32 libx32gcc-11-dev + - uses: Swatinem/rust-cache@v2 + - run: cargo build --target=${{ matrix.target }} --features=std + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" + run: cargo build --target=${{ matrix.target }} --features=std + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback + run: cargo build --features=std + - env: + 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, + s390x-unknown-linux-gnu, + i686-unknown-linux-gnu, + x86_64-unknown-linux-gnu, + x86_64-unknown-linux-gnux32, + ] + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2025-09-28 + 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 + env: + RUSTFLAGS: --cfg getrandom_backend="wasm_js" ${{ matrix.atomic.flags }} + strategy: + fail-fast: false + matrix: + target: [ + { description: Web, target: wasm32-unknown-unknown }, + { description: WasmV1, target: wasm32v1-none }, + ] + feature: [ + { description: no_std, feature: "--features wasm_js", build-std: "core,alloc", std: false }, + { feature: "--features wasm_js,std", build-std: "panic_abort,std", std: true }, + ] + atomic: [ + { flags: "" }, + { description: with Atomics, flags: "-Ctarget-feature=+atomics,bulk-memory" }, + ] + exclude: + - target: { target: wasm32v1-none } + feature: { std: true } + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@master + with: + targets: ${{ matrix.target.target }} + toolchain: nightly-2025-09-28 + components: rust-src + - uses: Swatinem/rust-cache@v2 + - name: Build + run: cargo build --target ${{ matrix.target.target }} ${{ matrix.feature.feature }} -Zbuild-std=${{ matrix.feature.build-std }} + + efi-rng: + name: UEFI RNG Protocol + runs-on: ubuntu-24.04 + strategy: + matrix: + target: [ + aarch64-unknown-uefi, + x86_64-unknown-uefi, + i686-unknown-uefi, + ] + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@nightly # Required to build libstd + with: + components: rust-src + - uses: Swatinem/rust-cache@v2 + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="efi_rng" + run: cargo build -Z build-std=std --target=${{ matrix.target }} --features std + + rdrand-uefi: + name: RDRAND UEFI + runs-on: ubuntu-24.04 + strategy: + matrix: + target: [ + x86_64-unknown-uefi, + i686-unknown-uefi, + ] + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@nightly # Required to build libcore + with: + components: rust-src + - uses: Swatinem/rust-cache@v2 + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand" + run: cargo build -Z build-std=core --target=${{ matrix.target }} + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand" + run: cargo build -Z build-std=std --target=${{ matrix.target }} --features std + + rndr: + name: RNDR + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + targets: aarch64-unknown-linux-gnu, aarch64-apple-darwin + - uses: Swatinem/rust-cache@v2 + - name: RNDR enabled at compile time (Linux) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr" -C target-feature=+rand + run: cargo build --target=aarch64-unknown-linux-gnu + - name: Runtime RNDR detection without std (Linux) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr" + run: cargo build --target=aarch64-unknown-linux-gnu + - name: Runtime RNDR detection with std (macOS) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr" + run: cargo build --target=aarch64-unknown-linux-gnu --features std + + no-atomics: + name: No Atomics + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@stable + with: + targets: riscv32i-unknown-none-elf + - uses: Swatinem/rust-cache@v2 + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="custom" + run: cargo build --target riscv32i-unknown-none-elf + + unsupported: + name: Runtime error + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@v2 + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="unsupported" + run: cargo build --target wasm32-unknown-unknown diff --git a/.github/workflows/nopanic.yaml b/.github/workflows/nopanic.yaml new file mode 100644 index 000000000..5903c2e26 --- /dev/null +++ b/.github/workflows/nopanic.yaml @@ -0,0 +1,146 @@ +# This CI config checks that getrandom's backend implementation +# do not contain potential panics. +# +# It is unclear how to implement a no-panic check for esp_idf, fuchsia, +# vxworks, hermit, and solid backends, so we do not check them here, +# but they still should be panic-free. We do not check wasm_js backend +# since glue code generated by wasm-bindgen contains potential panics, +# so resulting WASM files inevitably contain potential panics for any +# code which non-trivially interacts with the JS enviroment. +name: No panic + +on: + push: + branches: master + pull_request: + branches: master + +permissions: + contents: read + +defaults: + run: + working-directory: nopanic_check + +env: + RUSTFLAGS: "-Dwarnings" + +jobs: + linux: + name: Linux + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + targets: wasm32-wasip1, wasm32-wasip2 + - uses: Swatinem/rust-cache@v2 + + - name: Build (linux_android_with_fallback.rs) + run: cargo build --release + - name: Check (linux_android_with_fallback.rs) + run: (exit $( grep -c panic target/release/libgetrandom_wrapper.so )) + + - name: Build (getrandom.rs) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" + run: cargo build --release + - name: Check (getrandom.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" + run: cargo build --release + - name: Check (rdrand.rs) + run: (exit $( grep -c panic target/release/libgetrandom_wrapper.so )) + + - name: Build (custom.rs) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="custom" + run: cargo build --release + - name: Check (custom.rs) + run: (exit $( grep -c panic target/release/libgetrandom_wrapper.so )) + + - name: Build (wasi.rs, preview 1) + run: cargo build --release --target wasm32-wasip1 + - name: Check (wasi.rs, preview 1) + run: (exit $( grep -c panic target/wasm32-wasip1/release/getrandom_wrapper.wasm )) + + - name: Build (wasi.rs, preview 2) + run: cargo build --release --target wasm32-wasip2 + - name: Check (wasi.rs, preview 2) + run: (exit $( grep -c panic target/wasm32-wasip2/release/getrandom_wrapper.wasm )) + + cross: + name: Cross + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: rust-src + targets: aarch64-unknown-linux-gnu,x86_64-unknown-netbsd,x86_64-unknown-freebsd,x86_64-pc-solaris + - uses: Swatinem/rust-cache@v2 + # TODO: use pre-compiled cross after a new (post-0.2.5) release + - name: Install cross + env: + # Allow compiler warnings during cross installation + RUSTFLAGS: + run: cargo install cross --force --git https://github.com/cross-rs/cross + + - name: Build (rndr.rs) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr" + run: cross build --release --target=aarch64-unknown-linux-gnu + - name: Check (rndr.rs) + run: (exit $( grep -c panic target/aarch64-unknown-linux-gnu/release/libgetrandom_wrapper.so )) + + - name: Build (netbsd.rs) + run: cross build --release --target=x86_64-unknown-netbsd + - name: Check (netbsd.rs) + run: (exit $( grep -c panic target/x86_64-unknown-netbsd/release/libgetrandom_wrapper.so )) + + - name: Build (solaris.rs) + run: cross build --release --target=x86_64-pc-solaris + - name: Check (solaris.rs) + run: (exit $( grep -c panic target/x86_64-pc-solaris/release/libgetrandom_wrapper.so )) + + macos: + name: macOS + runs-on: macos-14 + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + targets: aarch64-apple-ios + - uses: Swatinem/rust-cache@v2 + + # We do not need the grep check since linker fails + # if `panic_nonexistent` can not be eliminated + - name: Build (getentropy.rs) + run: cargo build --release --target=aarch64-apple-darwin + - name: Build (apple-other.rs) + run: cargo build --release --target=aarch64-apple-ios + + windows: + name: Windows + runs-on: windows-2022 + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2025-09-28 + components: rust-src + - run: cargo build --release + - run: cargo build --release --target=x86_64-win7-windows-msvc -Zbuild-std="std,panic_abort" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 40db0124d..df5f0ec0a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,4 +1,4 @@ -name: Tests +name: Test on: push: @@ -13,116 +13,82 @@ permissions: env: CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: 1 RUSTFLAGS: "-Dwarnings" jobs: - check-doc: - name: Docs, deadlinks, minimal dependencies - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly # Needed for -Z minimal-versions and doc_cfg - - name: Install precompiled cargo-deadlinks - run: | - VERSION=0.8.1 - URL="https://github.com/deadlinks/cargo-deadlinks/releases/download/${VERSION}/cargo-deadlinks-linux" - wget -O ~/.cargo/bin/cargo-deadlinks $URL - chmod +x ~/.cargo/bin/cargo-deadlinks - cargo deadlinks --version - - uses: Swatinem/rust-cache@v2 - - name: Generate Docs - env: - RUSTDOCFLAGS: --cfg docsrs - run: cargo deadlinks -- --features=custom,std - - run: | - cargo generate-lockfile -Z minimal-versions - cargo test --features=custom,std - - main-tests: - name: Tier 1 Test + tier1: + name: Tier 1 runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-22.04, windows-2022] - toolchain: [nightly, beta, stable, 1.36] + os: [ubuntu-24.04, windows-2022] + toolchain: [nightly, beta, stable, "1.63"] # Only Test macOS on stable to reduce macOS CI jobs include: - # x86_64-apple-darwin. - - os: macos-12 - toolchain: stable # aarch64-apple-darwin. - os: macos-14 toolchain: stable steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} - uses: Swatinem/rust-cache@v2 - run: cargo test + # Make sure enabling the std feature doesn't break anything - 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 - linux-tests: - name: Linux Test - runs-on: ubuntu-22.04 + linux: + name: Linux + runs-on: ubuntu-24.04 strategy: matrix: - target: [ - x86_64-unknown-linux-musl, - i686-unknown-linux-gnu, - i686-unknown-linux-musl, - ] + target: [x86_64-unknown-linux-musl, i686-unknown-linux-musl] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - - name: Install multilib - run: sudo apt-get update && sudo apt-get install gcc-multilib - uses: Swatinem/rust-cache@v2 - run: cargo test --target=${{ matrix.target }} --features=std + - env: + 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 + run: cargo test --features=std + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_test_linux_without_fallback + RUSTDOCFLAGS: -Dwarnings --cfg getrandom_test_linux_without_fallback + run: cargo test --features=std + - env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand" + RUSTDOCFLAGS: -Dwarnings --cfg getrandom_backend="rdrand" + run: cargo test --features=std - ios-tests: - name: iOS Simulator Test - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [macos-12, macos-14] - # Only test on stable to reduce macOS CI jobs - toolchain: [stable] - include: - # The Aarch64 device simulator doesn't seem to work on an x86-64 host - # and the x86_64 device simulator doesn't seem to work on an Aarch64 - # host, at least within GitHub Actions. - - os: macos-12 - target: x86_64-apple-ios - ios_platform: auto-ios-x86_64 - - os: macos-14 - target: aarch64-apple-ios-sim - ios_platform: auto-ios-aarch64-sim + ios: + name: iOS Simulator + runs-on: macos-14 steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@master with: - targets: ${{ matrix.target }} - # There is no precompiled cargo-dinghy for Aarch64. The precompiled - # x86_64 binary runs on ARM64 macOS via Rosetta 2, but it fails to - # correctly interface with macOS toolchain. + toolchain: 1.88.0 + targets: aarch64-apple-ios-sim - name: Install precompiled cargo-dinghy - if: ${{ matrix.target == 'x86_64-apple-ios' }} run: | - VERSION=0.6.2 + VERSION=0.8.1 URL="https://github.com/sonos/dinghy/releases/download/${VERSION}/cargo-dinghy-macos-${VERSION}.tgz" wget -O - $URL | tar -xz --strip-components=1 -C ~/.cargo/bin - - name: cargo install cargo-dinghy - if: ${{ matrix.target == 'aarch64-apple-ios-sim' }} - run: | - VERSION=0.6.2 - cargo install cargo-dinghy --version ${VERSION} - name: Check cargo-dinghy version. run: cargo dinghy --version - name: Setup Simulator @@ -139,10 +105,10 @@ jobs: echo "device=$SIM_ID" >> $GITHUB_ENV - uses: Swatinem/rust-cache@v2 - name: Run tests - run: cargo dinghy -p ${{ matrix.ios_platform }} -d ${{ env.device }} test + run: cargo dinghy -p auto-ios-aarch64-sim -d ${{ env.device }} test - windows-tests: - name: Windows Test + windows: + name: Windows runs-on: windows-2022 strategy: matrix: @@ -152,16 +118,80 @@ jobs: stable-i686-msvc, ] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} - uses: Swatinem/rust-cache@v2 - run: cargo test --features=std - cross-tests: - name: Cross Test - runs-on: ubuntu-22.04 + windows7: + name: Windows 7 (on Windows 10) + runs-on: windows-2022 + steps: + - uses: actions/checkout@v5 + # Win7 targets are Tier3, so pin a nightly where libstd builds. + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2025-09-28 + components: rust-src + - uses: Swatinem/rust-cache@v2 + - run: cargo test --target=x86_64-win7-windows-msvc -Z build-std --features=std + - run: cargo test --target=i686-win7-windows-msvc -Z build-std --features=std + + sanitizer-linux: + name: Sanitizer Linux + runs-on: ${{ matrix.runner }} + strategy: + matrix: + include: + # MemorySanitizer won't run in QEMU so we can't run it in cross: + # https://github.com/llvm/llvm-project/issues/65144 + - arch: aarch64 + runner: ubuntu-24.04-arm + - arch: x86_64 + runner: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2025-09-28 + components: rust-src + # Use --all-targets to skip doctests, which don't work with the -Zsanitizer=memory flag. + # See: https://github.com/rust-lang/rust/issues/134172 + - name: default configuration + env: + RUSTFLAGS: -Dwarnings -Zsanitizer=memory + run: cargo test --all-targets -Zbuild-std --target=${{ matrix.arch }}-unknown-linux-gnu + - name: --cfg getrandom_backend="linux_getrandom" + env: + RUSTFLAGS: --cfg getrandom_backend="linux_getrandom" -Dwarnings -Zsanitizer=memory + run: cargo test --all-targets -Zbuild-std --target=${{ matrix.arch }}-unknown-linux-gnu + - name: --cfg getrandom_backend="linux_raw" + env: + RUSTFLAGS: --cfg getrandom_backend="linux_raw" -Dwarnings -Zsanitizer=memory + run: cargo test --all-targets -Zbuild-std --target=${{ matrix.arch }}-unknown-linux-gnu + - name: --cfg getrandom_backend="linux_fallback" + env: + RUSTFLAGS: --cfg getrandom_backend="linux_fallback" -Dwarnings -Zsanitizer=memory + run: cargo test --all-targets -Zbuild-std --target=${{ matrix.arch }}-unknown-linux-gnu + - if: ${{ matrix.arch == 'x86_64' }} + name: --cfg getrandom_backend="rdrand" + env: + RUSTFLAGS: --cfg getrandom_backend="rdrand" -Dwarnings -Zsanitizer=memory + run: cargo test --all-targets -Zbuild-std --target=${{ matrix.arch }}-unknown-linux-gnu + - name: --cfg getrandom_test_linux_fallback + env: + RUSTFLAGS: --cfg getrandom_test_linux_fallback -Dwarnings -Zsanitizer=memory + run: cargo test --all-targets -Zbuild-std --target=${{ matrix.arch }}-unknown-linux-gnu + - name: --cfg getrandom_test_linux_without_fallback + env: + RUSTFLAGS: --cfg getrandom_test_linux_without_fallback -Dwarnings -Zsanitizer=memory + run: cargo test --all-targets -Zbuild-std --target=${{ matrix.arch }}-unknown-linux-gnu + + cross: + name: Cross + runs-on: ubuntu-24.04 strategy: matrix: target: [ @@ -169,11 +199,16 @@ jobs: # TODO: add Android tests back when the cross cuts a new release. # See: https://github.com/cross-rs/cross/issues/1222 # aarch64-linux-android, - powerpc-unknown-linux-gnu, - wasm32-unknown-emscripten, + # This target is currently broken: + # https://github.com/rust-random/getrandom/actions/runs/15109500597/job/42465556156 + #powerpc-unknown-linux-gnu, + riscv64gc-unknown-linux-gnu, + # This target is currently broken: + # https://github.com/rust-random/getrandom/actions/runs/12949235459/job/36119546920 + #wasm32-unknown-emscripten, ] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Install precompiled cross run: | VERSION=v0.2.5 @@ -183,199 +218,138 @@ jobs: - name: Test run: cross test --no-fail-fast --target=${{ matrix.target }} --features=std - macos-link: - name: macOS ARM64 Build/Link - runs-on: macos-12 + freebsd: + name: FreeBSD VM + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly + - uses: actions/checkout@v5 + - name: Test in FreeBSD + uses: vmactions/freebsd-vm@v1 with: - targets: aarch64-apple-darwin, aarch64-apple-ios - components: rust-src - - uses: Swatinem/rust-cache@v2 - - run: cargo test --no-run --target=aarch64-apple-darwin --features=std - - run: cargo test --no-run --target=aarch64-apple-ios --features=std - - run: cargo test --no-run --target=aarch64-apple-watchos-sim -Zbuild-std --features=std + envs: 'RUSTFLAGS' + usesh: true + prepare: | + pkg install -y rust + run: cargo test - cross-link: - name: Cross Build/Link - runs-on: ubuntu-22.04 - strategy: - matrix: - target: [ - sparcv9-sun-solaris, - x86_64-unknown-illumos, - x86_64-unknown-freebsd, - x86_64-unknown-netbsd, - ] + openbsd: + name: OpenBSD VM + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v3 - - name: Install precompiled cross - run: | - VERSION=v0.2.5 - URL=https://github.com/cross-rs/cross/releases/download/${VERSION}/cross-x86_64-unknown-linux-gnu.tar.gz - wget -O - $URL | tar -xz -C ~/.cargo/bin - cross --version - - name: Build Tests - run: cross test --no-run --target=${{ matrix.target }} --features=std + - uses: actions/checkout@v5 + - name: Test in OpenBSD + uses: vmactions/openbsd-vm@v1 + with: + envs: 'RUSTFLAGS' + usesh: true + prepare: | + pkg_add rust + run: cargo test + + netbsd: + name: NetBSD VM + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - name: Test in NetBSD + uses: vmactions/netbsd-vm@v1 + with: + envs: 'RUSTFLAGS' + usesh: true + prepare: | + /usr/sbin/pkg_add rust + run: | + cargo test + RUSTFLAGS="--cfg getrandom_test_netbsd_fallback -D warnings" cargo test - web-tests: - name: Web Test + web: + name: ${{ matrix.rust.description }} + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: - include: - - os: ubuntu-22.04 - host: x86_64-unknown-linux-musl - - os: windows-2022 - host: x86_64-pc-windows-msvc - # We get spurious failures on Windows, see: - # https://github.com/rust-random/getrandom/issues/400 - continue-on-error: true - - os: macos-12 - host: x86_64-apple-darwin - runs-on: ${{ matrix.os }} + rust: + - { + description: Web, + version: stable, + flags: '-Dwarnings --cfg getrandom_backend="wasm_js"', + args: '--features=std,wasm_js', + } + - { + description: Web with Atomics, + version: nightly, + components: rust-src, + flags: '-Dwarnings --cfg getrandom_backend="wasm_js" -Ctarget-feature=+atomics,+bulk-memory', + args: '--features=std,wasm_js -Zbuild-std=panic_abort,std', + } steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - - run: choco install wget - if: runner.os == 'Windows' + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust.version }} + components: ${{ matrix.rust.components }} - name: Install precompiled wasm-pack shell: bash run: | - VERSION=v0.12.1 - URL=https://github.com/rustwasm/wasm-pack/releases/download/${VERSION}/wasm-pack-${VERSION}-${{ matrix.host }}.tar.gz + VERSION=v0.13.1 + URL=https://github.com/rustwasm/wasm-pack/releases/download/${VERSION}/wasm-pack-${VERSION}-x86_64-unknown-linux-musl.tar.gz wget -O - $URL | tar -xz --strip-components=1 -C ~/.cargo/bin wasm-pack --version - uses: Swatinem/rust-cache@v2 - name: Test (Node) - run: wasm-pack test --node --features=js + env: + RUSTFLAGS: ${{ matrix.rust.flags }} + RUSTDOCFLAGS: ${{ matrix.rust.flags }} + run: wasm-pack test --node -- ${{ matrix.rust.args }} - name: Test (Firefox) - run: wasm-pack test --headless --firefox --features=js,test-in-browser + env: + WASM_BINDGEN_USE_BROWSER: 1 + RUSTFLAGS: ${{ matrix.rust.flags }} + RUSTDOCFLAGS: ${{ matrix.rust.flags }} + run: wasm-pack test --headless --firefox -- ${{ matrix.rust.args }} - name: Test (Chrome) - run: wasm-pack test --headless --chrome --features=js,test-in-browser - - name: Test (Edge) - if: runner.os == 'Windows' - run: wasm-pack test --headless --chrome --chromedriver $Env:EDGEWEBDRIVER\msedgedriver.exe --features=js,test-in-browser - - name: Test (Safari) - if: runner.os == 'macOS' - run: wasm-pack test --headless --safari --features=js,test-in-browser - - name: Test (custom getrandom) - run: wasm-pack test --node --features=custom - - wasm64-tests: - name: wasm64 Build/Link - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly # Need to build libstd - with: - components: rust-src - - uses: Swatinem/rust-cache@v2 - - name: Build and Link tests (build-std) - # This target is Tier 3, so we have to build libstd ourselves. - # We currently cannot run these tests because wasm-bindgen-test-runner - # does not yet support memory64. - run: cargo test --no-run -Z build-std=std,panic_abort --target=wasm64-unknown-unknown --features=js + env: + WASM_BINDGEN_USE_BROWSER: 1 + RUSTFLAGS: ${{ matrix.rust.flags }} + RUSTDOCFLAGS: ${{ matrix.rust.flags }} + run: wasm-pack test --headless --chrome -- ${{ matrix.rust.args }} + - name: Test (dedicated worker) + env: + WASM_BINDGEN_USE_DEDICATED_WORKER: 1 + RUSTFLAGS: ${{ matrix.rust.flags }} + RUSTDOCFLAGS: ${{ matrix.rust.flags }} + run: wasm-pack test --headless --firefox -- ${{ matrix.rust.args }} + - name: Test (shared worker) + env: + WASM_BINDGEN_USE_SHARED_WORKER: 1 + RUSTFLAGS: ${{ matrix.rust.flags }} + RUSTDOCFLAGS: ${{ matrix.rust.flags }} + run: wasm-pack test --headless --firefox -- ${{ matrix.rust.args }} + - name: Test (service worker) + env: + WASM_BINDGEN_USE_SERVICE_WORKER: 1 + RUSTFLAGS: ${{ matrix.rust.flags }} + RUSTDOCFLAGS: ${{ matrix.rust.flags }} + # Firefox doesn't support module service workers and therefor can't import scripts + run: wasm-pack test --headless --chrome -- ${{ matrix.rust.args }} - wasi-tests: - name: WASI Test - runs-on: ubuntu-22.04 + wasi: + name: WASI + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@master with: - targets: wasm32-wasi - - name: Install precompiled wasmtime + toolchain: 1.82 + targets: wasm32-wasip1,wasm32-wasip2 + - name: Install Wasmtime run: | - VERSION=v2.0.0 + VERSION=v24.0.0 URL=https://github.com/bytecodealliance/wasmtime/releases/download/${VERSION}/wasmtime-${VERSION}-x86_64-linux.tar.xz wget -O - $URL | tar -xJ --strip-components=1 -C ~/.cargo/bin wasmtime --version - uses: Swatinem/rust-cache@v2 - - run: cargo test --target wasm32-wasi - - build-tier2: - name: Tier 2 Build - runs-on: ubuntu-22.04 - strategy: - matrix: - target: [ - x86_64-unknown-fuchsia, - x86_64-unknown-redox, - x86_64-fortanix-unknown-sgx, - ] - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.target }} - - uses: Swatinem/rust-cache@v2 - - name: Build - run: cargo build --target=${{ matrix.target }} --features=std - - build-tier3: - name: Tier 3 Build - runs-on: ubuntu-22.04 - strategy: - matrix: - # Supported tier 3 targets without libstd support - target: [ - x86_64-unknown-hermit, - x86_64-wrs-vxworks, - aarch64-kmc-solid_asp3, - armv6k-nintendo-3ds, - armv7-sony-vita-newlibeabihf, - riscv32imc-esp-espidf, - aarch64-unknown-nto-qnx710, - # `std` support still in progress. Can be moved up with the other - # apple targets after https://github.com/rust-lang/rust/pull/103503 - aarch64-apple-tvos, - ] - include: - # Supported tier 3 targets with libstd support - - target: x86_64-unknown-openbsd - features: ["std"] - - target: x86_64-unknown-dragonfly - features: ["std"] - - target: x86_64-unknown-haiku - features: ["std"] - # Unsupported tier 3 targets to test the rdrand feature - - target: x86_64-unknown-uefi - features: ["rdrand"] - - target: x86_64-unknown-l4re-uclibc - features: ["rdrand"] - - target: i686-unknown-hurd-gnu - features: ["std"] - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly # Required to build libcore - with: - components: rust-src - - uses: Swatinem/rust-cache@v2 - - run: cargo build -Z build-std=${{ contains(matrix.features, 'std') && 'std' || 'core'}} --target=${{ matrix.target }} --features="${{ join(matrix.features, ',') }}" - - build-no-atomics: - name: No Atomics Build - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - with: - targets: riscv32i-unknown-none-elf - - uses: Swatinem/rust-cache@v2 - - run: cargo build --features custom --target riscv32i-unknown-none-elf - - clippy-fmt: - name: Clippy + rustfmt - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v1 - - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt, clippy - - uses: Swatinem/rust-cache@v2 - - name: clippy - run: cargo clippy --all --features=custom,std - - name: fmt - run: cargo fmt --all -- --check + - name: WASI 0.1 Test + run: cargo test --target wasm32-wasip1 + - name: WASI 0.2 Test + run: cargo test --target wasm32-wasip2 diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml new file mode 100644 index 000000000..99b0cbaa6 --- /dev/null +++ b/.github/workflows/workspace.yml @@ -0,0 +1,124 @@ +name: Workspace + +on: + push: + branches: master + pull_request: + branches: master + +permissions: + contents: read + +jobs: + clippy: + name: Clippy + runs-on: ubuntu-24.04 + env: + RUSTFLAGS: "-Dwarnings" + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@master + with: + # We need Nightly for -Zbuild-std. + # Fixed Nigthly version is used to prevent + # CI failures which are not relevant to PR changes + # on introduction of new Clippy lints. + toolchain: nightly-2025-09-28 + components: clippy,rust-src + - name: std feature + run: cargo clippy --features std + - name: custom backend + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="custom" + run: cargo clippy -Zbuild-std=core --target riscv32i-unknown-none-elf + - name: iOS (apple-other.rs) + run: cargo clippy -Zbuild-std=core --target x86_64-apple-ios + - name: ESP-IDF (espidf.rs) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="esp_idf" + run: cargo clippy -Zbuild-std=core --target riscv32imc-esp-espidf + - name: Fuchsia (fuchsia.rs) + run: cargo clippy -Zbuild-std=core --target x86_64-unknown-fuchsia + - name: OpenBSD (getentropy.rs) + run: cargo clippy -Zbuild-std=core --target x86_64-unknown-openbsd + - name: Hermit (hermit.rs) + run: cargo clippy -Zbuild-std=core --target x86_64-unknown-hermit + - name: Web WASM (wasm_js.rs) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" + run: cargo clippy -Zbuild-std --target wasm32-unknown-unknown --features wasm_js + - name: Web WASM with atomics (wasm_js.rs) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" -Ctarget-feature=+atomics,+bulk-memory + run: cargo clippy -Zbuild-std --target wasm32-unknown-unknown --features wasm_js + - name: Linux (getrandom.rs) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" + 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) + run: cargo clippy -Zbuild-std=core --target x86_64-fortanix-unknown-sgx + - name: RNDR (rndr.rs) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rndr" + run: cargo clippy -Zbuild-std=core --target aarch64-unknown-linux-gnu + - name: EFI RNG (efi_rng.rs) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="efi_rng" + run: cargo clippy -Zbuild-std=std --target x86_64-unknown-uefi + - name: Solaris (solaris.rs) + run: cargo clippy -Zbuild-std=core --target x86_64-pc-solaris + - name: SOLID (solid.rs) + run: cargo clippy -Zbuild-std=core --target aarch64-kmc-solid_asp3 + - name: Redox (use_file.rs) + run: cargo clippy -Zbuild-std=core --target x86_64-unknown-redox + - name: VxWorks (vxworks.rs) + run: cargo clippy -Zbuild-std=core --target x86_64-wrs-vxworks + - name: WASI preview 1 (wasi.rs) + run: cargo clippy -Zbuild-std=core --target wasm32-wasip1 + - name: WASI preview 2 (wasi.rs) + run: cargo clippy -Zbuild-std=core,alloc --target wasm32-wasip2 + - name: Windows 7 (windows7.rs) + run: cargo clippy -Zbuild-std=core --target x86_64-win7-windows-msvc + - name: Windows (windows.rs) + run: cargo clippy -Zbuild-std=core --target x86_64-pc-windows-msvc + + fmt: + name: rustfmt + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - uses: Swatinem/rust-cache@v2 + - name: fmt + run: cargo fmt --all -- --check + + check-doc: + name: rustdoc + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@master + with: + # We need Nightly for doc_auto_cfg + toolchain: nightly-2025-09-28 + - uses: Swatinem/rust-cache@v2 + - name: Generate Docs + env: + RUSTDOCFLAGS: "-Dwarnings --cfg docsrs" + run: cargo doc --no-deps --features std + + typos: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: crate-ci/typos@v1 diff --git a/.gitignore b/.gitignore index 435eeeee4..51778e4a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ /target **/*.rs.bk -Cargo.lock -*.ts -*.js -*.wasm +nopanic_check/Cargo.lock +nopanic_check/target/ diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 000000000..89bf65dff --- /dev/null +++ b/.typos.toml @@ -0,0 +1,7 @@ +[files] +extend-exclude = [ + ".git/" +] + +[default.extend-words] +"nto" = "nto" diff --git a/CHANGELOG.md b/CHANGELOG.md index b6763b58f..71f9c7ce8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,183 @@ 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). +## [0.3.4] - 2025-10-14 + +### Major change to `wasm_js` backend + +Now, when the `wasm_js` feature is enabled, the `wasm_js` backend will be used +by default. Users of `wasm32-unknown-unknown` targeting JavaScript environments +like the Web and Node.js will no longer need to specify: +``` +--cfg getrandom_backend="wasm_js" +``` +in `RUSTFLAGS` for the crate to compile. They can now simple enable a feature. + +Note: this should not affect non-JS users of the `wasm32-unknown-unknown` +target. Using `--cfg getrandom_backend` will still override the source of +randomness _even if_ the `wasm_js` feature is enabled. This includes +`--cfg getrandom_backend=custom` and `--cfg getrandom_backend=unsupported`. + +For more information, see the discussions in [#671], [#675], and [#730]. + +### Added +- `unsupported` opt-in backend [#667] +- `windows_legacy` opt-in backend [#724] + +### Changed +- Implement Memory Sanitizer unpoisoning more precisely [#678] +- Relax MSRV for the `linux_raw` opt-in backend on ARM targets [#688] +- Use `getrandom` syscall on all RISC-V Linux targets [#699] +- Replaced `wasi` dependency with `wasip2` [#721] +- Enable `wasm_js` backend by default if the `wasm_js` feature is enabled [#730] + +### Removed +- Unstable `rustc-dep-of-std` crate feature [#694] + +[#667]: https://github.com/rust-random/getrandom/pull/667 +[#671]: https://github.com/rust-random/getrandom/issues/671 +[#675]: https://github.com/rust-random/getrandom/pull/675 +[#678]: https://github.com/rust-random/getrandom/pull/678 +[#688]: https://github.com/rust-random/getrandom/pull/688 +[#694]: https://github.com/rust-random/getrandom/pull/694 +[#699]: https://github.com/rust-random/getrandom/pull/699 +[#721]: https://github.com/rust-random/getrandom/pull/721 +[#724]: https://github.com/rust-random/getrandom/pull/724 +[#730]: https://github.com/rust-random/getrandom/pull/730 + +## [0.3.3] - 2025-05-09 + +### Changed +- Doc improvements [#632] [#634] [#635] +- Add crate version to docs.rs links used in `compile_error!`s [#639] + +### Fixed +- Error handling in WASI p1 [#661] + +[#632]: https://github.com/rust-random/getrandom/pull/632 +[#634]: https://github.com/rust-random/getrandom/pull/634 +[#635]: https://github.com/rust-random/getrandom/pull/635 +[#639]: https://github.com/rust-random/getrandom/pull/639 +[#661]: https://github.com/rust-random/getrandom/pull/661 + +## [0.3.2] - 2025-03-17 + +### Added +- `efi_rng` opt-in backend [#570] +- `linux_raw` opt-in backend [#572] +- `.cargo/config.toml` example in the crate-level docs [#591] +- `getrandom_test_linux_without_fallback` configuration flag to test that file fallback + is not triggered in the `linux_android_with_fallback` backend [#605] +- Built-in support for `*-linux-none` targets [#618] +- Cygwin support [#626] + +### Changed +- Update `wasi` dependency to v0.14 [#594] +- Add `#[inline]` attribute to the inner functions [#596] +- Update WASI and Emscripten links in the crate-level docs [#597] +- Do not use `dlsym` on MUSL targets in the `linux_android_with_fallback` backend [#602] +- Remove `linux_android.rs` and use `getrandom.rs` instead [#603] +- Always use `RtlGenRandom` on Windows targets when compiling with pre-1.78 Rust [#610] +- Internal representation of the `Error` type [#614] +- Remove `windows-targets` dependency and use [`raw-dylib`] directly [#627] + +### Removed +- `Error::INTERNAL_START` and `Error::CUSTOM_START` associated constants [#614] + +[#570]: https://github.com/rust-random/getrandom/pull/570 +[#572]: https://github.com/rust-random/getrandom/pull/572 +[#591]: https://github.com/rust-random/getrandom/pull/591 +[#594]: https://github.com/rust-random/getrandom/pull/594 +[#596]: https://github.com/rust-random/getrandom/pull/596 +[#597]: https://github.com/rust-random/getrandom/pull/597 +[#602]: https://github.com/rust-random/getrandom/pull/602 +[#603]: https://github.com/rust-random/getrandom/pull/603 +[#605]: https://github.com/rust-random/getrandom/pull/605 +[#610]: https://github.com/rust-random/getrandom/pull/610 +[#614]: https://github.com/rust-random/getrandom/pull/614 +[#618]: https://github.com/rust-random/getrandom/pull/618 +[#626]: https://github.com/rust-random/getrandom/pull/626 +[#627]: https://github.com/rust-random/getrandom/pull/627 +[`raw-dylib`]: https://doc.rust-lang.org/reference/items/external-blocks.html?highlight=link#dylib-versus-raw-dylib + +## [0.3.1] - 2025-01-28 + +### Fixed +- Build error on Android [#588] + +[#588]: https://github.com/rust-random/getrandom/pull/588 + +## [0.3.0] - 2025-01-25 + +### Breaking Changes + +#### Changed +- Bump MSRV to 1.63 [#542] +- Rename `getrandom` and `getrandom_uninit` functions to `fill` and `fill_uninit` respectively [#532] + +#### Removed +- `wasm32-wasi` target support (use `wasm32-wasip1` or `wasm32-wasip2` instead) [#499] +- `linux_disable_fallback`, `rdrand`, `js`, `test-in-browser`, and `custom` crate features + in favor of configuration flags [#504] +- `register_custom_getrandom!` macro [#504] +- Implementation of `From` for `Error` and `Error::code` method [#507] +- Internet Explorer 11 support [#554] +- Target-specific associated `Error` constants [#562] + +### Changed +- Use `ProcessPrng` on Windows 10 and up, and use `RtlGenRandom` on older Windows versions [#415] +- Do not use locale-specific `strerror_r` for retrieving error code descriptions [#440] +- Avoid assuming `usize` is the native word size in the `rdrand` backend [#442] +- Do not read from `errno` when `libc` did not indicate error on Solaris [#448] +- Switch from `libpthread`'s mutex to `futex` on Linux and to `nanosleep`-based wait loop + on other targets in the `use_file` backend [#490] +- Do not retry on `EAGAIN` while polling `/dev/random` on Linux [#522] +- Remove separate codepath for Node.js in the `wasm_js` backend + (bumps minimum supported Node.js version to v19) [#557] +- Use `js_namespace` in the `wasm_js` backend [#559] + +### Added +- `wasm32-wasip1` and `wasm32-wasip2` support [#499] +- `getrandom_backend` configuration flag for selection of opt-in backends [#504] +- `Error::new_custom` method [#507] +- `rndr` opt-in backend [#512] +- Automatic MemorySanitizer support [#521] [#571] +- `u32` and `u64` functions for generating random values of the respective type [#544] +- `wasm32v1-none` support in the `wasm_js` backend [#560] +- `wasm_js` crate feature which allows users to enable the `wasm_js` opt-in backend [#574] + +### Fixed +- NetBSD fallback code based on `KERN_ARND` [#555] + +[#415]: https://github.com/rust-random/getrandom/pull/415 +[#440]: https://github.com/rust-random/getrandom/pull/440 +[#442]: https://github.com/rust-random/getrandom/pull/442 +[#448]: https://github.com/rust-random/getrandom/pull/448 +[#490]: https://github.com/rust-random/getrandom/pull/490 +[#499]: https://github.com/rust-random/getrandom/pull/499 +[#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 +[#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 +[#542]: https://github.com/rust-random/getrandom/pull/542 +[#544]: https://github.com/rust-random/getrandom/pull/544 +[#554]: https://github.com/rust-random/getrandom/pull/554 +[#555]: https://github.com/rust-random/getrandom/pull/555 +[#557]: https://github.com/rust-random/getrandom/pull/557 +[#559]: https://github.com/rust-random/getrandom/pull/559 +[#560]: https://github.com/rust-random/getrandom/pull/560 +[#562]: https://github.com/rust-random/getrandom/pull/562 +[#571]: https://github.com/rust-random/getrandom/pull/571 +[#574]: https://github.com/rust-random/getrandom/pull/574 + +## [0.2.16] - 2025-04-22 +### Added +- Cygwin support (backport of [#626]) [#654] + +[#654]: https://github.com/rust-random/getrandom/pull/654 + ## [0.2.15] - 2024-05-06 ### Added - Apple visionOS support [#410] @@ -96,7 +273,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Update MSRV to 1.36 [#291] - Use getentropy on Emscripten [#307] -- Solaris: consistantly use `/dev/random` source [#310] +- Solaris: consistently use `/dev/random` source [#310] - Move 3ds selection above rdrand/js/custom fallback [#312] - Remove buffer zeroing from Node.js implementation [#315] - Use `open` instead of `open64` [#326] @@ -454,6 +631,12 @@ Publish initial implementation. ## [0.0.0] - 2019-01-19 Publish an empty template library. +[0.3.4]: https://github.com/rust-random/getrandom/compare/v0.3.3...v0.3.4 +[0.3.3]: https://github.com/rust-random/getrandom/compare/v0.3.2...v0.3.3 +[0.3.2]: https://github.com/rust-random/getrandom/compare/v0.3.1...v0.3.2 +[0.3.1]: https://github.com/rust-random/getrandom/compare/v0.3.0...v0.3.1 +[0.3.0]: https://github.com/rust-random/getrandom/compare/v0.2.15...v0.3.0 +[0.2.16]: https://github.com/rust-random/getrandom/compare/v0.2.15...v0.2.16 [0.2.15]: https://github.com/rust-random/getrandom/compare/v0.2.14...v0.2.15 [0.2.14]: https://github.com/rust-random/getrandom/compare/v0.2.13...v0.2.14 [0.2.13]: https://github.com/rust-random/getrandom/compare/v0.2.12...v0.2.13 @@ -469,7 +652,7 @@ Publish an empty template library. [0.2.3]: https://github.com/rust-random/getrandom/compare/v0.2.2...v0.2.3 [0.2.2]: https://github.com/rust-random/getrandom/compare/v0.2.1...v0.2.2 [0.2.1]: https://github.com/rust-random/getrandom/compare/v0.2.0...v0.2.1 -[0.2.0]: https://github.com/rust-random/getrandom/compare/v0.1.15...v0.2.0 +[0.2.0]: https://github.com/rust-random/getrandom/compare/v0.1.16...v0.2.0 [0.1.16]: https://github.com/rust-random/getrandom/compare/v0.1.15...v0.1.16 [0.1.15]: https://github.com/rust-random/getrandom/compare/v0.1.14...v0.1.15 [0.1.14]: https://github.com/rust-random/getrandom/compare/v0.1.13...v0.1.14 diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..671aa98f3 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,292 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "getrandom" +version = "0.3.4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", + "wasm-bindgen-test", +] + +[[package]] +name = "js-sys" +version = "0.3.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "minicov" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aee0a0f5343de9221a0d233b04520ed8dc2e6728dce180b1dcd9288ec9d9fa3c" +dependencies = [ + "js-sys", + "minicov", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a369369e4360c2884c3168d22bded735c43cccae97bbc147586d4b480edd138d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "web-sys" +version = "0.3.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" diff --git a/Cargo.toml b/Cargo.toml index 4de6dcfd6..933b9d9cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "getrandom" -version = "0.2.15" # Also update html_root_url in lib.rs when bumping this -edition = "2018" +version = "0.3.4" +edition = "2021" +rust-version = "1.63" # Sync tests.yml and README.md. authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" description = "A small cross-platform library for retrieving random data from system source" @@ -10,58 +11,90 @@ repository = "https://github.com/rust-random/getrandom" categories = ["os", "no-std"] exclude = [".*"] +[features] +# Implement std::error::Error for getrandom::Error and +# use std to retrieve OS error descriptions +std = [] + +# Optional backend: wasm_js +# This flag enables the wasm_js backend and uses it by default on wasm32 where +# the target_os is unknown. The getrandom_backend cfg may override this. +# WARNING: It is highly recommended to enable this feature only for binary crates and tests, +# i.e. avoid unconditionally enabling it in library crates. +wasm_js = ["dep:wasm-bindgen", "dep:js-sys"] + [dependencies] cfg-if = "1" -# When built as part of libstd -compiler_builtins = { version = "0.1", optional = true } -core = { version = "1.0", optional = true, package = "rustc-std-workspace-core" } +# getrandom / linux_android_with_fallback +[target.'cfg(all(any(target_os = "linux", target_os = "android"), not(any(all(target_os = "linux", target_env = ""), getrandom_backend = "custom", getrandom_backend = "linux_raw", getrandom_backend = "rdrand", getrandom_backend = "rndr"))))'.dependencies] +libc = { version = "0.2.154", default-features = false } -[target.'cfg(unix)'.dependencies] +# 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 } -[target.'cfg(target_os = "wasi")'.dependencies] -wasi = { version = "0.11", default-features = false } +# efi_rng +[target.'cfg(all(target_os = "uefi", getrandom_backend = "efi_rng"))'.dependencies] +r-efi = { version = "5.1", default-features = false } -[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] -wasm-bindgen = { version = "0.2.62", default-features = false, optional = true } -js-sys = { version = "0.3", optional = true } -[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dev-dependencies] -wasm-bindgen-test = "0.3.18" +# getentropy +[target.'cfg(any(target_os = "macos", target_os = "openbsd", target_os = "vita", target_os = "emscripten"))'.dependencies] +libc = { version = "0.2.154", default-features = false } -[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 -js = ["wasm-bindgen", "js-sys"] -# Feature to enable custom RNG implementations -custom = [] -# Unstable feature to support being a libstd dependency -rustc-dep-of-std = [ - "compiler_builtins", - "core", - "libc/rustc-dep-of-std", - "wasi/rustc-dep-of-std", +# getrandom +[target.'cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "hurd", target_os = "illumos", target_os = "cygwin", all(target_os = "horizon", target_arch = "arm")))'.dependencies] +libc = { version = "0.2.154", default-features = false } + +# netbsd +[target.'cfg(target_os = "netbsd")'.dependencies] +libc = { version = "0.2.154", default-features = false } + +# solaris +[target.'cfg(target_os = "solaris")'.dependencies] +libc = { version = "0.2.154", default-features = false } + +# use_file +[target.'cfg(any(target_os = "haiku", target_os = "redox", target_os = "nto", target_os = "aix"))'.dependencies] +libc = { version = "0.2.154", default-features = false } + +# vxworks +[target.'cfg(target_os = "vxworks")'.dependencies] +libc = { version = "0.2.154", default-features = false } + +# wasi_p2 +[target.'cfg(all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"))'.dependencies] +wasip2 = { version = "1", default-features = false } + +# wasm_js +[target.'cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))'.dependencies] +wasm-bindgen = { version = "0.2.98", default-features = false, optional = true } +[target.'cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"), target_feature = "atomics"))'.dependencies] +js-sys = { version = "0.3.77", default-features = false, optional = true } +[target.'cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))'.dev-dependencies] +wasm-bindgen-test = "0.3" + +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = [ + 'cfg(getrandom_backend, values("custom", "efi_rng", "rdrand", "rndr", "linux_getrandom", "linux_raw", "wasm_js", "windows_legacy", "unsupported"))', + 'cfg(getrandom_msan)', + 'cfg(getrandom_test_linux_fallback)', + 'cfg(getrandom_test_linux_without_fallback)', + 'cfg(getrandom_test_netbsd_fallback)', + 'cfg(target_os, values("cygwin"))', # TODO(MSRV 1.86): Remove this. ] -# Unstable/test-only feature to run wasm-bindgen tests in a browser -test-in-browser = [] [package.metadata.docs.rs] -features = ["std", "custom"] -rustdoc-args = ["--cfg", "docsrs"] +features = ["std"] # workaround for https://github.com/cross-rs/cross/issues/1345 [package.metadata.cross.target.x86_64-unknown-netbsd] pre-build = [ - "mkdir -p /tmp/netbsd", - "curl https://cdn.netbsd.org/pub/NetBSD/NetBSD-9.2/amd64/binary/sets/base.tar.xz -O", - "tar -C /tmp/netbsd -xJf base.tar.xz", - "cp /tmp/netbsd/usr/lib/libexecinfo.so /usr/local/x86_64-unknown-netbsd/lib", - "rm base.tar.xz", - "rm -rf /tmp/netbsd", + "mkdir -p /tmp/netbsd", + "curl -fO https://cdn.netbsd.org/pub/NetBSD/NetBSD-9.3/amd64/binary/sets/base.tar.xz", + "tar -C /tmp/netbsd -xJf base.tar.xz", + "cp /tmp/netbsd/usr/lib/libexecinfo.so /usr/local/x86_64-unknown-netbsd/lib", + "rm base.tar.xz", + "rm -rf /tmp/netbsd", ] diff --git a/LICENSE-MIT b/LICENSE-MIT index 8ca28a1a0..e54440a9e 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2018-2024 The rust-random Project Developers +Copyright (c) 2018-2025 The rust-random Project Developers Copyright (c) 2014 The Rust Project Developers Permission is hereby granted, free of charge, to any diff --git a/README.md b/README.md index b4b5a2b56..13d4388a2 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,308 @@ -# getrandom +# getrandom: system's random number generator -[![Build Status]][GitHub Actions] [![Crate]][crates.io] [![Documentation]][docs.rs] [![Dependency Status]][deps.rs] [![Downloads]][crates.io] [![License]][LICENSE-MIT] +[![Build Status]][GitHub Actions] +[![Crate]][crates.io] +[![Documentation]][docs.rs] +[![Dependency Status]][deps.rs] +[![Downloads]][crates.io] +[![License]][LICENSE-MIT] -[GitHub Actions]: https://github.com/rust-random/getrandom/actions?query=workflow:Tests+branch:master -[Build Status]: https://github.com/rust-random/getrandom/actions/workflows/tests.yml/badge.svg?branch=master -[crates.io]: https://crates.io/crates/getrandom -[Crate]: https://img.shields.io/crates/v/getrandom -[docs.rs]: https://docs.rs/getrandom -[Documentation]: https://docs.rs/getrandom/badge.svg -[deps.rs]: https://deps.rs/repo/github/rust-random/getrandom -[Dependency Status]: https://deps.rs/repo/github/rust-random/getrandom/status.svg -[Downloads]: https://img.shields.io/crates/d/getrandom -[LICENSE-MIT]: https://raw.githubusercontent.com/rust-random/getrandom/master/LICENSE-MIT -[License]: https://img.shields.io/crates/l/getrandom +`getrandom` is a Rust library for retrieving random data from (operating) system sources. +It is assumed that the system always provides high-quality, cryptographically secure random +data, ideally backed by hardware entropy sources. This crate derives its name from +the Linux `getrandom` syscall but is cross-platform, roughly supporting the same set +of platforms as Rust's `std` library. -A Rust library for retrieving random data from (operating) system sources. It is -assumed that the system always provides high-quality cryptographically secure random -data, ideally backed by hardware entropy sources. This crate derives its name -from Linux's `getrandom` function, but is cross-platform, roughly supporting -the same set of platforms as Rust's `std` lib. - -This is a low-level API. Most users should prefer using high-level random-number +This is a low-level API. Most users should prefer using a higher-level random-number library like [`rand`]. [`rand`]: https://crates.io/crates/rand ## Usage -Add this to your `Cargo.toml`: +Add the `getrandom` dependency to your `Cargo.toml` file: ```toml [dependencies] -getrandom = "0.2" +getrandom = "0.3" ``` -Then invoke the `getrandom` function: +Then invoke the `fill` function on a byte buffer to fill it with random data: ```rust -fn get_random_buf() -> Result<[u8; 32], getrandom::Error> { - let mut buf = [0u8; 32]; - getrandom::getrandom(&mut buf)?; - Ok(buf) +fn get_random_u128() -> Result { + let mut buf = [0u8; 16]; + getrandom::fill(&mut buf)?; + Ok(u128::from_ne_bytes(buf)) } ``` -For more information about supported targets, entropy sources, `no_std` targets, -crate features, WASM support and Custom RNGs see the -[`getrandom` documentation](https://docs.rs/getrandom/latest) and -[`getrandom::Error` documentation](https://docs.rs/getrandom/latest/getrandom/struct.Error.html). +## Supported targets -## Minimum Supported Rust Version +| Target | Target Triple | Implementation +| ------------------ | ------------------ | -------------- +| Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call if available, otherwise [`/dev/urandom`][2] after successfully polling `/dev/random` +| Windows 10+ | `*‑windows‑*` | [`ProcessPrng`] on Rust 1.78+, [`RtlGenRandom`] otherwise +| Windows 7, 8 | `*-win7‑windows‑*` | [`RtlGenRandom`] +| macOS | `*‑apple‑darwin` | [`getentropy`][3] +| iOS, tvOS, watchOS | `*‑apple‑{ios,tvos,watchos}` | [`CCRandomGenerateBytes`] +| FreeBSD | `*‑freebsd` | [`getrandom`][5] +| OpenBSD | `*‑openbsd` | [`getentropy`][7] +| NetBSD | `*‑netbsd` | [`getrandom`][16] if available, otherwise [`kern.arandom`][8] +| Dragonfly BSD | `*‑dragonfly` | [`getrandom`][9] +| Solaris | `*‑solaris` | [`getrandom`][11] with `GRND_RANDOM` +| illumos | `*‑illumos` | [`getrandom`][12] +| Fuchsia OS | `*‑fuchsia` | [`cprng_draw`] +| Redox | `*‑redox` | `/dev/urandom` +| Haiku | `*‑haiku` | `/dev/urandom` (identical to `/dev/random`) +| Hermit | `*-hermit` | [`sys_read_entropy`] +| Hurd | `*-hurd-*` | [`getrandom`][17] +| SGX | `x86_64‑*‑sgx` | [`RDRAND`] +| VxWorks | `*‑wrs‑vxworks‑*` | `randABytes` after checking entropy pool initialization with `randSecure` +| Emscripten | `*‑emscripten` | [`getentropy`][13] +| WASI 0.1 | `wasm32‑wasip1` | [`random_get`] +| WASI 0.2 | `wasm32‑wasip2` | [`get-random-u64`] +| SOLID | `*-kmc-solid_*` | `SOLID_RNG_SampleRandomBytes` +| Nintendo 3DS | `*-nintendo-3ds` | [`getrandom`][18] +| ESP-IDF | `*‑espidf` | [`esp_fill_random`] WARNING: see "Early Boot" section below +| PS Vita | `*-vita-*` | [`getentropy`][19] +| QNX Neutrino | `*‑nto-qnx*` | [`/dev/urandom`][14] (identical to `/dev/random`) +| AIX | `*-ibm-aix` | [`/dev/urandom`][15] +| Cygwin | `*-cygwin` | [`getrandom`][20] (based on [`RtlGenRandom`]) + +Pull Requests that add support for new targets to `getrandom` are always welcome. + +### Opt-in backends + +`getrandom` also provides optional (opt-in) backends, which allow users to customize the source +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`]. Enabled by the `wasm_js` feature ([see below](#webassembly-support)). +| `efi_rng` | UEFI | `*-unknown‑uefi` | [`EFI_RNG_PROTOCOL`] with `EFI_RNG_ALGORITHM_RAW` (requires `std` and Nightly compiler) +| `windows_legacy` | Windows | `*-windows-*` | [`RtlGenRandom`] +| `custom` | All targets | `*` | User-provided custom implementation (see [custom backend]) +| `unsupported` | All targets | `*` | Always returns `Err(Error::UNSUPPORTED)` (see [unsupported backend]) + +Opt-in backends can be enabled using the `getrandom_backend` configuration flag. +The flag can be set either by specifying the `rustflags` field in [`.cargo/config.toml`]: +```toml +# It's recommended to set the flag on a per-target basis: +[target.wasm32-unknown-unknown] +rustflags = ['--cfg', 'getrandom_backend="wasm_js"'] +``` + +Or by using the `RUSTFLAGS` environment variable: + +```sh +RUSTFLAGS='--cfg getrandom_backend="linux_getrandom"' cargo build +``` + +Enabling an opt-in backend will replace the backend used by default. Doing this for +an incorrect target (e.g. using `linux_getrandom` while compiling for a Windows target) +will result in a compilation error. Be extremely careful while using opt-in backends, +as incorrect configuration may result in vulnerable applications or applications +that always panic. -This crate requires Rust 1.36.0 or later. +Note that using an opt-in backend in a library (e.g. for tests or benchmarks) +WILL NOT have any effect on its downstream users. -## Platform Support +[`.cargo/config.toml`]: https://doc.rust-lang.org/cargo/reference/config.html -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. +### Raw Linux syscall support -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. +Currently the `linux_raw` backend supports only targets with stabilized `asm!` macro, +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 +`int 0x80` instruction to perform syscall. + +### WebAssembly support + +This crate fully supports the [WASI] and [Emscripten] targets. However, +the `wasm32-unknown-unknown` target (i.e. the target used by `wasm-pack`) +is not automatically supported since, from the target name alone, we cannot deduce +which JavaScript interface should be used (or if JavaScript is available at all). + +We do not include support for this target in the default configuration because +our JS backend (supporting web browsers, web workers and Node.js v19 or later) +requires [`wasm-bindgen`], **bloating `Cargo.lock`** and +**potentially breaking builds** on non-web WASM platforms. + +To enable `getrandom`'s functionality on `wasm32-unknown-unknown` using the Web +Crypto methods [described above][opt-in] via [`wasm-bindgen`], enable the +`wasm_js` feature flag. Setting `RUSTFLAGS='--cfg getrandom_backend="wasm_js"'` +is allowed but is no longer required and does nothing (it was required in a +prior version of this crate). + +WARNING: enabling the `wasm_js` feature will bloat `Cargo.lock` on all platforms +(where [`wasm-bindgen`] is not an existing dependency) and is known to cause +build issues on some non-web WASM platforms, even when a different backend is +selected via `getrandom_backend`. + +### Custom backend + +If this crate does not support your target out of the box or you have to use +a non-default entropy source, then you can provide a custom implementation. +You need to enable the custom backend as described in the +[opt-in backends][opt-in] section. + +Next, you need to define an `extern` function with the following signature: + +```rust +use getrandom::Error; + +#[no_mangle] +unsafe extern "Rust" fn __getrandom_v03_custom( + dest: *mut u8, + len: usize, +) -> Result<(), Error> { + todo!() +} +``` + +This function should, ideally, be defined in the root crate of your project, +e.g. in your `main.rs`. This function MUST be defined only once for your +project, i.e. upstream library crates SHOULD NOT define it outside of +tests and benchmarks. Improper configuration of this backend may result +in linking errors. + +The function accepts a pointer to a buffer that should be filled with random +data and its length in bytes. Note that the buffer MAY be uninitialized. +On success, the function should return `Ok(())` and fully fill the input buffer; +otherwise, it should return an error value. + +While wrapping functions which work with byte slices you should fully initialize +the buffer before passing it to the function: +```rust +use getrandom::Error; + +fn my_entropy_source(buf: &mut [u8]) -> Result<(), getrandom::Error> { + // ... + Ok(()) +} + +#[no_mangle] +unsafe extern "Rust" fn __getrandom_v03_custom( + dest: *mut u8, + len: usize, +) -> Result<(), Error> { + let buf = unsafe { + // fill the buffer with zeros + core::ptr::write_bytes(dest, 0, len); + // create mutable byte slice + core::slice::from_raw_parts_mut(dest, len) + }; + my_entropy_source(buf) +} +``` + +### Unsupported backend + +In some rare scenarios, you might be compiling this crate for an unsupported +target (e.g. `wasm32-unknown-unknown`), but this crate's functionality +is not actually used by your code. If you are confident that `getrandom` is +not used in your project, but it gets pulled nevertheless by one of your +dependencies, then you can enable the `unsupported` backend, which always +returns `Err(Error::UNSUPPORTED)`. + +### 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 +the opt-in custom backend. + +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 that remove support for +outdated platform versions. + +### `/dev/urandom` fallback on Linux and Android + +On Linux targets, the `/dev/urandom` fallback is present only if either `target_env` +is `musl`, or `target_arch` is one of the following: `aarch64`, `arm`, `powerpc`, +`powerpc64`, `s390x`, `x86`, `x86_64`. Other supported targets [require][platform-support] +kernel versions that support the `getrandom` system call, so the fallback is not needed. + +On Android targets the fallback is present only for the following `target_arch`es: +`aarch64`, `arm`, `x86`, `x86_64`. Other `target_arch`es (e.g. RISC-V) require +sufficiently high API levels. + +The fallback can be disabled by enabling the `linux_getrandom` opt-in backend. +Note that doing so will bump minimum supported Linux kernel version to 3.17 +and Android API level to 23 (Marshmallow). + +### Early boot + +Sometimes, early in the boot process, the OS has not collected enough +entropy to securely seed its RNG. This is especially common on virtual +machines, where standard "random" events are hard to come by. + +Some operating system interfaces always block until the RNG is securely +seeded. This can take anywhere from a few seconds to more than a minute. +A few (Linux, NetBSD and Solaris) offer a choice between blocking and +getting an error; in these cases, we always choose to block. + +On Linux (when the `getrandom` system call is not available), reading from +`/dev/urandom` never blocks, even when the OS hasn't collected enough +entropy yet. To avoid returning low-entropy bytes, we first poll +`/dev/random` and only switch to `/dev/urandom` once this has succeeded. + +On OpenBSD, this kind of entropy accounting isn't available, and on +NetBSD, blocking on it is discouraged. On these platforms, nonblocking +interfaces are used, even when reliable entropy may not be available. +On the platforms where it is used, the reliability of entropy accounting +itself isn't free from controversy. This library provides randomness +sourced according to the platform's best practices, but each platform has +its own limits on the grade of randomness it can promise in environments +with few sources of entropy. + +On ESP-IDF, if `esp_fill_random` is used before enabling WiFi, BT, or the +voltage noise entropy source (SAR ADC), the Hardware RNG will only be seeded +via RC_FAST_CLK. This can occur during early boot unless +`bootloader_random_enable()` is called. For more information see the +[ESP-IDF RNG Docs][esp-idf-rng] or the +[RNG section of the ESP32 Technical Reference Manual][esp-trng-docs]. + +## Error handling + +We always prioritize failure over returning known insecure "random" bytes. +Generally, on supported platforms, failure is highly unlikely, though not +impossible. If an error does occur, it is likely that it will occur +on every call to `getrandom`. Therefore, after the first successful call, +one can be reasonably confident that no errors will occur. + +## Panic handling + +We strive to eliminate all potential panics from our backend implementations. +In other words, when compiled with optimizations enabled, the generated +binary code for `getrandom` functions should not contain any panic branches. +Even if the platform misbehaves and returns an unexpected result, +our code should correctly handle it and return an error, e.g. +[`Error::UNEXPECTED`]. + +## Sanitizer support + +If your code uses [`fill_uninit`] and you enable +[MemorySanitizer](https://doc.rust-lang.org/beta/unstable-book/compiler-flags/sanitizer.html#memorysanitizer) +(i.e. `-Zsanitizer=memory`), we will automatically handle unpoisoning +of the destination buffer filled by `fill_uninit`. + +You can run sanitizer tests for your crate dependent on `getrandom` like this: +```sh +RUSTFLAGS="-Zsanitizer=memory" cargo test -Zbuild-std --target=x86_64-unknown-linux-gnu +``` + +## Minimum Supported Rust Version + +This crate requires Rust 1.63 or later. ## License @@ -77,5 +319,68 @@ Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. +[//]: # (badges) + +[GitHub Actions]: https://github.com/rust-random/getrandom/actions?query=branch:master +[Build Status]: https://github.com/rust-random/getrandom/actions/workflows/tests.yml/badge.svg?branch=master +[crates.io]: https://crates.io/crates/getrandom +[Crate]: https://img.shields.io/crates/v/getrandom +[docs.rs]: https://docs.rs/getrandom +[Documentation]: https://docs.rs/getrandom/badge.svg +[deps.rs]: https://deps.rs/repo/github/rust-random/getrandom +[Dependency Status]: https://deps.rs/repo/github/rust-random/getrandom/status.svg +[Downloads]: https://img.shields.io/crates/d/getrandom +[License]: https://img.shields.io/crates/l/getrandom + +[//]: # (supported targets) + +[1]: https://manned.org/getrandom.2 +[2]: https://manned.org/urandom.4 +[3]: https://www.unix.com/man-page/mojave/2/getentropy/ +[4]: https://www.unix.com/man-page/mojave/4/urandom/ +[5]: https://www.freebsd.org/cgi/man.cgi?query=getrandom&manpath=FreeBSD+12.0-stable +[7]: https://man.openbsd.org/getentropy.2 +[8]: https://man.netbsd.org/sysctl.7 +[9]: https://leaf.dragonflybsd.org/cgi/web-man?command=getrandom +[11]: https://docs.oracle.com/cd/E88353_01/html/E37841/getrandom-2.html +[12]: https://illumos.org/man/2/getrandom +[13]: https://github.com/emscripten-core/emscripten/pull/12240 +[14]: https://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.neutrino.utilities/topic/r/random.html +[15]: https://www.ibm.com/docs/en/aix/7.3?topic=files-random-urandom-devices +[16]: https://man.netbsd.org/getrandom.2 +[17]: https://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getrandom +[18]: https://github.com/rust3ds/shim-3ds/commit/b01d2568836dea2a65d05d662f8e5f805c64389d +[19]: https://github.com/vitasdk/newlib/blob/2d869fe47aaf02b8e52d04e9a2b79d5b210fd016/newlib/libc/sys/vita/getentropy.c +[20]: https://github.com/cygwin/cygwin/blob/main/winsup/cygwin/libc/getentropy.cc + +[`ProcessPrng`]: https://learn.microsoft.com/en-us/windows/win32/seccng/processprng +[`RtlGenRandom`]: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom +[`Crypto.getRandomValues`]: https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues +[`RDRAND`]: https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide +[`RNDR`]: https://developer.arm.com/documentation/ddi0601/2024-06/AArch64-Registers/RNDR--Random-Number +[`CCRandomGenerateBytes`]: https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html +[`cprng_draw`]: https://fuchsia.dev/fuchsia-src/zircon/syscalls/cprng_draw +[`esp_fill_random`]: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html#functions +[esp-idf-rng]: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html +[esp-trng-docs]: https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf#rng +[`EFI_RNG_PROTOCOL`]: https://uefi.org/specs/UEFI/2.10/37_Secure_Technologies.html#efi-rng-protocol +[`random_get`]: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-buf_len-size---errno +[`get-random-u64`]: https://github.com/WebAssembly/WASI/blob/v0.2.1/wasip2/random/random.wit#L23-L28 +[configuration flags]: #configuration-flags +[custom backend]: #custom-backend +[unsupported backend]: #unsupported-backend +[`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen +[`module`]: https://rustwasm.github.io/wasm-bindgen/reference/attributes/on-js-imports/module.html +[`sys_read_entropy`]: https://github.com/hermit-os/kernel/blob/315f58ff5efc81d9bf0618af85a59963ff55f8b1/src/syscalls/entropy.rs#L47-L55 +[platform-support]: https://doc.rust-lang.org/stable/rustc/platform-support.html +[WASI]: https://github.com/WebAssembly/WASI +[Emscripten]: https://emscripten.org +[opt-in]: #opt-in-backends + +[//]: # (licenses) + [LICENSE-APACHE]: https://github.com/rust-random/getrandom/blob/master/LICENSE-APACHE [LICENSE-MIT]: https://github.com/rust-random/getrandom/blob/master/LICENSE-MIT + +[`Error::UNEXPECTED`]: https://docs.rs/getrandom/latest/getrandom/struct.Error.html#associatedconstant.UNEXPECTED +[`fill_uninit`]: https://docs.rs/getrandom/latest/getrandom/fn.fill_uninit.html diff --git a/benches/buffer.rs b/benches/buffer.rs index b32be4336..0063a453a 100644 --- a/benches/buffer.rs +++ b/benches/buffer.rs @@ -1,24 +1,74 @@ #![feature(test, maybe_uninit_uninit_array_transpose)] extern crate test; -use std::mem::MaybeUninit; +use std::{ + mem::{size_of, MaybeUninit}, + slice, +}; // Call getrandom on a zero-initialized stack buffer #[inline(always)] -fn bench_getrandom() { +fn bench_fill() { let mut buf = [0u8; N]; - getrandom::getrandom(&mut buf).unwrap(); - test::black_box(&buf as &[u8]); + getrandom::fill(&mut buf).unwrap(); + test::black_box(&buf[..]); } -// Call getrandom_uninit on an uninitialized stack buffer +// Call fill_uninit on an uninitialized stack buffer #[inline(always)] -fn bench_getrandom_uninit() { +fn bench_fill_uninit() { let mut uninit = [MaybeUninit::uninit(); N]; - let buf: &[u8] = getrandom::getrandom_uninit(&mut uninit).unwrap(); + let buf: &[u8] = getrandom::fill_uninit(&mut uninit).unwrap(); test::black_box(buf); } +#[bench] +pub fn bench_u32(b: &mut test::Bencher) { + #[inline(never)] + fn inner() -> u32 { + getrandom::u32().unwrap() + } + b.bytes = 4; + b.iter(inner); +} +#[bench] +pub fn bench_u32_via_fill(b: &mut test::Bencher) { + #[inline(never)] + fn inner() -> u32 { + let mut res = MaybeUninit::::uninit(); + let dst: &mut [MaybeUninit] = + unsafe { slice::from_raw_parts_mut(res.as_mut_ptr().cast(), size_of::()) }; + getrandom::fill_uninit(dst).unwrap(); + unsafe { res.assume_init() } + } + b.bytes = 4; + b.iter(inner); +} + +#[bench] +pub fn bench_u64(b: &mut test::Bencher) { + #[inline(never)] + fn inner() -> u64 { + getrandom::u64().unwrap() + } + b.bytes = 8; + b.iter(inner); +} + +#[bench] +pub fn bench_u64_via_fill(b: &mut test::Bencher) { + #[inline(never)] + fn inner() -> u64 { + let mut res = MaybeUninit::::uninit(); + let dst: &mut [MaybeUninit] = + unsafe { slice::from_raw_parts_mut(res.as_mut_ptr().cast(), size_of::()) }; + getrandom::fill_uninit(dst).unwrap(); + unsafe { res.assume_init() } + } + b.bytes = 8; + b.iter(inner); +} + // We benchmark using #[inline(never)] "inner" functions for two reasons: // - Avoiding inlining reduces a source of variance when running benchmarks. // - It is _much_ easier to get the assembly or IR for the inner loop. @@ -30,20 +80,20 @@ macro_rules! bench { ( $name:ident, $size:expr ) => { pub mod $name { #[bench] - pub fn bench_getrandom(b: &mut test::Bencher) { + pub fn bench_fill(b: &mut test::Bencher) { #[inline(never)] fn inner() { - super::bench_getrandom::<{ $size }>() + super::bench_fill::<{ $size }>() } b.bytes = $size as u64; b.iter(inner); } #[bench] - pub fn bench_getrandom_uninit(b: &mut test::Bencher) { + pub fn bench_fill_uninit(b: &mut test::Bencher) { #[inline(never)] fn inner() { - super::bench_getrandom_uninit::<{ $size }>() + super::bench_fill_uninit::<{ $size }>() } b.bytes = $size as u64; diff --git a/build.rs b/build.rs new file mode 100644 index 000000000..28068d977 --- /dev/null +++ b/build.rs @@ -0,0 +1,57 @@ +use std::{env, ffi::OsString, process::Command}; + +/// Tries to get the minor version of the Rust compiler in use. +/// If it fails for any reason, returns `None`. +/// +/// Based on the `rustc_version` crate. +fn rustc_minor_version() -> Option { + let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc")); + let mut cmd = if let Some(wrapper) = env::var_os("RUSTC_WRAPPER").filter(|w| !w.is_empty()) { + let mut cmd = Command::new(wrapper); + cmd.arg(rustc); + cmd + } else { + Command::new(rustc) + }; + + let out = cmd.arg("-vV").output().ok()?; + + if !out.status.success() { + return None; + } + + let stdout = std::str::from_utf8(&out.stdout).ok()?; + + // Assumes that the first line contains "rustc 1.xx.0-channel (abcdef 2025-01-01)" + // where "xx" is the minor version which we want to extract + let mut lines = stdout.lines(); + let first_line = lines.next()?; + let minor_ver_str = first_line.split('.').nth(1)?; + minor_ver_str.parse().ok() +} + +fn main() { + // Automatically detect cfg(sanitize = "memory") even if cfg(sanitize) isn't + // supported. Build scripts get cfg() info, even if the cfg is unstable. + println!("cargo:rerun-if-changed=build.rs"); + let sanitizers = std::env::var("CARGO_CFG_SANITIZE").unwrap_or_default(); + if sanitizers.contains("memory") { + println!("cargo:rustc-cfg=getrandom_msan"); + } + + // Use `RtlGenRandom` on older compiler versions since win7 targets + // TODO(MSRV 1.78): Remove this check + let target_family = env::var_os("CARGO_CFG_TARGET_FAMILY").and_then(|f| f.into_string().ok()); + if target_family.as_deref() == Some("windows") { + /// Minor version of the Rust compiler in which win7 targets were inroduced + const WIN7_INTRODUCED_MINOR_VER: u64 = 78; + + match rustc_minor_version() { + Some(minor_ver) if minor_ver < WIN7_INTRODUCED_MINOR_VER => { + println!("cargo:rustc-cfg=getrandom_backend=\"windows_legacy\""); + } + None => println!("cargo:warning=Couldn't detect minor version of the Rust compiler"), + _ => {} + } + } +} diff --git a/nopanic_check/Cargo.toml b/nopanic_check/Cargo.toml new file mode 100644 index 000000000..0b838eaa2 --- /dev/null +++ b/nopanic_check/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "nopanic_check" +description = "Helper crate for checking that getrandom implementation does not contain potential panics" +version = "0.1.0" +edition = "2021" +publish = false + +[workspace] + +[lib] +name = "getrandom_wrapper" +crate-type = ["cdylib"] + +[dependencies] +getrandom = { path = ".." } + +[profile.release] +panic = "abort" +strip = true +lto = "fat" + +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = [ + 'cfg(getrandom_backend, values("custom"))', +] diff --git a/nopanic_check/src/lib.rs b/nopanic_check/src/lib.rs new file mode 100644 index 000000000..9a6a02b90 --- /dev/null +++ b/nopanic_check/src/lib.rs @@ -0,0 +1,30 @@ +// WASI preview 2 requires enabled std +#![cfg_attr(not(all(target_arch = "wasm32", target_env = "p2")), no_std)] + +#[cfg(not(any(test, all(target_arch = "wasm32", target_env = "p2"))))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + extern "C" { + fn panic_nonexistent() -> !; + } + unsafe { panic_nonexistent() } +} + +#[no_mangle] +pub extern "C" fn getrandom_wrapper(buf_ptr: *mut u8, buf_len: usize) -> u32 { + let buf = unsafe { core::slice::from_raw_parts_mut(buf_ptr.cast(), buf_len) }; + let res = getrandom::fill_uninit(buf).map(|_| ()); + unsafe { core::mem::transmute(res) } +} + +#[cfg(getrandom_backend = "custom")] +#[no_mangle] +unsafe extern "Rust" fn __getrandom_v03_custom( + dest: *mut u8, + len: usize, +) -> Result<(), getrandom::Error> { + for i in 0..len { + core::ptr::write(dest.add(i), i as u8); + } + Ok(()) +} diff --git a/src/apple-other.rs b/src/apple-other.rs deleted file mode 100644 index 167d8cf0f..000000000 --- a/src/apple-other.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Implementation for iOS, tvOS, and watchOS where `getentropy` is unavailable. -use crate::Error; -use core::{ffi::c_void, mem::MaybeUninit}; - -// libsystem contains the libc of Darwin, and every binary ends up linked against it either way. This -// makes it a more lightweight choice compared to `Security.framework`. -extern "C" { - // This RNG uses a thread-local CSPRNG to provide data, which is seeded by the operating system's root CSPRNG. - // Its the best option after `getentropy` on modern Darwin-based platforms that also avoids the - // high startup costs and linking of Security.framework. - // - // While its just an implementation detail, `Security.framework` just calls into this anyway. - fn CCRandomGenerateBytes(bytes: *mut c_void, size: usize) -> i32; -} - -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - let ret = unsafe { CCRandomGenerateBytes(dest.as_mut_ptr() as *mut c_void, dest.len()) }; - // kCCSuccess (from CommonCryptoError.h) is always zero. - if ret != 0 { - Err(Error::IOS_SEC_RANDOM) - } else { - Ok(()) - } -} diff --git a/src/backends.rs b/src/backends.rs new file mode 100644 index 000000000..991d413b5 --- /dev/null +++ b/src/backends.rs @@ -0,0 +1,209 @@ +//! System-specific implementations. +//! +//! This module should provide `fill_inner` with the signature +//! `fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error>`. +//! The function MUST fully initialize `dest` when `Ok(())` is returned; +//! the function may need to use `sanitizer::unpoison` as well. +//! The function MUST NOT ever write uninitialized bytes into `dest`, +//! regardless of what value it returns. + +cfg_if! { + if #[cfg(getrandom_backend = "custom")] { + mod custom; + pub use custom::*; + } else if #[cfg(getrandom_backend = "linux_getrandom")] { + mod getrandom; + mod sanitizer; + pub use getrandom::*; + } else if #[cfg(getrandom_backend = "linux_raw")] { + mod linux_raw; + mod sanitizer; + pub use linux_raw::*; + } else if #[cfg(getrandom_backend = "rdrand")] { + mod rdrand; + pub use rdrand::*; + } else if #[cfg(getrandom_backend = "rndr")] { + mod rndr; + pub use rndr::*; + } else if #[cfg(getrandom_backend = "efi_rng")] { + mod efi_rng; + pub use efi_rng::*; + } else if #[cfg(getrandom_backend = "windows_legacy")] { + mod windows_legacy; + pub use windows_legacy::*; + } else if #[cfg(getrandom_backend = "wasm_js")] { + cfg_if! { + if #[cfg(feature = "wasm_js")] { + mod wasm_js; + pub use wasm_js::*; + } else { + compile_error!(concat!( + "The \"wasm_js\" backend requires the `wasm_js` feature \ + for `getrandom`. For more information see: \ + https://docs.rs/getrandom/", env!("CARGO_PKG_VERSION"), "/#webassembly-support" + )); + } + } + } else if #[cfg(getrandom_backend = "unsupported")] { + mod unsupported; + pub use unsupported::*; + } else if #[cfg(all(target_os = "linux", target_env = ""))] { + mod linux_raw; + mod sanitizer; + pub use linux_raw::*; + } else if #[cfg(target_os = "espidf")] { + mod esp_idf; + pub use esp_idf::*; + } else if #[cfg(any( + target_os = "haiku", + target_os = "redox", + target_os = "nto", + target_os = "aix", + ))] { + mod use_file; + pub use use_file::*; + } else if #[cfg(any( + target_os = "macos", + target_os = "openbsd", + target_os = "vita", + target_os = "emscripten", + ))] { + mod getentropy; + pub use getentropy::*; + } else if #[cfg(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 + // [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", + // Minimum supported Linux kernel version for MUSL targets + // is not specified explicitly (as of Rust 1.77) and they + // are used in practice to target pre-3.17 kernels. + all( + target_env = "musl", + not( + any( + target_arch = "riscv64", + target_arch = "riscv32", + ), + ), + ), + ), + ) + ))] { + mod use_file; + mod linux_android_with_fallback; + mod sanitizer; + pub use linux_android_with_fallback::*; + } else if #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "hurd", + target_os = "illumos", + target_os = "cygwin", + // Check for target_arch = "arm" to only include the 3DS. Does not + // include the Nintendo Switch (which is target_arch = "aarch64"). + all(target_os = "horizon", target_arch = "arm"), + ))] { + mod getrandom; + #[cfg(any(target_os = "android", target_os = "linux"))] + mod sanitizer; + pub use getrandom::*; + } else if #[cfg(target_os = "solaris")] { + mod solaris; + pub use solaris::*; + } else if #[cfg(target_os = "netbsd")] { + mod netbsd; + pub use netbsd::*; + } else if #[cfg(target_os = "fuchsia")] { + mod fuchsia; + pub use fuchsia::*; + } else if #[cfg(any( + target_os = "ios", + target_os = "visionos", + target_os = "watchos", + target_os = "tvos", + ))] { + mod apple_other; + pub use apple_other::*; + } else if #[cfg(all(target_arch = "wasm32", target_os = "wasi"))] { + cfg_if! { + if #[cfg(target_env = "p1")] { + mod wasi_p1; + pub use wasi_p1::*; + } else if #[cfg(target_env = "p2")] { + mod wasi_p2; + pub use wasi_p2::*; + } else { + compile_error!( + "Unknown version of WASI (only previews 1 and 2 are supported) \ + or Rust version older than 1.80 was used" + ); + } + } + } else if #[cfg(target_os = "hermit")] { + mod hermit; + pub use hermit::*; + } else if #[cfg(target_os = "vxworks")] { + mod vxworks; + pub use vxworks::*; + } else if #[cfg(target_os = "solid_asp3")] { + mod solid; + pub use solid::*; + } else if #[cfg(all(windows, target_vendor = "win7"))] { + mod windows_legacy; + pub use windows_legacy::*; + } else if #[cfg(windows)] { + mod windows; + pub use windows::*; + } else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] { + mod rdrand; + pub use rdrand::*; + } else if #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] { + cfg_if! { + if #[cfg(feature = "wasm_js")] { + mod wasm_js; + pub use wasm_js::*; + } else { + compile_error!(concat!( + "The wasm32-unknown-unknown targets are not supported by default; \ + you may need to enable the \"wasm_js\" configuration flag. Note \ + that enabling the `wasm_js` feature flag alone is insufficient. \ + For more information see: \ + https://docs.rs/getrandom/", env!("CARGO_PKG_VERSION"), "/#webassembly-support" + )); + } + } + } else { + compile_error!(concat!( + "target is not supported. You may need to define a custom backend see: \ + https://docs.rs/getrandom/", env!("CARGO_PKG_VERSION"), "/#custom-backend" + )); + } +} diff --git a/src/backends/apple_other.rs b/src/backends/apple_other.rs new file mode 100644 index 000000000..c7b51c0e0 --- /dev/null +++ b/src/backends/apple_other.rs @@ -0,0 +1,21 @@ +//! Implementation for iOS, tvOS, and watchOS where `getentropy` is unavailable. +use crate::Error; +use core::{ffi::c_void, mem::MaybeUninit}; + +pub use crate::util::{inner_u32, inner_u64}; + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let dst_ptr = dest.as_mut_ptr().cast::(); + let ret = unsafe { libc::CCRandomGenerateBytes(dst_ptr, dest.len()) }; + if ret == libc::kCCSuccess { + Ok(()) + } else { + Err(Error::IOS_RANDOM_GEN) + } +} + +impl Error { + /// Call to `CCRandomGenerateBytes` failed. + pub(crate) const IOS_RANDOM_GEN: Error = Self::new_internal(10); +} diff --git a/src/backends/custom.rs b/src/backends/custom.rs new file mode 100644 index 000000000..c505481ad --- /dev/null +++ b/src/backends/custom.rs @@ -0,0 +1,13 @@ +//! An implementation which calls out to an externally defined function. +use crate::Error; +use core::mem::MaybeUninit; + +pub use crate::util::{inner_u32, inner_u64}; + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + extern "Rust" { + fn __getrandom_v03_custom(dest: *mut u8, len: usize) -> Result<(), Error>; + } + unsafe { __getrandom_v03_custom(dest.as_mut_ptr().cast(), dest.len()) } +} diff --git a/src/backends/efi_rng.rs b/src/backends/efi_rng.rs new file mode 100644 index 000000000..768c8cc8c --- /dev/null +++ b/src/backends/efi_rng.rs @@ -0,0 +1,124 @@ +//! Implementation for UEFI using EFI_RNG_PROTOCOL +use crate::Error; +use core::{ + mem::MaybeUninit, + ptr::{self, null_mut, NonNull}, + sync::atomic::{AtomicPtr, Ordering::Relaxed}, +}; +use r_efi::{ + efi::{BootServices, Handle}, + protocols::rng, +}; + +extern crate std; + +pub use crate::util::{inner_u32, inner_u64}; + +#[cfg(not(target_os = "uefi"))] +compile_error!("`efi_rng` backend can be enabled only for UEFI targets!"); + +static RNG_PROTOCOL: AtomicPtr = AtomicPtr::new(null_mut()); + +#[cold] +#[inline(never)] +fn init() -> Result, Error> { + const HANDLE_SIZE: usize = size_of::(); + + let boot_services = std::os::uefi::env::boot_services() + .ok_or(Error::BOOT_SERVICES_UNAVAILABLE)? + .cast::(); + + let mut handles = [ptr::null_mut(); 16]; + // `locate_handle` operates with length in bytes + let mut buf_size = handles.len() * HANDLE_SIZE; + let mut guid = rng::PROTOCOL_GUID; + let ret = unsafe { + ((*boot_services.as_ptr()).locate_handle)( + r_efi::efi::BY_PROTOCOL, + &mut guid, + null_mut(), + &mut buf_size, + handles.as_mut_ptr(), + ) + }; + + if ret.is_error() { + return Err(Error::from_uefi_code(ret.as_usize())); + } + + let handles_len = buf_size / HANDLE_SIZE; + let handles = handles.get(..handles_len).ok_or(Error::UNEXPECTED)?; + + let system_handle = std::os::uefi::env::image_handle(); + for &handle in handles { + let mut protocol: MaybeUninit<*mut rng::Protocol> = MaybeUninit::uninit(); + + let mut protocol_guid = rng::PROTOCOL_GUID; + let ret = unsafe { + ((*boot_services.as_ptr()).open_protocol)( + handle, + &mut protocol_guid, + protocol.as_mut_ptr().cast(), + system_handle.as_ptr(), + ptr::null_mut(), + r_efi::system::OPEN_PROTOCOL_GET_PROTOCOL, + ) + }; + + let protocol = if ret.is_error() { + continue; + } else { + let protocol = unsafe { protocol.assume_init() }; + NonNull::new(protocol).ok_or(Error::UNEXPECTED)? + }; + + // Try to use the acquired protocol handle + let mut buf = [0u8; 8]; + let mut alg_guid = rng::ALGORITHM_RAW; + let ret = unsafe { + ((*protocol.as_ptr()).get_rng)( + protocol.as_ptr(), + &mut alg_guid, + buf.len(), + buf.as_mut_ptr(), + ) + }; + + if ret.is_error() { + continue; + } + + RNG_PROTOCOL.store(protocol.as_ptr(), Relaxed); + return Ok(protocol); + } + Err(Error::NO_RNG_HANDLE) +} + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let protocol = match NonNull::new(RNG_PROTOCOL.load(Relaxed)) { + Some(p) => p, + None => init()?, + }; + + let mut alg_guid = rng::ALGORITHM_RAW; + let ret = unsafe { + ((*protocol.as_ptr()).get_rng)( + protocol.as_ptr(), + &mut alg_guid, + dest.len(), + dest.as_mut_ptr().cast::(), + ) + }; + + if ret.is_error() { + Err(Error::from_uefi_code(ret.as_usize())) + } else { + Ok(()) + } +} + +impl Error { + pub(crate) const BOOT_SERVICES_UNAVAILABLE: Error = Self::new_internal(10); + pub(crate) const NO_RNG_HANDLE: Error = Self::new_internal(11); +} diff --git a/src/espidf.rs b/src/backends/esp_idf.rs similarity index 84% rename from src/espidf.rs rename to src/backends/esp_idf.rs index 7da5ca88e..4d1689dc7 100644 --- a/src/espidf.rs +++ b/src/backends/esp_idf.rs @@ -2,11 +2,14 @@ use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; +pub use crate::util::{inner_u32, inner_u64}; + extern "C" { fn esp_fill_random(buf: *mut c_void, len: usize) -> u32; } -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { // Not that NOT enabling WiFi, BT, or the voltage noise entropy source (via `bootloader_random_enable`) // will cause ESP-IDF to return pseudo-random numbers based on the voltage noise entropy, after the initial boot process: // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html diff --git a/src/backends/fuchsia.rs b/src/backends/fuchsia.rs new file mode 100644 index 000000000..b5f1ade54 --- /dev/null +++ b/src/backends/fuchsia.rs @@ -0,0 +1,16 @@ +//! Implementation for Fuchsia Zircon +use crate::Error; +use core::mem::MaybeUninit; + +pub use crate::util::{inner_u32, inner_u64}; + +#[link(name = "zircon")] +extern "C" { + fn zx_cprng_draw(buffer: *mut u8, length: usize); +} + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + unsafe { zx_cprng_draw(dest.as_mut_ptr().cast::(), dest.len()) } + Ok(()) +} diff --git a/src/backends/getentropy.rs b/src/backends/getentropy.rs new file mode 100644 index 000000000..ed181f019 --- /dev/null +++ b/src/backends/getentropy.rs @@ -0,0 +1,27 @@ +//! Implementation using getentropy(2) +//! +//! Available since: +//! - macOS 10.12 +//! - OpenBSD 5.6 +//! - Emscripten 2.0.5 +//! - vita newlib since Dec 2021 +//! +//! For these targets, we use getentropy(2) because getrandom(2) doesn't exist. +use crate::Error; +use core::{ffi::c_void, mem::MaybeUninit}; + +pub use crate::util::{inner_u32, inner_u64}; + +#[path = "../util_libc.rs"] +mod util_libc; + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + for chunk in dest.chunks_mut(256) { + let ret = unsafe { libc::getentropy(chunk.as_mut_ptr().cast::(), chunk.len()) }; + if ret != 0 { + return Err(util_libc::last_os_error()); + } + } + Ok(()) +} diff --git a/src/getrandom.rs b/src/backends/getrandom.rs similarity index 55% rename from src/getrandom.rs rename to src/backends/getrandom.rs index bc5836530..f3c33c3cb 100644 --- a/src/getrandom.rs +++ b/src/backends/getrandom.rs @@ -15,11 +15,25 @@ //! GRND_RANDOM is not recommended. On NetBSD/FreeBSD/Dragonfly/3ds, it does //! nothing. On illumos, the default pool is used to implement getentropy(2), //! so we assume it is acceptable here. -use crate::{util_libc::sys_fill_exact, Error}; +use crate::Error; use core::mem::MaybeUninit; -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - sys_fill_exact(dest, |buf| unsafe { - libc::getrandom(buf.as_mut_ptr() as *mut libc::c_void, buf.len(), 0) +pub use crate::util::{inner_u32, inner_u64}; + +#[path = "../util_libc.rs"] +mod util_libc; + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + util_libc::sys_fill_exact(dest, |buf| unsafe { + let ret = libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0); + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[allow(unused_unsafe)] // TODO(MSRV 1.65): Remove this. + unsafe { + super::sanitizer::unpoison_linux_getrandom_result(buf, ret); + } + + ret }) } diff --git a/src/backends/hermit.rs b/src/backends/hermit.rs new file mode 100644 index 000000000..34d7cdbb9 --- /dev/null +++ b/src/backends/hermit.rs @@ -0,0 +1,53 @@ +//! Implementation for Hermit +use crate::Error; +use core::mem::MaybeUninit; + +extern "C" { + fn sys_read_entropy(buffer: *mut u8, length: usize, flags: u32) -> isize; + // Note that `sys_secure_rand32/64` are implemented using `sys_read_entropy`: + // https://github.com/hermit-os/kernel/blob/430da84/src/syscalls/entropy.rs#L62-L104 + // But this may change in future and can depend on compilation target, + // so to future-proof we use these "syscalls". + fn sys_secure_rand32(value: *mut u32) -> i32; + fn sys_secure_rand64(value: *mut u64) -> i32; +} + +#[inline] +pub fn inner_u32() -> Result { + let mut res = MaybeUninit::uninit(); + let ret = unsafe { sys_secure_rand32(res.as_mut_ptr()) }; + match ret { + 0 => Ok(unsafe { res.assume_init() }), + -1 => Err(Error::UNSUPPORTED), + _ => Err(Error::UNEXPECTED), + } +} + +#[inline] +pub fn inner_u64() -> Result { + let mut res = MaybeUninit::uninit(); + let ret = unsafe { sys_secure_rand64(res.as_mut_ptr()) }; + match ret { + 0 => Ok(unsafe { res.assume_init() }), + -1 => Err(Error::UNSUPPORTED), + _ => Err(Error::UNEXPECTED), + } +} + +#[inline] +pub fn fill_inner(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { + while !dest.is_empty() { + let res = unsafe { sys_read_entropy(dest.as_mut_ptr().cast::(), dest.len(), 0) }; + match res { + res if res > 0 => { + let len = usize::try_from(res).map_err(|_| Error::UNEXPECTED)?; + dest = dest.get_mut(len..).ok_or(Error::UNEXPECTED)?; + } + code => { + let code = i32::try_from(code).map_err(|_| Error::UNEXPECTED)?; + return Err(Error::from_neg_error_code(code)); + } + } + } + Ok(()) +} diff --git a/src/backends/linux_android_with_fallback.rs b/src/backends/linux_android_with_fallback.rs new file mode 100644 index 000000000..6c9dd0653 --- /dev/null +++ b/src/backends/linux_android_with_fallback.rs @@ -0,0 +1,103 @@ +//! Implementation for Linux / Android with `/dev/urandom` fallback +use super::{sanitizer, use_file}; +use crate::Error; +use core::{ + ffi::c_void, + mem::{transmute, MaybeUninit}, + ptr::NonNull, + sync::atomic::{AtomicPtr, Ordering}, +}; +use use_file::util_libc; + +pub use crate::util::{inner_u32, inner_u64}; + +type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t; + +/// Sentinel value which indicates that `libc::getrandom` either not available, +/// or not supported by kernel. +const NOT_AVAILABLE: NonNull = unsafe { NonNull::new_unchecked(usize::MAX as *mut c_void) }; + +static GETRANDOM_FN: AtomicPtr = AtomicPtr::new(core::ptr::null_mut()); + +#[cold] +#[inline(never)] +fn init() -> NonNull { + // Use static linking to `libc::getrandom` on MUSL targets and `dlsym` everywhere else + #[cfg(not(target_env = "musl"))] + let raw_ptr = { + static NAME: &[u8] = b"getrandom\0"; + let name_ptr = NAME.as_ptr().cast::(); + unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) } + }; + #[cfg(target_env = "musl")] + let raw_ptr = { + let fptr: GetRandomFn = libc::getrandom; + unsafe { transmute::(fptr) } + }; + + let res_ptr = match NonNull::new(raw_ptr) { + Some(fptr) => { + let getrandom_fn = unsafe { transmute::, GetRandomFn>(fptr) }; + let dangling_ptr = NonNull::dangling().as_ptr(); + // Check that `getrandom` syscall is supported by kernel + let res = unsafe { getrandom_fn(dangling_ptr, 0, 0) }; + if cfg!(getrandom_test_linux_fallback) { + NOT_AVAILABLE + } else if res.is_negative() { + match util_libc::last_os_error().raw_os_error() { + Some(libc::ENOSYS) => NOT_AVAILABLE, // 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) => NOT_AVAILABLE, // Blocked by seccomp + _ => fptr, + } + } else { + fptr + } + } + None => NOT_AVAILABLE, + }; + + #[cfg(getrandom_test_linux_without_fallback)] + if res_ptr == NOT_AVAILABLE { + panic!("Fallback is triggered with enabled `getrandom_test_linux_without_fallback`") + } + + GETRANDOM_FN.store(res_ptr.as_ptr(), Ordering::Release); + res_ptr +} + +// Prevent inlining of the fallback implementation +#[inline(never)] +fn use_file_fallback(dest: &mut [MaybeUninit]) -> Result<(), Error> { + use_file::fill_inner(dest) +} + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Despite being only a single atomic variable, we still cannot always use + // Ordering::Relaxed, as we need to make sure a successful call to `init` + // is "ordered before" any data read through the returned pointer (which + // occurs when the function is called). Our implementation mirrors that of + // the one in libstd, meaning that the use of non-Relaxed operations is + // probably unnecessary. + let raw_ptr = GETRANDOM_FN.load(Ordering::Acquire); + let fptr = match NonNull::new(raw_ptr) { + Some(p) => p, + None => init(), + }; + + if fptr == NOT_AVAILABLE { + use_file_fallback(dest) + } else { + // note: `transmute` is currently the only way to convert a pointer into a function reference + let getrandom_fn = unsafe { transmute::, GetRandomFn>(fptr) }; + util_libc::sys_fill_exact(dest, |buf| unsafe { + let ret = getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0); + sanitizer::unpoison_linux_getrandom_result(buf, ret); + ret + }) + } +} diff --git a/src/backends/linux_raw.rs b/src/backends/linux_raw.rs new file mode 100644 index 000000000..2a74e5852 --- /dev/null +++ b/src/backends/linux_raw.rs @@ -0,0 +1,143 @@ +//! Implementation for Linux / Android using `asm!`-based syscalls. +use super::sanitizer; +pub use crate::util::{inner_u32, inner_u64}; +use crate::{Error, MaybeUninit}; + +#[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")] { + // TODO(MSRV-1.78): Also check `target_abi = "eabi"`. + // 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", + // TODO(MSRV-1.82): replace with `nr = const __NR_getrandom,` + "mov r7, #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")] { + // TODO(MSRV-1.78): Also check `any(target_abi = "", target_abi = "ilp32")` above. + // According to the ILP32 patch for the kernel that hasn't yet + // been merged into the mainline, "AARCH64/ILP32 ABI uses standard + // syscall table [...] with the exceptions listed below," where + // getrandom is not mentioned as an exception. + 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")] { + // TODO(MSRV-1.78): Also check `any(target_abi = "", target_abi = "ilp32")` above. + 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 = "s390x")] { + const __NR_getrandom: u32 = 349; + core::arch::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 + // and `sysenter`/`syscall` have their own portability issues, + // so we use the simple "legacy" way of doing syscalls. + core::arch::asm!( + "int $$0x80", + 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")] { + // TODO(MSRV-1.78): Add `any(target_abi = "", target_abi = "x32")` above. + const __X32_SYSCALL_BIT: u32 = 0x40000000; + const OFFSET: u32 = if cfg!(target_pointer_width = "32") { __X32_SYSCALL_BIT } else { 0 }; + const __NR_getrandom: u32 = OFFSET + 318; + + core::arch::asm!( + "syscall", + in("rax") __NR_getrandom, + in("rdi") buf, + in("rsi") buflen, + in("rdx") flags, + lateout("rax") r0, + lateout("rcx") _, + lateout("r11") _, + options(nostack, preserves_flags) + ); + } else { + compile_error!("`linux_raw` backend does not support this target arch"); + } + } + + 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; + + loop { + let ret = unsafe { getrandom_syscall(dest.as_mut_ptr().cast(), dest.len(), 0) }; + unsafe { sanitizer::unpoison_linux_getrandom_result(dest, ret) }; + 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 = i32::try_from(ret).map_err(|_| Error::UNEXPECTED)?; + return Err(Error::from_neg_error_code(code)); + } + } + } +} diff --git a/src/backends/netbsd.rs b/src/backends/netbsd.rs new file mode 100644 index 000000000..f228a8b13 --- /dev/null +++ b/src/backends/netbsd.rs @@ -0,0 +1,78 @@ +//! Implementation for NetBSD +//! +//! `getrandom(2)` was introduced in NetBSD 10. To support older versions we +//! implement our own weak linkage to it, and provide a fallback based on the +//! KERN_ARND sysctl. +use crate::Error; +use core::{ + cmp, + ffi::c_void, + mem::{self, MaybeUninit}, + ptr, + sync::atomic::{AtomicPtr, Ordering}, +}; + +pub use crate::util::{inner_u32, inner_u64}; + +#[path = "../util_libc.rs"] +mod util_libc; + +unsafe extern "C" fn polyfill_using_kern_arand( + buf: *mut c_void, + buflen: libc::size_t, + flags: libc::c_uint, +) -> libc::ssize_t { + debug_assert_eq!(flags, 0); + + const MIB_LEN: libc::c_uint = 2; + static MIB: [libc::c_int; MIB_LEN as usize] = [libc::CTL_KERN, libc::KERN_ARND]; + + // NetBSD will only return up to 256 bytes at a time, and + // older NetBSD kernels will fail on longer buffers. + let mut len = cmp::min(buflen, 256); + let ret = unsafe { libc::sysctl(MIB.as_ptr(), MIB_LEN, buf, &mut len, ptr::null(), 0) }; + + match ret { + 0 if len <= 256 => libc::ssize_t::try_from(len).expect("len is in the range of 0..=256"), + -1 => -1, + // Zero return result will be converted into `Error::UNEXPECTED` by `sys_fill_exact` + _ => 0, + } +} + +type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t; + +static GETRANDOM: AtomicPtr = AtomicPtr::new(ptr::null_mut()); + +#[cold] +#[inline(never)] +fn init() -> *mut c_void { + static NAME: &[u8] = b"getrandom\0"; + let name_ptr = NAME.as_ptr().cast::(); + let mut ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) }; + if ptr.is_null() || cfg!(getrandom_test_netbsd_fallback) { + // Verify `polyfill_using_kern_arand` has the right signature. + const POLYFILL: GetRandomFn = polyfill_using_kern_arand; + ptr = POLYFILL as *mut c_void; + } + GETRANDOM.store(ptr, Ordering::Release); + ptr +} + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Despite being only a single atomic variable, we still cannot always use + // Ordering::Relaxed, as we need to make sure a successful call to `init` + // is "ordered before" any data read through the returned pointer (which + // occurs when the function is called). Our implementation mirrors that of + // the one in libstd, meaning that the use of non-Relaxed operations is + // probably unnecessary. + let mut fptr = GETRANDOM.load(Ordering::Acquire); + if fptr.is_null() { + fptr = init(); + } + let fptr = unsafe { mem::transmute::<*mut c_void, GetRandomFn>(fptr) }; + util_libc::sys_fill_exact(dest, |buf| unsafe { + fptr(buf.as_mut_ptr().cast::(), buf.len(), 0) + }) +} diff --git a/src/rdrand.rs b/src/backends/rdrand.rs similarity index 66% rename from src/rdrand.rs rename to src/backends/rdrand.rs index f527c8c64..609fcc386 100644 --- a/src/rdrand.rs +++ b/src/backends/rdrand.rs @@ -1,28 +1,38 @@ //! RDRAND backend for x86(-64) targets -use crate::{lazy::LazyBool, util::slice_as_uninit, Error}; +use crate::{util::slice_as_uninit, Error}; use core::mem::{size_of, MaybeUninit}; +#[path = "../lazy.rs"] +mod lazy; + +#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] +compile_error!("`rdrand` backend can be enabled only for x86 and x86-64 targets!"); + cfg_if! { if #[cfg(target_arch = "x86_64")] { use core::arch::x86_64 as arch; use arch::_rdrand64_step as rdrand_step; + type Word = u64; } else if #[cfg(target_arch = "x86")] { use core::arch::x86 as arch; use arch::_rdrand32_step as rdrand_step; + type Word = u32; } } +static RDRAND_GOOD: lazy::LazyBool = lazy::LazyBool::new(); + // Recommendation from "Intel® Digital Random Number Generator (DRNG) Software // Implementation Guide" - Section 5.2.1 and "Intel® 64 and IA-32 Architectures // Software Developer’s Manual" - Volume 1 - Section 7.3.17.1. const RETRY_LIMIT: usize = 10; #[target_feature(enable = "rdrand")] -unsafe fn rdrand() -> Option { +unsafe fn rdrand() -> Option { for _ in 0..RETRY_LIMIT { let mut val = 0; if rdrand_step(&mut val) == 1 { - return Some(val as usize); + return Some(val); } } None @@ -91,21 +101,12 @@ fn is_rdrand_good() -> bool { unsafe { self_test() } } -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - static RDRAND_GOOD: LazyBool = LazyBool::new(); - if !RDRAND_GOOD.unsync_init(is_rdrand_good) { - return Err(Error::NO_RDRAND); - } - // SAFETY: After this point, we know rdrand is supported. - unsafe { rdrand_exact(dest) }.ok_or(Error::FAILED_RDRAND) -} - // TODO: make this function safe when we have feature(target_feature_11) #[target_feature(enable = "rdrand")] unsafe fn rdrand_exact(dest: &mut [MaybeUninit]) -> Option<()> { // We use chunks_exact_mut instead of chunks_mut as it allows almost all // calls to memcpy to be elided by the compiler. - let mut chunks = dest.chunks_exact_mut(size_of::()); + let mut chunks = dest.chunks_exact_mut(size_of::()); for chunk in chunks.by_ref() { let src = rdrand()?.to_ne_bytes(); chunk.copy_from_slice(slice_as_uninit(&src)); @@ -119,3 +120,63 @@ unsafe fn rdrand_exact(dest: &mut [MaybeUninit]) -> Option<()> { } Some(()) } + +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "rdrand")] +unsafe fn rdrand_u32() -> Option { + rdrand().map(crate::util::truncate) +} + +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "rdrand")] +unsafe fn rdrand_u64() -> Option { + rdrand() +} + +#[cfg(target_arch = "x86")] +#[target_feature(enable = "rdrand")] +unsafe fn rdrand_u32() -> Option { + rdrand() +} + +#[cfg(target_arch = "x86")] +#[target_feature(enable = "rdrand")] +unsafe fn rdrand_u64() -> Option { + let a = rdrand()?; + let b = rdrand()?; + Some((u64::from(a) << 32) | u64::from(b)) +} + +#[inline] +pub fn inner_u32() -> Result { + if !RDRAND_GOOD.unsync_init(is_rdrand_good) { + return Err(Error::NO_RDRAND); + } + // SAFETY: After this point, we know rdrand is supported. + unsafe { rdrand_u32() }.ok_or(Error::FAILED_RDRAND) +} + +#[inline] +pub fn inner_u64() -> Result { + if !RDRAND_GOOD.unsync_init(is_rdrand_good) { + return Err(Error::NO_RDRAND); + } + // SAFETY: After this point, we know rdrand is supported. + unsafe { rdrand_u64() }.ok_or(Error::FAILED_RDRAND) +} + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + if !RDRAND_GOOD.unsync_init(is_rdrand_good) { + return Err(Error::NO_RDRAND); + } + // SAFETY: After this point, we know rdrand is supported. + unsafe { rdrand_exact(dest) }.ok_or(Error::FAILED_RDRAND) +} + +impl Error { + /// RDRAND instruction failed due to a hardware issue. + pub(crate) const FAILED_RDRAND: Error = Self::new_internal(10); + /// RDRAND instruction unsupported on this target. + pub(crate) const NO_RDRAND: Error = Self::new_internal(11); +} diff --git a/src/backends/rndr.rs b/src/backends/rndr.rs new file mode 100644 index 000000000..eea741a2d --- /dev/null +++ b/src/backends/rndr.rs @@ -0,0 +1,145 @@ +//! RNDR register backend for aarch64 targets +//! +//! Arm Architecture Reference Manual for A-profile architecture: +//! ARM DDI 0487K.a, ID032224, D23.2.147 RNDR, Random Number +use crate::{ + util::{slice_as_uninit, truncate}, + Error, +}; +use core::arch::asm; +use core::mem::{size_of, MaybeUninit}; + +#[cfg(not(target_arch = "aarch64"))] +compile_error!("the `rndr` backend can be enabled only for AArch64 targets!"); + +const RETRY_LIMIT: usize = 5; + +/// Read a random number from the aarch64 RNDR register +/// +/// Callers must ensure that FEAT_RNG is available on the system +/// The function assumes that the RNDR register is available +/// If it fails to read a random number, it will retry up to 5 times +/// After 5 failed reads the function will return `None` +#[target_feature(enable = "rand")] +unsafe fn rndr() -> Option { + for _ in 0..RETRY_LIMIT { + let mut x: u64; + let mut nzcv: u64; + + // AArch64 RNDR register is accessible by s3_3_c2_c4_0 + asm!( + "mrs {x}, RNDR", + "mrs {nzcv}, NZCV", + x = out(reg) x, + nzcv = out(reg) nzcv, + ); + + // If the hardware returns a genuine random number, PSTATE.NZCV is set to 0b0000 + if nzcv == 0 { + return Some(x); + } + } + + None +} + +#[target_feature(enable = "rand")] +unsafe fn rndr_fill(dest: &mut [MaybeUninit]) -> Option<()> { + let mut chunks = dest.chunks_exact_mut(size_of::()); + for chunk in chunks.by_ref() { + let src = rndr()?.to_ne_bytes(); + chunk.copy_from_slice(slice_as_uninit(&src)); + } + + let tail = chunks.into_remainder(); + let n = tail.len(); + if n > 0 { + let src = rndr()?.to_ne_bytes(); + tail.copy_from_slice(slice_as_uninit(&src[..n])); + } + Some(()) +} + +#[cfg(target_feature = "rand")] +fn is_rndr_available() -> bool { + true +} + +#[cfg(not(target_feature = "rand"))] +fn is_rndr_available() -> bool { + #[path = "../lazy.rs"] + mod lazy; + static RNDR_GOOD: lazy::LazyBool = lazy::LazyBool::new(); + + cfg_if::cfg_if! { + if #[cfg(feature = "std")] { + extern crate std; + RNDR_GOOD.unsync_init(|| std::arch::is_aarch64_feature_detected!("rand")) + } else if #[cfg(target_os = "linux")] { + /// Check whether FEAT_RNG is available on the system + /// + /// Requires the caller either be running in EL1 or be on a system supporting MRS + /// emulation. Due to the above, the implementation is currently restricted to Linux. + /// + /// Relying on runtime detection bumps minimum supported Linux kernel version to 4.11. + fn mrs_check() -> bool { + let mut id_aa64isar0: u64; + + // If FEAT_RNG is implemented, ID_AA64ISAR0_EL1.RNDR (bits 60-63) are 0b0001 + // This is okay to do from EL0 in Linux because Linux will emulate MRS as per + // https://docs.kernel.org/arch/arm64/cpu-feature-registers.html + unsafe { + asm!( + "mrs {id}, ID_AA64ISAR0_EL1", + id = out(reg) id_aa64isar0, + ); + } + + (id_aa64isar0 >> 60) & 0xf >= 1 + } + + RNDR_GOOD.unsync_init(mrs_check) + } else { + compile_error!( + "RNDR `no_std` runtime detection is currently supported only on Linux targets. \ + Either enable the `std` crate feature, or `rand` target feature at compile time." + ); + } + } +} + +#[inline] +pub fn inner_u32() -> Result { + if !is_rndr_available() { + return Err(Error::RNDR_NOT_AVAILABLE); + } + // SAFETY: after this point, we know the `rand` target feature is enabled + let res = unsafe { rndr() }; + res.map(truncate).ok_or(Error::RNDR_FAILURE) +} + +#[inline] +pub fn inner_u64() -> Result { + if !is_rndr_available() { + return Err(Error::RNDR_NOT_AVAILABLE); + } + // SAFETY: after this point, we know the `rand` target feature is enabled + let res = unsafe { rndr() }; + res.ok_or(Error::RNDR_FAILURE) +} + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + if !is_rndr_available() { + return Err(Error::RNDR_NOT_AVAILABLE); + } + // SAFETY: after this point, we know the `rand` target feature is enabled + unsafe { rndr_fill(dest).ok_or(Error::RNDR_FAILURE) } +} + +impl Error { + /// RNDR register read failed due to a hardware issue. + pub(crate) const RNDR_FAILURE: Error = Self::new_internal(10); + /// RNDR register is not supported on this target. + pub(crate) const RNDR_NOT_AVAILABLE: Error = Self::new_internal(11); +} diff --git a/src/backends/sanitizer.rs b/src/backends/sanitizer.rs new file mode 100644 index 000000000..0e074d43e --- /dev/null +++ b/src/backends/sanitizer.rs @@ -0,0 +1,59 @@ +use core::mem::MaybeUninit; + +/// Unpoisons `buf` if MSAN support is enabled. +/// +/// Most backends do not need to unpoison their output. Rust language- and +/// library- provided functionality unpoisons automatically. Similarly, libc +/// either natively supports MSAN and/or MSAN hooks libc-provided functions +/// to unpoison outputs on success. Only when all of these things are +/// bypassed do we need to do it ourselves. +/// +/// The call to unpoison should be done as close to the write as possible. +/// For example, if the backend partially fills the output buffer in chunks, +/// each chunk should be unpoisoned individually. This way, the correctness of +/// the chunking logic can be validated (in part) using MSAN. +pub unsafe fn unpoison(buf: &mut [MaybeUninit]) { + cfg_if! { + if #[cfg(getrandom_msan)] { + extern "C" { + fn __msan_unpoison(a: *mut core::ffi::c_void, size: usize); + } + let a = buf.as_mut_ptr().cast(); + let size = buf.len(); + #[allow(unused_unsafe)] // TODO(MSRV 1.65): Remove this. + unsafe { + __msan_unpoison(a, size); + } + } else { + let _ = buf; + } + } +} + +/// Interprets the result of the `getrandom` syscall of Linux, unpoisoning any +/// written part of `buf`. +/// +/// `buf` must be the output buffer that was originally passed to the `getrandom` +/// syscall. +/// +/// `ret` must be the result returned by `getrandom`. If `ret` is negative or +/// larger than the length of `buf` then nothing is done. +/// +/// Memory Sanitizer only intercepts `getrandom` on this condition (from its +/// source code): +/// ```c +/// #define SANITIZER_INTERCEPT_GETRANDOM \ +/// ((SI_LINUX && __GLIBC_PREREQ(2, 25)) || SI_FREEBSD || SI_SOLARIS) +/// ``` +/// So, effectively, we have to assume that it is never intercepted on Linux. +#[cfg(any(target_os = "android", target_os = "linux"))] +pub unsafe fn unpoison_linux_getrandom_result(buf: &mut [MaybeUninit], ret: isize) { + if let Ok(bytes_written) = usize::try_from(ret) { + if let Some(written) = buf.get_mut(..bytes_written) { + #[allow(unused_unsafe)] // TODO(MSRV 1.65): Remove this. + unsafe { + unpoison(written) + } + } + } +} diff --git a/src/solaris.rs b/src/backends/solaris.rs similarity index 67% rename from src/solaris.rs rename to src/backends/solaris.rs index 8a3401e0f..c27f91a5f 100644 --- a/src/solaris.rs +++ b/src/backends/solaris.rs @@ -12,22 +12,30 @@ //! For more information, see the man page linked in lib.rs and this blog post: //! https://blogs.oracle.com/solaris/post/solaris-new-system-calls-getentropy2-and-getrandom2 //! which also explains why this crate should not use getentropy(2). -use crate::{util_libc::last_os_error, Error}; -use core::mem::MaybeUninit; +use crate::Error; +use core::{ffi::c_void, mem::MaybeUninit}; + +pub use crate::util::{inner_u32, inner_u64}; + +#[path = "../util_libc.rs"] +mod util_libc; const MAX_BYTES: usize = 1024; -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { for chunk in dest.chunks_mut(MAX_BYTES) { - let ptr = chunk.as_mut_ptr() as *mut libc::c_void; + let ptr = chunk.as_mut_ptr().cast::(); let ret = unsafe { libc::getrandom(ptr, chunk.len(), libc::GRND_RANDOM) }; // In case the man page has a typo, we also check for negative ret. - if ret <= 0 { - return Err(last_os_error()); - } // If getrandom(2) succeeds, it should have completely filled chunk. - if (ret as usize) != chunk.len() { - return Err(Error::UNEXPECTED); + match usize::try_from(ret) { + // Good. Keep going. + Ok(ret) if ret == chunk.len() => {} + // The syscall failed. + Ok(0) => return Err(util_libc::last_os_error()), + // All other cases should be impossible. + _ => return Err(Error::UNEXPECTED), } } Ok(()) diff --git a/src/backends/solid.rs b/src/backends/solid.rs new file mode 100644 index 000000000..caa773f81 --- /dev/null +++ b/src/backends/solid.rs @@ -0,0 +1,19 @@ +//! Implementation for SOLID +use crate::Error; +use core::mem::MaybeUninit; + +pub use crate::util::{inner_u32, inner_u64}; + +extern "C" { + pub fn SOLID_RNG_SampleRandomBytes(buffer: *mut u8, length: usize) -> i32; +} + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let ret = unsafe { SOLID_RNG_SampleRandomBytes(dest.as_mut_ptr().cast::(), dest.len()) }; + if ret >= 0 { + Ok(()) + } else { + Err(Error::from_neg_error_code(ret)) + } +} diff --git a/src/backends/unsupported.rs b/src/backends/unsupported.rs new file mode 100644 index 000000000..4ea381fc4 --- /dev/null +++ b/src/backends/unsupported.rs @@ -0,0 +1,9 @@ +//! Implementation that errors at runtime. +use crate::Error; +use core::mem::MaybeUninit; + +pub use crate::util::{inner_u32, inner_u64}; + +pub fn fill_inner(_dest: &mut [MaybeUninit]) -> Result<(), Error> { + Err(Error::UNSUPPORTED) +} diff --git a/src/backends/use_file.rs b/src/backends/use_file.rs new file mode 100644 index 000000000..071ce930b --- /dev/null +++ b/src/backends/use_file.rs @@ -0,0 +1,234 @@ +//! Implementations that just need to read from a file +use crate::Error; +use core::{ + ffi::c_void, + mem::MaybeUninit, + sync::atomic::{AtomicI32, Ordering}, +}; + +#[cfg(not(any(target_os = "android", target_os = "linux")))] +pub use crate::util::{inner_u32, inner_u64}; + +#[path = "../util_libc.rs"] +pub(super) mod util_libc; + +/// For all platforms, we use `/dev/urandom` rather than `/dev/random`. +/// For more information see the linked man pages in lib.rs. +/// - On Linux, "/dev/urandom is preferred and sufficient in all use cases". +/// - On Redox, only /dev/urandom is provided. +/// - On AIX, /dev/urandom will "provide cryptographically secure output". +/// - On Haiku and QNX Neutrino they are identical. +const FILE_PATH: &[u8] = b"/dev/urandom\0"; + +// File descriptor is a "nonnegative integer", so we can safely use negative sentinel values. +const FD_UNINIT: libc::c_int = -1; +const FD_ONGOING_INIT: libc::c_int = -2; + +// In theory `libc::c_int` could be something other than `i32`, but for the +// targets we currently support that use `use_file`, it is always `i32`. +// If/when we add support for a target where that isn't the case, we may +// need to use a different atomic type or make other accommodations. The +// compiler will let us know if/when that is the case, because the +// `FD.store(fd)` would fail to compile. +// +// The opening of the file, by libc/libstd/etc. may write some unknown +// state into in-process memory. (Such state may include some sanitizer +// bookkeeping, or we might be operating in a unikernal-like environment +// where all the "kernel" file descriptor bookkeeping is done in our +// process.) `get_fd_locked` stores into FD using `Ordering::Release` to +// ensure any such state is synchronized. `get_fd` loads from `FD` with +// `Ordering::Acquire` to synchronize with it. +static FD: AtomicI32 = AtomicI32::new(FD_UNINIT); + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let mut fd = FD.load(Ordering::Acquire); + if fd == FD_UNINIT || fd == FD_ONGOING_INIT { + fd = open_or_wait()?; + } + util_libc::sys_fill_exact(dest, |buf| unsafe { + libc::read(fd, buf.as_mut_ptr().cast::(), buf.len()) + }) +} + +/// Open a file in read-only mode. +/// +/// # Panics +/// If `path` does not contain any zeros. +// TODO: Move `path` to `CStr` and use `CStr::from_bytes_until_nul` (MSRV 1.69) +// or C-string literals (MSRV 1.77) for statics +fn open_readonly(path: &[u8]) -> Result { + assert!(path.contains(&0)); + loop { + let fd = unsafe { + libc::open( + path.as_ptr().cast::(), + libc::O_RDONLY | libc::O_CLOEXEC, + ) + }; + if fd >= 0 { + return Ok(fd); + } + let err = util_libc::last_os_error(); + // We should try again if open() was interrupted. + if err.raw_os_error() != Some(libc::EINTR) { + return Err(err); + } + } +} + +#[cold] +#[inline(never)] +fn open_or_wait() -> Result { + loop { + match FD.load(Ordering::Acquire) { + FD_UNINIT => { + let res = FD.compare_exchange_weak( + FD_UNINIT, + FD_ONGOING_INIT, + Ordering::AcqRel, + Ordering::Relaxed, + ); + if res.is_ok() { + break; + } + } + FD_ONGOING_INIT => sync::wait(), + fd => return Ok(fd), + } + } + + let res = open_fd(); + let val = match res { + Ok(fd) => fd, + Err(_) => FD_UNINIT, + }; + FD.store(val, Ordering::Release); + + // On non-Linux targets `wait` is just 1 ms sleep, + // so we don't need any explicit wake up in addition + // to updating value of `FD`. + #[cfg(any(target_os = "android", target_os = "linux"))] + sync::wake(); + + res +} + +fn open_fd() -> Result { + #[cfg(any(target_os = "android", target_os = "linux"))] + sync::wait_until_rng_ready()?; + let fd = open_readonly(FILE_PATH)?; + debug_assert!(fd >= 0); + Ok(fd) +} + +#[cfg(not(any(target_os = "android", target_os = "linux")))] +mod sync { + /// Sleep 1 ms before checking `FD` again. + /// + /// On non-Linux targets the critical section only opens file, + /// which should not block, so in the unlikely contended case, + /// we can sleep-wait for the opening operation to finish. + pub(super) fn wait() { + let rqtp = libc::timespec { + tv_sec: 0, + tv_nsec: 1_000_000, + }; + let mut rmtp = libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }; + // We do not care if sleep gets interrupted, so the return value is ignored + unsafe { + libc::nanosleep(&rqtp, &mut rmtp); + } + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +mod sync { + use super::{open_readonly, util_libc::last_os_error, Error, FD, FD_ONGOING_INIT}; + + /// Wait for atomic `FD` to change value from `FD_ONGOING_INIT` to something else. + /// + /// Futex syscall with `FUTEX_WAIT` op puts the current thread to sleep + /// until futex syscall with `FUTEX_WAKE` op gets executed for `FD`. + /// + /// For more information read: https://www.man7.org/linux/man-pages/man2/futex.2.html + pub(super) fn wait() { + let op = libc::FUTEX_WAIT | libc::FUTEX_PRIVATE_FLAG; + let timeout_ptr = core::ptr::null::(); + let ret = unsafe { libc::syscall(libc::SYS_futex, &FD, op, FD_ONGOING_INIT, timeout_ptr) }; + // FUTEX_WAIT should return either 0 or EAGAIN error + debug_assert!({ + match ret { + 0 => true, + -1 => last_os_error().raw_os_error() == Some(libc::EAGAIN), + _ => false, + } + }); + } + + /// Wake up all threads which wait for value of atomic `FD` to change. + pub(super) fn wake() { + let op = libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG; + let ret = unsafe { libc::syscall(libc::SYS_futex, &FD, op, libc::INT_MAX) }; + debug_assert!(ret >= 0); + } + + // Polls /dev/random to make sure it is ok to read from /dev/urandom. + // + // Polling avoids draining the estimated entropy from /dev/random; + // short-lived processes reading even a single byte from /dev/random could + // be problematic if they are being executed faster than entropy is being + // collected. + // + // OTOH, reading a byte instead of polling is more compatible with + // sandboxes that disallow `poll()` but which allow reading /dev/random, + // e.g. sandboxes that assume that `poll()` is for network I/O. This way, + // fewer applications will have to insert pre-sandbox-initialization logic. + // Often (blocking) file I/O is not allowed in such early phases of an + // application for performance and/or security reasons. + // + // It is hard to write a sandbox policy to support `libc::poll()` because + // it may invoke the `poll`, `ppoll`, `ppoll_time64` (since Linux 5.1, with + // newer versions of glibc), and/or (rarely, and probably only on ancient + // systems) `select`. depending on the libc implementation (e.g. glibc vs + // musl), libc version, potentially the kernel version at runtime, and/or + // the target architecture. + // + // BoringSSL and libstd don't try to protect against insecure output from + // `/dev/urandom'; they don't open `/dev/random` at all. + // + // OpenSSL uses `libc::select()` unless the `dev/random` file descriptor + // is too large; if it is too large then it does what we do here. + // + // libsodium uses `libc::poll` similarly to this. + pub(super) fn wait_until_rng_ready() -> Result<(), Error> { + let fd = open_readonly(b"/dev/random\0")?; + let mut pfd = libc::pollfd { + fd, + events: libc::POLLIN, + revents: 0, + }; + + let res = loop { + // A negative timeout means an infinite timeout. + let res = unsafe { libc::poll(&mut pfd, 1, -1) }; + if res >= 0 { + // We only used one fd, and cannot timeout. + debug_assert_eq!(res, 1); + break Ok(()); + } + let err = last_os_error(); + // Assuming that `poll` is called correctly, + // on Linux it can return only EINTR and ENOMEM errors. + match err.raw_os_error() { + Some(libc::EINTR) => continue, + _ => break Err(err), + } + }; + unsafe { libc::close(fd) }; + res + } +} diff --git a/src/backends/vxworks.rs b/src/backends/vxworks.rs new file mode 100644 index 000000000..5f5e6773b --- /dev/null +++ b/src/backends/vxworks.rs @@ -0,0 +1,54 @@ +//! Implementation for VxWorks +use crate::Error; +use core::{ + cmp::Ordering::{Equal, Greater, Less}, + mem::MaybeUninit, + sync::atomic::{AtomicBool, Ordering::Relaxed}, +}; + +#[path = "../util_libc.rs"] +mod util_libc; + +pub use crate::util::{inner_u32, inner_u64}; + +static RNG_INIT: AtomicBool = AtomicBool::new(false); + +#[cold] +fn init() -> Result<(), Error> { + let ret = unsafe { libc::randSecure() }; + match ret.cmp(&0) { + Greater => RNG_INIT.store(true, Relaxed), + Equal => unsafe { + libc::usleep(10); + }, + Less => return Err(Error::VXWORKS_RAND_SECURE), + } + Ok(()) +} + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + while !RNG_INIT.load(Relaxed) { + init()?; + } + + // Prevent overflow of i32 + let chunk_size = usize::try_from(i32::MAX).expect("VxWorks does not support 16-bit targets"); + for chunk in dest.chunks_mut(chunk_size) { + let chunk_len: libc::c_int = chunk + .len() + .try_into() + .expect("chunk size is bounded by i32::MAX"); + let p: *mut libc::c_uchar = chunk.as_mut_ptr().cast(); + let ret = unsafe { libc::randABytes(p, chunk_len) }; + if ret != 0 { + return Err(util_libc::last_os_error()); + } + } + Ok(()) +} + +impl Error { + /// On VxWorks, call to `randSecure` failed (random number generator is not yet initialized). + pub(crate) const VXWORKS_RAND_SECURE: Error = Self::new_internal(10); +} diff --git a/src/backends/wasi_p1.rs b/src/backends/wasi_p1.rs new file mode 100644 index 000000000..25b5ca3b7 --- /dev/null +++ b/src/backends/wasi_p1.rs @@ -0,0 +1,32 @@ +//! Implementation for WASI Preview 1 +use crate::Error; +use core::mem::MaybeUninit; + +pub use crate::util::{inner_u32, inner_u64}; + +// This linking is vendored from the wasi crate: +// https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2344-2350 +#[link(wasm_import_module = "wasi_snapshot_preview1")] +extern "C" { + fn random_get(arg0: i32, arg1: i32) -> i32; +} + +/// WASI p1 uses `u16` for error codes in its witx definitions: +/// https://github.com/WebAssembly/WASI/blob/38454e9e/legacy/preview1/witx/typenames.witx#L34-L39 +const MAX_ERROR_CODE: i32 = u16::MAX as i32; + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Based on the wasi code: + // https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2046-2062 + // Note that size of an allocated object can not be bigger than isize::MAX bytes. + // WASI 0.1 supports only 32-bit WASM, so casting length to `i32` is safe. + #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] + let ret = unsafe { random_get(dest.as_mut_ptr() as i32, dest.len() as i32) }; + match ret { + 0 => Ok(()), + // WASI functions should return positive error codes which are smaller than `MAX_ERROR_CODE` + code if code <= MAX_ERROR_CODE => Err(Error::from_neg_error_code(-code)), + _ => Err(Error::UNEXPECTED), + } +} diff --git a/src/backends/wasi_p2.rs b/src/backends/wasi_p2.rs new file mode 100644 index 000000000..d16fdff99 --- /dev/null +++ b/src/backends/wasi_p2.rs @@ -0,0 +1,47 @@ +//! Implementation for WASI Preview 2. +use crate::Error; +use core::{mem::MaybeUninit, ptr::copy_nonoverlapping}; +use wasip2::random::random::get_random_u64; + +#[inline] +pub fn inner_u32() -> Result { + let val = get_random_u64(); + Ok(crate::util::truncate(val)) +} + +#[inline] +pub fn inner_u64() -> Result { + Ok(get_random_u64()) +} + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let (prefix, chunks, suffix) = unsafe { dest.align_to_mut::>() }; + + // We use `get_random_u64` instead of `get_random_bytes` because the latter creates + // an allocation due to the Wit IDL [restrictions][0]. This should be fine since + // the main use case of `getrandom` is seed generation. + // + // [0]: https://github.com/WebAssembly/wasi-random/issues/27 + if !prefix.is_empty() { + let val = get_random_u64(); + let src = (&val as *const u64).cast(); + unsafe { + copy_nonoverlapping(src, prefix.as_mut_ptr(), prefix.len()); + } + } + + for dst in chunks { + dst.write(get_random_u64()); + } + + if !suffix.is_empty() { + let val = get_random_u64(); + let src = (&val as *const u64).cast(); + unsafe { + copy_nonoverlapping(src, suffix.as_mut_ptr(), suffix.len()); + } + } + + Ok(()) +} diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs new file mode 100644 index 000000000..1320d9fc4 --- /dev/null +++ b/src/backends/wasm_js.rs @@ -0,0 +1,72 @@ +//! Implementation for WASM based on Web and Node.js +use crate::Error; +use core::mem::MaybeUninit; + +pub use crate::util::{inner_u32, inner_u64}; + +#[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] +compile_error!("`wasm_js` backend can be enabled only for OS-less WASM targets!"); + +use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; + +// Maximum buffer size allowed in `Crypto.getRandomValuesSize` is 65536 bytes. +// See https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues +const MAX_BUFFER_SIZE: usize = 65536; + +#[cfg(not(target_feature = "atomics"))] +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + for chunk in dest.chunks_mut(MAX_BUFFER_SIZE) { + if get_random_values(chunk).is_err() { + return Err(Error::WEB_CRYPTO); + } + } + Ok(()) +} + +#[cfg(target_feature = "atomics")] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // getRandomValues does not work with all types of WASM memory, + // so we initially write to browser memory to avoid exceptions. + let buf_len = usize::min(dest.len(), MAX_BUFFER_SIZE); + let buf_len_u32 = buf_len + .try_into() + .expect("buffer length is bounded by MAX_BUFFER_SIZE"); + let buf = js_sys::Uint8Array::new_with_length(buf_len_u32); + for chunk in dest.chunks_mut(buf_len) { + let chunk_len = chunk + .len() + .try_into() + .expect("chunk length is bounded by MAX_BUFFER_SIZE"); + // The chunk can be smaller than buf's length, so we call to + // JS to create a smaller view of buf without allocation. + let sub_buf = if chunk_len == buf_len_u32 { + &buf + } else { + &buf.subarray(0, chunk_len) + }; + + if get_random_values(sub_buf).is_err() { + return Err(Error::WEB_CRYPTO); + } + + sub_buf.copy_to_uninit(chunk); + } + Ok(()) +} + +#[wasm_bindgen] +extern "C" { + // Crypto.getRandomValues() + #[cfg(not(target_feature = "atomics"))] + #[wasm_bindgen(js_namespace = ["globalThis", "crypto"], js_name = getRandomValues, catch)] + fn get_random_values(buf: &mut [MaybeUninit]) -> Result<(), JsValue>; + #[cfg(target_feature = "atomics")] + #[wasm_bindgen(js_namespace = ["globalThis", "crypto"], js_name = getRandomValues, catch)] + fn get_random_values(buf: &js_sys::Uint8Array) -> Result<(), JsValue>; +} + +impl Error { + /// The environment does not support the Web Crypto API. + pub(crate) const WEB_CRYPTO: Error = Self::new_internal(10); +} diff --git a/src/backends/windows.rs b/src/backends/windows.rs new file mode 100644 index 000000000..b1e10e7fc --- /dev/null +++ b/src/backends/windows.rs @@ -0,0 +1,61 @@ +//! Implementation for Windows 10 and later +//! +//! On Windows 10 and later, ProcessPrng "is the primary interface to the +//! user-mode per-processor PRNGs" and only requires bcryptprimitives.dll, +//! making it a better option than the other Windows RNG APIs: +//! - BCryptGenRandom: https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom +//! - Requires bcrypt.dll (which loads bcryptprimitives.dll anyway) +//! - Can cause crashes/hangs as BCrypt accesses the Windows Registry: +//! https://github.com/rust-lang/rust/issues/99341 +//! - Causes issues inside sandboxed code: +//! https://issues.chromium.org/issues/40277768 +//! - CryptGenRandom: https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom +//! - Deprecated and not available on UWP targets +//! - Requires advapi32.lib/advapi32.dll (in addition to bcryptprimitives.dll) +//! - Thin wrapper around ProcessPrng +//! - RtlGenRandom: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom +//! - Deprecated and not available on UWP targets +//! - Requires advapi32.dll (in addition to bcryptprimitives.dll) +//! - Requires using name "SystemFunction036" +//! - Thin wrapper around ProcessPrng +//! +//! For more information see the Windows RNG Whitepaper: https://aka.ms/win10rng +use crate::Error; +use core::mem::MaybeUninit; + +pub use crate::util::{inner_u32, inner_u64}; + +// Binding to the Windows.Win32.Security.Cryptography.ProcessPrng API. As +// bcryptprimitives.dll lacks an import library, we use "raw-dylib". This +// was added in Rust 1.65 for x86_64/aarch64 and in Rust 1.71 for x86. +// We don't need MSRV 1.71, as we only use this backend on Rust 1.78 and later. +#[cfg_attr( + target_arch = "x86", + link( + name = "bcryptprimitives", + kind = "raw-dylib", + import_name_type = "undecorated" + ) +)] +#[cfg_attr( + not(target_arch = "x86"), + link(name = "bcryptprimitives", kind = "raw-dylib") +)] +extern "system" { + fn ProcessPrng(pbdata: *mut u8, cbdata: usize) -> BOOL; +} +#[allow(clippy::upper_case_acronyms, clippy::incompatible_msrv)] +type BOOL = core::ffi::c_int; // MSRV 1.64, similarly OK for this backend. +const TRUE: BOOL = 1; + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let result = unsafe { ProcessPrng(dest.as_mut_ptr().cast::(), dest.len()) }; + // Since Windows 10, calls to the user-mode RNG are guaranteed to never + // fail during runtime (rare windows W); `ProcessPrng` will only ever + // return 1 (which is how windows represents TRUE). + // See the bottom of page 6 of the aforementioned Windows RNG + // whitepaper for more information. + debug_assert!(result == TRUE); + Ok(()) +} diff --git a/src/backends/windows_legacy.rs b/src/backends/windows_legacy.rs new file mode 100644 index 000000000..4b3f09278 --- /dev/null +++ b/src/backends/windows_legacy.rs @@ -0,0 +1,48 @@ +//! Legacy implementation for Windows XP and later +//! +//! For targets where we cannot use ProcessPrng (added in Windows 10), we use +//! RtlGenRandom. See windows.rs for a more detailed discussion of the Windows +//! RNG APIs (and why we don't use BCryptGenRandom). On versions prior to +//! Windows 10, this implementation is secure. On Windows 10 and later, this +//! implementation behaves identically to the windows.rs implementation, except +//! that it forces the loading of an additional DLL (advapi32.dll). +//! +//! This implementation will not work on UWP targets (which lack advapi32.dll), +//! but such targets require Windows 10, so can use the standard implementation. +use crate::Error; +use core::{ffi::c_void, mem::MaybeUninit}; + +pub use crate::util::{inner_u32, inner_u64}; + +#[cfg(not(windows))] +compile_error!("`windows_legacy` backend can be enabled only for Windows targets!"); + +// Binding to the Windows.Win32.Security.Authentication.Identity.RtlGenRandom +// API. Don't use windows-targets as it doesn't support Windows 7 targets. +#[link(name = "advapi32")] +extern "system" { + #[link_name = "SystemFunction036"] + fn RtlGenRandom(randombuffer: *mut c_void, randombufferlength: u32) -> BOOLEAN; +} +#[allow(clippy::upper_case_acronyms)] +type BOOLEAN = u8; +const TRUE: BOOLEAN = 1u8; + +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Prevent overflow of u32 + let chunk_size = usize::try_from(i32::MAX).expect("Windows does not support 16-bit targets"); + for chunk in dest.chunks_mut(chunk_size) { + let chunk_len = u32::try_from(chunk.len()).expect("chunk size is bounded by i32::MAX"); + let ret = unsafe { RtlGenRandom(chunk.as_mut_ptr().cast::(), chunk_len) }; + if ret != TRUE { + return Err(Error::WINDOWS_RTL_GEN_RANDOM); + } + } + Ok(()) +} + +impl Error { + /// Call to Windows [`RtlGenRandom`](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom) failed. + pub(crate) const WINDOWS_RTL_GEN_RANDOM: Error = Self::new_internal(10); +} diff --git a/src/custom.rs b/src/custom.rs deleted file mode 100644 index 79be7fc26..000000000 --- a/src/custom.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! An implementation which calls out to an externally defined function. -use crate::{util::uninit_slice_fill_zero, Error}; -use core::{mem::MaybeUninit, num::NonZeroU32}; - -/// Register a function to be invoked by `getrandom` on unsupported targets. -/// -/// ## Writing a custom `getrandom` implementation -/// -/// The function to register must have the same signature as -/// [`getrandom::getrandom`](crate::getrandom). The function can be defined -/// wherever you want, either in root crate or a dependent crate. -/// -/// For example, if we wanted a `failure-getrandom` crate containing an -/// implementation that always fails, we would first depend on `getrandom` -/// (for the [`Error`] type) in `failure-getrandom/Cargo.toml`: -/// ```toml -/// [dependencies] -/// getrandom = "0.2" -/// ``` -/// Note that the crate containing this function does **not** need to enable the -/// `"custom"` Cargo feature. -/// -/// Next, in `failure-getrandom/src/lib.rs`, we define our function: -/// ```rust -/// use core::num::NonZeroU32; -/// use getrandom::Error; -/// -/// // Some application-specific error code -/// const MY_CUSTOM_ERROR_CODE: u32 = Error::CUSTOM_START + 42; -/// pub fn always_fail(buf: &mut [u8]) -> Result<(), Error> { -/// let code = NonZeroU32::new(MY_CUSTOM_ERROR_CODE).unwrap(); -/// Err(Error::from(code)) -/// } -/// ``` -/// -/// ## Registering a custom `getrandom` implementation -/// -/// Functions can only be registered in the root binary crate. Attempting to -/// register a function in a non-root crate will result in a linker error. -/// This is similar to -/// [`#[panic_handler]`](https://doc.rust-lang.org/nomicon/panic-handler.html) or -/// [`#[global_allocator]`](https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/global-allocators.html), -/// where helper crates define handlers/allocators but only the binary crate -/// actually _uses_ the functionality. -/// -/// To register the function, we first depend on `failure-getrandom` _and_ -/// `getrandom` in `Cargo.toml`: -/// ```toml -/// [dependencies] -/// failure-getrandom = "0.1" -/// getrandom = { version = "0.2", features = ["custom"] } -/// ``` -/// -/// Then, we register the function in `src/main.rs`: -/// ```rust -/// # mod failure_getrandom { pub fn always_fail(_: &mut [u8]) -> Result<(), getrandom::Error> { unimplemented!() } } -/// use failure_getrandom::always_fail; -/// use getrandom::register_custom_getrandom; -/// -/// register_custom_getrandom!(always_fail); -/// ``` -/// -/// Now any user of `getrandom` (direct or indirect) on this target will use the -/// registered function. As noted in the -/// [top-level documentation](index.html#custom-implementations) this -/// registration only has an effect on unsupported targets. -#[macro_export] -macro_rules! register_custom_getrandom { - ($path:path) => { - // TODO(MSRV 1.37): change to unnamed block - const __GETRANDOM_INTERNAL: () = { - // We use Rust ABI to be safe against potential panics in the passed function. - #[no_mangle] - unsafe fn __getrandom_custom(dest: *mut u8, len: usize) -> u32 { - // Make sure the passed function has the type of getrandom::getrandom - type F = fn(&mut [u8]) -> ::core::result::Result<(), $crate::Error>; - let _: F = $crate::getrandom; - let f: F = $path; - let slice = ::core::slice::from_raw_parts_mut(dest, len); - match f(slice) { - Ok(()) => 0, - Err(e) => e.code().get(), - } - } - }; - }; -} - -#[allow(dead_code)] -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - extern "Rust" { - fn __getrandom_custom(dest: *mut u8, len: usize) -> u32; - } - // Previously we always passed a valid, initialized slice to - // `__getrandom_custom`. Ensure `dest` has been initialized for backward - // compatibility with implementations that rely on that (e.g. Rust - // implementations that construct a `&mut [u8]` slice from `dest` and - // `len`). - let dest = uninit_slice_fill_zero(dest); - let ret = unsafe { __getrandom_custom(dest.as_mut_ptr(), dest.len()) }; - match NonZeroU32::new(ret) { - None => Ok(()), - Some(code) => Err(Error::from(code)), - } -} diff --git a/src/error.rs b/src/error.rs index 13c81c7af..284533d10 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,22 @@ -use core::{fmt, num::NonZeroU32}; +#[cfg(feature = "std")] +extern crate std; + +use core::fmt; + +// This private alias mirrors `std::io::RawOsError`: +// https://doc.rust-lang.org/std/io/type.RawOsError.html) +cfg_if::cfg_if!( + if #[cfg(target_os = "uefi")] { + // See the UEFI spec for more information: + // https://uefi.org/specs/UEFI/2.10/Apx_D_Status_Codes.html + type RawOsError = usize; + type NonZeroRawOsError = core::num::NonZeroUsize; + const UEFI_ERROR_FLAG: RawOsError = 1 << (RawOsError::BITS - 1); + } else { + type RawOsError = i32; + type NonZeroRawOsError = core::num::NonZeroI32; + } +); /// A small and `no_std` compatible error type /// @@ -6,113 +24,155 @@ use core::{fmt, num::NonZeroU32}; /// if so, which error code the OS gave the application. If such an error is /// encountered, please consult with your system documentation. /// -/// Internally this type is a NonZeroU32, with certain values reserved for -/// certain purposes, see [`Error::INTERNAL_START`] and [`Error::CUSTOM_START`]. -/// /// *If this crate's `"std"` Cargo feature is enabled*, then: /// - [`getrandom::Error`][Error] implements /// [`std::error::Error`](https://doc.rust-lang.org/std/error/trait.Error.html) /// - [`std::io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html) implements /// [`From`](https://doc.rust-lang.org/std/convert/trait.From.html). -#[derive(Copy, Clone, Eq, PartialEq)] -pub struct Error(NonZeroU32); -const fn internal_error(n: u16) -> Error { - // SAFETY: code > 0 as INTERNAL_START > 0 and adding n won't overflow a u32. - let code = Error::INTERNAL_START + (n as u32); - Error(unsafe { NonZeroU32::new_unchecked(code) }) -} +// note: on non-UEFI targets OS errors are represented as negative integers, +// while on UEFI targets OS errors have the highest bit set to 1. +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct Error(NonZeroRawOsError); impl Error { /// This target/platform is not supported by `getrandom`. - pub const UNSUPPORTED: Error = internal_error(0); + pub const UNSUPPORTED: Error = Self::new_internal(0); /// The platform-specific `errno` returned a non-positive value. - pub const ERRNO_NOT_POSITIVE: Error = internal_error(1); + pub const ERRNO_NOT_POSITIVE: Error = Self::new_internal(1); /// Encountered an unexpected situation which should not happen in practice. - pub const UNEXPECTED: Error = internal_error(2); - /// Call to [`CCRandomGenerateBytes`](https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html) failed - /// on iOS, tvOS, or waatchOS. - // TODO: Update this constant name in the next breaking release. - pub const IOS_SEC_RANDOM: Error = internal_error(3); - /// Call to Windows [`RtlGenRandom`](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom) failed. - pub const WINDOWS_RTL_GEN_RANDOM: Error = internal_error(4); - /// RDRAND instruction failed due to a hardware issue. - pub const FAILED_RDRAND: Error = internal_error(5); - /// RDRAND instruction unsupported on this target. - pub const NO_RDRAND: Error = internal_error(6); - /// The environment does not support the Web Crypto API. - pub const WEB_CRYPTO: Error = internal_error(7); - /// Calling Web Crypto API `crypto.getRandomValues` failed. - pub const WEB_GET_RANDOM_VALUES: Error = internal_error(8); - /// On VxWorks, call to `randSecure` failed (random number generator is not yet initialized). - pub const VXWORKS_RAND_SECURE: Error = internal_error(11); - /// Node.js does not have the `crypto` CommonJS module. - pub const NODE_CRYPTO: Error = internal_error(12); - /// Calling Node.js function `crypto.randomFillSync` failed. - pub const NODE_RANDOM_FILL_SYNC: Error = internal_error(13); - /// Called from an ES module on Node.js. This is unsupported, see: - /// . - pub const NODE_ES_MODULE: Error = internal_error(14); - - /// Codes below this point represent OS Errors (i.e. positive i32 values). - /// Codes at or above this point, but below [`Error::CUSTOM_START`] are - /// reserved for use by the `rand` and `getrandom` crates. - pub const INTERNAL_START: u32 = 1 << 31; - - /// Codes at or above this point can be used by users to define their own - /// custom errors. - pub const CUSTOM_START: u32 = (1 << 31) + (1 << 30); + pub const UNEXPECTED: Error = Self::new_internal(2); + + /// Internal errors can be in the range of 2^16..2^17 + const INTERNAL_START: RawOsError = 1 << 16; + /// Custom errors can be in the range of 2^17..(2^17 + 2^16) + const CUSTOM_START: RawOsError = 1 << 17; + + /// Creates a new instance of an `Error` from a negative error code. + #[cfg(not(target_os = "uefi"))] + #[allow(dead_code)] + pub(super) fn from_neg_error_code(code: RawOsError) -> Self { + if code < 0 { + let code = NonZeroRawOsError::new(code).expect("`code` is negative"); + Self(code) + } else { + Error::UNEXPECTED + } + } + + /// Creates a new instance of an `Error` from an UEFI error code. + #[cfg(target_os = "uefi")] + #[allow(dead_code)] + pub(super) fn from_uefi_code(code: RawOsError) -> Self { + if code & UEFI_ERROR_FLAG != 0 { + let code = NonZeroRawOsError::new(code).expect("The highest bit of `code` is set to 1"); + Self(code) + } else { + Self::UNEXPECTED + } + } /// Extract the raw OS error code (if this error came from the OS) /// /// This method is identical to [`std::io::Error::raw_os_error()`][1], except - /// that it works in `no_std` contexts. If this method returns `None`, the - /// error value can still be formatted via the `Display` implementation. + /// that it works in `no_std` contexts. On most targets this method returns + /// `Option`, but some platforms (e.g. UEFI) may use a different primitive + /// type like `usize`. Consult with the [`RawOsError`] docs for more information. + /// + /// If this method returns `None`, the error value can still be formatted via + /// the `Display` implementation. /// /// [1]: https://doc.rust-lang.org/std/io/struct.Error.html#method.raw_os_error + /// [`RawOsError`]: https://doc.rust-lang.org/std/io/type.RawOsError.html #[inline] - pub fn raw_os_error(self) -> Option { - if self.0.get() < Self::INTERNAL_START { - match () { - #[cfg(target_os = "solid_asp3")] - // On SOLID, negate the error code again to obtain the original - // error code. - () => Some(-(self.0.get() as i32)), - #[cfg(not(target_os = "solid_asp3"))] - () => Some(self.0.get() as i32), + pub fn raw_os_error(self) -> Option { + let code = self.0.get(); + + // note: in this method we need to cover only backends which rely on + // `Error::{from_error_code, from_errno, from_uefi_code}` methods, + // on all other backends this method always returns `None`. + + #[cfg(target_os = "uefi")] + { + if code & UEFI_ERROR_FLAG != 0 { + Some(code) + } else { + None + } + } + + #[cfg(not(target_os = "uefi"))] + { + // On most targets `std` expects positive error codes while retrieving error strings: + // - `libc`-based targets use `strerror_r` which expects positive error codes. + // - Hermit relies on the `hermit-abi` crate, which expects positive error codes: + // https://docs.rs/hermit-abi/0.4.0/src/hermit_abi/errno.rs.html#400-532 + // - WASIp1 uses the same conventions as `libc`: + // https://github.com/rust-lang/rust/blob/1.85.0/library/std/src/sys/pal/wasi/os.rs#L57-L67 + // + // The only exception is Solid, `std` expects negative system error codes, see: + // https://github.com/rust-lang/rust/blob/1.85.0/library/std/src/sys/pal/solid/error.rs#L5-L31 + if code >= 0 { + None + } else if cfg!(not(target_os = "solid_asp3")) { + code.checked_neg() + } else { + Some(code) } - } else { - None } } - /// Extract the bare error code. - /// - /// This code can either come from the underlying OS, or be a custom error. - /// Use [`Error::raw_os_error()`] to disambiguate. - #[inline] - pub const fn code(self) -> NonZeroU32 { - self.0 + /// Creates a new instance of an `Error` from a particular custom error code. + pub const fn new_custom(n: u16) -> Error { + // SAFETY: code > 0 as CUSTOM_START > 0 and adding `n` won't overflow `RawOsError`. + let code = Error::CUSTOM_START + (n as RawOsError); + Error(unsafe { NonZeroRawOsError::new_unchecked(code) }) } -} -cfg_if! { - if #[cfg(unix)] { - fn os_err(errno: i32, buf: &mut [u8]) -> Option<&str> { - let buf_ptr = buf.as_mut_ptr() as *mut libc::c_char; - if unsafe { libc::strerror_r(errno, buf_ptr, buf.len()) } != 0 { - return None; - } + /// Creates a new instance of an `Error` from a particular internal error code. + pub(crate) const fn new_internal(n: u16) -> Error { + // SAFETY: code > 0 as INTERNAL_START > 0 and adding `n` won't overflow `RawOsError`. + let code = Error::INTERNAL_START + (n as RawOsError); + Error(unsafe { NonZeroRawOsError::new_unchecked(code) }) + } - // Take up to trailing null byte - let n = buf.len(); - let idx = buf.iter().position(|&b| b == 0).unwrap_or(n); - core::str::from_utf8(&buf[..idx]).ok() - } - } else { - fn os_err(_errno: i32, _buf: &mut [u8]) -> Option<&str> { - None - } + fn internal_desc(&self) -> Option<&'static str> { + let desc = match *self { + Error::UNSUPPORTED => "getrandom: this target is not supported", + Error::ERRNO_NOT_POSITIVE => "errno: did not return a positive value", + Error::UNEXPECTED => "unexpected situation", + #[cfg(any( + target_os = "ios", + target_os = "visionos", + target_os = "watchos", + target_os = "tvos", + ))] + Error::IOS_RANDOM_GEN => "SecRandomCopyBytes: iOS Security framework failure", + #[cfg(all(windows, target_vendor = "win7"))] + Error::WINDOWS_RTL_GEN_RANDOM => "RtlGenRandom: Windows system function failure", + #[cfg(all(feature = "wasm_js", getrandom_backend = "wasm_js"))] + Error::WEB_CRYPTO => "Web Crypto API is unavailable", + #[cfg(target_os = "vxworks")] + Error::VXWORKS_RAND_SECURE => "randSecure: VxWorks RNG module is not initialized", + + #[cfg(any( + getrandom_backend = "rdrand", + all(target_arch = "x86_64", target_env = "sgx") + ))] + Error::FAILED_RDRAND => "RDRAND: failed multiple times: CPU issue likely", + #[cfg(any( + getrandom_backend = "rdrand", + all(target_arch = "x86_64", target_env = "sgx") + ))] + Error::NO_RDRAND => "RDRAND: instruction not supported", + + #[cfg(getrandom_backend = "rndr")] + Error::RNDR_FAILURE => "RNDR: Could not generate a random number", + #[cfg(getrandom_backend = "rndr")] + Error::RNDR_NOT_AVAILABLE => "RNDR: Register not supported", + _ => return None, + }; + Some(desc) } } @@ -121,11 +181,9 @@ impl fmt::Debug for Error { let mut dbg = f.debug_struct("Error"); if let Some(errno) = self.raw_os_error() { dbg.field("os_error", &errno); - let mut buf = [0u8; 128]; - if let Some(err) = os_err(errno, &mut buf) { - dbg.field("description", &err); - } - } else if let Some(desc) = internal_desc(*self) { + #[cfg(feature = "std")] + dbg.field("description", &std::io::Error::from_raw_os_error(errno)); + } else if let Some(desc) = self.internal_desc() { dbg.field("internal_code", &self.0.get()); dbg.field("description", &desc); } else { @@ -138,52 +196,17 @@ impl fmt::Debug for Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(errno) = self.raw_os_error() { - let mut buf = [0u8; 128]; - match os_err(errno, &mut buf) { - Some(err) => err.fmt(f), - None => write!(f, "OS Error: {}", errno), + cfg_if! { + if #[cfg(feature = "std")] { + std::io::Error::from_raw_os_error(errno).fmt(f) + } else { + write!(f, "OS Error: {errno}") + } } - } else if let Some(desc) = internal_desc(*self) { + } else if let Some(desc) = self.internal_desc() { f.write_str(desc) } else { write!(f, "Unknown Error: {}", self.0.get()) } } } - -impl From for Error { - fn from(code: NonZeroU32) -> Self { - Self(code) - } -} - -fn internal_desc(error: Error) -> Option<&'static str> { - match error { - Error::UNSUPPORTED => Some("getrandom: this target is not supported"), - Error::ERRNO_NOT_POSITIVE => Some("errno: did not return a positive value"), - Error::UNEXPECTED => Some("unexpected situation"), - Error::IOS_SEC_RANDOM => Some("SecRandomCopyBytes: iOS Security framework failure"), - Error::WINDOWS_RTL_GEN_RANDOM => Some("RtlGenRandom: Windows system function failure"), - Error::FAILED_RDRAND => Some("RDRAND: failed multiple times: CPU issue likely"), - Error::NO_RDRAND => Some("RDRAND: instruction not supported"), - Error::WEB_CRYPTO => Some("Web Crypto API is unavailable"), - Error::WEB_GET_RANDOM_VALUES => Some("Calling Web API crypto.getRandomValues failed"), - Error::VXWORKS_RAND_SECURE => Some("randSecure: VxWorks RNG module is not initialized"), - Error::NODE_CRYPTO => Some("Node.js crypto CommonJS module is unavailable"), - Error::NODE_RANDOM_FILL_SYNC => Some("Calling Node.js API crypto.randomFillSync failed"), - Error::NODE_ES_MODULE => Some("Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support"), - _ => None, - } -} - -#[cfg(test)] -mod tests { - use super::Error; - use core::mem::size_of; - - #[test] - fn test_size() { - assert_eq!(size_of::(), 4); - assert_eq!(size_of::>(), 4); - } -} diff --git a/src/error_impls.rs b/src/error_std_impls.rs similarity index 100% rename from src/error_impls.rs rename to src/error_std_impls.rs diff --git a/src/fuchsia.rs b/src/fuchsia.rs deleted file mode 100644 index 11970685c..000000000 --- a/src/fuchsia.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Implementation for Fuchsia Zircon -use crate::Error; -use core::mem::MaybeUninit; - -#[link(name = "zircon")] -extern "C" { - fn zx_cprng_draw(buffer: *mut u8, length: usize); -} - -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - unsafe { zx_cprng_draw(dest.as_mut_ptr() as *mut u8, dest.len()) } - Ok(()) -} diff --git a/src/getentropy.rs b/src/getentropy.rs deleted file mode 100644 index 41bab8fe9..000000000 --- a/src/getentropy.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! Implementation using getentropy(2) -//! -//! Available since: -//! - macOS 10.12 -//! - OpenBSD 5.6 -//! - Emscripten 2.0.5 -//! - vita newlib since Dec 2021 -//! -//! For these targets, we use getentropy(2) because getrandom(2) doesn't exist. -use crate::{util_libc::last_os_error, Error}; -use core::mem::MaybeUninit; - -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - for chunk in dest.chunks_mut(256) { - let ret = unsafe { libc::getentropy(chunk.as_mut_ptr() as *mut libc::c_void, chunk.len()) }; - if ret != 0 { - return Err(last_os_error()); - } - } - Ok(()) -} diff --git a/src/hermit.rs b/src/hermit.rs deleted file mode 100644 index c4f619417..000000000 --- a/src/hermit.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Implementation for Hermit -use crate::Error; -use core::{mem::MaybeUninit, num::NonZeroU32}; - -/// Minimum return value which we should get from syscalls in practice, -/// because Hermit uses positive `i32`s for error codes: -/// https://github.com/hermitcore/libhermit-rs/blob/main/src/errno.rs -const MIN_RET_CODE: isize = -(i32::MAX as isize); - -extern "C" { - fn sys_read_entropy(buffer: *mut u8, length: usize, flags: u32) -> isize; -} - -pub fn getrandom_inner(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { - while !dest.is_empty() { - let res = unsafe { sys_read_entropy(dest.as_mut_ptr() as *mut u8, dest.len(), 0) }; - // Positive `isize`s can be safely casted to `usize` - if res > 0 && (res as usize) <= dest.len() { - dest = &mut dest[res as usize..]; - } else { - let err = match res { - MIN_RET_CODE..=-1 => NonZeroU32::new(-res as u32).unwrap().into(), - _ => Error::UNEXPECTED, - }; - return Err(err); - } - } - Ok(()) -} diff --git a/src/js.rs b/src/js.rs deleted file mode 100644 index e5428f50d..000000000 --- a/src/js.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Implementation for WASM based on Web and Node.js -use crate::Error; - -extern crate std; -use std::{mem::MaybeUninit, thread_local}; - -use js_sys::{global, Function, Uint8Array}; -use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; - -// Size of our temporary Uint8Array buffer used with WebCrypto methods -// Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues -const WEB_CRYPTO_BUFFER_SIZE: usize = 256; -// Node.js's crypto.randomFillSync requires the size to be less than 2**31. -const NODE_MAX_BUFFER_SIZE: usize = (1 << 31) - 1; - -enum RngSource { - Node(NodeCrypto), - Web(WebCrypto, Uint8Array), -} - -// JsValues are always per-thread, so we initialize RngSource for each thread. -// See: https://github.com/rustwasm/wasm-bindgen/pull/955 -thread_local!( - static RNG_SOURCE: Result = getrandom_init(); -); - -pub(crate) fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - RNG_SOURCE.with(|result| { - let source = result.as_ref().map_err(|&e| e)?; - - match source { - RngSource::Node(n) => { - for chunk in dest.chunks_mut(NODE_MAX_BUFFER_SIZE) { - // SAFETY: chunk is never used directly, the memory is only - // modified via the Uint8Array view, which is passed - // directly to JavaScript. Also, crypto.randomFillSync does - // not resize the buffer. We know the length is less than - // u32::MAX because of the chunking above. - // Note that this uses the fact that JavaScript doesn't - // have a notion of "uninitialized memory", this is purely - // a Rust/C/C++ concept. - let res = n.random_fill_sync(unsafe { - Uint8Array::view_mut_raw(chunk.as_mut_ptr() as *mut u8, chunk.len()) - }); - if res.is_err() { - return Err(Error::NODE_RANDOM_FILL_SYNC); - } - } - } - RngSource::Web(crypto, buf) => { - // getRandomValues does not work with all types of WASM memory, - // so we initially write to browser memory to avoid exceptions. - for chunk in dest.chunks_mut(WEB_CRYPTO_BUFFER_SIZE) { - // The chunk can be smaller than buf's length, so we call to - // JS to create a smaller view of buf without allocation. - let sub_buf = buf.subarray(0, chunk.len() as u32); - - if crypto.get_random_values(&sub_buf).is_err() { - return Err(Error::WEB_GET_RANDOM_VALUES); - } - - // SAFETY: `sub_buf`'s length is the same length as `chunk` - unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr() as *mut u8) }; - } - } - }; - Ok(()) - }) -} - -fn getrandom_init() -> Result { - let global: Global = global().unchecked_into(); - - // Get the Web Crypto interface if we are in a browser, Web Worker, Deno, - // or another environment that supports the Web Cryptography API. This - // also allows for user-provided polyfills in unsupported environments. - let crypto = match global.crypto() { - // Standard Web Crypto interface - c if c.is_object() => c, - // Node.js CommonJS Crypto module - _ if is_node(&global) => { - // If module.require isn't a valid function, we are in an ES module. - match Module::require_fn().and_then(JsCast::dyn_into::) { - Ok(require_fn) => match require_fn.call1(&global, &JsValue::from_str("crypto")) { - Ok(n) => return Ok(RngSource::Node(n.unchecked_into())), - Err(_) => return Err(Error::NODE_CRYPTO), - }, - Err(_) => return Err(Error::NODE_ES_MODULE), - } - } - // IE 11 Workaround - _ => match global.ms_crypto() { - c if c.is_object() => c, - _ => return Err(Error::WEB_CRYPTO), - }, - }; - - let buf = Uint8Array::new_with_length(WEB_CRYPTO_BUFFER_SIZE as u32); - Ok(RngSource::Web(crypto, buf)) -} - -// Taken from https://www.npmjs.com/package/browser-or-node -fn is_node(global: &Global) -> bool { - let process = global.process(); - if process.is_object() { - let versions = process.versions(); - if versions.is_object() { - return versions.node().is_string(); - } - } - false -} - -#[wasm_bindgen] -extern "C" { - // Return type of js_sys::global() - type Global; - - // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/) - type WebCrypto; - // Getters for the WebCrypto API - #[wasm_bindgen(method, getter)] - fn crypto(this: &Global) -> WebCrypto; - #[wasm_bindgen(method, getter, js_name = msCrypto)] - fn ms_crypto(this: &Global) -> WebCrypto; - // Crypto.getRandomValues() - #[wasm_bindgen(method, js_name = getRandomValues, catch)] - fn get_random_values(this: &WebCrypto, buf: &Uint8Array) -> Result<(), JsValue>; - - // Node JS crypto module (https://nodejs.org/api/crypto.html) - type NodeCrypto; - // crypto.randomFillSync() - #[wasm_bindgen(method, js_name = randomFillSync, catch)] - fn random_fill_sync(this: &NodeCrypto, buf: Uint8Array) -> Result<(), JsValue>; - - // Ideally, we would just use `fn require(s: &str)` here. However, doing - // this causes a Webpack warning. So we instead return the function itself - // and manually invoke it using call1. This also lets us to check that the - // function actually exists, allowing for better error messages. See: - // https://github.com/rust-random/getrandom/issues/224 - // https://github.com/rust-random/getrandom/issues/256 - type Module; - #[wasm_bindgen(getter, static_method_of = Module, js_class = module, js_name = require, catch)] - fn require_fn() -> Result; - - // Node JS process Object (https://nodejs.org/api/process.html) - #[wasm_bindgen(method, getter)] - fn process(this: &Global) -> Process; - type Process; - #[wasm_bindgen(method, getter)] - fn versions(this: &Process) -> Versions; - type Versions; - #[wasm_bindgen(method, getter)] - fn node(this: &Versions) -> JsValue; -} diff --git a/src/lazy.rs b/src/lazy.rs index 100ce1eaf..b191aa6d7 100644 --- a/src/lazy.rs +++ b/src/lazy.rs @@ -1,4 +1,5 @@ -use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; +//! Helpers built around pointer-sized atomics. +use core::sync::atomic::{AtomicUsize, Ordering}; // This structure represents a lazily initialized static usize value. Useful // when it is preferable to just rerun initialization instead of locking. @@ -18,27 +19,34 @@ use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; // } // the effects of c() or writes to shared memory will not necessarily be // observed and additional synchronization methods may be needed. -pub(crate) struct LazyUsize(AtomicUsize); +struct LazyUsize(AtomicUsize); impl LazyUsize { - pub const fn new() -> Self { + // The initialization is not completed. + const UNINIT: usize = usize::MAX; + + const fn new() -> Self { Self(AtomicUsize::new(Self::UNINIT)) } - // The initialization is not completed. - pub const UNINIT: usize = usize::max_value(); - // Runs the init() function at most once, returning the value of some run of // init(). Multiple callers can run their init() functions in parallel. // init() should always return the same value, if it succeeds. - pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize { + fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize { + #[cold] + fn do_init(this: &LazyUsize, init: impl FnOnce() -> usize) -> usize { + let val = init(); + this.0.store(val, Ordering::Relaxed); + val + } + // Relaxed ordering is fine, as we only have a single atomic variable. - let mut val = self.0.load(Relaxed); - if val == Self::UNINIT { - val = init(); - self.0.store(val, Relaxed); + let val = self.0.load(Ordering::Relaxed); + if val != Self::UNINIT { + val + } else { + do_init(self, init) } - val } } @@ -51,6 +59,6 @@ impl LazyBool { } pub fn unsync_init(&self, init: impl FnOnce() -> bool) -> bool { - self.0.unsync_init(|| init() as usize) != 0 + self.0.unsync_init(|| usize::from(init())) != 0 } } diff --git a/src/lib.rs b/src/lib.rs index bc3695b6a..adb7e5b7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,356 +1,47 @@ -//! Interface to the operating system's random number generator. -//! -//! # Supported targets -//! -//! | Target | Target Triple | Implementation -//! | ----------------- | ------------------ | -------------- -//! | Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call if available, otherwise [`/dev/urandom`][2] after successfully polling `/dev/random` -//! | Windows | `*‑windows‑*` | [`BCryptGenRandom`] -//! | macOS | `*‑apple‑darwin` | [`getentropy`][3] -//! | iOS, tvOS, watchOS | `*‑apple‑ios`, `*-apple-tvos`, `*-apple-watchos` | [`CCRandomGenerateBytes`] -//! | FreeBSD | `*‑freebsd` | [`getrandom`][5] -//! | OpenBSD | `*‑openbsd` | [`getentropy`][7] -//! | NetBSD | `*‑netbsd` | [`getrandom`][16] if available, otherwise [`kern.arandom`][8] -//! | Dragonfly BSD | `*‑dragonfly` | [`getrandom`][9] -//! | Solaris | `*‑solaris` | [`getrandom`][11] (with `GRND_RANDOM`) -//! | illumos | `*‑illumos` | [`getrandom`][12] -//! | Fuchsia OS | `*‑fuchsia` | [`cprng_draw`] -//! | Redox | `*‑redox` | `/dev/urandom` -//! | Haiku | `*‑haiku` | `/dev/urandom` (identical to `/dev/random`) -//! | Hermit | `*-hermit` | [`sys_read_entropy`] -//! | Hurd | `*-hurd-*` | [`getrandom`][17] -//! | SGX | `x86_64‑*‑sgx` | [`RDRAND`] -//! | VxWorks | `*‑wrs‑vxworks‑*` | `randABytes` after checking entropy pool initialization with `randSecure` -//! | ESP-IDF | `*‑espidf` | [`esp_fill_random`] -//! | Emscripten | `*‑emscripten` | [`getentropy`][13] -//! | WASI | `wasm32‑wasi` | [`random_get`] -//! | Web Browser and Node.js | `wasm*‑*‑unknown` | [`Crypto.getRandomValues`] if available, then [`crypto.randomFillSync`] if on Node.js, see [WebAssembly support] -//! | SOLID | `*-kmc-solid_*` | `SOLID_RNG_SampleRandomBytes` -//! | Nintendo 3DS | `*-nintendo-3ds` | [`getrandom`][18] -//! | PS Vita | `*-vita-*` | [`getentropy`][13] -//! | QNX Neutrino | `*‑nto-qnx*` | [`/dev/urandom`][14] (identical to `/dev/random`) -//! | AIX | `*-ibm-aix` | [`/dev/urandom`][15] -//! -//! Pull Requests that add support for new targets to `getrandom` are always welcome. -//! -//! ## Unsupported targets -//! -//! By default, `getrandom` will not compile on unsupported targets, but certain -//! features allow a user to select a "fallback" implementation if no supported -//! implementation exists. -//! -//! All of the below mechanisms only affect unsupported -//! targets. Supported targets will _always_ use their supported implementations. -//! This prevents a crate from overriding a secure source of randomness -//! (either accidentally or intentionally). -//! -//! ## `/dev/urandom` fallback on Linux and Android -//! -//! On Linux targets the fallback is present only if either `target_env` is `musl`, -//! or `target_arch` is one of the following: `aarch64`, `arm`, `powerpc`, `powerpc64`, -//! `s390x`, `x86`, `x86_64`. Other supported targets [require][platform-support] -//! kernel versions which support `getrandom` system call, so fallback is not needed. -//! -//! On Android targets the fallback is present only for the following `target_arch`es: -//! `aarch64`, `arm`, `x86`, `x86_64`. Other `target_arch`es (e.g. RISC-V) require -//! sufficiently high API levels. -//! -//! The fallback can be disabled by enabling the `linux_disable_fallback` crate feature. -//! Note that doing so will bump minimum supported Linux kernel version to 3.17 and -//! Android API level to 23 (Marshmallow). -//! -//! ### RDRAND on x86 -//! -//! *If the `rdrand` Cargo feature is enabled*, `getrandom` will fallback to using -//! the [`RDRAND`] instruction to get randomness on `no_std` `x86`/`x86_64` -//! targets. This feature has no effect on other CPU architectures. -//! -//! ### WebAssembly support -//! -//! This crate fully supports the -//! [`wasm32-wasi`](https://github.com/CraneStation/wasi) and -//! [`wasm32-unknown-emscripten`](https://www.hellorust.com/setup/emscripten/) -//! targets. However, the `wasm32-unknown-unknown` target (i.e. the target used -//! by `wasm-pack`) is not automatically -//! supported since, from the target name alone, we cannot deduce which -//! JavaScript interface is in use (or if JavaScript is available at all). -//! -//! Instead, *if the `js` Cargo feature is enabled*, this crate will assume -//! that you are building for an environment containing JavaScript, and will -//! call the appropriate methods. Both web browser (main window and Web Workers) -//! and Node.js environments are supported, invoking the methods -//! [described above](#supported-targets) using the [`wasm-bindgen`] toolchain. -//! -//! To enable the `js` Cargo feature, add the following to the `dependencies` -//! section in your `Cargo.toml` file: -//! ```toml -//! [dependencies] -//! getrandom = { version = "0.2", features = ["js"] } -//! ``` -//! -//! This can be done even if `getrandom` is not a direct dependency. Cargo -//! allows crates to enable features for indirect dependencies. -//! -//! This feature should only be enabled for binary, test, or benchmark crates. -//! Library crates should generally not enable this feature, leaving such a -//! decision to *users* of their library. Also, libraries should not introduce -//! their own `js` features *just* to enable `getrandom`'s `js` feature. -//! -//! This feature has no effect on targets other than `wasm32-unknown-unknown`. -//! -//! #### Node.js ES module support -//! -//! Node.js supports both [CommonJS modules] and [ES modules]. Due to -//! limitations in wasm-bindgen's [`module`] support, we cannot directly -//! support ES Modules running on Node.js. However, on Node v15 and later, the -//! module author can add a simple shim to support the Web Cryptography API: -//! ```js -//! import { webcrypto } from 'node:crypto' -//! globalThis.crypto = webcrypto -//! ``` -//! 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 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 -//! -//! The [`register_custom_getrandom!`] macro allows a user to mark their own -//! function as the backing implementation for [`getrandom`]. See the macro's -//! documentation for more information about writing and registering your own -//! custom implementations. -//! -//! Note that registering a custom implementation only has an effect on targets -//! that would otherwise not compile. Any supported targets (including those -//! using `rdrand` and `js` Cargo features) continue using their normal -//! implementations even if a function is registered. -//! -//! ## Early boot -//! -//! Sometimes, early in the boot process, the OS has not collected enough -//! entropy to securely seed its RNG. This is especially common on virtual -//! machines, where standard "random" events are hard to come by. -//! -//! Some operating system interfaces always block until the RNG is securely -//! seeded. This can take anywhere from a few seconds to more than a minute. -//! A few (Linux, NetBSD and Solaris) offer a choice between blocking and -//! getting an error; in these cases, we always choose to block. -//! -//! On Linux (when the `getrandom` system call is not available), reading from -//! `/dev/urandom` never blocks, even when the OS hasn't collected enough -//! entropy yet. To avoid returning low-entropy bytes, we first poll -//! `/dev/random` and only switch to `/dev/urandom` once this has succeeded. -//! -//! On OpenBSD, this kind of entropy accounting isn't available, and on -//! NetBSD, blocking on it is discouraged. On these platforms, nonblocking -//! interfaces are used, even when reliable entropy may not be available. -//! On the platforms where it is used, the reliability of entropy accounting -//! itself isn't free from controversy. This library provides randomness -//! sourced according to the platform's best practices, but each platform has -//! its own limits on the grade of randomness it can promise in environments -//! with few sources of entropy. -//! -//! ## Error handling -//! -//! We always choose failure over returning known insecure "random" bytes. In -//! general, on supported platforms, failure is highly unlikely, though not -//! impossible. If an error does occur, then it is likely that it will occur -//! on every call to `getrandom`, hence after the first successful call one -//! can be reasonably confident that no errors will occur. -//! -//! [1]: https://manned.org/getrandom.2 -//! [2]: https://manned.org/urandom.4 -//! [3]: https://www.unix.com/man-page/mojave/2/getentropy/ -//! [4]: https://www.unix.com/man-page/mojave/4/urandom/ -//! [5]: https://www.freebsd.org/cgi/man.cgi?query=getrandom&manpath=FreeBSD+12.0-stable -//! [7]: https://man.openbsd.org/getentropy.2 -//! [8]: https://man.netbsd.org/sysctl.7 -//! [9]: https://leaf.dragonflybsd.org/cgi/web-man?command=getrandom -//! [11]: https://docs.oracle.com/cd/E88353_01/html/E37841/getrandom-2.html -//! [12]: https://illumos.org/man/2/getrandom -//! [13]: https://github.com/emscripten-core/emscripten/pull/12240 -//! [14]: https://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.neutrino.utilities/topic/r/random.html -//! [15]: https://www.ibm.com/docs/en/aix/7.3?topic=files-random-urandom-devices -//! [16]: https://man.netbsd.org/getrandom.2 -//! [17]: https://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getrandom -//! [18]: https://github.com/rust3ds/shim-3ds/commit/b01d2568836dea2a65d05d662f8e5f805c64389d -//! -//! [`BCryptGenRandom`]: https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom -//! [`Crypto.getRandomValues`]: https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues -//! [`RDRAND`]: https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide -//! [`CCRandomGenerateBytes`]: https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html -//! [`cprng_draw`]: https://fuchsia.dev/fuchsia-src/zircon/syscalls/cprng_draw -//! [`crypto.randomFillSync`]: https://nodejs.org/api/crypto.html#cryptorandomfillsyncbuffer-offset-size -//! [`esp_fill_random`]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html#_CPPv415esp_fill_randomPv6size_t -//! [`random_get`]: https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#-random_getbuf-pointeru8-buf_len-size---errno -//! [WebAssembly support]: #webassembly-support -//! [`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen -//! [`module`]: https://rustwasm.github.io/wasm-bindgen/reference/attributes/on-js-imports/module.html -//! [CommonJS modules]: https://nodejs.org/api/modules.html -//! [ES modules]: https://nodejs.org/api/esm.html -//! [`sys_read_entropy`]: https://github.com/hermit-os/kernel/blob/315f58ff5efc81d9bf0618af85a59963ff55f8b1/src/syscalls/entropy.rs#L47-L55 -//! [platform-support]: https://doc.rust-lang.org/stable/rustc/platform-support.html +// Overwrite links to crate items with intra-crate links +//! [`Error::UNEXPECTED`]: Error::UNEXPECTED +//! [`fill_uninit`]: fill_uninit +#![no_std] #![doc( html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png", - html_favicon_url = "https://www.rust-lang.org/favicon.ico", - html_root_url = "https://docs.rs/getrandom/0.2.15" + html_favicon_url = "https://www.rust-lang.org/favicon.ico" )] -#![no_std] +#![doc = include_str!("../README.md")] #![warn(rust_2018_idioms, unused_lifetimes, missing_docs)] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(getrandom_backend = "efi_rng", feature(uefi_std))] +#![deny( + clippy::cast_lossless, + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::cast_ptr_alignment, + clippy::cast_sign_loss, + clippy::char_lit_as_u8, + clippy::checked_conversions, + clippy::fn_to_numeric_cast, + clippy::fn_to_numeric_cast_with_truncation, + clippy::ptr_as_ptr, + clippy::unnecessary_cast, + clippy::useless_conversion +)] #[macro_use] extern crate cfg_if; -use crate::util::{slice_as_uninit_mut, slice_assume_init_mut}; use core::mem::MaybeUninit; +mod backends; mod error; mod util; -// To prevent a breaking change when targets are added, we always export the -// register_custom_getrandom macro, so old Custom RNG crates continue to build. -#[cfg(feature = "custom")] -mod custom; + #[cfg(feature = "std")] -mod error_impls; +mod error_std_impls; pub use crate::error::Error; -// System-specific implementations. -// -// These should all provide getrandom_inner with the signature -// `fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error>`. -// The function MUST fully initialize `dest` when `Ok(())` is returned. -// The function MUST NOT ever write uninitialized bytes into `dest`, -// regardless of what value it returns. -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 = "macos", - target_os = "openbsd", - target_os = "vita", - target_os = "emscripten", - ))] { - mod util_libc; - #[path = "getentropy.rs"] mod imp; - } else if #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "hurd", - target_os = "illumos", - // Check for target_arch = "arm" to only include the 3DS. Does not - // include the Nintendo Switch (which is target_arch = "aarch64"). - all(target_os = "horizon", target_arch = "arm"), - ))] { - mod util_libc; - #[path = "getrandom.rs"] mod imp; - } 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 - // [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", - // Minimum supported Linux kernel version for MUSL targets - // is not specified explicitly (as of Rust 1.77) and they - // are used in practice to target pre-3.17 kernels. - target_env = "musl", - ), - ) - ), - ))] { - 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(target_os = "solaris")] { - mod util_libc; - #[path = "solaris.rs"] mod imp; - } else if #[cfg(target_os = "netbsd")] { - mod util_libc; - #[path = "netbsd.rs"] mod imp; - } else if #[cfg(target_os = "fuchsia")] { - #[path = "fuchsia.rs"] mod imp; - } else if #[cfg(any(target_os = "ios", target_os = "visionos", target_os = "watchos", target_os = "tvos"))] { - #[path = "apple-other.rs"] mod imp; - } else if #[cfg(all(target_arch = "wasm32", target_os = "wasi"))] { - #[path = "wasi.rs"] mod imp; - } else if #[cfg(target_os = "hermit")] { - #[path = "hermit.rs"] mod imp; - } else if #[cfg(target_os = "vxworks")] { - mod util_libc; - #[path = "vxworks.rs"] mod imp; - } else if #[cfg(target_os = "solid_asp3")] { - #[path = "solid.rs"] mod imp; - } else if #[cfg(target_os = "espidf")] { - #[path = "espidf.rs"] mod imp; - } else if #[cfg(windows)] { - #[path = "windows.rs"] mod imp; - } else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] { - mod lazy; - #[path = "rdrand.rs"] mod imp; - } else if #[cfg(all(feature = "rdrand", - any(target_arch = "x86_64", target_arch = "x86")))] { - mod lazy; - #[path = "rdrand.rs"] mod imp; - } else if #[cfg(all(feature = "js", - any(target_arch = "wasm32", target_arch = "wasm64"), - target_os = "unknown"))] { - #[path = "js.rs"] mod imp; - } else if #[cfg(feature = "custom")] { - use custom as imp; - } else if #[cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), - target_os = "unknown"))] { - compile_error!("the wasm*-unknown-unknown targets are not supported by \ - default, you may need to enable the \"js\" feature. \ - For more information see: \ - https://docs.rs/getrandom/#webassembly-support"); - } else { - compile_error!("target is not supported, for more information see: \ - https://docs.rs/getrandom/#unsupported-targets"); - } -} - -/// Fill `dest` with random bytes from the system's preferred random number -/// source. +/// Fill `dest` with random bytes from the system's preferred random number source. /// /// This function returns an error on any failure, including partial reads. We /// make no guarantees regarding the contents of `dest` on error. If `dest` is @@ -362,17 +53,27 @@ cfg_if! { /// In general, `getrandom` will be fast enough for interactive usage, though /// significantly slower than a user-space CSPRNG; for the latter consider /// [`rand::thread_rng`](https://docs.rs/rand/*/rand/fn.thread_rng.html). +/// +/// # Examples +/// +/// ``` +/// # fn main() -> Result<(), getrandom::Error> { +/// let mut buf = [0u8; 32]; +/// getrandom::fill(&mut buf)?; +/// # Ok(()) } +/// ``` #[inline] -pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { - // SAFETY: The `&mut MaybeUninit<_>` reference doesn't escape, and - // `getrandom_uninit` guarantees it will never de-initialize any part of - // `dest`. - getrandom_uninit(unsafe { slice_as_uninit_mut(dest) })?; +pub fn fill(dest: &mut [u8]) -> Result<(), Error> { + // SAFETY: The `&mut MaybeUninit<_>` reference doesn't escape, + // and `fill_uninit` guarantees it will never de-initialize + // any part of `dest`. + fill_uninit(unsafe { util::slice_as_uninit_mut(dest) })?; Ok(()) } -/// Version of the `getrandom` function which fills `dest` with random bytes -/// returns a mutable reference to those bytes. +/// Fill potentially uninitialized buffer `dest` with random bytes from +/// the system's preferred random number source and return a mutable +/// reference to those bytes. /// /// On successful completion this function is guaranteed to return a slice /// which points to the same memory as `dest` and has the same length. @@ -389,15 +90,49 @@ pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { /// #![feature(maybe_uninit_uninit_array)] /// # fn main() -> Result<(), getrandom::Error> { /// let mut buf = core::mem::MaybeUninit::uninit_array::<1024>(); -/// let buf: &mut [u8] = getrandom::getrandom_uninit(&mut buf)?; +/// let buf: &mut [u8] = getrandom::fill_uninit(&mut buf)?; /// # Ok(()) } /// ``` #[inline] -pub fn getrandom_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error> { +pub fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error> { if !dest.is_empty() { - imp::getrandom_inner(dest)?; + backends::fill_inner(dest)?; } - // SAFETY: `dest` has been fully initialized by `imp::getrandom_inner` + + #[cfg(getrandom_msan)] + extern "C" { + fn __msan_unpoison(a: *mut core::ffi::c_void, size: usize); + } + + // SAFETY: `dest` has been fully initialized by `imp::fill_inner` // since it returned `Ok`. - Ok(unsafe { slice_assume_init_mut(dest) }) + Ok(unsafe { util::slice_assume_init_mut(dest) }) +} + +/// Get random `u32` from the system's preferred random number source. +/// +/// # Examples +/// +/// ``` +/// # fn main() -> Result<(), getrandom::Error> { +/// let rng_seed = getrandom::u32()?; +/// # Ok(()) } +/// ``` +#[inline] +pub fn u32() -> Result { + backends::inner_u32() +} + +/// Get random `u64` from the system's preferred random number source. +/// +/// # Examples +/// +/// ``` +/// # fn main() -> Result<(), getrandom::Error> { +/// let rng_seed = getrandom::u64()?; +/// # Ok(()) } +/// ``` +#[inline] +pub fn u64() -> Result { + backends::inner_u64() } diff --git a/src/linux_android.rs b/src/linux_android.rs deleted file mode 100644 index 93a649452..000000000 --- a/src/linux_android.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Implementation for Linux / Android without `/dev/urandom` fallback -use crate::{util_libc, Error}; -use core::mem::MaybeUninit; - -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - util_libc::sys_fill_exact(dest, util_libc::getrandom_syscall) -} diff --git a/src/linux_android_with_fallback.rs b/src/linux_android_with_fallback.rs deleted file mode 100644 index 0f5ea8a99..000000000 --- a/src/linux_android_with_fallback.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! 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]) -> 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) - } 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 - } -} diff --git a/src/netbsd.rs b/src/netbsd.rs deleted file mode 100644 index b8a770f5d..000000000 --- a/src/netbsd.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! Implementation for NetBSD -use crate::{ - util_libc::{sys_fill_exact, Weak}, - Error, -}; -use core::{mem::MaybeUninit, ptr}; - -fn kern_arnd(buf: &mut [MaybeUninit]) -> libc::ssize_t { - static MIB: [libc::c_int; 2] = [libc::CTL_KERN, libc::KERN_ARND]; - let mut len = buf.len(); - let ret = unsafe { - libc::sysctl( - MIB.as_ptr(), - MIB.len() as libc::c_uint, - buf.as_mut_ptr() as *mut _, - &mut len, - ptr::null(), - 0, - ) - }; - if ret == -1 { - -1 - } else { - len as libc::ssize_t - } -} - -type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::ssize_t; - -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - // getrandom(2) was introduced in NetBSD 10.0 - static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") }; - if let Some(fptr) = GETRANDOM.ptr() { - let func: GetRandomFn = unsafe { core::mem::transmute(fptr) }; - return sys_fill_exact(dest, |buf| unsafe { - func(buf.as_mut_ptr() as *mut u8, buf.len(), 0) - }); - } - - // NetBSD will only return up to 256 bytes at a time, and - // older NetBSD kernels will fail on longer buffers. - for chunk in dest.chunks_mut(256) { - sys_fill_exact(chunk, kern_arnd)? - } - Ok(()) -} diff --git a/src/solid.rs b/src/solid.rs deleted file mode 100644 index cae8caf66..000000000 --- a/src/solid.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Implementation for SOLID -use crate::Error; -use core::{mem::MaybeUninit, num::NonZeroU32}; - -extern "C" { - pub fn SOLID_RNG_SampleRandomBytes(buffer: *mut u8, length: usize) -> i32; -} - -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - let ret = unsafe { SOLID_RNG_SampleRandomBytes(dest.as_mut_ptr() as *mut u8, dest.len()) }; - if ret >= 0 { - Ok(()) - } else { - // ITRON error numbers are always negative, so we negate it so that it - // falls in the dedicated OS error range (1..INTERNAL_START). - Err(NonZeroU32::new((-ret) as u32).unwrap().into()) - } -} diff --git a/src/use_file.rs b/src/use_file.rs deleted file mode 100644 index bd643ae5a..000000000 --- a/src/use_file.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! Implementations that just need to read from a file -use crate::{ - util_libc::{open_readonly, sys_fill_exact}, - Error, -}; -use core::{ - cell::UnsafeCell, - mem::MaybeUninit, - sync::atomic::{AtomicUsize, Ordering::Relaxed}, -}; - -/// For all platforms, we use `/dev/urandom` rather than `/dev/random`. -/// For more information see the linked man pages in lib.rs. -/// - On Linux, "/dev/urandom is preferred and sufficient in all use cases". -/// - On Redox, only /dev/urandom is provided. -/// - On AIX, /dev/urandom will "provide cryptographically secure output". -/// - On Haiku and QNX Neutrino they are identical. -const FILE_PATH: &str = "/dev/urandom\0"; -const FD_UNINIT: usize = usize::max_value(); - -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - let fd = get_rng_fd()?; - sys_fill_exact(dest, |buf| unsafe { - libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) - }) -} - -// Returns the file descriptor for the device file used to retrieve random -// bytes. The file will be opened exactly once. All subsequent calls will -// return the same file descriptor. This file descriptor is never closed. -fn get_rng_fd() -> Result { - static FD: AtomicUsize = AtomicUsize::new(FD_UNINIT); - fn get_fd() -> Option { - match FD.load(Relaxed) { - FD_UNINIT => None, - val => Some(val as libc::c_int), - } - } - - // Use double-checked locking to avoid acquiring the lock if possible. - if let Some(fd) = get_fd() { - return Ok(fd); - } - - // SAFETY: We use the mutex only in this method, and we always unlock it - // before returning, making sure we don't violate the pthread_mutex_t API. - static MUTEX: Mutex = Mutex::new(); - unsafe { MUTEX.lock() }; - let _guard = DropGuard(|| unsafe { MUTEX.unlock() }); - - if let Some(fd) = get_fd() { - return Ok(fd); - } - - // On Linux, /dev/urandom might return insecure values. - #[cfg(any(target_os = "android", target_os = "linux"))] - wait_until_rng_ready()?; - - let fd = unsafe { open_readonly(FILE_PATH)? }; - // The fd always fits in a usize without conflicting with FD_UNINIT. - debug_assert!(fd >= 0 && (fd as usize) < FD_UNINIT); - FD.store(fd as usize, Relaxed); - - Ok(fd) -} - -// Succeeds once /dev/urandom is safe to read from -#[cfg(any(target_os = "android", target_os = "linux"))] -fn wait_until_rng_ready() -> Result<(), Error> { - // Poll /dev/random to make sure it is ok to read from /dev/urandom. - let fd = unsafe { open_readonly("/dev/random\0")? }; - let mut pfd = libc::pollfd { - fd, - events: libc::POLLIN, - revents: 0, - }; - let _guard = DropGuard(|| unsafe { - libc::close(fd); - }); - - loop { - // A negative timeout means an infinite timeout. - let res = unsafe { libc::poll(&mut pfd, 1, -1) }; - if res >= 0 { - debug_assert_eq!(res, 1); // We only used one fd, and cannot timeout. - return Ok(()); - } - let err = crate::util_libc::last_os_error(); - match err.raw_os_error() { - Some(libc::EINTR) | Some(libc::EAGAIN) => continue, - _ => return Err(err), - } - } -} - -struct Mutex(UnsafeCell); - -impl Mutex { - const fn new() -> Self { - Self(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER)) - } - unsafe fn lock(&self) { - let r = libc::pthread_mutex_lock(self.0.get()); - debug_assert_eq!(r, 0); - } - unsafe fn unlock(&self) { - let r = libc::pthread_mutex_unlock(self.0.get()); - debug_assert_eq!(r, 0); - } -} - -unsafe impl Sync for Mutex {} - -struct DropGuard(F); - -impl Drop for DropGuard { - fn drop(&mut self) { - self.0() - } -} diff --git a/src/util.rs b/src/util.rs index 1c4e70ba4..d42c26e75 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,13 +1,16 @@ #![allow(dead_code)] -use core::{mem::MaybeUninit, ptr}; +use crate::Error; +use core::{mem::MaybeUninit, ptr, slice}; /// Polyfill for `maybe_uninit_slice` feature's /// `MaybeUninit::slice_assume_init_mut`. Every element of `slice` must have /// been initialized. #[inline(always)] +#[allow(unused_unsafe)] // TODO(MSRV 1.65): Remove this. pub unsafe fn slice_assume_init_mut(slice: &mut [MaybeUninit]) -> &mut [T] { + let ptr = ptr_from_mut::<[MaybeUninit]>(slice) as *mut [T]; // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. - &mut *(slice as *mut [MaybeUninit] as *mut [T]) + unsafe { &mut *ptr } } #[inline] @@ -18,10 +21,9 @@ pub fn uninit_slice_fill_zero(slice: &mut [MaybeUninit]) -> &mut [u8] { #[inline(always)] pub fn slice_as_uninit(slice: &[T]) -> &[MaybeUninit] { + let ptr = ptr_from_ref::<[T]>(slice) as *const [MaybeUninit]; // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. - // There is no risk of writing a `MaybeUninit` into the result since - // the result isn't mutable. - unsafe { &*(slice as *const [T] as *const [MaybeUninit]) } + unsafe { &*ptr } } /// View an mutable initialized array as potentially-uninitialized. @@ -29,7 +31,54 @@ pub fn slice_as_uninit(slice: &[T]) -> &[MaybeUninit] { /// This is unsafe because it allows assigning uninitialized values into /// `slice`, which would be undefined behavior. #[inline(always)] +#[allow(unused_unsafe)] // TODO(MSRV 1.65): Remove this. pub unsafe fn slice_as_uninit_mut(slice: &mut [T]) -> &mut [MaybeUninit] { + let ptr = ptr_from_mut::<[T]>(slice) as *mut [MaybeUninit]; // SAFETY: `MaybeUninit` is guaranteed to be layout-compatible with `T`. - &mut *(slice as *mut [T] as *mut [MaybeUninit]) + unsafe { &mut *ptr } +} + +// TODO: MSRV(1.76.0): Replace with `core::ptr::from_mut`. +fn ptr_from_mut(r: &mut T) -> *mut T { + r +} + +// TODO: MSRV(1.76.0): Replace with `core::ptr::from_ref`. +fn ptr_from_ref(r: &T) -> *const T { + r +} + +/// Default implementation of `inner_u32` on top of `fill_uninit` +#[inline] +pub fn inner_u32() -> Result { + let mut res = MaybeUninit::::uninit(); + // SAFETY: the created slice has the same size as `res` + let dst = unsafe { + let p: *mut MaybeUninit = res.as_mut_ptr().cast(); + slice::from_raw_parts_mut(p, core::mem::size_of::()) + }; + crate::fill_uninit(dst)?; + // SAFETY: `dst` has been fully initialized by `imp::fill_inner` + // since it returned `Ok`. + Ok(unsafe { res.assume_init() }) +} + +/// Default implementation of `inner_u64` on top of `fill_uninit` +#[inline] +pub fn inner_u64() -> Result { + let mut res = MaybeUninit::::uninit(); + // SAFETY: the created slice has the same size as `res` + let dst = unsafe { + let p: *mut MaybeUninit = res.as_mut_ptr().cast(); + slice::from_raw_parts_mut(p, core::mem::size_of::()) + }; + crate::fill_uninit(dst)?; + // SAFETY: `dst` has been fully initialized by `imp::fill_inner` + // since it returned `Ok`. + Ok(unsafe { res.assume_init() }) +} + +/// Truncates `u64` and returns the lower 32 bits as `u32` +pub(crate) fn truncate(val: u64) -> u32 { + u32::try_from(val & u64::from(u32::MAX)).expect("The higher 32 bits are masked") } diff --git a/src/util_libc.rs b/src/util_libc.rs index 129362d5d..24c53c0c9 100644 --- a/src/util_libc.rs +++ b/src/util_libc.rs @@ -1,15 +1,8 @@ -#![allow(dead_code)] use crate::Error; -use core::{ - mem::MaybeUninit, - num::NonZeroU32, - ptr::NonNull, - sync::atomic::{fence, AtomicPtr, Ordering}, -}; -use libc::c_void; +use core::mem::MaybeUninit; cfg_if! { - if #[cfg(any(target_os = "netbsd", target_os = "openbsd", target_os = "android"))] { + if #[cfg(any(target_os = "netbsd", target_os = "openbsd", target_os = "android", target_os = "cygwin"))] { use libc::__errno as errno_location; } else if #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "hurd", target_os = "redox", target_os = "dragonfly"))] { use libc::__errno_location as errno_location; @@ -40,26 +33,37 @@ cfg_if! { } } -pub fn last_os_error() -> Error { - let errno = unsafe { get_errno() }; +pub(crate) fn last_os_error() -> Error { + // We assume that on all targets which use the `util_libc` module `c_int` is equal to `i32` + let errno: i32 = unsafe { get_errno() }; + if errno > 0 { - Error::from(NonZeroU32::new(errno as u32).unwrap()) + let code = errno + .checked_neg() + .expect("Positive number can be always negated"); + Error::from_neg_error_code(code) } else { Error::ERRNO_NOT_POSITIVE } } -// Fill a buffer by repeatedly invoking a system call. The `sys_fill` function: -// - should return -1 and set errno on failure -// - should return the number of bytes written on success -pub fn sys_fill_exact( +/// Fill a buffer by repeatedly invoking `sys_fill`. +/// +/// The `sys_fill` function: +/// - should return -1 and set errno on failure +/// - should return the number of bytes written on success +#[allow(dead_code)] +pub(crate) fn sys_fill_exact( mut buf: &mut [MaybeUninit], sys_fill: impl Fn(&mut [MaybeUninit]) -> libc::ssize_t, ) -> Result<(), Error> { while !buf.is_empty() { let res = sys_fill(buf); match res { - res if res > 0 => buf = buf.get_mut(res as usize..).ok_or(Error::UNEXPECTED)?, + res if res > 0 => { + let len = usize::try_from(res).map_err(|_| Error::UNEXPECTED)?; + buf = buf.get_mut(len..).ok_or(Error::UNEXPECTED)?; + } -1 => { let err = last_os_error(); // We should try again if the call was interrupted. @@ -75,88 +79,3 @@ pub fn sys_fill_exact( } Ok(()) } - -// A "weak" binding to a C function that may or may not be present at runtime. -// Used for supporting newer OS features while still building on older systems. -// Based off of the DlsymWeak struct in libstd: -// https://github.com/rust-lang/rust/blob/1.61.0/library/std/src/sys/unix/weak.rs#L84 -// except that the caller must manually cast self.ptr() to a function pointer. -pub struct Weak { - name: &'static str, - addr: AtomicPtr, -} - -impl Weak { - // A non-null pointer value which indicates we are uninitialized. This - // constant should ideally not be a valid address of a function pointer. - // However, if by chance libc::dlsym does return UNINIT, there will not - // be undefined behavior. libc::dlsym will just be called each time ptr() - // is called. This would be inefficient, but correct. - // TODO: Replace with core::ptr::invalid_mut(1) when that is stable. - const UNINIT: *mut c_void = 1 as *mut c_void; - - // Construct a binding to a C function with a given name. This function is - // unsafe because `name` _must_ be null terminated. - pub const unsafe fn new(name: &'static str) -> Self { - Self { - name, - addr: AtomicPtr::new(Self::UNINIT), - } - } - - // Return the address of a function if present at runtime. Otherwise, - // return None. Multiple callers can call ptr() concurrently. It will - // always return _some_ value returned by libc::dlsym. However, the - // dlsym function may be called multiple times. - pub fn ptr(&self) -> Option> { - // Despite having only a single atomic variable (self.addr), we still - // cannot always use Ordering::Relaxed, as we need to make sure a - // successful call to dlsym() is "ordered before" any data read through - // the returned pointer (which occurs when the function is called). - // Our implementation mirrors that of the one in libstd, meaning that - // the use of non-Relaxed operations is probably unnecessary. - match self.addr.load(Ordering::Relaxed) { - Self::UNINIT => { - let symbol = self.name.as_ptr() as *const _; - let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, symbol) }; - // Synchronizes with the Acquire fence below - self.addr.store(addr, Ordering::Release); - NonNull::new(addr) - } - addr => { - let func = NonNull::new(addr)?; - fence(Ordering::Acquire); - Some(func) - } - } - } -} - -// SAFETY: path must be null terminated, FD must be manually closed. -pub unsafe fn open_readonly(path: &str) -> Result { - debug_assert_eq!(path.as_bytes().last(), Some(&0)); - loop { - let fd = libc::open(path.as_ptr() as *const _, libc::O_RDONLY | libc::O_CLOEXEC); - if fd >= 0 { - return Ok(fd); - } - let err = last_os_error(); - // We should try again if open() was interrupted. - if err.raw_os_error() != Some(libc::EINTR) { - return Err(err); - } - } -} - -/// Thin wrapper around the `getrandom()` Linux system call -#[cfg(any(target_os = "android", target_os = "linux"))] -pub fn getrandom_syscall(buf: &mut [MaybeUninit]) -> 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 - } -} diff --git a/src/vxworks.rs b/src/vxworks.rs deleted file mode 100644 index 7ca9d6bfd..000000000 --- a/src/vxworks.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Implementation for VxWorks -use crate::{util_libc::last_os_error, Error}; -use core::{ - mem::MaybeUninit, - sync::atomic::{AtomicBool, Ordering::Relaxed}, -}; - -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - static RNG_INIT: AtomicBool = AtomicBool::new(false); - while !RNG_INIT.load(Relaxed) { - let ret = unsafe { libc::randSecure() }; - if ret < 0 { - return Err(Error::VXWORKS_RAND_SECURE); - } else if ret > 0 { - RNG_INIT.store(true, Relaxed); - break; - } - unsafe { libc::usleep(10) }; - } - - // Prevent overflow of i32 - for chunk in dest.chunks_mut(i32::max_value() as usize) { - let ret = unsafe { libc::randABytes(chunk.as_mut_ptr() as *mut u8, chunk.len() as i32) }; - if ret != 0 { - return Err(last_os_error()); - } - } - Ok(()) -} diff --git a/src/wasi.rs b/src/wasi.rs deleted file mode 100644 index d6c8a912c..000000000 --- a/src/wasi.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! Implementation for WASI -use crate::Error; -use core::{ - mem::MaybeUninit, - num::{NonZeroU16, NonZeroU32}, -}; -use wasi::random_get; - -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - unsafe { random_get(dest.as_mut_ptr() as *mut u8, dest.len()) }.map_err(|e| { - // The WASI errno will always be non-zero, but we check just in case. - match NonZeroU16::new(e.raw()) { - Some(r) => Error::from(NonZeroU32::from(r)), - None => Error::ERRNO_NOT_POSITIVE, - } - }) -} diff --git a/src/windows.rs b/src/windows.rs deleted file mode 100644 index 2d1c48351..000000000 --- a/src/windows.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! Implementation for Windows -use crate::Error; -use core::{ffi::c_void, mem::MaybeUninit, num::NonZeroU32, ptr}; - -const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002; - -#[link(name = "bcrypt")] -extern "system" { - fn BCryptGenRandom( - hAlgorithm: *mut c_void, - pBuffer: *mut u8, - cbBuffer: u32, - dwFlags: u32, - ) -> u32; -} - -// Forbidden when targetting UWP -#[cfg(not(target_vendor = "uwp"))] -#[link(name = "advapi32")] -extern "system" { - #[link_name = "SystemFunction036"] - fn RtlGenRandom(RandomBuffer: *mut c_void, RandomBufferLength: u32) -> u8; -} - -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - // Prevent overflow of u32 - for chunk in dest.chunks_mut(u32::max_value() as usize) { - // BCryptGenRandom was introduced in Windows Vista - let ret = unsafe { - BCryptGenRandom( - ptr::null_mut(), - chunk.as_mut_ptr() as *mut u8, - chunk.len() as u32, - BCRYPT_USE_SYSTEM_PREFERRED_RNG, - ) - }; - // NTSTATUS codes use the two highest bits for severity status. - if ret >> 30 == 0b11 { - // Failed. Try RtlGenRandom as a fallback. - #[cfg(not(target_vendor = "uwp"))] - { - let ret = - unsafe { RtlGenRandom(chunk.as_mut_ptr() as *mut c_void, chunk.len() as u32) }; - if ret != 0 { - continue; - } - } - // We zeroize the highest bit, so the error code will reside - // inside the range designated for OS codes. - let code = ret ^ (1 << 31); - // SAFETY: the second highest bit is always equal to one, - // so it's impossible to get zero. Unfortunately the type - // system does not have a way to express this yet. - let code = unsafe { NonZeroU32::new_unchecked(code) }; - return Err(Error::from(code)); - } - } - Ok(()) -} diff --git a/tests/common/mod.rs b/tests/common/mod.rs deleted file mode 100644 index 666f7f570..000000000 --- a/tests/common/mod.rs +++ /dev/null @@ -1,100 +0,0 @@ -use super::getrandom_impl; - -#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] -use wasm_bindgen_test::wasm_bindgen_test as test; - -#[cfg(feature = "test-in-browser")] -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -fn test_zero() { - // Test that APIs are happy with zero-length requests - getrandom_impl(&mut [0u8; 0]).unwrap(); -} - -// Return the number of bits in which s1 and s2 differ -#[cfg(not(feature = "custom"))] -fn num_diff_bits(s1: &[u8], s2: &[u8]) -> usize { - assert_eq!(s1.len(), s2.len()); - s1.iter() - .zip(s2.iter()) - .map(|(a, b)| (a ^ b).count_ones() as usize) - .sum() -} - -// Tests the quality of calling getrandom on two large buffers -#[test] -#[cfg(not(feature = "custom"))] -fn test_diff() { - let mut v1 = [0u8; 1000]; - getrandom_impl(&mut v1).unwrap(); - - let mut v2 = [0u8; 1000]; - getrandom_impl(&mut v2).unwrap(); - - // Between 3.5 and 4.5 bits per byte should differ. Probability of failure: - // ~ 2^(-94) = 2 * CDF[BinomialDistribution[8000, 0.5], 3500] - let d = num_diff_bits(&v1, &v2); - assert!(d > 3500); - assert!(d < 4500); -} - -// Tests the quality of calling getrandom repeatedly on small buffers -#[test] -#[cfg(not(feature = "custom"))] -fn test_small() { - // For each buffer size, get at least 256 bytes and check that between - // 3 and 5 bits per byte differ. Probability of failure: - // ~ 2^(-91) = 64 * 2 * CDF[BinomialDistribution[8*256, 0.5], 3*256] - for size in 1..=64 { - let mut num_bytes = 0; - let mut diff_bits = 0; - while num_bytes < 256 { - let mut s1 = vec![0u8; size]; - getrandom_impl(&mut s1).unwrap(); - let mut s2 = vec![0u8; size]; - getrandom_impl(&mut s2).unwrap(); - - num_bytes += size; - diff_bits += num_diff_bits(&s1, &s2); - } - assert!(diff_bits > 3 * num_bytes); - assert!(diff_bits < 5 * num_bytes); - } -} - -#[test] -fn test_huge() { - let mut huge = [0u8; 100_000]; - getrandom_impl(&mut huge).unwrap(); -} - -// On WASM, the thread API always fails/panics -#[cfg(not(target_arch = "wasm32"))] -#[test] -fn test_multithreading() { - extern crate std; - use std::{sync::mpsc::channel, thread, vec}; - - let mut txs = vec![]; - for _ in 0..20 { - let (tx, rx) = channel(); - txs.push(tx); - - thread::spawn(move || { - // wait until all the tasks are ready to go. - rx.recv().unwrap(); - let mut v = [0u8; 1000]; - - for _ in 0..100 { - getrandom_impl(&mut v).unwrap(); - thread::yield_now(); - } - }); - } - - // start all the tasks - for tx in txs.iter() { - tx.send(()).unwrap(); - } -} diff --git a/tests/custom.rs b/tests/custom.rs deleted file mode 100644 index b085094be..000000000 --- a/tests/custom.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Test that a custom handler works on wasm32-unknown-unknown -#![cfg(all( - target_arch = "wasm32", - target_os = "unknown", - feature = "custom", - not(feature = "js") -))] - -use wasm_bindgen_test::wasm_bindgen_test as test; - -use core::num::NonZeroU32; -use getrandom::{getrandom, register_custom_getrandom, Error}; - -fn len7_err() -> Error { - NonZeroU32::new(Error::INTERNAL_START + 7).unwrap().into() -} - -fn super_insecure_rng(buf: &mut [u8]) -> Result<(), Error> { - // `getrandom` guarantees it will not call any implementation if the output - // buffer is empty. - assert!(!buf.is_empty()); - // Length 7 buffers return a custom error - if buf.len() == 7 { - return Err(len7_err()); - } - // Otherwise, fill bytes based on input length - let mut start = buf.len() as u8; - for b in buf { - *b = start; - start = start.wrapping_mul(3); - } - Ok(()) -} - -register_custom_getrandom!(super_insecure_rng); - -use getrandom::getrandom as getrandom_impl; -mod common; - -#[test] -fn custom_rng_output() { - let mut buf = [0u8; 4]; - assert_eq!(getrandom(&mut buf), Ok(())); - assert_eq!(buf, [4, 12, 36, 108]); - - let mut buf = [0u8; 3]; - assert_eq!(getrandom(&mut buf), Ok(())); - assert_eq!(buf, [3, 9, 27]); -} - -#[test] -fn rng_err_output() { - assert_eq!(getrandom(&mut [0; 7]), Err(len7_err())); -} diff --git a/tests/mod.rs b/tests/mod.rs new file mode 100644 index 000000000..9f1e63387 --- /dev/null +++ b/tests/mod.rs @@ -0,0 +1,297 @@ +use core::mem::MaybeUninit; +use getrandom::{fill, fill_uninit}; + +#[cfg(all(feature = "wasm_js", target_arch = "wasm32", target_os = "unknown"))] +use wasm_bindgen_test::wasm_bindgen_test as test; + +#[test] +fn test_zero() { + // Test that APIs are happy with zero-length requests + fill(&mut [0u8; 0]).unwrap(); + let res = fill_uninit(&mut []).unwrap(); + assert!(res.is_empty()); +} + +trait DiffBits: Sized { + fn diff_bits(ab: (&Self, &Self)) -> usize; +} + +impl DiffBits for u8 { + fn diff_bits((a, b): (&Self, &Self)) -> usize { + (a ^ b).count_ones() as usize + } +} + +impl DiffBits for u32 { + fn diff_bits((a, b): (&Self, &Self)) -> usize { + (a ^ b).count_ones() as usize + } +} + +impl DiffBits for u64 { + fn diff_bits((a, b): (&Self, &Self)) -> usize { + (a ^ b).count_ones() as usize + } +} + +// Return the number of bits in which s1 and s2 differ +fn num_diff_bits(s1: &[T], s2: &[T]) -> usize { + assert_eq!(s1.len(), s2.len()); + s1.iter().zip(s2.iter()).map(T::diff_bits).sum() +} + +// TODO: use `[const { MaybeUninit::uninit() }; N]` after MSRV is bumped to 1.79+ +// or `MaybeUninit::uninit_array` +fn uninit_vec(n: usize) -> Vec> { + vec![MaybeUninit::uninit(); n] +} + +// Tests the quality of calling getrandom on two large buffers +#[test] +fn test_diff() { + const N: usize = 1000; + let mut v1 = [0u8; N]; + let mut v2 = [0u8; N]; + fill(&mut v1).unwrap(); + fill(&mut v2).unwrap(); + + let mut t1 = uninit_vec(N); + let mut t2 = uninit_vec(N); + let r1 = fill_uninit(&mut t1).unwrap(); + let r2 = fill_uninit(&mut t2).unwrap(); + assert_eq!(r1.len(), N); + assert_eq!(r2.len(), N); + + // Between 3.5 and 4.5 bits per byte should differ. Probability of failure: + // ~ 2^(-94) = 2 * CDF[BinomialDistribution[8000, 0.5], 3500] + let d1 = num_diff_bits(&v1, &v2); + assert!(d1 > 3500); + assert!(d1 < 4500); + let d2 = num_diff_bits(r1, r2); + assert!(d2 > 3500); + assert!(d2 < 4500); +} + +#[test] +fn test_diff_u32() { + const N: usize = 1000 / 4; + let mut v1 = [0u32; N]; + let mut v2 = [0u32; N]; + for v in v1.iter_mut() { + *v = getrandom::u32().unwrap(); + } + for v in v2.iter_mut() { + *v = getrandom::u32().unwrap(); + } + + // Between 3.5 and 4.5 bits per byte should differ. Probability of failure: + // ~ 2^(-94) = 2 * CDF[BinomialDistribution[8000, 0.5], 3500] + let d1 = num_diff_bits(&v1, &v2); + assert!(d1 > 3500); + assert!(d1 < 4500); +} + +#[test] +fn test_diff_u64() { + const N: usize = 1000 / 8; + let mut v1 = [0u64; N]; + let mut v2 = [0u64; N]; + for v in v1.iter_mut() { + *v = getrandom::u64().unwrap(); + } + for v in v2.iter_mut() { + *v = getrandom::u64().unwrap(); + } + + // Between 3.5 and 4.5 bits per byte should differ. Probability of failure: + // ~ 2^(-94) = 2 * CDF[BinomialDistribution[8000, 0.5], 3500] + let d1 = num_diff_bits(&v1, &v2); + assert!(d1 > 3500); + assert!(d1 < 4500); +} + +#[test] +fn test_small() { + const N: usize = 64; + // For each buffer size, get at least 256 bytes and check that between + // 3 and 5 bits per byte differ. Probability of failure: + // ~ 2^(-91) = 64 * 2 * CDF[BinomialDistribution[8*256, 0.5], 3*256] + for size in 1..=N { + let mut num_bytes = 0; + let mut diff_bits = 0; + while num_bytes < 256 { + let mut buf1 = [0u8; N]; + let mut buf2 = [0u8; N]; + + let s1 = &mut buf1[..size]; + let s2 = &mut buf2[..size]; + + fill(s1).unwrap(); + fill(s2).unwrap(); + + num_bytes += size; + diff_bits += num_diff_bits(s1, s2); + } + assert!(diff_bits > 3 * num_bytes); + assert!(diff_bits < 5 * num_bytes); + } +} + +// Tests the quality of calling getrandom repeatedly on small buffers +#[test] +fn test_small_uninit() { + const N: usize = 64; + // For each buffer size, get at least 256 bytes and check that between + // 3 and 5 bits per byte differ. Probability of failure: + // ~ 2^(-91) = 64 * 2 * CDF[BinomialDistribution[8*256, 0.5], 3*256] + for size in 1..=N { + let mut num_bytes = 0; + let mut diff_bits = 0; + while num_bytes < 256 { + let mut buf1 = uninit_vec(N); + let mut buf2 = uninit_vec(N); + + let s1 = &mut buf1[..size]; + let s2 = &mut buf2[..size]; + + let r1 = fill_uninit(s1).unwrap(); + let r2 = fill_uninit(s2).unwrap(); + assert_eq!(r1.len(), size); + assert_eq!(r2.len(), size); + + num_bytes += size; + diff_bits += num_diff_bits(r1, r2); + } + assert!(diff_bits > 3 * num_bytes); + assert!(diff_bits < 5 * num_bytes); + } +} + +#[test] +fn test_huge() { + let mut huge = [0u8; 100_000]; + fill(&mut huge).unwrap(); +} + +#[test] +fn test_huge_uninit() { + const N: usize = 100_000; + let mut huge = uninit_vec(N); + let res = fill_uninit(&mut huge).unwrap(); + assert_eq!(res.len(), N); +} + +#[test] +#[cfg_attr( + target_arch = "wasm32", + ignore = "The thread API always fails/panics on WASM" +)] +fn test_multithreading() { + extern crate std; + use std::{sync::mpsc::channel, thread, vec}; + + let mut txs = vec![]; + for _ in 0..20 { + let (tx, rx) = channel(); + txs.push(tx); + + thread::spawn(move || { + // wait until all the tasks are ready to go. + rx.recv().unwrap(); + let mut v = [0u8; 1000]; + + for _ in 0..100 { + fill(&mut v).unwrap(); + thread::yield_now(); + } + }); + } + + // start all the tasks + for tx in txs.iter() { + tx.send(()).unwrap(); + } +} + +#[cfg(getrandom_backend = "custom")] +mod custom { + use getrandom::Error; + + struct Xoshiro128PlusPlus { + s: [u32; 4], + } + + impl Xoshiro128PlusPlus { + fn new(mut seed: u64) -> Self { + const PHI: u64 = 0x9e3779b97f4a7c15; + let mut s = [0u32; 4]; + for val in s.iter_mut() { + seed = seed.wrapping_add(PHI); + let mut z = seed; + z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9); + z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb); + z = z ^ (z >> 31); + *val = z as u32; + } + Self { s } + } + + fn next_u32(&mut self) -> u32 { + let res = self.s[0] + .wrapping_add(self.s[3]) + .rotate_left(7) + .wrapping_add(self.s[0]); + + let t = self.s[1] << 9; + + self.s[2] ^= self.s[0]; + self.s[3] ^= self.s[1]; + self.s[1] ^= self.s[2]; + self.s[0] ^= self.s[3]; + + self.s[2] ^= t; + + self.s[3] = self.s[3].rotate_left(11); + + res + } + } + + // This implementation uses current timestamp as a PRNG seed. + // + // WARNING: this custom implementation is for testing purposes ONLY! + #[no_mangle] + unsafe extern "Rust" fn __getrandom_v03_custom(dest: *mut u8, len: usize) -> Result<(), Error> { + use std::time::{SystemTime, UNIX_EPOCH}; + + assert_ne!(len, 0); + + if len == 142 { + return Err(Error::new_custom(142)); + } + + let dest_u32 = dest.cast::(); + let ts = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let mut rng = Xoshiro128PlusPlus::new(ts.as_nanos() as u64); + for i in 0..len / 4 { + let val = rng.next_u32(); + core::ptr::write_unaligned(dest_u32.add(i), val); + } + if len % 4 != 0 { + let start = 4 * (len / 4); + for i in start..len { + let val = rng.next_u32(); + core::ptr::write_unaligned(dest.add(i), val as u8); + } + } + Ok(()) + } + + // Test that enabling the custom feature indeed uses the custom implementation + #[test] + fn test_custom() { + let mut buf = [0u8; 142]; + let res = getrandom::fill(&mut buf); + assert!(res.is_err()); + } +} diff --git a/tests/normal.rs b/tests/normal.rs deleted file mode 100644 index 5fff13b38..000000000 --- a/tests/normal.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Don't test on custom wasm32-unknown-unknown -#![cfg(not(all( - target_arch = "wasm32", - target_os = "unknown", - feature = "custom", - not(feature = "js") -)))] - -// Use the normal getrandom implementation on this architecture. -use getrandom::getrandom as getrandom_impl; -mod common; diff --git a/tests/rdrand.rs b/tests/rdrand.rs deleted file mode 100644 index a355c31ee..000000000 --- a/tests/rdrand.rs +++ /dev/null @@ -1,22 +0,0 @@ -// We only test the RDRAND-based RNG source on supported architectures. -#![cfg(any(target_arch = "x86_64", target_arch = "x86"))] - -// rdrand.rs expects to be part of the getrandom main crate, so we need these -// additional imports to get rdrand.rs to compile. -use getrandom::Error; -#[macro_use] -extern crate cfg_if; -#[path = "../src/lazy.rs"] -mod lazy; -#[path = "../src/rdrand.rs"] -mod rdrand; -#[path = "../src/util.rs"] -mod util; - -// The rdrand implementation has the signature of getrandom_uninit(), but our -// tests expect getrandom_impl() to have the signature of getrandom(). -fn getrandom_impl(dest: &mut [u8]) -> Result<(), Error> { - rdrand::getrandom_inner(unsafe { util::slice_as_uninit_mut(dest) })?; - Ok(()) -} -mod common;