Fixed missing bracket in src/options.c

This commit is contained in:
Fabian Druschke
2025-11-29 21:00:07 +01:00
16 changed files with 1719 additions and 186 deletions

514
README.md
View File

@@ -1,87 +1,231 @@
# nwipe
![GitHub CI badge](https://github.com/martijnvanbrummelen/nwipe/workflows/ci_ubuntu_latest/badge.svg)
[![GitHub release](https://img.shields.io/github/release/martijnvanbrummelen/nwipe)](https://github.com/martijnvanbrummelen/nwipe/releases/)
nwipe is a fork of the dwipe command originally used by Darik's Boot and Nuke (DBAN). nwipe was created out of a need to run the DBAN dwipe command outside of DBAN, in order to allow its use with any host distribution, thus giving better hardware support.
`nwipe` is a fork of the `dwipe` command originally used by Darik's Boot and Nuke (DBAN).
It was created to run the DBAN erase engine on any Linux distribution, with better and more modern hardware support.
nwipe is a program that will securely erase the entire contents of disks. It can wipe a single drive or multiple disks simultaneously. It can operate as both a command line tool without a GUI or with a ncurses GUI as shown in the example below:
`nwipe` securely erases the entire contents of block devices. It can wipe a single drive or multiple disks in parallel, either:
> **Warning**
> For some of nwipes features such as smart data in the PDF certificate, HPA/DCO detection and other uses, nwipe utilises smartmontools and hdparm. Therefore both hdparm & smartmontools are a mandatory requirement if you want all of nwipes features to be fully available. If you do not install smartmontools and hdparm, nwipe will provide a warning in the log that these programs cannot be found but will still run but many important features may not work as they should do.
- as a **command-line tool** without a GUI, or
- with an **ncurses-based GUI**, as shown below:
![Example wipe](/images/example_wipe.gif)
> **Warning**
> For some of nwipes features such as SMART data in the PDF certificate, HPA/DCO detection and other functions, nwipe uses external tools: **smartmontools** and **hdparm**.
> Both `hdparm` and `smartmontools` are **mandatory** if you want all nwipe features to be fully available.
> If they are not installed, nwipe will log a warning and continue, but many important features will not work as intended.
<i>The video above shows six drives being simultaneously erased. It skips to the completion of all six wipes and shows five drives that were successfully erased and one drive that failed due to an I/O error. The drive that failed would then normally be physically destroyed. The five drives that were successfully wiped with zero errors or failures can then be redeployed.</i>
![Example wipe](https://github.com/martijnvanbrummelen/nwipe/raw/master/images/example_wipe.gif)
*The animation above shows six drives being erased in parallel. The view jumps to the completion of all six wipes and shows five drives successfully wiped and one that failed due to an I/O error. The failing drive would typically be physically destroyed. Drives that complete with zero errors can be safely redeployed.*
![nwipe_certificate_0 35_5s](https://github.com/martijnvanbrummelen/nwipe/assets/22084881/cf181a9c-af2d-4bca-a6ed-15a4726cb12b)
<i>The snapshot above shows nwipe's three page PDF certificate, drive identifiable information such as serial numbers have been anonymised using the nwipe command line option -q</i>
*The screenshot above shows nwipes threepage PDF certificate. Drive-identifying data such as serial numbers has been anonymised using the `-q` / `--quiet` option.*
---
## New in v0.40 (upcoming)
The upcoming **v0.40** release introduces several major improvements:
- **AES-256-CTR PRNG**
Highperformance, cryptographically secure stream generator (AES-NI accelerated where available).
- **Large, aligned I/O buffers**
Significantly fewer syscalls and better throughput, especially on fast SSDs and NVMe.
- **Configurable I/O modes**
- `--io-mode=auto` (default): try O_DIRECT, fall back to cached I/O if not supported
- `--directio` / `--io-mode=direct`: force direct I/O (O_DIRECT), no fallback
- `--cachedio` / `--io-mode=cached`: force kernel cached I/O, never attempt O_DIRECT
- **Improved sync behaviour for cached I/O**
Sync intervals are again based on a predictable number of bytes written, ensuring timely detection of disk / USB errors without excessive overhead.
- **Enhanced device exclusion**
`--exclude` now works cleanly with paths like `/dev/disk/by-id/*`, making it easier to exclude specific drives by stable IDs.
- **Stronger seeding with `getrandom()`**
nwipe now uses the Linux `getrandom()` syscall for PRNG seeding and no longer depends on `/dev/urandom`.
- **New BMB21-2019 erase method**
Implements the Chinese State Secrets Bureau BMB21-2019 technical requirement for data sanitisation.
---
## Erasure methods
The user can select from a variety of recognised secure erase methods which include:
* Fill With Zeros - Fills the device with zeros (0x00).
* Fill With Ones - Fills the device with ones (0xFF).
* RCMP TSSIT OPS-II - Royal Canadian Mounted Police Technical Security Standard, OPS-II.
* DoD Short - The American Department of Defense 5220.22-M short 3 pass wipe (passes 1, 2 & 7).
* DoD 5220.22M - The American Department of Defense 5220.22-M full 7 pass wipe.
* Gutmann Wipe - Peter Gutmann's method (Secure Deletion of Data from Magnetic and Solid-State Memory).
* PRNG Stream - Fills the device with a stream from the PRNG.
* Verify Zeros - This method only reads the device and checks that it is filled with zeros (0x00).
* Verify Ones - This method only reads the device and checks that it is filled with ones (0xFF).
* HMG IS5 enhanced - Secure Sanitisation of Protectively Marked Information or Sensitive Information.
* Schneier Wipe - Bruce Schneier's method (7-pass mixed pattern).
The user can select from a variety of recognised secure erase methods, including:
nwipe also includes the following pseudo random number generators (PRNG):
* Mersenne Twister
* ISAAC
* ISAAC-64
* Additive Lagged Fibonacci Generator
* XORoshiro-256
In addition to the above, the following PRNG will be available in future versions:
* AES-256-CTR
- **Fill With Zeros**
Fills the device with zeros (`0x00`).
These can be used to overwrite a drive with a stream of randomly generated characters.
- **Fill With Ones**
Fills the device with ones (`0xFF`).
nwipe can be found in many [Linux distro repositories](#which-linux-distro-uses-the-latest-nwipe).
- **RCMP TSSIT OPS-II**
Royal Canadian Mounted Police Technical Security Standard, OPS-II.
nwipe is also included in [ShredOS](https://github.com/PartialVolume/shredos.x86_64) which was developed in particular to showcase nwipe as a fast-to-boot standalone method similar to DBAN. ShredOS always contains the latest nwipe version.
- **DoD Short**
U.S. Department of Defense 5220.22-M **short** 3-pass wipe
(passes 1, 2 & 7 from the full specification).
## Limitations regarding solid state drives
In the current form nwipe does not sanitize solid-state drives (hereinafter referred to as SSDs)
of any form (SAS / SATA / NVMe) and / or form factor (2.5" / 3.5" / PCI) fully due to their nature:
SSDs, as the transistors contained in the memory modules are subject to wear, contain in most cases
additional memory modules installed as failover for broken sectors outside
of the host accessible space (frequently referred to as "overprovisioning") and for garbage collection.
Some manufacturers reserve access to these areas only to disk's own controller and firmware.
It is therefor always advised to use nwipe / ShredOS in conjunction with the manufacturer's or hardware vendor's own tools for sanitization to assure
full destruction of the information contained on the disk.
Given that most vendors and manufacturers do not provide open source tools, it is advised to validate the outcome by comparing the data on the disk before and after sanitization.
A list of the most common tools and instructions for SSD wipes can be found in the [SSD Guide](ssd-guide.md).
- **DoD 5220.22-M (Full)**
Full 7-pass U.S. DoD 5220.22-M wipe.
## Compiling & Installing
- **Gutmann Wipe**
Peter Gutmann's 35-pass method (“Secure Deletion of Data from Magnetic and Solid-State Memory”).
For a development setup, see the [Hacking section](#hacking) below. For a bootable version of the very latest nwipe master that you can write to an USB flash drive or CD/DVD, see the [Quick and easy bootable version of nwipe master section](#quick--easy-usb-bootable-version-of-nwipe-master-for-x86_64-systems) below.
- **PRNG Stream**
Fills the device with a stream generated by the selected PRNG engine.
`nwipe` requires the following libraries to be installed:
- **Verify Zeros**
Reads the device and verifies it is filled with zeros (`0x00`).
* ncurses
* pthreads
* parted
* libconfig
- **Verify Ones**
Reads the device and verifies it is filled with ones (`0xFF`).
`nwipe` also requires the following program to be installed, it will abort with a warning if not found:
- **HMG IS5 Enhanced**
UK HMG IS5 (Enhanced) sanitisation method for protectively marked or sensitive information.
* hdparm (as of current master and v0.35+)
- **Schneier Wipe**
Bruce Schneier's 7-pass mixedpattern algorithm.
and optionally, but recommended, the following programs:
- **BMB21-2019** *(new in v0.40)*
Chinese State Secrets Bureau BMB21-2019 technical requirement for data sanitisation.
This method overwrites the device with ones (`0xFF`), then zeros (`0x00`), followed by three passes of PRNG-generated random data, and finishes with a final pass of ones (`0xFF`).
* dmidecode
* readlink
* smartmontools
---
## PRNG engines
nwipe includes multiple pseudorandom number generators (PRNGs) for methods that require random data:
- **AES-256-CTR** *(new in v0.40)*
Cryptographically secure, highthroughput counter-mode stream cipher, using hardware AES-NI where available.
- **XORoshiro-256**
Very fast, highquality non-cryptographic generator, suitable for highvolume random wiping where a CSPRNG is not strictly required.
- **Mersenne Twister**
Well-known highperiod PRNG.
- **ISAAC / ISAAC-64**
(Indirection, Shift, Accumulate, Add, and Count) generators.
- **Additive Lagged Fibonacci Generator**
These PRNGs can be selected at runtime (see the man page for the exact CLI options) and are used by any wipe method that requires random patterns (for example PRNG Stream, Schneier or BMB21 random passes).
---
## I/O subsystem and Direct I/O
Starting with v0.40 the nwipe I/O layer has been significantly modernised:
### Large, aligned I/O buffers
nwipe now uses **large, aligned multi-megabyte buffers** when reading and writing:
- Reduces the number of `read()` / `write()` calls.
- Improves throughput on fast devices (SSD / NVMe / high-speed RAID).
- Ensures correct alignment for Direct I/O (O_DIRECT) where supported.
### I/O mode selection
You can now explicitly control how nwipe accesses the device:
```bash
--io-mode=auto # default
--io-mode=direct # equivalent to --directio
--io-mode=cached # equivalent to --cachedio
--directio # force O_DIRECT (no fallback)
--cachedio # force kernel cached I/O only
````
* **auto**
Try to open the device with `O_DIRECT`. If the kernel or filesystem does not support it (EINVAL/EOPNOTSUPP), nwipe falls back to cached I/O and logs a warning.
* **direct** / `--directio`
Force Direct I/O. If `O_DIRECT` is not supported for the device, opening the device fails and the wipe will not proceed. This is useful for strict testing and benchmarking.
* **cached** / `--cachedio`
Always use kernel buffered I/O. `O_DIRECT` is never attempted. This is closest to the behaviour of older nwipe releases.
### Sync behaviour
The `--sync` option controls how often nwipe performs `fdatasync()` when using **cached I/O**:
* The value represents the number of **device hardware blocks** (typically 512 or 4096 bytes) between syncs.
* Internally this value is scaled to match nwipes large I/O buffer size so that the effective **bytes between syncs** remain in a reasonable range (tens to hundreds of MB, depending on the default and device).
* This ensures:
* timely detection of I/O and hardware errors in cached I/O mode, and
* good throughput for normal use.
In **Direct I/O** mode (`--directio` / `--io-mode=direct`), periodic sync is disabled: write errors are reported immediately by `write()`, so `fdatasync()` provides no additional safety.
See the `nwipe(8)` man page for detailed `--sync` semantics and examples.
---
## SSD considerations and limitations
In its current form, nwipe **cannot fully sanitise** solid state drives (SSDs) of any interface type:
* SAS / SATA / NVMe
* Form factors such as 2.5", 3.5", M.2, PCIe, etc.
This is due to how SSDs internally manage data:
* SSDs use wear-levelling and frequently maintain additional, non-host-accessible memory (overprovisioning).
* Failed blocks may be remapped to reserved areas that are not directly addressable by the OS.
* Many vendors restrict low-level access to these areas to the drives own controller and firmware.
For secure SSD sanitisation, it is strongly recommended to:
1. Use nwipe / ShredOS **in combination with vendor-specific tools**, for example:
* manufacturer Secure Erase,
* NVMe format / sanitize commands, or
* hardware vendorprovided utilities, and
2. Independently validate the result, comparing drive contents before and after sanitisation where feasible.
A list of common SSD vendor tools and guidance can be found in the separate [SSD Guide](ssd-guide.md).
---
## Compiling & installing
For development work, see the [Hacking](#hacking) section below.
For a **bootable image** with the latest nwipe master that you can write to a USB stick or CD/DVD, see [Quick & easy, USB bootable version](#quick--easy-usb-bootable-version-of-nwipe-master-for-x86_64-systems).
### Dependencies
`nwipe` requires the following libraries:
* `ncurses`
* `pthreads`
* `parted`
* `libconfig`
`nwipe` also requires the following program and will abort with a warning if not found:
* **hdparm** (as of current master and v0.35+)
The following tools are optional but **strongly recommended**:
* `dmidecode`
* `coreutils` (for `readlink`)
* `smartmontools`
These tools enable features such as:
* HPA/DCO detection
* SMART data for PDF certificates (especially for USB bridge devices)
* SMBIOS/DMI host information in the log
* Correct bus type detection (ATA/USB/etc.) and proper operation of `--nousb`
### Debian & Ubuntu prerequisites
If you are compiling `nwipe` from source, the following libraries will need to be installed first:
If you are compiling `nwipe` from source on Debian/Ubuntu:
```bash
sudo apt install \
@@ -96,149 +240,251 @@ sudo apt install \
dmidecode \
coreutils \
smartmontools \
hdparm
hdparm \
```
### Fedora prerequisites
### Fedora / RHEL / CentOS Stream prerequisites
```bash
sudo bash
dnf update
dnf groupinstall "Development Tools"
dnf groupinstall "C Development Tools and Libraries"
yum install ncurses-devel
yum install parted-devel
yum install libconfig-devel
yum install libconfig++-devel
yum install dmidecode
yum install coreutils
yum install smartmontools
yum install hdparm
sudo dnf update -y
sudo dnf groupinstall -y "Development Tools" "C Development Tools and Libraries"
sudo dnf install -y \
ncurses-devel \
parted-devel \
libconfig-devel \
libconfig++-devel \
dmidecode \
coreutils \
smartmontools \
hdparm
```
Note: The following programs are optionally installed although recommended. 1. dmidecode 2. readlink 3. smartmontools.
#### hdparm [REQUIRED]
hdparm provides some of the information regarding disk size in sectors as related to the host protected area (HPA) and device configuration overlay (DCO). We do however have our own function that directly access the DCO to obtain the 'real max sectors' so reliance on hdparm may be removed at a future date.
### Arch Linux / Manjaro prerequisites
#### dmidecode [RECOMMENDED]
dmidecode provides SMBIOS/DMI host data to stdout or the log file. If you don't install it you won't see the SMBIOS/DMI host data at the beginning of nwipes log.
```bash
sudo pacman -Syu --needed \
base-devel \
ncurses \
parted \
libconfig \
dmidecode \
coreutils \
smartmontools \
hdparm
```
### openSUSE (Leap / Tumbleweed) prerequisites
#### coreutils (provides readlink) [RECOMMENDED]
readlink determines the bus type, i.e. ATA, USB etc. Without it the --nousb option won't work and bus type information will be missing from nwipes selection and wipe windows. The coreutils package is often automatically installed as default in most if not all distros.
```bash
sudo zypper refresh
sudo zypper install -y \
gcc \
make \
automake \
autoconf \
libtool \
ncurses-devel \
libparted-devel \
libconfig-devel \
libconfig++-devel \
dmidecode \
coreutils \
smartmontools \
hdparm
```
#### smartmontools [REQUIRED]
smartmontools obtains serial number information for supported USB to IDE/SATA adapters. Without it, drives plugged into USB ports will not show serial number information.
If you want a quick and easy way to keep your copy of nwipe running the latest master release of nwipe see the [automating the download and compilation](#automating-the-download-and-compilation-process-for-debian-based-distros) section.
Note: `dmidecode`, `readlink` (from `coreutils`) and `smartmontools` are technically optional, but recommended for full feature support.
### Compilation
First create all the autoconf files:
```
Generate the autoconf files:
```bash
./autogen.sh
```
Then compile & install using the following standard commands:
```
Then configure, build and install:
```bash
./configure
make format (only required if submitting pull requests)
make format # only required if you intend to submit pull requests
make
make install
sudo make install
```
Then run nwipe!
```
Run nwipe:
```bash
cd src
sudo ./nwipe
```
or
```
or simply:
```bash
sudo nwipe
```
### Hacking
---
If you wish to submit pull requests to this code we would prefer you enable all warnings when compiling.
This can be done using the following compile commands:
## Hacking
```
If you intend to submit patches or pull requests, we recommend enabling full warnings in your development build.
For a debugfriendly build:
```bash
./configure --prefix=/usr CFLAGS='-O0 -g -Wall -Wextra'
make format (necessary if submitting pull requests)
make format # necessary if submitting pull requests
make
make install
sudo make install
```
The `-O0 -g` flags disable optimisations. This is required if you're debugging with `gdb` in an IDE such as Kdevelop. With these optimisations enabled you won't be able to see the values of many variables in nwipe, not to mention the IDE won't step through the code properly.
* `-O0 -g`
Disables optimisation and includes debug symbols. This is important if you are debugging with `gdb` or an IDE (e.g. KDevelop), and want accurate stepping and variable inspection.
The `-Wall` and `-Wextra` flags enable all compiler warnings. Please submit code with zero warnings.
* `-Wall -Wextra`
Enables most useful compiler warnings. Please submit code with **zero warnings**.
Also make sure that your changes are consistent with the coding style defined in the `.clang-format` file, using:
```
The code style is defined via `.clang-format`. Before submitting:
```bash
make format
```
You will need `clang-format` installed to use the `format` command.
Once done with your coding then the released/patch/fixed code can be compiled, with all the normal optimisations, using:
```
./configure --prefix=/usr && make && make install
You will need `clang-format` installed for this step.
To rebuild a "release-like" binary with normal optimisations after development:
```bash
./configure --prefix=/usr
make
sudo make install
```
## Automating the download and compilation process for Debian based distros.
---
Here's a script that will do just that! It will create a directory in your home folder called 'nwipe_master'. It installs all the libraries required to compile the software (build-essential) and all the libraries that nwipe requires (libparted etc). It downloads the latest master copy of nwipe from GitHub. It then compiles the software and then runs the latest nwipe. It doesn't write over the version of nwipe that's installed in the repository (If you had nwipe already installed). To run the latest master version of nwipe manually you would run it like this `sudo ~/nwipe_master/nwipe/src/nwipe`
## Automating download and compilation (Debian-based distros)
You can run the script multiple times; the first time it's run it will install all the libraries; subsequent times it will just say the libraries are up to date. As it always downloads a fresh copy of the nwipe master from GitHub, you can always stay up to date. Just run it to get the latest version of nwipe. It only takes 11 seconds to compile on my i7.
Below is a convenience script that:
If you already have nwipe installed from the repository, you need to take care which version you are running. If you typed `nwipe` from any directory it will always run the original repository copy of nwipe. To run the latest nwipe you have to explicitly tell it where the new copy is, e.g in the directory `~/nwipe_master/nwipe/src`. That's why you would run it by typing `sudo ~/nwipe_master/nwipe/src/nwipe` alternatively you could cd to the directory and run it like this:
1. Creates `~/nwipe_master`
2. Installs all required build dependencies
3. Downloads the latest master branch from GitHub
4. Builds nwipe
5. Runs the freshly built nwipe
```
cd ~/nwipe_master/nwipe/src
./nwipe
```
It does **not** overwrite any nwipe installed from your distributions repository.
Note the ./, that means only look in the current directory for nwipe. if you forgot to type ./ the computer would run the older repository version of nwipe.
Save the following as `buildnwipe`, then make it executable with `chmod +x buildnwipe`:
Once you have copied the script below into a file called buildnwipe, you need to give the file execute permissions `chmod +x buildnwipe` before you can run it.
```
```bash
#!/bin/bash
cd "$HOME"
nwipe_directory="nwipe_master"
mkdir $nwipe_directory
cd $nwipe_directory
sudo apt install build-essential pkg-config automake libncurses5-dev autotools-dev libparted-dev libconfig-dev libconfig++-dev dmidecode readlink smartmontools git
mkdir -p "$nwipe_directory"
cd "$nwipe_directory"
sudo apt install -y \
build-essential \
pkg-config \
automake \
libncurses5-dev \
autotools-dev \
libparted-dev \
libconfig-dev \
libconfig++-dev \
dmidecode \
readlink \
smartmontools \
hdparm \
git
rm -rf nwipe
git clone https://github.com/martijnvanbrummelen/nwipe.git
cd "nwipe"
cd nwipe
./autogen.sh
./configure
make
cd "src"
cd src
sudo ./nwipe
```
## Quick & Easy, USB bootable version of nwipe master for x86_64 systems.
If you want to just try out a bootable version of nwipe you can download [ShredOS](https://github.com/PartialVolume/shredos.x86_64). The ShredOS image is around 60MB and can be written to an USB flash drive in seconds, allowing you to boot straight into the latest version of nwipe. ShredOS is available for x86_64 (64-bit) and i686 (32-bit) CPU architectures and will boot both legacy BIOS and UEFI devices. It comes as .IMG (bootable USB flash drive image) or .ISO (for CD-R/DVD-R). Instructions and download can be found [here](https://github.com/PartialVolume/shredos.x86_64#obtaining-and-writing-shredos-to-a-usb-flash-drive-the-easy-way-).
To run the latest master later on:
```bash
sudo ~/nwipe_master/nwipe/src/nwipe
```
If you already have nwipe from your distros repo installed, remember:
* `nwipe` → runs the packaged version in your `$PATH`
* `./nwipe` in `~/nwipe_master/nwipe/src` → runs the freshly built master
---
## Quick & easy, USB bootable version of nwipe master for x86_64 systems
If you prefer a bootable image containing the latest nwipe master, use **ShredOS**:
* [ShredOS](https://github.com/PartialVolume/shredos.x86_64)
ShredOS:
* is ~60 MB in size,
* can be written to a USB flash drive in seconds,
* boots directly into a minimal environment running the latest nwipe,
* is available for x86_64 (64-bit) and i686 (32-bit),
* supports both legacy BIOS and UEFI.
It is provided as:
* `.img` (USB bootable image), and
* `.iso` (for CD/DVD).
See the ShredOS README for detailed instructions on downloading and writing the image.
---
## Which Linux distro uses the latest nwipe?
See [Repology](https://repology.org/project/nwipe/versions)
And in addition checkout the following distros that all include nwipe:
You can see an overview at:
- [ShredOS](https://github.com/PartialVolume/shredos.x86_64) Always has the latest nwipe release.
- [netboot.xyz](https://github.com/netbootxyz/netboot.xyz) Can network-boot ShredOS.
- [DiskDump](https://github.com/Awire9966/DiskDump) nwipe on Debian livecd, can wipe eMMC chips.
- [partedmagic](https://partedmagic.com)
- [SystemRescueCD](https://www.system-rescue.org)
- [gparted live](https://sourceforge.net/projects/gparted/files/gparted-live-testing/1.2.0-2/)
- [grml](https://grml.org/)
* [Repology](https://repology.org/project/nwipe/versions)
Know of other distros that include nwipe? Then please let us know or issue a PR on this README.md. Thanks.
Distributions known to include nwipe:
* [ShredOS](https://github.com/PartialVolume/shredos.x86_64) always ships the latest nwipe release.
* [netboot.xyz](https://github.com/netbootxyz/netboot.xyz) can network-boot ShredOS.
* [DiskDump](https://github.com/Awire9966/DiskDump) nwipe on a Debian live CD, can wipe eMMC.
* [Parted Magic](https://partedmagic.com)
* [SystemRescue](https://www.system-rescue.org)
* [GParted Live](https://sourceforge.net/projects/gparted/files/gparted-live-testing/1.2.0-2/)
* [Grml](https://grml.org/)
If you know of other distributions that ship nwipe, please let us know or send a PR updating this README.
---
## Bugs
Bugs can be reported on GitHub:
https://github.com/martijnvanbrummelen/nwipe
Bugs, feature requests, and pull requests are welcome on GitHub:
* [https://github.com/martijnvanbrummelen/nwipe](https://github.com/martijnvanbrummelen/nwipe)
Please include:
* your distribution and version,
* the nwipe version (or git commit hash),
* hardware details (especially for I/O-related issues),
* log output and command line options used.
---
## License
GNU General Public License v2.0
nwipe is licensed under the **GNU General Public License v2.0**.
See the `LICENSE` file for details.

View File

@@ -11,6 +11,7 @@ AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
AC_PROG_CXX
PKG_PROG_PKG_CONFIG
# Checks for libraries.

View File

@@ -22,6 +22,26 @@ same as dwipe, with a few changes:
- SIGUSR1 can be used to log the stats of the current wipe.
.TP
- Additional wiping methods and PRNGs.
.TP
- Configurable I/O modes (cached, direct, auto) using large I/O buffers for higher throughput.
.TP
- Support for stable device paths such as \fI/dev/disk/by-id/\fR.
.PP
All PRNG implementations are seeded using the Linux
.BR getrandom (2)
system call instead of reading from
.IR /dev/urandom
via a file descriptor.
.SH DEVICES
.PP
Devices can be specified either as block device nodes (for example
.IR /dev/sda ,
.IR /dev/nvme0n1 ,
.IR /dev/mapper/cryptroot )
or via stable symlinks under
.IR /dev/disk/by-id/ .
nwipe will resolve these paths and operate on the underlying block device.
.SH OPTIONS
.TP
@@ -41,17 +61,54 @@ Power off system on completion of wipe delayed for one minute. During
this one minute delay you can abort the shutdown by typing sudo shutdown -c
.TP
\fB\-\-sync\fR=\fINUM\fR
Will perform a syn after NUM writes (default: 100000).
Specify how often nwipe performs an fdatasync() during cached I/O mode.
The value refers to the number of *device hardware blocks* (commonly 512 or
4096 bytes) written before triggering a sync. Since nwipe now writes using
large multi-megabyte buffers, this value is automatically scaled so the sync
interval in bytes is consistent with historic behaviour.
The default value (100000) results in a sync approximately every 50400 MB,
similar to earlier nwipe releases. This ensures timely detection of I/O errors
while maintaining good throughput.
This setting has no effect when using --directio, as write() returns errors
immediately under direct I/O.
.IP
0 \- fdatasync after the disk is completely written
fdatasync errors not detected until completion.
0 is not recommended as disk errors may cause nwipe
to appear to hang.
0 \- Perform one sync only at the end of the pass.
Not advised; errors may only be detected after the entire wipe.
.IP
1 \- fdatasync after every write.
Warning: Lower values will reduce wipe speeds.
1 \- Sync immediately after each write.
Extremely safe but extremely slow.
.IP
1000 \- fdatasync after 1000 writes.
1000 \- Sync after the equivalent of 1000 hardware blocks.
Useful for testing or more aggressive error detection.
.TP
\fB\-\-cachedio\fR
Use buffered I/O with large write buffers (page cache enabled). This is the
default on most systems and generally gives the best performance for
rotational disks.
.TP
\fB\-\-directio\fR
Use direct I/O with large write buffers. This opens devices with
.BR O_DIRECT
to bypass the page cache. It can be useful when running multiple wipes in
parallel or when you do not want to pollute the system page cache. On some
devices this may be slower than cached I/O.
.TP
\fB\-\-io\-mode\fR=\fIMODE\fR
Select the I/O mode explicitly. \fIMODE\fR can be:
.IP
\fBauto\fR \- (default) automatically choose the best supported mode for
the device and kernel.
.IP
\fBcached\fR \- force buffered I/O.
.IP
\fBdirect\fR \- force direct I/O (\fBO_DIRECT\fR).
.IP
Large I/O buffers are used in all modes to maximise throughput.
.TP
\fB\-\-noblank\fR
Do not perform the final blanking pass after the wipe (default is to blank,
@@ -72,6 +129,9 @@ Do not show the GUI interface. Can only be used with the autonuke option.
Nowait option is automatically invoked with the nogui option.
SIGUSR1 can be used to retrieve the current wiping statistics.
.TP
\fB\-\-pdftag\fR
Enables a field on the PDF that holds a tag that identifies the host computer
.TP
\fB\-v\fR, \fB\-\-verbose\fR
Log more messages, useful for debugging.
.TP
@@ -88,7 +148,7 @@ Please mind that HMG IS5 enhanced always verifies the last (PRNG) pass
regardless of this option.
.TP
\fB\-m\fR, \fB\-\-method\fR=\fIMETHOD\fR
The wiping method (default: dodshort).
The wiping method (default: prng).
.IP
dod522022m / dod \- 7 pass DOD 5220.22\-M method
.IP
@@ -111,6 +171,13 @@ verify_one \- Verifies disk is one (0xFF) filled
is5enh \- HMG IS5 enhanced
.IP
bruce7 \- Schneier Bruce 7-pass mixed pattern
.IP
bmb \- Chinese BMB21-2019 State Secrets Bureau standard.
This method overwrites the device with ones (0xFF),
then zeros (0x00), followed by three passes of PRNG-
generated random data, and finishes with a final pass
of ones (0xFF). Designed to meet the BMB21-2019
technical sanitization requirements.
.TP
\fB\-l\fR, \fB\-\-logfile\fR=\fIFILE\fR
Filename to log to. Default is STDOUT.
@@ -121,8 +188,13 @@ Defaults to ".".
If \fIDIR\fR is set to \fInoPDF\fR no report PDF files are written.
.TP
\fB\-p\fR, \fB\-\-prng\fR=\fIMETHOD\fR
The PRNG option (default: xoroshiro256_prng).
(mersenne|twister|isaac|isaac64|add_lagg_fibonacci_prng|xoroshiro256_prng)
The PRNG option (default: aes_ctr_prng).
(mersenne|twister|isaac|isaac64|add_lagg_fibonacci_prng|xoroshiro256_prng|aes_ctr_prng)
.IP
\fBaes_ctr_prng\fR uses the Linux kernel AF_ALG interface to AES\-CTR as a
cryptographically strong stream generator. It is seeded via
.BR getrandom (2)
and requires kernel crypto support for AES\-CTR.
.TP
\fB\-q\fR, \fB\-\-quiet\fR
Anonymize serial numbers, GUI & logs display:
@@ -137,11 +209,14 @@ Up to ten comma separated devices to be excluded, examples:
--exclude=/dev/sdc
--exclude=/dev/sdc,/dev/sdd
--exclude=/dev/sdc,/dev/sdd,/dev/mapper/cryptswap1
--dev/disk/by-path/pci-0000:00:17.0-ata-1
.SH BUGS
Please see the GitHub site for the latest list:
(https://github.com/martijnvanbrummelen/nwipe/issues)
.SH AUTHOR
nwipe is developed by Martijn van Brummelen <github@brumit.nl>.
.SH "SEE ALSO"
.BR shred (1),
.BR dwipe (1),

View File

@@ -6,5 +6,5 @@ AM_LDFLAGS =
# this lists the binaries to produce, the (non-PHONY, binary) targets in
# the previous manual Makefile
bin_PROGRAMS = nwipe
nwipe_SOURCES = context.h logging.h options.h prng.h version.h temperature.h nwipe.c gui.c method.h pass.c device.c gui.h isaac_rand/isaac_standard.h isaac_rand/isaac_rand.h isaac_rand/isaac_rand.c isaac_rand/isaac64.h isaac_rand/isaac64.c mt19937ar-cok/mt19937ar-cok.c nwipe.h mt19937ar-cok/mt19937ar-cok.h alfg/add_lagg_fibonacci_prng.h alfg/add_lagg_fibonacci_prng.c xor/xoroshiro256_prng.h xor/xoroshiro256_prng.c pass.h device.h logging.c method.c options.c prng.c version.c temperature.c PDFGen/pdfgen.h PDFGen/pdfgen.c create_pdf.c create_pdf.h embedded_images/shred_db.jpg.c embedded_images/shred_db.jpg.h embedded_images/tick_erased.jpg.c embedded_images/tick_erased.jpg.h embedded_images/redcross.c embedded_images/redcross.h hpa_dco.h hpa_dco.c miscellaneous.h miscellaneous.c embedded_images/nwipe_exclamation.jpg.h embedded_images/nwipe_exclamation.jpg.c conf.h conf.c customers.h customers.c hddtemp_scsi/hddtemp.h hddtemp_scsi/scsi.h hddtemp_scsi/scsicmds.h hddtemp_scsi/get_scsi_temp.c hddtemp_scsi/scsi.c hddtemp_scsi/scsicmds.c
nwipe_SOURCES = context.h logging.h options.h prng.h version.h temperature.h nwipe.c gui.c method.h pass.c device.c gui.h isaac_rand/isaac_standard.h isaac_rand/isaac_rand.h isaac_rand/isaac_rand.c isaac_rand/isaac64.h isaac_rand/isaac64.c mt19937ar-cok/mt19937ar-cok.c nwipe.h mt19937ar-cok/mt19937ar-cok.h alfg/add_lagg_fibonacci_prng.h alfg/add_lagg_fibonacci_prng.c xor/xoroshiro256_prng.h xor/xoroshiro256_prng.c aes/aes_ctr_prng.h aes/aes_ctr_prng.cpp pass.h device.h logging.c method.c options.c prng.c version.c temperature.c PDFGen/pdfgen.h PDFGen/pdfgen.c create_pdf.c create_pdf.h embedded_images/shred_db.jpg.c embedded_images/shred_db.jpg.h embedded_images/tick_erased.jpg.c embedded_images/tick_erased.jpg.h embedded_images/redcross.c embedded_images/redcross.h hpa_dco.h hpa_dco.c miscellaneous.h miscellaneous.c embedded_images/nwipe_exclamation.jpg.h embedded_images/nwipe_exclamation.jpg.c conf.h conf.c customers.h customers.c hddtemp_scsi/hddtemp.h hddtemp_scsi/scsi.h hddtemp_scsi/scsicmds.h hddtemp_scsi/get_scsi_temp.c hddtemp_scsi/scsi.c hddtemp_scsi/scsicmds.c
nwipe_LDADD = $(PARTED_LIBS) $(LIBCONFIG)

425
src/aes/aes_ctr_prng.cpp Normal file
View File

@@ -0,0 +1,425 @@
/**
* @file
* @brief High-throughput AES-CTR PRNG for nwipe using Linux AF_ALG.
*
* @details
* This translation unit implements a cryptographically strong pseudorandom
* byte stream based on AES-CTR, leveraging the Linux kernel's crypto API
* (AF_ALG) for hardware-accelerated AES (AES-NI/VAES/NEON/SVE where available).
*
* Motivation:
* - nwipe must supply multi-GB/s of random data to saturate modern NVMe/RAID.
* - User-space OpenSSL-based paths in older builds plateaued around ~250 MB/s
* on some systems due to syscall/memory-copy patterns not tuned for the
* workload.
* - The kernel provides highly optimized AES implementations and scheduling.
*
* Key properties:
* - A single AF_ALG operation socket is opened *once per thread* and reused
* for all generation calls (low syscall overhead).
* - Each generation produces a fixed-size chunk (see CHUNK) by issuing exactly
* two syscalls: `sendmsg()` (to supply IV and length) and `read()` (to fetch
* the keystream).
* - Counter management (increment) is done in user space for determinism.
*
* @warning
* IV (Counter) Encoding:
* This implementation builds the 128-bit AES-CTR IV by storing two 64-bit
* limbs in **little-endian** order (low limb at IV[0..7], high limb at
* IV[8..15]) and then incrementing the 128-bit value in little-endian form.
* This deviates from the big-endian counter semantics commonly assumed by
* RFC-style AES-CTR specifications. The stream remains secure (uniqueness
* of IVs is preserved) but is **not interoperable** with generic RFC-CTR
* streams. See `aes_ctr_prng.h` for a prominent header-level note.
*
* Threading:
* - `tls_op_fd` is thread-local; each thread owns its kernel op-socket.
* - The kernel cipher is re-entrant. No shared state in this TU is writable
* across threads.
*
* Error handling:
* - Functions return `0` on success and `-1` on failure. When underlying
* syscalls fail, `-1` is returned; callers may consult `errno` as usual.
*/
// ============================================================================================
// WHY THIS FILE EXISTS
// --------------------------------------------------------------------------------------------
// nwipe, a secure disk-wiping tool, needs cryptographically strong random data at multi-GB/s
// in order to keep up with todays NVMe and RAID arrays. Users complained when the classic
// user-space OpenSSL path plateaued around ~250 MB/s on modern CPUs. The Linux kernel
// already ships an extremely fast AES implementation (with transparent AES-NI / VAES / NEON
// acceleration) that can be accessed from user space via the AF_ALG socket family. By
// delegating the heavy crypto to the kernel we gain all of the following *for free*:
// • Perfectly tuned instruction selection per CPU (AES-NI, VAES, SVE, etc.)
// • Full cache-line prefetch scheduling written by kernel crypto maintainers
// • Zero-copy when the cipher runs in the same core
// • Automatic fall-back to software if the CPU lacks AES-NI
//
// DESIGN OVERVIEW (TL;DR)
// ----------------------
// ┌─ userspace ───────────────────────────────────────────────────────────────────────────────┐
// │ +-------------------------------+ │
// │ nwipe | aes_ctr_state_t (256 bit) | (1) initialise, store key+counter │
// │ +-------------------------------+ │
// │ │ ▲ │
// │ │ (2) sendmsg() + read() per fixed-size chunk │ │
// └─────────────────────┼───────────────────────────────────────────────────────────┤ kernel │
// │ │ space │
// persistent FD ▼ │ │
// ┌──────────────────────┐ │ │
// │ AF_ALG op socket │ (ctr(aes)) │ │
// └──────────────────────┘ └─────────┘
//
// Public ABI stability:
// ---------------------
// The fd is *not* part of the public state (preserves libnwipe ABI). A TU-local,
// thread-local descriptor is used internally; multiple PRNG instances per thread
// share the same op-socket as needed.
//
// Safety / threading:
// -------------------
// • The kernel cipher is re-entrant. Thread-local fd avoids cross-thread hazards.
// • Counter increments occur in user space; one aes_ctr_state_t per thread.
// ==============================================================================================
#include "aes_ctr_prng.h" // public header (256-bit state, extern "C" API)
#include <sys/socket.h> // socket(), bind(), accept(), sendmsg()
#include <linux/if_alg.h> // AF_ALG constants and skcipher API
#include <unistd.h> // read(), close()
#include <cstring> // memcpy(), memset(), strcpy()
#include <array> // std::array for control buffer
// ----------------------------------------------------------------------------------------------
// CONFIGURABLE CHUNK SIZE
// ----------------------------------------------------------------------------------------------
// The per-call output size (CHUNK) can be configured at compile time via
// AES_CTR_PRNG_CHUNK_BYTES. Default is 128 KiB.
// Example:
// gcc -DAES_CTR_PRNG_CHUNK_BYTES="(64u*1024u)" ...
// ----------------------------------------------------------------------------------------------
#ifndef AES_CTR_PRNG_CHUNK_BYTES
#define AES_CTR_PRNG_CHUNK_BYTES (128u * 1024u) // 128 KiB default
#endif
// ----------------------------------------------------------------------------------------------
// GLOBAL 256-BIT KEY
// ----------------------------------------------------------------------------------------------
// • Loaded from user-supplied seed in aes_ctr_prng_init().
// • Intended to remain constant for the process lifetime (or until re-init).
// • Exposed (non-static) so out-of-TU tests can assert correct key handling.
//
// @note Consider zeroizing on shutdown to avoid key retention in core dumps.
// ----------------------------------------------------------------------------------------------
unsigned char global_key[32];
// ----------------------------------------------------------------------------------------------
// THREAD-LOCAL OPERATION SOCKET (one per nwipe thread)
// ----------------------------------------------------------------------------------------------
// Portable TLS qualifier: C++11 `thread_local` or GCC/Clang `__thread` for C builds.
//
// @invariant tls_op_fd == -1 ⇒ this thread has not opened the op-socket yet.
// tls_op_fd >= 0 ⇒ valid AF_ALG operation socket for "ctr(aes)".
//
// @thread_safety Thread-local; no synchronization required.
// ----------------------------------------------------------------------------------------------
#if defined(__cplusplus) && __cplusplus >= 201103L
#define PRNG_THREAD_LOCAL thread_local
#else
#define PRNG_THREAD_LOCAL __thread
#endif
PRNG_THREAD_LOCAL static int tls_op_fd = -1; // -1 ⇒ not yet opened in this thread
// ----------------------------------------------------------------------------------------------
// CONSTANTS / INTERNAL HELPERS
// ----------------------------------------------------------------------------------------------
namespace {
/**
* @brief AES block size in bytes (by specification).
*/
constexpr std::size_t AES_BLOCK = 16u;
/**
* @brief Fixed-size generation granularity per kernel call.
* @details
* Adjust at build time via AES_CTR_PRNG_CHUNK_BYTES to balance syscall
* overhead vs. latency and memory traffic.
* Typical values: 16 KiB (legacy default), 64 KiB, 128 KiB.
*/
constexpr std::size_t CHUNK = AES_CTR_PRNG_CHUNK_BYTES;
static_assert(CHUNK % AES_BLOCK == 0,
"AES_CTR_PRNG_CHUNK_BYTES must be a multiple of AES_BLOCK (16 bytes)");
/// Number of AES-CTR blocks produced per CHUNK.
constexpr std::size_t BLOCKS_PER_CHUNK = CHUNK / AES_BLOCK;
/**
* @brief Store a 64-bit integer in little-endian byte order.
*
* @param v 64-bit value.
* @param buf Destination pointer; must point to at least 8 writable bytes.
*
* @note
* This function enforces a little-endian layout regardless of host endianness.
* For hot paths you may consider an optimized version using memcpy/bswap on
* big-endian hosts instead of byte-wise stores.
*/
static inline void store64_le(uint64_t v, unsigned char *buf)
{
for (int i = 0; i < 8; ++i)
buf[i] = static_cast<unsigned char>(v >> (8 * i));
}
/**
* @class ControlBuilder
* @brief Helper to assemble `msghdr` and control messages for AF_ALG.
*
* @details
* Builds the control payload for one `sendmsg()` call against an AF_ALG
* skcipher operation socket:
* - Control message #1: `ALG_SET_OP = ALG_OP_ENCRYPT`
* - Control message #2: `ALG_SET_IV` with a 128-bit IV
* - Data iovec: points at the plaintext buffer (here: zero-bytes of length CHUNK)
*
* All data structures live on the stack; constructing this helper is O(1).
*
* @note
* The kernel expects `ivlen` as a host-endian u32 followed by `iv` bytes.
* "Network order not required" is intentional for AF_ALG skcipher IVs.
*/
class ControlBuilder {
public:
/**
* @param iv 128-bit IV (counter value), passed as 16 bytes.
* @param plain Pointer to plaintext buffer (here: all-zero array).
* @param len Plaintext length in bytes; determines keystream length.
*/
ControlBuilder(const unsigned char iv[16], void *plain, size_t len)
{
// ---------- Data iovec ----------
iov_.iov_base = plain;
iov_.iov_len = len;
// ---------- msghdr --------------
msg_.msg_name = nullptr; // already bound via bind()
msg_.msg_namelen = 0;
msg_.msg_iov = &iov_;
msg_.msg_iovlen = 1;
msg_.msg_control = control_.data();
msg_.msg_controllen = control_.size();
msg_.msg_flags = 0;
// ---------- CMSG #1 : ALG_SET_OP = ENCRYPT ----------
cmsghdr *c1 = CMSG_FIRSTHDR(&msg_);
c1->cmsg_level = SOL_ALG;
c1->cmsg_type = ALG_SET_OP;
c1->cmsg_len = CMSG_LEN(sizeof(uint32_t));
*reinterpret_cast<uint32_t*>(CMSG_DATA(c1)) = ALG_OP_ENCRYPT;
// ---------- CMSG #2 : ALG_SET_IV ----------
cmsghdr *c2 = CMSG_NXTHDR(&msg_, c1);
c2->cmsg_level = SOL_ALG;
c2->cmsg_type = ALG_SET_IV;
c2->cmsg_len = CMSG_LEN(sizeof(uint32_t) + 16);
uint32_t ivlen = 16; // host endian; not network order
std::memcpy(CMSG_DATA(c2), &ivlen, sizeof(ivlen));
std::memcpy(CMSG_DATA(c2) + sizeof(ivlen), iv, 16);
}
/// @return Prepared msghdr suitable for `sendmsg()`.
struct msghdr *msg() { return &msg_; }
private:
// Control buffer sufficient for both control messages.
std::array<char,
CMSG_SPACE(sizeof(uint32_t)) +
CMSG_SPACE(sizeof(uint32_t) + 16)> control_{};
struct msghdr msg_{};
struct iovec iov_{};
};
/**
* @brief Open a "ctr(aes)" skcipher operation socket via AF_ALG.
*
* @details
* Performs the `socket()` → `bind()` → `setsockopt(ALG_SET_KEY)` → `accept()`
* sequence. The returned fd is the operation socket used for `sendmsg()`+`read()`.
*
* @param key AES key (32 bytes for AES-256).
* @return Operation socket fd (>= 0) on success, or -1 on failure.
*
* @warning
* This function does not set `FD_CLOEXEC` nor handle `SIGPIPE`. Consider using
* `SOCK_CLOEXEC` on `socket()` and `accept4()` and `MSG_NOSIGNAL` on `sendmsg()`
* in hardened builds.
*/
static int open_ctr_socket(const unsigned char key[32])
{
// 1. Create transform socket (AF_ALG family).
int tfm = ::socket(AF_ALG, SOCK_SEQPACKET, 0);
if (tfm < 0) return -1;
// 2. Describe requested algorithm: type = "skcipher", name = "ctr(aes)".
sockaddr_alg sa = {};
sa.salg_family = AF_ALG;
std::strcpy(reinterpret_cast<char*>(sa.salg_type), "skcipher");
std::strcpy(reinterpret_cast<char*>(sa.salg_name), "ctr(aes)");
if (::bind(tfm, reinterpret_cast<sockaddr*>(&sa), sizeof(sa)) < 0) {
::close(tfm); return -1;
}
// 3. Upload 256-bit key.
if (::setsockopt(tfm, SOL_ALG, ALG_SET_KEY, key, 32) < 0) {
::close(tfm); return -1;
}
// 4. Accept operation socket — the fd we will use for sendmsg/read.
int op = ::accept(tfm, nullptr, nullptr);
::close(tfm); // transform socket no longer needed
return op; // may be -1 on error
}
/**
* @brief Increment a 128-bit little-endian counter by @p n AES blocks.
*
* @details
* The counter is represented as two 64-bit little-endian limbs in state->s[0..1].
* The increment is performed modulo 2^128 with carry propagation from low to high.
*
* @param st PRNG state with s[0]=lo, s[1]=hi limbs.
* @param n Number of AES blocks to add.
*
* @note
* This is **little-endian** counter arithmetic; see the big file-level warning
* about non-RFC CTR semantics.
*/
static void ctr_add(aes_ctr_state_t *st, uint64_t n)
{
uint64_t old = st->s[0];
st->s[0] += n;
if (st->s[0] < old) ++st->s[1]; // handle carry
}
} // namespace (anonymous)
// =================================================================================================
// PUBLIC C API IMPLEMENTATION
// =================================================================================================
extern "C" {
/**
* @brief Initialize PRNG state and lazily open the per-thread AF_ALG socket.
*
* @param[out] state Pointer to PRNG state (must be non-null).
* @param[in] init_key Seed as an array of unsigned long; must provide >= 32 bytes.
* @param[in] key_length Number of `unsigned long` words in @p init_key.
*
* @retval 0 Success.
* @retval -1 Invalid parameters or AF_ALG setup failure.
*
* @details
* - Zeroes the entire state and copies the first 128 bits of the seed into the
* 128-bit counter (little-endian limb order).
* - Saves the first 256 bits as the AES-256 key in @c global_key.
* - Opens the AF_ALG operation socket for "ctr(aes)" on first call in this
* thread and stores the fd in thread-local storage.
*
* @warning
* The chosen IV scheme is little-endian and not RFC-interoperable.
* Do not mix with external AES-CTR generators expecting big-endian counters.
*/
int aes_ctr_prng_init(aes_ctr_state_t *state,
unsigned long init_key[],
unsigned long key_length)
{
if (!state || !init_key || key_length * sizeof(unsigned long) < 32)
return -1;
// Zero entire state, then put seed[0..15] into counter (LE limbs).
std::memset(state, 0, sizeof(*state));
std::memcpy(state->s, init_key, sizeof(uint64_t) * 2);
// Remember full AES-256 key (32 bytes) for possible re-opens.
std::memcpy(global_key, init_key, 32);
// Open per-thread socket on first call in this thread.
if (tls_op_fd == -1) {
tls_op_fd = open_ctr_socket(global_key);
if (tls_op_fd < 0) return -1;
}
return 0;
}
/**
* @brief Produce exactly CHUNK bytes of keystream into @p bufpos.
*
* @param[in] state PRNG state (counter source).
* @param[out] bufpos Destination buffer; must hold at least CHUNK bytes.
*
* @retval 0 Success (CHUNK bytes written).
* @retval -1 Parameter error or syscall failure.
*
* @details
* Sequence per call:
* 1. Assemble a 128-bit IV by storing s[0] (low) and s[1] (high) as
* little-endian 64-bit words into a 16-byte buffer.
* 2. Build the AF_ALG control message (ALG_SET_OP=ENCRYPT, ALG_SET_IV=IV)
* and data iovec pointing to a static all-zero plaintext of length CHUNK.
* 3. `sendmsg()` the request and `read()` back exactly CHUNK bytes of
* ciphertext — which, because plaintext is zero, equals the keystream.
* 4. Increment the 128-bit counter by `BLOCKS_PER_CHUNK`.
*
* @note
* The zero-plaintext buffer is static and zero-initialized once; the kernel
* will not read uninitialized memory. Using zero plaintext is standard for
* obtaining the raw AES-CTR keystream.
*/
int aes_ctr_prng_genrand_128k_to_buf(aes_ctr_state_t *state,
unsigned char *bufpos)
{
if (!state || !bufpos || tls_op_fd < 0)
return -1;
// --- Construct 128-bit IV from counter (little-endian limbs) -------------
unsigned char iv[16];
store64_le(state->s[0], iv); // little-endian low limb
store64_le(state->s[1], iv + 8); // little-endian high limb
// --- Build msghdr ---------------------------------------------------------
static unsigned char zeros[CHUNK] = {0}; // static → zero-initialised once
ControlBuilder ctl(iv, zeros, CHUNK);
// --- sendmsg() + read() ---------------------------------------------------
if (::sendmsg(tls_op_fd, ctl.msg(), 0) != (ssize_t)CHUNK) return -1;
if (::read (tls_op_fd, bufpos, CHUNK) != (ssize_t)CHUNK) return -1;
// --- Advance counter ------------------------------------------------------
ctr_add(state, BLOCKS_PER_CHUNK);
return 0;
}
/**
* @brief Optional cleanup helper (explicitly closes the per-thread op-socket).
*
* @retval 0 Always succeeds.
*
* @details
* The kernel will close FDs at process exit, but explicit shutdown helps
* test harnesses and avoids keeping descriptors alive across exec().
* Consider zeroizing @c global_key here for defense-in-depth.
*/
int aes_ctr_prng_shutdown(void)
{
if (tls_op_fd >= 0) {
::close(tls_op_fd);
tls_op_fd = -1;
}
return 0;
}
} // extern "C"

60
src/aes/aes_ctr_prng.h Normal file
View File

@@ -0,0 +1,60 @@
#ifndef AES_CTR_PRNG_H
#define AES_CTR_PRNG_H
/* Minimal public header for AES-256-CTR PRNG (Linux AF_ALG backend)
*
* Implementation detail:
* - Uses a persistent AF_ALG "ctr(aes)" operation socket opened at init.
* - No socket setup overhead during generation only sendmsg + read.
* - Thread-safety: Not safe unless externally synchronized.
*
* Public state remains exactly 256 bits (4×64-bit words) to allow for
* minimalistic integration in nwipe and similar tools.
*/
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* PRNG state: exactly 256 bits (4 × 64-bit words)
*
* s[0] = counter low
* s[1] = counter high
* s[2], s[3] = reserved
*/
typedef struct aes_ctr_state_s {
uint64_t s[4];
} aes_ctr_state_t;
/* Initialize with >=32 bytes of seed (init_key as unsigned-long array)
*
* On first call, also opens the persistent AF_ALG socket.
* Returns 0 on success, -1 on failure.
*/
int aes_ctr_prng_init(aes_ctr_state_t *state,
unsigned long init_key[],
unsigned long key_length);
/* Generate one 128 KiB chunk of random data into bufpos.
*
* Returns 0 on success, -1 on failure.
* Uses the persistent AF_ALG socket.
*/
int aes_ctr_prng_genrand_128k_to_buf(aes_ctr_state_t *state,
unsigned char *bufpos);
/* Optional: Close the persistent AF_ALG socket at program shutdown.
*
* Not required by nwipe, but recommended for tools embedding this code.
*/
int aes_ctr_prng_shutdown(void);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* AES_CTR_PRNG_H */

View File

@@ -122,6 +122,7 @@ int nwipe_conf_init()
*/
nwipe_conf_populate( "PDF_Certificate.PDF_Enable", "ENABLED" );
nwipe_conf_populate( "PDF_Certificate.PDF_Preview", "DISABLED" );
nwipe_conf_populate( "PDF_Certificate.PDF_tag", "DISABLED" );
/**
* The currently selected customer that will be printed on the report

View File

@@ -50,8 +50,9 @@
struct pdf_doc* pdf;
struct pdf_object* page;
char model_header[50] = ""; /* Model text in the header */
char serial_header[30] = ""; /* Serial number text in the header */
char model_header[55] = ""; /* Model text in the header */
char serial_header[35] = ""; /* Serial number text in the header */
char hostid_header[DMIDECODE_RESULT_LENGTH + 15] = ""; /* host identification, UUID, serial number, system tag */
char barcode[100] = ""; /* Contents of the barcode, i.e model:serial */
char pdf_footer[MAX_PDF_FOOTER_TEXT_LENGTH];
float height;
@@ -129,10 +130,19 @@ int create_pdf( nwipe_context_t* ptr )
/* Obtain page page_width */
page_width = pdf_page_width( page_1 );
/**********************************************
* Initialise serial no. to unknown if empty
*/
if( c->device_serial_no[0] == 0 )
{
snprintf( c->device_serial_no, sizeof( c->device_serial_no ), "Unknown" );
}
/*********************************************************************
* Create header and footer on page 1, with the exception of the green
* tick/red icon which is set from the 'status' section below
*/
pdf_header_footer_text( c, "Page 1 - Erasure Status" );
/* ------------------------ */
@@ -228,10 +238,6 @@ int create_pdf( nwipe_context_t* ptr )
* Serial no.
*/
pdf_add_text( pdf, NULL, "Serial:", 12, 340, 410, PDF_GRAY );
if( c->device_serial_no[0] == 0 )
{
snprintf( c->device_serial_no, sizeof( c->device_serial_no ), "Unknown" );
}
pdf_set_font( pdf, "Helvetica-Bold" );
pdf_add_text( pdf, NULL, c->device_serial_no, text_size_data, 380, 410, PDF_BLACK );
pdf_set_font( pdf, "Helvetica" );
@@ -994,19 +1000,38 @@ void create_header_and_footer( nwipe_context_t* c, char* page_title )
void pdf_header_footer_text( nwipe_context_t* c, char* page_title )
{
extern char dmidecode_system_serial_number[DMIDECODE_RESULT_LENGTH];
extern char dmidecode_system_uuid[DMIDECODE_RESULT_LENGTH];
pdf_add_text_wrap( pdf, NULL, pdf_footer, 12, 0, 30, PDF_BLACK, page_width, PDF_ALIGN_CENTER, &height );
pdf_add_line( pdf, NULL, 50, 50, 550, 50, 3, PDF_BLACK );
pdf_add_line( pdf, NULL, 50, 650, 550, 650, 3, PDF_BLACK );
pdf_add_line( pdf, NULL, 50, 50, 550, 50, 3, PDF_BLACK ); // Footer full width Line
pdf_add_line( pdf, NULL, 50, 650, 550, 650, 3, PDF_BLACK ); // Header full width Line
pdf_add_line( pdf, NULL, 175, 728, 425, 728, 3, PDF_BLACK ); // Header Page number, disk model divider line
pdf_add_image_data( pdf, NULL, 45, 665, 100, 100, bin2c_shred_db_jpg, 27063 );
pdf_set_font( pdf, "Helvetica-Bold" );
snprintf( model_header, sizeof( model_header ), " %s: %s ", "Model", c->device_model );
pdf_add_text_wrap( pdf, NULL, model_header, 14, 0, 755, PDF_BLACK, page_width, PDF_ALIGN_CENTER, &height );
snprintf( serial_header, sizeof( serial_header ), " %s: %s ", "S/N", c->device_serial_no );
pdf_add_text_wrap( pdf, NULL, serial_header, 14, 0, 735, PDF_BLACK, page_width, PDF_ALIGN_CENTER, &height );
if( nwipe_options.PDFtag )
{
snprintf( model_header, sizeof( model_header ), " %s: %s ", "Disk Model", c->device_model );
pdf_add_text_wrap( pdf, NULL, model_header, 11, 0, 710, PDF_BLACK, page_width, PDF_ALIGN_CENTER, &height );
snprintf( serial_header, sizeof( serial_header ), " %s: %s ", "Disk S/N", c->device_serial_no );
pdf_add_text_wrap( pdf, NULL, serial_header, 11, 0, 695, PDF_BLACK, page_width, PDF_ALIGN_CENTER, &height );
snprintf( hostid_header, sizeof( hostid_header ), " %s: %s ", "System S/N", dmidecode_system_serial_number );
pdf_add_text_wrap( pdf, NULL, hostid_header, 11, 0, 680, PDF_BLACK, page_width, PDF_ALIGN_CENTER, &height );
snprintf( hostid_header, sizeof( hostid_header ), " %s: %s ", "System uuid", dmidecode_system_uuid );
pdf_add_text_wrap( pdf, NULL, hostid_header, 11, 0, 665, PDF_BLACK, page_width, PDF_ALIGN_CENTER, &height );
}
else
{
snprintf( model_header, sizeof( model_header ), " %s: %s ", "Disk Model", c->device_model );
pdf_add_text_wrap( pdf, NULL, model_header, 11, 0, 695, PDF_BLACK, page_width, PDF_ALIGN_CENTER, &height );
snprintf( serial_header, sizeof( serial_header ), " %s: %s ", "Disk S/N", c->device_serial_no );
pdf_add_text_wrap( pdf, NULL, serial_header, 11, 0, 680, PDF_BLACK, page_width, PDF_ALIGN_CENTER, &height );
}
pdf_set_font( pdf, "Helvetica" );
pdf_add_text_wrap( pdf, NULL, "Disk Erasure Report", 24, 0, 695, PDF_BLACK, page_width, PDF_ALIGN_CENTER, &height );
pdf_add_text_wrap( pdf, NULL, "Disk Erasure Report", 24, 0, 760, PDF_BLACK, page_width, PDF_ALIGN_CENTER, &height );
snprintf( barcode, sizeof( barcode ), "%s:%s", c->device_model, c->device_serial_no );
pdf_add_text_wrap( pdf, NULL, page_title, 14, 0, 670, PDF_BLACK, page_width, PDF_ALIGN_CENTER, &height );
pdf_add_text_wrap( pdf, NULL, page_title, 14, 0, 740, PDF_BLACK, page_width, PDF_ALIGN_CENTER, &height );
pdf_add_barcode( pdf, NULL, PDF_BARCODE_128A, 100, 790, 400, 25, barcode, PDF_BLACK );
}

View File

@@ -49,6 +49,120 @@
int check_device( nwipe_context_t*** c, PedDevice* dev, int dcount );
char* trim( char* str );
static void nwipe_normalize_serial( char* serial );
/*
* Resolve a device path (including /dev/disk/by-* symlinks) to its
* underlying block device id (dev_t).
*
* Returns 0 on success and fills *out_rdev.
* Returns -1 on error or if the path is not a block device.
*/
static int nwipe_path_to_rdev( const char* path, dev_t* out_rdev )
{
struct stat st;
if( path == NULL || out_rdev == NULL )
{
errno = EINVAL;
return -1;
}
/*
* stat() follows symlinks by default, which is what we want for
* persistent names in /dev/disk/by-id, /dev/disk/by-path, etc.
*/
if( stat( path, &st ) != 0 )
{
return -1;
}
if( !S_ISBLK( st.st_mode ) )
{
/* Not a block device node. */
errno = ENOTBLK;
return -1;
}
*out_rdev = st.st_rdev;
return 0;
}
/*
* Check whether a candidate device node should be excluded based on the
* --exclude list. Matching is done primarily by device identity
* (major/minor via st_rdev), so persistent names like /dev/disk/by-id/*
* are safe. We keep legacy string-based matching as a fallback.
*
* Returns 1 if the candidate should be excluded, 0 otherwise.
*/
static int nwipe_is_excluded_device( const char* candidate_devnode )
{
dev_t cand_rdev;
int have_cand_rdev;
int i;
/* Try to resolve the candidate device to a dev_t. */
have_cand_rdev = ( nwipe_path_to_rdev( candidate_devnode, &cand_rdev ) == 0 );
for( i = 0; i < MAX_NUMBER_EXCLUDED_DRIVES; i++ )
{
const char* ex = nwipe_options.exclude[i];
dev_t ex_rdev;
int have_ex_rdev;
const char* base;
/* Empty slot in the exclude array. */
if( ex == NULL || ex[0] == 0 )
{
continue;
}
/*
* First try: both candidate and exclude entry resolve to block
* devices; compare device ids (major/minor).
*/
have_ex_rdev = ( nwipe_path_to_rdev( ex, &ex_rdev ) == 0 );
if( have_cand_rdev && have_ex_rdev && ex_rdev == cand_rdev )
{
nwipe_log( NWIPE_LOG_NOTICE, "Device %s excluded as per command line option -e", candidate_devnode );
return 1;
}
/*
* Fallback 1: exact string match. This keeps compatibility with
* older usage like --exclude=/dev/sda or --exclude=/dev/mapper/cryptswap1.
*/
if( strcmp( candidate_devnode, ex ) == 0 )
{
nwipe_log( NWIPE_LOG_NOTICE, "Device %s excluded as per command line option -e", candidate_devnode );
return 1;
}
/*
* Fallback 2: match against the basename only, so that an
* exclude entry like "sda" still works even if the full path is
* /dev/sda.
*/
base = strrchr( candidate_devnode, '/' );
if( base != NULL )
{
base++;
}
else
{
base = candidate_devnode;
}
if( strcmp( base, ex ) == 0 )
{
nwipe_log( NWIPE_LOG_NOTICE, "Device %s excluded as per command line option -e", candidate_devnode );
return 1;
}
}
return 0;
}
extern int terminate_signal;
@@ -138,15 +252,11 @@ int check_device( nwipe_context_t*** c, PedDevice* dev, int dcount )
bus = 0;
/* Check whether this drive is on the excluded drive list ? */
idx = 0;
while( idx < MAX_NUMBER_EXCLUDED_DRIVES )
/* Check whether this drive is on the excluded drive list. */
if( nwipe_is_excluded_device( dev->path ) )
{
if( !strcmp( dev->path, nwipe_options.exclude[idx++] ) )
{
nwipe_log( NWIPE_LOG_NOTICE, "Device %s excluded as per command line option -e", dev->path );
return 0;
}
/* Already logged inside nwipe_is_excluded_device(). */
return 0;
}
/* Check whether the user has specified using the --nousb option
@@ -251,22 +361,26 @@ int check_device( nwipe_context_t*** c, PedDevice* dev, int dcount )
next_device->device_size_text = next_device->device_size_txt;
next_device->result = -2;
/* Attempt to get serial number of device.
*/
next_device->device_serial_no[0] = 0; /* initialise the serial number */
/* Attempt to get serial number of device. */
next_device->device_serial_no[0] = '\0'; /* initialise the serial number */
if( ( fd = open( next_device->device_name = dev->path, O_RDONLY ) ) == ERR )
fd = open( next_device->device_name = dev->path, O_RDONLY );
if( fd == ERR )
{
nwipe_log( NWIPE_LOG_WARNING, "Unable to open device %s to obtain serial number", next_device->device_name );
}
/*
* We don't check the ioctl return status because there are plenty of situations where a serial number may not be
* returned by ioctl such as USB drives, logical volumes, encryted volumes, so the log file would have multiple
* benign ioctl errors reported which isn't necessarily a problem.
*/
ioctl( fd, HDIO_GET_IDENTITY, &next_device->identity );
close( fd );
else
{
/*
* We don't check the ioctl return status because there are plenty of
* situations where a serial number may not be returned by ioctl such as
* USB drives, logical volumes, encrypted volumes, so the log file
* would have multiple benign ioctl errors reported which isn't
* necessarily a problem.
*/
ioctl( fd, HDIO_GET_IDENTITY, &next_device->identity );
close( fd );
}
for( idx = 0; idx < NWIPE_SERIALNUMBER_LENGTH; idx++ )
{
@@ -316,6 +430,9 @@ int check_device( nwipe_context_t*** c, PedDevice* dev, int dcount )
* of those strings we should explicitly terminate the string */
next_device->device_serial_no[NWIPE_SERIALNUMBER_LENGTH] = 0;
/* Ensure the serial number cannot break the ncurses UI. */
nwipe_normalize_serial( next_device->device_serial_no );
/* Initialise the variables that toggle the [size][temp c] with [HPA status]
* Not currently used, but may be used in the future or for other purposes
*/
@@ -515,6 +632,39 @@ char* trim( char* str )
return str;
}
/*
* Remove non-ASCII and control characters from a serial number string,
* then trim leading/trailing whitespace and left-justify it in-place.
* This keeps the value safe for ncurses output.
*/
static void nwipe_normalize_serial( char* serial )
{
unsigned char ch;
char* src;
char* dst;
if( serial == NULL )
{
return;
}
src = dst = serial;
while( ( ch = (unsigned char) *src++ ) != '\0' )
{
if( isascii( ch ) && !iscntrl( ch ) )
{
*dst++ = (char) ch;
}
/* Alle remaining control characters will be dropped ( >0x7F) */
}
*dst = '\0';
/* Use existing trim() function */
trim( serial );
}
int nwipe_get_device_bus_type_and_serialno( char* device, nwipe_device_t* bus, int* is_ssd, char* serialnumber )
{
/* The caller provides a string that contains the device, i.e. /dev/sdc, also a pointer
@@ -562,6 +712,14 @@ int nwipe_get_device_bus_type_and_serialno( char* device, nwipe_device_t* bus, i
"serial number:", "lu wwn device id:", "logical unit id:", "" /* Don't remove this empty string !, important */
};
/* Ensure the serialnumber buffer is in a defined state even if we
* never find a "serial number:" line in smartctl output.
*/
if( serialnumber != NULL )
{
memset( serialnumber, 0, NWIPE_SERIALNUMBER_LENGTH + 1 );
}
/* Initialise return value */
set_return_value = 0;

View File

@@ -1640,14 +1640,14 @@ void nwipe_gui_prng( void )
extern nwipe_prng_t nwipe_twister;
extern nwipe_prng_t nwipe_isaac;
extern nwipe_prng_t nwipe_isaac64;
extern nwipe_prng_t nwipe_aes_ctr_prng;
extern nwipe_prng_t nwipe_xoroshiro256_prng;
extern nwipe_prng_t nwipe_add_lagg_fibonacci_prng;
extern nwipe_prng_t nwipe_aes_ctr_prng;
extern int terminate_signal;
/* The number of implemented PRNGs. */
const int count = 5;
const int count = 6;
/* The first tabstop. */
const int tab1 = 2;
@@ -1689,6 +1689,10 @@ void nwipe_gui_prng( void )
{
focus = 4;
}
if( nwipe_options.prng == &nwipe_aes_ctr_prng )
{
focus = 5;
}
do
{
/* Clear the main window. */
@@ -1705,6 +1709,7 @@ void nwipe_gui_prng( void )
mvwprintw( main_window, yy++, tab1, " %s", nwipe_isaac64.label );
mvwprintw( main_window, yy++, tab1, " %s", nwipe_add_lagg_fibonacci_prng.label );
mvwprintw( main_window, yy++, tab1, " %s", nwipe_xoroshiro256_prng.label );
mvwprintw( main_window, yy++, tab1, " %s", nwipe_aes_ctr_prng.label );
yy++;
/* Print the cursor. */
@@ -1879,6 +1884,30 @@ void nwipe_gui_prng( void )
tab1,
"especially for legacy systems, due to its efficiency and minimal demands. " );
break;
case 5:
mvwprintw(
main_window, yy++, tab1, "AES-256 in Counter Mode (CTR), securely implemented by Fabian Druschke" );
mvwprintw( main_window, yy++, tab1, "using the Linux kernel's AF_ALG cryptographic API for efficient" );
mvwprintw( main_window, yy++, tab1, "pseudo-random data generation with minimal user-space overhead." );
mvwprintw( main_window,
yy++,
tab1,
" " );
mvwprintw(
main_window, yy++, tab1, "This integration leverages potential hardware acceleration via AES-NI," );
mvwprintw(
main_window, yy++, tab1, "making AES-256 CTR ideal for secure and fast data wiping in nwipe." );
mvwprintw( main_window,
yy++,
tab1,
" " );
mvwprintw( main_window,
yy++,
tab1,
"Compliant with NIST SP 800-38A, it is a global standard for encryption." );
mvwprintw(
main_window, yy++, tab1, "Designed for 64-bit Linux systems with kernel CryptoAPI support." );
break;
}
/* switch */
@@ -1949,6 +1978,11 @@ void nwipe_gui_prng( void )
{
nwipe_options.prng = &nwipe_xoroshiro256_prng;
}
if( focus == 5 )
{
nwipe_options.prng = &nwipe_aes_ctr_prng;
}
return;
case KEY_BACKSPACE:

View File

@@ -507,6 +507,11 @@ void nwipe_log_OSinfo()
return;
}
/* Globally accessable dmidecode host identifiable data. */
char dmidecode_system_serial_number[DMIDECODE_RESULT_LENGTH];
char dmidecode_system_uuid[DMIDECODE_RESULT_LENGTH];
char dmidecode_baseboard_serial_number[DMIDECODE_RESULT_LENGTH];
int nwipe_log_sysinfo()
{
FILE* fp;
@@ -559,9 +564,16 @@ int nwipe_log_sysinfo()
char cmd[sizeof( dmidecode_keywords ) + sizeof( dmidecode_command2 )];
unsigned int keywords_idx;
unsigned int i;
keywords_idx = 0;
/* Initialise every character in the string with zero */
for( i = 0; i < DMIDECODE_RESULT_LENGTH; i++ )
{
dmidecode_system_serial_number[i] = 0;
}
p_dmidecode_command = 0;
if( system( "which dmidecode > /dev/null 2>&1" ) )
@@ -619,11 +631,35 @@ int nwipe_log_sysinfo()
else
{
nwipe_log( NWIPE_LOG_INFO, "%s = %s", &dmidecode_keywords[keywords_idx][0][0], path );
/* if system-serial-number copy result to extern string */
if( keywords_idx == 5 )
{
strncpy( dmidecode_system_serial_number, path, DMIDECODE_RESULT_LENGTH );
dmidecode_system_serial_number[DMIDECODE_RESULT_LENGTH - 1] = 0;
}
if( keywords_idx == 6 )
{
strncpy( dmidecode_system_uuid, path, DMIDECODE_RESULT_LENGTH );
dmidecode_system_uuid[DMIDECODE_RESULT_LENGTH - 1] = 0;
}
}
}
else
{
nwipe_log( NWIPE_LOG_INFO, "%s = %s", &dmidecode_keywords[keywords_idx][0][0], path );
/* if system-serial-number copy result to extern string */
if( keywords_idx == 5 )
{
strncpy( dmidecode_system_serial_number, path, DMIDECODE_RESULT_LENGTH );
dmidecode_system_serial_number[DMIDECODE_RESULT_LENGTH - 1] = 0;
}
if( keywords_idx == 6 )
{
strncpy( dmidecode_system_uuid, path, DMIDECODE_RESULT_LENGTH );
dmidecode_system_uuid[DMIDECODE_RESULT_LENGTH - 1] = 0;
}
}
}
/* close */

View File

@@ -29,6 +29,8 @@
#define OS_info_Line_offset 31 /* OS_info line offset in log */
#define OS_info_Line_Length 48 /* OS_info line length */
#define DMIDECODE_RESULT_LENGTH 64
typedef enum nwipe_log_t_ {
NWIPE_LOG_NONE = 0,
NWIPE_LOG_DEBUG, // Output only when --verbose option used on cmd line.

View File

@@ -32,6 +32,49 @@
/* The global options struct. */
nwipe_options_t nwipe_options;
/*
* Executes the CPUID instruction and fills out the provided variables with the results.
* eax: The function/subfunction number to query with CPUID.
* *eax_out, *ebx_out, *ecx_out, *edx_out: Pointers to variables where the CPUID output will be stored.
*/
void cpuid( uint32_t eax, uint32_t* eax_out, uint32_t* ebx_out, uint32_t* ecx_out, uint32_t* edx_out )
{
#if defined( __i386__ ) || defined( __x86_64__ ) /* only on x86 */
#if defined( _MSC_VER ) /* MSVC */
int r[4];
__cpuid( r, eax );
*eax_out = r[0];
*ebx_out = r[1];
*ecx_out = r[2];
*edx_out = r[3];
#elif defined( __GNUC__ ) /* GCC/Clang */
__asm__ __volatile__( "cpuid"
: "=a"( *eax_out ), "=b"( *ebx_out ), "=c"( *ecx_out ), "=d"( *edx_out )
: "a"( eax ) );
#else
#error "Unsupported compiler"
#endif
#else /* not-x86 */
(void) eax;
*eax_out = *ebx_out = *ecx_out = *edx_out = 0; /* CPUID = 0 */
#endif
}
/*
* Checks if the AES-NI instruction set is supported by the processor.
* Returns 1 (true) if supported, 0 (false) otherwise.
*/
int has_aes_ni( void )
{
#if defined( __i386__ ) || defined( __x86_64__ ) /* only for x86 */
uint32_t eax, ebx, ecx, edx;
cpuid( 1, &eax, &ebx, &ecx, &edx );
return ( ecx & ( 1u << 25 ) ) != 0; /* Bit 25 = AES-NI */
#else /* ARM, RISC-V … */
return 0; /* no AES-NI */
#endif
}
int nwipe_options_parse( int argc, char** argv )
{
extern char* optarg; // The working getopt option argument.
@@ -44,6 +87,7 @@ int nwipe_options_parse( int argc, char** argv )
extern nwipe_prng_t nwipe_isaac64;
extern nwipe_prng_t nwipe_add_lagg_fibonacci_prng;
extern nwipe_prng_t nwipe_xoroshiro256_prng;
extern nwipe_prng_t nwipe_aes_ctr_prng;
/* The getopt() result holder. */
int nwipe_opt;
@@ -122,6 +166,9 @@ int nwipe_options_parse( int argc, char** argv )
{ "cachedio", no_argument, 0, 0 },
{ "io-mode", required_argument, 0, 0 },
/* Enables a field on the PDF that holds a tag that identifies the host computer */
{ "pdftag", no_argument, 0, 0 },
/* Display program version. */
{ "verbose", no_argument, 0, 'v' },
@@ -135,8 +182,26 @@ int nwipe_options_parse( int argc, char** argv )
nwipe_options.autonuke = 0;
nwipe_options.autopoweroff = 0;
nwipe_options.method = &nwipe_random;
nwipe_options.prng =
( sizeof( unsigned long int ) >= 8 ) ? &nwipe_xoroshiro256_prng : &nwipe_add_lagg_fibonacci_prng;
/*
* Determines and sets the default PRNG based on AES-NI support and system architecture.
* It selects AES-CTR PRNG if AES-NI is supported, xoroshiro256 for 64-bit systems without AES-NI,
* and add lagged Fibonacci for 32-bit systems.
*/
if( has_aes_ni() )
{
nwipe_options.prng = &nwipe_aes_ctr_prng;
}
else if( sizeof( unsigned long int ) >= 8 )
{
nwipe_options.prng = &nwipe_xoroshiro256_prng;
nwipe_log( NWIPE_LOG_WARNING, "CPU doesn't support AES New Instructions, opting for XORoshiro-256 instead." );
}
else
{
nwipe_options.prng = &nwipe_add_lagg_fibonacci_prng;
}
nwipe_options.rounds = 1;
nwipe_options.noblank = 0;
nwipe_options.nousb = 0;
@@ -148,11 +213,12 @@ int nwipe_options_parse( int argc, char** argv )
nwipe_options.verbose = 0;
nwipe_options.verify = NWIPE_VERIFY_LAST;
nwipe_options.io_mode = NWIPE_IO_MODE_AUTO; /* Default: auto-select I/O mode. */
nwipe_options.PDFtag = 0;
memset( nwipe_options.logfile, '\0', sizeof( nwipe_options.logfile ) );
memset( nwipe_options.PDFreportpath, '\0', sizeof( nwipe_options.PDFreportpath ) );
strncpy( nwipe_options.PDFreportpath, ".", 2 );
/* Read PDF settings from nwipe.conf if available */
/* Read PDF Enable/Disable settings from nwipe.conf if available */
if( ( ret = nwipe_conf_read_setting( "PDF_Certificate.PDF_Enable", &read_value ) ) )
{
/* error occurred */
@@ -186,6 +252,40 @@ int nwipe_options_parse( int argc, char** argv )
}
}
/* Read PDF tag Enable/Disable settings from nwipe.conf if available */
if( ( ret = nwipe_conf_read_setting( "PDF_Certificate.PDF_tag", &read_value ) ) )
{
/* error occurred */
nwipe_log( NWIPE_LOG_ERROR,
"nwipe_conf_read_setting():Error reading PDF_Certificate.PDF_tag from nwipe.conf, ret code %i",
ret );
/* Use default values */
nwipe_options.PDFtag = 1;
}
else
{
if( !strcmp( read_value, "ENABLED" ) )
{
nwipe_options.PDFtag = 1;
}
else
{
if( !strcmp( read_value, "DISABLED" ) )
{
nwipe_options.PDFtag = 0;
}
else
{
// error occurred
nwipe_log(
NWIPE_LOG_ERROR,
"PDF_Certificate.PDF_tag in nwipe.conf returned a value that was neither ENABLED or DISABLED" );
nwipe_options.PDFtag = 0; // Default to Enabled
}
}
}
/* PDF Preview enable/disable */
if( ( ret = nwipe_conf_read_setting( "PDF_Certificate.PDF_Preview", &read_value ) ) )
{
@@ -360,6 +460,10 @@ int nwipe_options_parse( int argc, char** argv )
fprintf( stderr, "Error: Unknown I/O mode '%s' (expected auto|direct|cached).\n", optarg );
exit( EINVAL );
}
}
if( strcmp( nwipe_options_long[i].name, "pdftag" ) == 0 )
{
nwipe_options.PDFtag = 1;
break;
}
@@ -555,6 +659,11 @@ int nwipe_options_parse( int argc, char** argv )
nwipe_options.prng = &nwipe_xoroshiro256_prng;
break;
}
if( strcmp( optarg, "aes_ctr_prng" ) == 0 )
{
nwipe_options.prng = &nwipe_aes_ctr_prng;
break;
}
/* Else we do not know this PRNG. */
fprintf( stderr, "Error: Unknown prng '%s'.\n", optarg );
@@ -613,6 +722,7 @@ void nwipe_options_log( void )
extern nwipe_prng_t nwipe_isaac64;
extern nwipe_prng_t nwipe_add_lagg_fibonacci_prng;
extern nwipe_prng_t nwipe_xoroshiro256_prng;
extern nwipe_prng_t nwipe_aes_ctr_prng;
/**
* Prints a manifest of options to the log.
@@ -672,6 +782,10 @@ void nwipe_options_log( void )
{
nwipe_log( NWIPE_LOG_NOTICE, " prng = XORoshiro-256" );
}
else if( nwipe_options.prng == &nwipe_aes_ctr_prng )
{
nwipe_log( NWIPE_LOG_NOTICE, " prng = AES-CTR New Instructions (EXPERIMENTAL!)" );
}
else if( nwipe_options.prng == &nwipe_isaac )
{
nwipe_log( NWIPE_LOG_NOTICE, " prng = Isaac" );
@@ -767,7 +881,7 @@ void display_help()
puts( " -P, --PDFreportpath=PATH Path to write PDF reports to. Default is \".\"" );
puts( " If set to \"noPDF\" no PDF reports are written.\n" );
puts( " -p, --prng=METHOD PRNG option "
"(mersenne|twister|isaac|isaac64|add_lagg_fibonacci_prng|xoroshiro256_prng)\n" );
"(mersenne|twister|isaac|isaac64|add_lagg_fibonacci_prng|xoroshiro256_prng|aes_ctr_prng)\n" );
puts( " -q, --quiet Anonymize logs and the GUI by removing unique data, i.e." );
puts( " serial numbers, LU WWN Device ID, and SMBIOS/DMI data." );
puts( " XXXXXX = S/N exists, ????? = S/N not obtainable\n" );
@@ -784,10 +898,14 @@ void display_help()
puts( " option. Send SIGUSR1 to log current stats.\n" );
puts( " --nousb Do NOT show or wipe any USB devices whether in GUI" );
puts( " mode, --nogui or --autonuke modes.\n" );
puts( " --pdftag Enables a field on the PDF that holds a tag that\n" );
puts( " identifies the host computer\n" );
puts( " -e, --exclude=DEVICES Up to ten comma separated devices to be excluded." );
puts( " --exclude=/dev/sdc" );
puts( " --exclude=/dev/sdc,/dev/sdd" );
puts( " --exclude=/dev/sdc,/dev/sdd,/dev/mapper/cryptswap1\n" );
puts( " --exclude=/dev/disk/by-id/ata-XXXXXXXX" );
puts( " --exclude=/dev/disk/by-path/pci-0000:00:17.0-ata-1\n" );
puts( "" );
exit( EXIT_SUCCESS );
}

View File

@@ -75,6 +75,7 @@ typedef struct
int verbose; // Make log more verbose
int PDF_enable; // 0=PDF creation disabled, 1=PDF creation enabled
int PDF_preview_details; // 0=Disable preview Org/Cust/date/time before drive selection, 1=Enable Preview
int PDFtag; // Enable display of hostID, such as UUID or serial no. on PDF report.
nwipe_verify_t verify; // A flag to indicate whether writes should be verified.
nwipe_io_mode_t io_mode; // Runtime I/O mode selection (auto/direct/cached).
} nwipe_options_t;

View File

@@ -27,6 +27,7 @@
#include "isaac_rand/isaac64.h"
#include "alfg/add_lagg_fibonacci_prng.h" //Lagged Fibonacci generator prototype
#include "xor/xoroshiro256_prng.h" //XORoshiro-256 prototype
#include "aes/aes_ctr_prng.h" // AES-NI prototype
nwipe_prng_t nwipe_twister = { "Mersenne Twister (mt19937ar-cok)", nwipe_twister_init, nwipe_twister_read };
@@ -40,6 +41,9 @@ nwipe_prng_t nwipe_add_lagg_fibonacci_prng = { "Lagged Fibonacci generator",
/* XOROSHIRO-256 PRNG Structure */
nwipe_prng_t nwipe_xoroshiro256_prng = { "XORoshiro-256", nwipe_xoroshiro256_prng_init, nwipe_xoroshiro256_prng_read };
/* AES-CTR-NI PRNG Structure */
nwipe_prng_t nwipe_aes_ctr_prng = { "AES-CTR (Kernel)", nwipe_aes_ctr_prng_init, nwipe_aes_ctr_prng_read };
/* Print given number of bytes from unsigned integer number to a byte stream buffer starting with low-endian. */
static inline void u32_to_buffer( u8* restrict buffer, u32 val, const int len )
{
@@ -340,3 +344,340 @@ int nwipe_xoroshiro256_prng_read( NWIPE_PRNG_READ_SIGNATURE )
return 0; // Success
}
/**
* @brief Initialize the AES-CTR PRNG state for this thread.
*
* @details
* Initializes the thread-local PRNG based on the supplied seed and resets the
* ring-buffer prefetch cache. The underlying AES-CTR implementation uses a
* persistent AF_ALG operation socket per thread, opened lazily by
* aes_ctr_prng_init(). The public state only stores a 128-bit counter while
* the kernel keeps the expanded AES key schedule.
*
* @param[in,out] state Pointer to an opaque PRNG state handle. If `*state` is
* `NULL`, this function allocates it with `calloc()`.
* @param[in] seed Seed material (must contain at least 32 bytes).
* @param[in] ... Remaining parameters as defined by NWIPE_PRNG_INIT_SIGNATURE.
*
* @note
* The ring is intentionally left empty to keep init fast. Callers may choose to
* "prefill" by invoking refill_stash_thread_local(*state, SIZE_OF_AES_CTR_PRNG)
* once to amortize first-use latency for tiny reads.
*
* @retval 0 Success.
* @retval -1 Allocation or initialization failure (already logged).
*/
/*
* High-throughput wrapper with a thread-local ring-buffer prefetch
* ----------------------------------------------------------------
* This glue layer implements NWIPE_PRNG_INIT / NWIPE_PRNG_READ around the
* persistent kernel-AES PRNG. It maintains a lock-free, thread-local ring
* buffer ("stash") that caches keystream blocks produced in fixed-size chunks
* (SIZE_OF_AES_CTR_PRNG; e.g., 16 KiB or 256 KiB).
*
* Rationale:
* - Nwipe frequently requests small slices (e.g., 32 B, 512 B, 4 KiB). Issuing
* one kernel call per small read would be syscall- and copy-bound.
* - By fetching larger chunks and serving small reads from the ring buffer,
* we reduce syscall rate and memory traffic and approach memcpy-limited
* throughput on modern CPUs with AES acceleration.
*
* Why a ring buffer (over a linear stash + memmove):
* - No O(n) memmove() when the buffer fills with a tail of unread bytes.
* - Constant-time head/tail updates via modulo arithmetic.
* - Better cache locality and fewer TLB/cache misses; improved prefetching.
*/
/** @def NW_THREAD_LOCAL
* @brief Portable thread-local specifier for C11 and GNU C.
*
* The ring buffer and its indices are thread-local, so no synchronization
* (locks/atomics) is required. Do not share this state across threads.
*/
#if defined( __STDC_VERSION__ ) && __STDC_VERSION__ >= 201112L
#define NW_THREAD_LOCAL _Thread_local
#else
#define NW_THREAD_LOCAL __thread
#endif
/** @def NW_ALIGN
* @brief Minimal alignment helper for hot buffers/structures.
*
* 64-byte alignment targets typical cacheline boundaries to reduce false
* sharing and improve hardware prefetch effectiveness for linear scans.
*/
#if defined( __GNUC__ ) || defined( __clang__ )
#define NW_ALIGN( N ) __attribute__( ( aligned( N ) ) )
#else
#define NW_ALIGN( N ) _Alignas( N )
#endif
/**
* @def STASH_CAPACITY
* @brief Ring capacity in bytes (power-of-two; multiple of CHUNK).
*
* @details
* Defaults to 1 MiB. Must be:
* - a power of two (allows modulo via bitmask),
* - a multiple of SIZE_OF_AES_CTR_PRNG, so each produced chunk fits whole.
*
* @note
* Practical choices: 512 KiB … 4 MiB depending on CHUNK size and workload.
* For SIZE_OF_AES_CTR_PRNG = 256 KiB, 1 MiB yields four in-flight chunks and
* works well for nwipes small-read patterns.
*/
#ifndef STASH_CAPACITY
#define STASH_CAPACITY ( 1u << 20 ) /* 1 MiB */
#endif
#if defined( __STDC_VERSION__ ) && __STDC_VERSION__ >= 201112L
_Static_assert( ( STASH_CAPACITY & ( STASH_CAPACITY - 1 ) ) == 0, "STASH_CAPACITY must be a power of two" );
_Static_assert( ( STASH_CAPACITY % SIZE_OF_AES_CTR_PRNG ) == 0,
"STASH_CAPACITY must be a multiple of SIZE_OF_AES_CTR_PRNG" );
#endif
/** @brief Thread-local ring buffer storage for prefetched keystream. */
NW_THREAD_LOCAL static unsigned char stash[STASH_CAPACITY] NW_ALIGN( 64 );
/**
* @name Ring indices (thread-local)
* @{
* @var rb_head Next read position (consumer cursor).
* @var rb_tail Next write position (producer cursor).
* @var rb_count Number of valid bytes currently stored.
*
* @invariant
* - 0 <= rb_count <= STASH_CAPACITY
* - rb_head, rb_tail in [0, STASH_CAPACITY)
* - (rb_tail - rb_head) mod STASH_CAPACITY == rb_count
*
* @warning
* These variables are TLS and must not be accessed from or shared with other
* threads. One PRNG instance per thread.
* @}
*/
NW_THREAD_LOCAL static size_t rb_head = 0; /* next byte to read */
NW_THREAD_LOCAL static size_t rb_tail = 0; /* next byte to write */
NW_THREAD_LOCAL static size_t rb_count = 0; /* occupied bytes */
/**
* @brief Free space available in the ring (bytes).
* @return Number of free bytes (0 … STASH_CAPACITY).
*/
static inline size_t rb_free( void )
{
return STASH_CAPACITY - rb_count;
}
/**
* @brief Contiguous readable bytes starting at @c rb_head (no wrap).
* @return Number of contiguous bytes available to read without split memcpy.
*/
static inline size_t rb_contig_used( void )
{
size_t to_end = STASH_CAPACITY - rb_head;
return ( rb_count < to_end ) ? rb_count : to_end;
}
/**
* @brief Contiguous writable bytes starting at @c rb_tail (no wrap).
* @return Number of contiguous bytes available to write without wrap.
*/
static inline size_t rb_contig_free( void )
{
size_t to_end = STASH_CAPACITY - rb_tail;
size_t free = rb_free();
return ( free < to_end ) ? free : to_end;
}
/**
* @brief Ensure at least @p need bytes are buffered in the ring.
*
* @details
* Production model:
* - The kernel PRNG produces keystream in fixed-size chunks
* (SIZE_OF_AES_CTR_PRNG bytes; e.g., 16 KiB or 256 KiB).
* - We only ever append *whole* chunks. If total free space is less than one
* chunk, no production occurs (non-blocking style); the caller should first
* consume data and try again.
*
* Wrap handling:
* - Fast path: if a contiguous free region of at least one chunk exists at
* @c rb_tail, generate directly into @c stash + rb_tail (zero extra copies).
* - Wrap path: otherwise, generate one chunk into a small temporary buffer and
* split-copy into [rb_tail..end) and [0..rest). This case is infrequent and
* still cheaper than memmoving ring contents.
*
* @param[in] state Pointer to the AES-CTR state (per-thread).
* @param[in] need Minimum number of bytes the caller would like to have ready.
*
* @retval 0 Success (or no space to produce yet).
* @retval -1 PRNG failure (aes_ctr_prng_genrand_128k_to_buf() error).
*
* @warning
* Thread-local only. Do not call concurrently from multiple threads that share
* the same TLS variables.
*/
static int refill_stash_thread_local( void* state, size_t need )
{
while( rb_count < need )
{
/* Not enough total free space for a full CHUNK → let the caller read first. */
if( rb_free() < SIZE_OF_AES_CTR_PRNG )
break;
size_t cf = rb_contig_free();
if( cf >= SIZE_OF_AES_CTR_PRNG )
{
/* Fast path: generate straight into the ring. */
if( aes_ctr_prng_genrand_128k_to_buf( (aes_ctr_state_t*) state, stash + rb_tail ) != 0 )
return -1;
rb_tail = ( rb_tail + SIZE_OF_AES_CTR_PRNG ) & ( STASH_CAPACITY - 1 );
rb_count += SIZE_OF_AES_CTR_PRNG;
}
else
{
/* Wrap path: temporary production, then split-copy. */
unsigned char tmp[SIZE_OF_AES_CTR_PRNG];
if( aes_ctr_prng_genrand_128k_to_buf( (aes_ctr_state_t*) state, tmp ) != 0 )
return -1;
size_t first = STASH_CAPACITY - rb_tail; /* bytes to physical end */
memcpy( stash + rb_tail, tmp, first );
memcpy( stash, tmp + first, SIZE_OF_AES_CTR_PRNG - first );
rb_tail = ( rb_tail + SIZE_OF_AES_CTR_PRNG ) & ( STASH_CAPACITY - 1 );
rb_count += SIZE_OF_AES_CTR_PRNG;
}
}
return 0;
}
/* ---------------- PRNG INIT ---------------- */
/**
* @brief Thread-local initialization wrapper around @c aes_ctr_prng_init().
*
* @param[in,out] state Address of the callers PRNG state pointer. If `*state`
* is `NULL`, this function allocates one `aes_ctr_state_t`.
* @param[in] seed Seed descriptor as defined by NWIPE_PRNG_INIT_SIGNATURE.
*
* @retval 0 Success.
* @retval -1 Allocation or backend initialization failure (logged).
*
* @note
* Resets the ring buffer to empty. Consider a one-time prefill if your workload
* is dominated by tiny reads.
*/
int nwipe_aes_ctr_prng_init( NWIPE_PRNG_INIT_SIGNATURE )
{
nwipe_log( NWIPE_LOG_NOTICE, "Initializing AES-CTR PRNG (thread-local ring buffer)" );
if( *state == NULL )
{
*state = calloc( 1, sizeof( aes_ctr_state_t ) );
if( *state == NULL )
{
nwipe_log( NWIPE_LOG_FATAL, "calloc() failed for PRNG state" );
return -1;
}
}
int rc = aes_ctr_prng_init(
(aes_ctr_state_t*) *state, (unsigned long*) seed->s, seed->length / sizeof( unsigned long ) );
if( rc != 0 )
{
nwipe_log( NWIPE_LOG_ERROR, "aes_ctr_prng_init() failed" );
return -1;
}
/* Reset ring to empty. */
rb_head = rb_tail = rb_count = 0;
return 0;
}
/* ---------------- PRNG READ ---------------- */
/**
* @brief Copy @p count bytes of keystream into @p buffer.
*
* @details
* Strategy:
* - If the request is "large" (>= CHUNK) and the ring is empty, use the
* direct-fill fast path and generate full CHUNKs directly into the output
* buffer to avoid an extra memcpy.
* - Otherwise, serve from the ring:
* * Ensure at least one byte is available via @c refill_stash_thread_local
* (non-blocking; production occurs only if one full CHUNK fits).
* * Copy the largest contiguous block starting at @c rb_head.
* * Opportunistically prefetch when sufficient free space exists to keep
* latency low for upcoming small reads.
*
* @param[out] buffer Destination buffer to receive keystream.
* @param[in] count Number of bytes to generate and copy.
* @param[in] ... Remaining parameters as defined by NWIPE_PRNG_READ_SIGNATURE.
*
* @retval 0 Success (exactly @p count bytes written).
* @retval -1 Backend/IO failure (already logged).
*
* @warning
* Per-thread API: do not share this state across threads.
*/
int nwipe_aes_ctr_prng_read( NWIPE_PRNG_READ_SIGNATURE )
{
unsigned char* out = buffer;
size_t bytes_left = count;
/* Fast path: for large reads, bypass the ring if currently empty.
* Generate full CHUNKs directly into the destination to save one memcpy. */
while( bytes_left >= SIZE_OF_AES_CTR_PRNG && rb_count == 0 )
{
if( aes_ctr_prng_genrand_128k_to_buf( (aes_ctr_state_t*) *state, out ) != 0 )
{
nwipe_log( NWIPE_LOG_ERROR, "PRNG direct fill failed" );
return -1;
}
out += SIZE_OF_AES_CTR_PRNG;
bytes_left -= SIZE_OF_AES_CTR_PRNG;
}
/* General path: serve from ring, refilling as needed. */
while( bytes_left > 0 )
{
/* Ensure at least one byte is available for tiny reads. Refill only
* produces if a full CHUNK fits; otherwise we try again once consumer
* progress frees enough space. */
if( rb_count == 0 )
{
if( refill_stash_thread_local( *state, 1 ) != 0 )
{
nwipe_log( NWIPE_LOG_ERROR, "PRNG refill failed" );
return -1;
}
if( rb_count == 0 )
continue; /* still no room for a CHUNK yet */
}
/* Copy the largest contiguous span starting at rb_head. */
size_t avail = rb_contig_used();
size_t take = ( bytes_left < avail ) ? bytes_left : avail;
memcpy( out, stash + rb_head, take );
rb_head = ( rb_head + take ) & ( STASH_CAPACITY - 1 );
rb_count -= take;
out += take;
bytes_left -= take;
/* Opportunistic prefetch to hide latency of future small reads. */
if( rb_free() >= ( 2 * SIZE_OF_AES_CTR_PRNG ) )
{
if( refill_stash_thread_local( *state, SIZE_OF_AES_CTR_PRNG ) != 0 )
{
nwipe_log( NWIPE_LOG_ERROR, "PRNG opportunistic refill failed" );
return -1;
}
}
}
return 0;
}

View File

@@ -63,6 +63,10 @@ int nwipe_add_lagg_fibonacci_prng_read( NWIPE_PRNG_READ_SIGNATURE );
int nwipe_xoroshiro256_prng_init( NWIPE_PRNG_INIT_SIGNATURE );
int nwipe_xoroshiro256_prng_read( NWIPE_PRNG_READ_SIGNATURE );
/* AES-CTR-NI prototypes. */
int nwipe_aes_ctr_prng_init( NWIPE_PRNG_INIT_SIGNATURE );
int nwipe_aes_ctr_prng_read( NWIPE_PRNG_READ_SIGNATURE );
/* Size of the twister is not derived from the architecture, but it is strictly 4 bytes */
#define SIZE_OF_TWISTER 4
@@ -76,4 +80,10 @@ int nwipe_xoroshiro256_prng_read( NWIPE_PRNG_READ_SIGNATURE );
/* Size of the XOROSHIRO-256 is not derived from the architecture, but it is strictly 32 bytes */
#define SIZE_OF_XOROSHIRO256_PRNG 32
/* AES-CTR generation chunk size: fixed 128 KiB (not architecture-dependent) */
#define SIZE_OF_AES_CTR_PRNG ( 128 * 1024 )
/* Thread-local prefetch ring buffer capacity: 1 MiB */
#define STASH_CAPACITY ( 1024 * 1024 )
#endif /* PRNG_H_ */