This patch fixes several issues that could cause garbage or control
characters to appear in the ncurses UI when displaying device serial
numbers.
Key changes:
- Added nwipe_normalize_serial() to strip control characters, non-ASCII
bytes and trim whitespace from all serial numbers before they are
shown in the UI.
- Initialize the serialnumber buffer in
nwipe_get_device_bus_type_and_serialno() to avoid passing undefined
data back to check_device() when no valid "Serial Number:" field is
found.
- Prevent ioctl(HDIO_GET_IDENTITY) from being called on an invalid file
descriptor when open() fails.
- Ensure consistent null termination and sanitize the final
device_serial_no regardless of whether it came from HDIO, smartctl
output, or quiet-mode anonymization.
These fixes resolve cases where devices (especially virtual/QEMU or
USB-attached drives) could report malformed or unexpected serial
strings, resulting in UI corruption such as extra characters, ^A, or
line wrapping.
device ID (major/minor), allowing persistent identifiers in /dev/disk/by-id/
and /dev/disk/by-path/ to be used safely. Legacy string-based matching is
preserved.
You can now specify --pdftag to enable the
display of system UUID and system serial
number information on the PDF report.
Nwipe defaults to not displaying system IDs
but some users like to record the system UUID
or serial number on the Erasure Report along
with the disk information.
This fixes an issue where a default blanking pass was added to methods which do not support it when in --nogui mode.
Existing GUI code overriding the option is never called in --nogui mode, so needs handling as part of option parsing.
Signed-off-by: desertwitch <24509509+desertwitch@users.noreply.github.com>
The internal PRNGs are now seeded via getrandom(2) rather than through a
long-lived file descriptor to /dev/urandom.
Previously, nwipe opened /dev/urandom once at startup, stored the file
descriptor in the nwipe_context_t (entropy_fd) and used read() on that fd
whenever seed material was required (e.g. DoD, Gutmann, OPS-II and generic
random passes). The path to the entropy source was hard-coded via
NWIPE_KNOB_ENTROPY ("/dev/urandom").
This change introduces a small helper in method.c:
- nwipe_read_entropy(buf, len)
which calls the getrandom(2) syscall in a loop until the requested number
of bytes has been filled, handling short reads and EINTR/EAGAIN. All
former uses of read(ctx->entropy_fd, ...) for seeding have been switched
to nwipe_read_entropy(), and the error messages in those places now
correctly report "getrandom" instead of "read" as the failing operation.
The nwipe_context_t no longer carries an entropy_fd and nwipe.c no longer
opens /dev/urandom at startup nor assigns that fd to each context. The
NWIPE_KNOB_ENTROPY macro and its only use site were removed. At runtime,
nwipe now directly consumes entropy via getrandom(2), with an optional
log notice stating that getrandom(2) is used as the entropy source.
Debug behaviour is preserved: any existing code that logs or dumps the
PRNG seed or pattern values after they have been obtained continues to
work unchanged, it simply sees data that originated from getrandom(2)
instead of /dev/urandom.
Rationale for preferring getrandom(2) over /dev/urandom via fd:
- getrandom(2) is a dedicated kernel API for CSPRNG output; it does not
depend on any device node or path existing in /dev. This avoids a
whole class of "weird environment" failures where /dev/urandom might
be missing, replaced, or mounted in a surprising way inside chroots,
containers, or minimal live systems.
- getrandom(2) guarantees that it will block until the kernel CRNG has
been properly initialized. Historically, /dev/urandom on very old
kernels could be read before the CRNG was fully seeded if the caller
did not implement extra checks; using getrandom(2) pushes that logic
into the kernel and makes the seeding semantics explicit.
- We no longer need to manage a process-wide entropy file descriptor:
there is no open(), no global fd to propagate into every context, and
nothing to close on shutdown. This simplifies the lifetime rules for
entropy, especially in the presence of multiple worker threads or any
future changes that might involve fork/exec.
- By avoiding a persistent fd, we also remove the (admittedly low but
non-zero) risk of that descriptor being accidentally clobbered,
reused, or inherited in unexpected ways in future code changes. Each
seed request is now an independent syscall that either succeeds or
fails cleanly with an errno.
- From a security perspective, /dev/urandom and getrandom(2) are backed
by the same kernel CSPRNG on modern Linux, so we do not weaken the
entropy source. Instead, we get stricter initialization guarantees
and a smaller attack surface (no device node, no path resolution, no
reliance on a specific /dev layout) while keeping performance and
quality where they should be for a disk wiping tool.
In short, this patch keeps all existing wipe patterns and debug output in
place, but replaces the plumbing underneath so that PRNG seeding is
simpler, more robust against odd environments, and aligned with the
modern Linux API for secure random numbers.
The fifteen lines of code that creates the header
and footer text in the PDF appear in two separate
places. The first occurrence in the create_pdf(..)
function and once in the create_header_and_footer(..)
function.
This duplicated code was combined into a third
function pdf_header_footer_text(..) and is now called
from the other functions.
This was done as I need to add some user selectable
changes to the header text that will include host
identification such as system tag, UUID, hostname
without creating further duplicated code.
The purpose of this commit is to add an additional
identifying piece of information to the pdf filename.
This was found to be necessary in the case of a user
wiping partitions as opposed to the whole disc. Currently
when wiping a partition the model name and serial number
is missing from the pdf content and pdf filename so by adding
the device name, it make it less likely that an existing pdf
will get overwritten. This is a stop gap fix as preferably
the disk model and serial no needs to be retrieved even
wiping just one partition.
Additional functions were added including retrieval of UUID,
however UUID was found to not be available for some USB
devices when wiping partitions. The UUID function remains
in the code and the UUID if available is output to the log
but is not used anywhere else at the moment.
Replaced the old memmove-based stash buffer with a true circular (ring) buffer
for the thread-local AES-CTR PRNG prefetch mechanism Increased Buffers to 1M stash and 128 KiB block.
Key improvements:
- Eliminates O(n) memmove() calls on buffer wrap → constant-time refill
- Avoids redundant memory copies and improves cache locality
- Supports larger prefetch capacities (256 KiB–1 MiB) without performance penalty
- Adds fast-path for large reads (direct 16 KiB chunks to user buffer)
- Aligns stash to 64 B for better cacheline performance
- Increased prefetch size to 1M. Increased block size to 128 KiB
- Reduced syscall overhead by increasing buffers
Result: measurable +5–20 % throughput gain on small-read workloads,
lower memory bandwidth usage, and more consistent latency across threads.
Problem
=======
The OpenSSL-based prelimininary, not yet committed userspace PRNG in nwipe
plateaued at ~250 MB/s, becoming the primary bottleneck when wiping modern
NVMe or RAID volumes that sustain gigabytes per second.
Solution
========
Replace the OpenSSL path with a kernel-accelerated AES-256-CTR generator that
streams 16 KiB keystream blocks through the AF_ALG “ctr(aes)” skcipher:
* Added aes_ctr_prng.cpp/.h
• Opens a per-thread AF_ALG operation socket once (lazy init).
• Builds a two-CMSG `sendmsg()` (ALG_SET_OP + ALG_SET_IV) and a single
`read()` per chunk – minimal syscall overhead.
• Public state (aes_ctr_state_t) intentionally remains 256 bit to preserve
ABI compatibility; socket FD is kept thread-local.
• Generates exactly 16 KiB per call, advancing an internal 128-bit counter.
* Comprehensive English comments explain every function, the ABI rationale and
the kernel interaction pattern.
Performance
-----------
On a Ryzen 9 7950X (VAES):
• Old OpenSSL path: ~260 MB/s
• New AF_ALG path : ~6.2 GB/s (≈ 24× faster, CPU-bound at ~7 % load)
Safety & Compatibility
----------------------
* Falls back automatically to the kernel’s software AES if AES-NI/VAES/SVE are
absent – no code changes required.
* No external dependencies beyond standard linux-headers.
* Optional `aes_ctr_prng_shutdown()` closes the FD, though the kernel would
reclaim it on exit anyway.
Testing
-------
* Added unit tests for counter wraparound and deterministic output with a
fixed seed (compared to OpenSSL reference vectors).
* Verified multi-threaded wiping on a 4 × NVMe RAID-0 → sustained device speed,
PRNG never starved the pipeline.
Future work
-----------
* Expose chunk size as a tunable CLI flag.
* Optionally copy keystream directly into the kernel’s page cache via `splice`.
Closes: #559 (Implement High-Quality Random Number Generation Using AES-CTR Mode with OpenSSL and AES-NI Support)