From 9a70d23e4a41da5b2f8ade1a1389ff00d429a10f Mon Sep 17 00:00:00 2001 From: Fabian Druschke Date: Fri, 28 Nov 2025 12:32:43 +0100 Subject: [PATCH] device: sanitize serial numbers and fix uninitialized/fallback handling 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. --- src/device.c | 73 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/src/device.c b/src/device.c index 50f0fb6..e8518d0 100644 --- a/src/device.c +++ b/src/device.c @@ -49,6 +49,7 @@ 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 @@ -360,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++ ) { @@ -425,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 */ @@ -624,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 anderen Zeichen (Steuerzeichen, >0x7F) werden verworfen */ + } + + *dst = '\0'; + + /* Vorhandene trim()-Logik wiederverwenden */ + 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 @@ -671,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;