diff --git a/src/Makefile.am b/src/Makefile.am index 2bd41aa..92df2d0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 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 +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 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 nwipe_LDADD = $(PARTED_LIBS) diff --git a/src/context.h b/src/context.h index 3a4d6e2..c26d2be 100644 --- a/src/context.h +++ b/src/context.h @@ -159,8 +159,12 @@ typedef struct nwipe_context_t_ char PDF_filename[256]; // The filename of the PDF certificate/report. int HPA_pre_erase_status; // 0 = No HPA found/disabled, 1 = HPA detected, 2 = Unknown, unable to checked int HPA_post_erase_status; // 0 = No HPA found/disabled, 1 = HPA detected, 2 = Unknown, unable to checked + u64 HPA_reported_set; // the 'HPA set' value reported hdparm -N, i.e the first value of n/n + u64 HPA_reported_real; // the 'HPA real' value reported hdparm -N, i.e the second value of n/n int DCO_pre_erase_status; // 0 = No DCO found, 1 = DCO detected, 2 = Unknown, unable to checked int DCO_post_erase_status; // 0 = No DCO found, 1 = DCO detected, 2 = Unknown, unable to checked + u64 DCO_reported_real_max_sectors; // real max sectors as reported by hdparm --dco-identify + /* * Identity contains the raw serial number of the drive * (where applicable), however, for use within nwipe use the diff --git a/src/device.c b/src/device.c index f984956..03754ca 100644 --- a/src/device.c +++ b/src/device.c @@ -340,7 +340,6 @@ int check_device( nwipe_context_t*** c, PedDevice* dev, int dcount ) check_HPA = 1; break; } - if( check_HPA == 1 ) { hpa_dco_status( next_device, PRE_WIPE_HPA_CHECK ); diff --git a/src/hpa_dco.c b/src/hpa_dco.c index 5d23148..5215df0 100644 --- a/src/hpa_dco.c +++ b/src/hpa_dco.c @@ -35,6 +35,17 @@ #include "logging.h" #include "options.h" #include "hpa_dco.h" +#include "miscellaneous.h" + +/* This function makes use of the hdparm program to determine HPA/DCO status. I would have prefered + * to write this function using low level access to the hardware however I would have needed + * to understand fully the various edge cases that would need be be dealt with. As I need to add + * HPA/detection to nwipe as quickly as possible I decided it would just be quicker to utilise hdparm + * rather than reinvent the wheel. However, I don't like doing it like this as a change in formatted + * output of hdparm could potentially break HPA/DCO detection requiring a fix. Anybody that wants to + * re-write this function for a purer nwipe without the use of hdparm then by all means please go + * ahead and submit a pull request to https://github.com/martijnvanbrummelen/nwipe + */ int hpa_dco_status( nwipe_context_t* ptr, int pre_or_post ) { @@ -44,17 +55,37 @@ int hpa_dco_status( nwipe_context_t* ptr, int pre_or_post ) int r; // A result buffer. int set_return_value; int exit_status; + int hpa_line_found; + int dco_line_found; FILE* fp; - char hdparm_command[] = "hdparm -N"; - char hdparm_command2[] = "/sbin/hdparm -N"; - char hdparm_command3[] = "/usr/bin/hdparm -N"; + char path_hdparm_cmd1_get_hpa[] = "hdparm -N"; + char path_hdparm_cmd2_get_hpa[] = "/sbin/hdparm -N"; + char path_hdparm_cmd3_get_hpa[] = "/usr/bin/hdparm -N"; + + char path_hdparm_cmd4_get_dco[] = "hdparm --dco-identify"; + char path_hdparm_cmd5_get_dco[] = "/sbin/hdparm --dco-identify"; + char path_hdparm_cmd6_get_dco[] = "/usr/bin/hdparm --dco-identify"; + char result[512]; - char final_cmd_hdparm[sizeof( hdparm_command3 ) + sizeof( c->device_name )]; + + char* p; + + /* Use the longest of the 'path_hdparm_cmd.....' strings above to + *determine size in the strings below + */ + char hdparm_cmd_get_hpa[sizeof( path_hdparm_cmd3_get_hpa ) + sizeof( c->device_name )]; + char hdparm_cmd_get_dco[sizeof( path_hdparm_cmd6_get_dco ) + sizeof( c->device_name )]; /* Initialise return value */ set_return_value = 0; + /* Construct the command including path to the binary if required, I do it like this to cope + * with distros that don't setup their paths in a standard way or maybe don't even define a + * path. By doing this we avoid the 'No such file or directory' message you would otherwise + * get on some distros. -> debian SID + */ + if( system( "which hdparm > /dev/null 2>&1" ) ) { if( system( "which /sbin/hdparm > /dev/null 2>&1" ) ) @@ -70,67 +101,148 @@ int hpa_dco_status( nwipe_context_t* ptr, int pre_or_post ) } else { - snprintf( final_cmd_hdparm, sizeof( final_cmd_hdparm ), "%s %s\n", hdparm_command3, c->device_name ); + snprintf( hdparm_cmd_get_hpa, + sizeof( hdparm_cmd_get_hpa ), + "%s %s\n", + path_hdparm_cmd3_get_hpa, + c->device_name ); + snprintf( hdparm_cmd_get_dco, + sizeof( hdparm_cmd_get_dco ), + "%s %s\n", + path_hdparm_cmd6_get_dco, + c->device_name ); } } else { - snprintf( final_cmd_hdparm, sizeof( final_cmd_hdparm ), "%s %s", hdparm_command2, c->device_name ); + snprintf( + hdparm_cmd_get_hpa, sizeof( hdparm_cmd_get_hpa ), "%s %s", path_hdparm_cmd2_get_hpa, c->device_name ); + snprintf( + hdparm_cmd_get_dco, sizeof( hdparm_cmd_get_dco ), "%s %s\n", path_hdparm_cmd5_get_dco, c->device_name ); } } else { - snprintf( final_cmd_hdparm, sizeof( final_cmd_hdparm ), "%s %s", hdparm_command, c->device_name ); + snprintf( hdparm_cmd_get_hpa, sizeof( hdparm_cmd_get_hpa ), "%s %s", path_hdparm_cmd1_get_hpa, c->device_name ); + snprintf( + hdparm_cmd_get_dco, sizeof( hdparm_cmd_get_dco ), "%s %s\n", path_hdparm_cmd4_get_dco, c->device_name ); } - nwipe_log( NWIPE_LOG_DEBUG, "hpa_dco_status: hdparm command %s", final_cmd_hdparm ); + /* Initialise the results buffer, so we don't some how inadvertently process a past result */ + memset( result, 0, sizeof( result ) ); - if( final_cmd_hdparm[0] != 0 ) + if( hdparm_cmd_get_hpa[0] != 0 ) { - fp = popen( final_cmd_hdparm, "r" ); + fp = popen( hdparm_cmd_get_hpa, "r" ); if( fp == NULL ) { - nwipe_log( NWIPE_LOG_WARNING, "hpa_dco_status: Failed to create stream to %s", final_cmd_hdparm ); + nwipe_log( NWIPE_LOG_WARNING, "hpa_dco_status: Failed to create stream to %s", hdparm_cmd_get_hpa ); set_return_value = 1; } if( fp != NULL ) { + hpa_line_found = 0; //* init */ + /* Read the output a line at a time - output it. */ while( fgets( result, sizeof( result ) - 1, fp ) != NULL ) { if( nwipe_options.verbose ) { - nwipe_log( NWIPE_LOG_DEBUG, "%s \n%s", final_cmd_hdparm, result ); + nwipe_log( NWIPE_LOG_DEBUG, "%s \n%s", hdparm_cmd_get_hpa, result ); } + /* Change the output of hdparm to lower case and search using lower case strings, to try + * to avoid minor changes in case in hdparm's output from breaking HPA/DCO detection + */ + strlower( result ); // convert the result to lower case + /* Scan the hdparm results for HPA is disabled */ - if( pre_or_post == PRE_WIPE_HPA_CHECK ) + if( strstr( result, "hpa is disabled" ) != 0 ) { - if( strstr( result, "HPA is disabled" ) != 0 ) + if( pre_or_post == PRE_WIPE_HPA_CHECK ) { c->HPA_pre_erase_status = HPA_DISABLED; - nwipe_log( NWIPE_LOG_INFO, "[GOOD]The host protected area is disabled on %s", c->device_name ); } else { - if( strstr( result, "HPA is enabled" ) != 0 ) + c->HPA_post_erase_status = HPA_DISABLED; + } + nwipe_log( NWIPE_LOG_INFO, "[GOOD] The host protected area is disabled on %s", c->device_name ); + hpa_line_found = 1; + break; + } + else + { + if( strstr( result, "hpa is enabled" ) != 0 ) + { + c->HPA_pre_erase_status = HPA_ENABLED; + nwipe_log( + NWIPE_LOG_WARNING, "[BAD] The host protected area is enabled on %s", c->device_name ); + hpa_line_found = 1; + break; + } + else + { + if( strstr( result, "invalid" ) != 0 ) { c->HPA_pre_erase_status = HPA_ENABLED; - nwipe_log( - NWIPE_LOG_WARNING, "[BAD]The host protected area is enabled on %s", c->device_name ); - } - else - { - c->HPA_pre_erase_status = HPA_UNKNOWN; + nwipe_log( NWIPE_LOG_WARNING, + "[UNSURE] hdparm reports invalid output, buggy drive firmware on %s?", + c->device_name ); + // We'll assume the HPA values are in the string as we may be able to extract something + // meaningful + hpa_line_found = 1; + break; } } } } + + /* if the line was found that contains hpa is enabled or disabled message + * then process the line, extracting the 'hpa set' and 'hpa real' values. + */ + if( hpa_line_found == 1 ) + { + /* Extract the 'HPA set' value, the first value in the line and convert + * to binary and save in context */ + c->HPA_reported_set = str_ascii_number_to_ll( result ); + + /* Extract the 'HPA real' value, the second value in the line and convert + * to binary and save in context, this is a little more difficult as sometimes + * a odd value is returned so instead of nnnnn/nnnnn you get nnnnnn/1(nnnnnn). + * So first we scan for a open bracket '(' then if there is no '(' we then start the + * search immediately after the '/'. + */ + if( ( p = strstr( result, "(" ) ) ) + { + c->HPA_reported_real = str_ascii_number_to_ll( p + 1 ); + } + else + { + if( ( p = strstr( result, "/" ) ) ) + { + c->HPA_reported_real = str_ascii_number_to_ll( p + 1 ); + } + } + nwipe_log( NWIPE_LOG_INFO, + "HPA values %lli / %lli on %s", + c->HPA_reported_set, + c->HPA_reported_real, + c->device_name ); + } + else + { + c->HPA_pre_erase_status = HPA_UNKNOWN; + nwipe_log( NWIPE_LOG_WARNING, + "[UNKNOWN] We can't find the HPA line, has hdparm ouput changed? %s", + c->device_name ); + } + /* close */ r = pclose( fp ); if( r > 0 ) @@ -140,7 +252,7 @@ int hpa_dco_status( nwipe_context_t* ptr, int pre_or_post ) { nwipe_log( NWIPE_LOG_WARNING, "hpa_dco_status(): hdparm failed, \"%s\" exit status = %u", - final_cmd_hdparm, + hdparm_cmd_get_hpa, exit_status ); } @@ -156,5 +268,160 @@ int hpa_dco_status( nwipe_context_t* ptr, int pre_or_post ) } } } + + /* Initialise the results buffer again, so we don't + * some how inadvertently process a past result */ + memset( result, 0, sizeof( result ) ); + + /* ----------------------------------------------- + * Run the dco identify command and determine the + * real max sectors, store it in the drive context + * for comparison against the hpa reported drive + * size values. + */ + + dco_line_found = 0; + + if( hdparm_cmd_get_dco[0] != 0 ) + { + + fp = popen( hdparm_cmd_get_dco, "r" ); + + if( fp == NULL ) + { + nwipe_log( NWIPE_LOG_WARNING, "hpa_dco_status: Failed to create stream to %s", hdparm_cmd_get_dco ); + + set_return_value = 1; + } + + if( fp != NULL ) + { + /* Read the output a line at a time - output it. */ + while( fgets( result, sizeof( result ) - 1, fp ) != NULL ) + { + /* Change the output of hdparm to lower case and search using lower case strings, to try + * to avoid minor changes in case in hdparm's output from breaking HPA/DCO detection */ + strlower( result ); + + if( nwipe_options.verbose ) + { + nwipe_log( NWIPE_LOG_DEBUG, "%s \n%s", hdparm_cmd_get_dco, result ); + } + + if( strstr( result, "real max sectors" ) != 0 ) + { + /* extract the real max sectors, convert to binary and store in drive context */ + dco_line_found = 1; + break; + } + } + /* DCO line found, now process it */ + if( dco_line_found == 1 ) + { + c->DCO_reported_real_max_sectors = str_ascii_number_to_ll( result ); + nwipe_log( NWIPE_LOG_INFO, + "DCO Real max sectors reported as %lli on %s", + c->DCO_reported_real_max_sectors, + c->device_name ); + } + else + { + c->DCO_reported_real_max_sectors = 0; + nwipe_log( NWIPE_LOG_INFO, "DCO Real max sectors not found" ); + } + + /* close */ + r = pclose( fp ); + if( r > 0 ) + { + exit_status = WEXITSTATUS( r ); + if( nwipe_options.verbose ) + { + nwipe_log( NWIPE_LOG_WARNING, + "hpa_dco_status(): hdparm failed, \"%s\" exit status = %u", + hdparm_cmd_get_dco, + exit_status ); + } + + if( exit_status == 127 ) + { + nwipe_log( NWIPE_LOG_WARNING, "Command not found. Installing hdparm is mandatory !" ); + set_return_value = 2; + if( nwipe_options.nousb ) + { + return set_return_value; + } + } + } + } + } + + /* Compare the results of hdparm -N (HPA set / HPA real) + * and hdparm --dco-identidy (real max sectors). All three + * values may be different or perhaps 'HPA set' and 'HPA real' are + * different and 'HPA real' matches 'real max sectors'. + * + * A perfect HPA disabled result would be where all three + * values are the same. It can then be considered that the + * HPA is disabled. + * + * If 'HPA set' and 'HPA real' are different then it + * can be considered that HPA is enabled) + */ + + /* Determine, based on the values of 'HPA set', 'HPA real, + * and 'real max sectors' whether we set the HPA + * flag as HPA_DISABLED, HPA_ENABLED or HPA_UNKNOWN. + * The HPA flag will be displayed in the GUI and on + * the certificate and is used to determine whether + * to reset the HPA. + */ + /* If all three values match then there is no hidden disc area. HPA is disabled. */ + if( ( c->HPA_reported_set == c->HPA_reported_real ) && c->DCO_reported_real_max_sectors == c->HPA_reported_set ) + { + c->HPA_pre_erase_status = HPA_DISABLED; + } + else + { + /* If HPA set and DCO max sectors are equal it can also be considered that HPA is disabled */ + if( c->HPA_reported_set == c->DCO_reported_real_max_sectors ) + { + c->HPA_pre_erase_status = HPA_DISABLED; + } + else + { + if( c->HPA_reported_set != c->DCO_reported_real_max_sectors ) + { + c->HPA_pre_erase_status = HPA_ENABLED; + } + } + } + + if( c->HPA_pre_erase_status == HPA_DISABLED ) + { + nwipe_log( NWIPE_LOG_INFO, "[GOOD] HPA is disabled on %s", c->device_name ); + } + else + { + if( c->HPA_pre_erase_status == HPA_ENABLED ) + { + nwipe_log( NWIPE_LOG_WARNING, "[BAD] HPA is enabled on %s", c->device_name ); + } + else + { + if( c->HPA_pre_erase_status == HPA_UNKNOWN ) + { + nwipe_log( + NWIPE_LOG_WARNING, "[UNKNOWN] We can't seem to determine the HPA status on %s", c->device_name ); + } + } + } + + /* Determine the size of the HPA and store the results in the + * context. + */ + + // WARNING Add code here + return set_return_value; } diff --git a/src/miscellaneous.c b/src/miscellaneous.c new file mode 100644 index 0000000..b155f07 --- /dev/null +++ b/src/miscellaneous.c @@ -0,0 +1,113 @@ +/* + * miscellaneous.c: functions that may be generally used throughout nwipes code, + * mainly string processing related functions. + * + * Copyright PartialVolume . + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "nwipe.h" + +/* Convert string to upper case + */ +void strupper( char* str ) +{ + int idx; + + idx = 0; + while( str[idx] != 0 ) + { + /* If upper case alpha character, change to lower case */ + if( str[idx] >= 'A' && str[idx] <= 'Z' ) + { + str[idx] -= 32; + } + + idx++; + } +} + +/* Convert string to lower case + */ +void strlower( char* str ) +{ + int idx; + + idx = 0; + while( str[idx] != 0 ) + { + /* If upper case alpha character, change to lower case */ + if( str[idx] >= 'A' && str[idx] <= 'Z' ) + { + str[idx] += 32; + } + + idx++; + } +} + +/* Search a string for a positive number, convert the first + * number found to binary and return the binary number. + * returns the number or -1 if number too large or -2 if + * no number found. + */ + +u64 str_ascii_number_to_ll( char* str ) +{ + int idx; + int idx2; + char number_copy[20]; + + idx = 0; // index into the main string we are searching + idx2 = 0; // index used for backup copy of ascii number + + while( str[idx] != 0 ) + { + /* Find the start of the number */ + if( str[idx] >= '0' && str[idx] <= '9' ) + { + while( str[idx] != 0 ) + { + /* Find the end of the number */ + if( str[idx] >= '0' && str[idx] <= '9' ) + { + if( idx2 < sizeof( number_copy ) - 1 ) + { + number_copy[idx2++] = str[idx++]; + } + else + { + /* Number is too large ! */ + return -1; + } + } + else + { + /* end found */ + number_copy[idx2] = 0; // terminate our copy + + /* convert ascii number to longlong */ + return atoll( number_copy ); + } + } + } + else + { + idx++; + } + } + return -2; /* no number found */ +} diff --git a/src/miscellaneous.h b/src/miscellaneous.h new file mode 100644 index 0000000..266fb95 --- /dev/null +++ b/src/miscellaneous.h @@ -0,0 +1,55 @@ +/* + * miscellaneous.h: header file for miscellaneous.c .. + * + * functions that may be generally used throughout nwipes code, + * mainly string processing related functions. + * + * Copyright PartialVolume . + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef MISCELLANEOUS_H_ +#define MISCELLANEOUS_H_ + +/** + * Convert the string from lower to upper case + * @param pointer to a null terminated string + * @return void + */ +void strupper( char* ); + +/** + * Convert the string from upper to lower case + * @param pointer to a null terminated string + * @return void + */ +void strlower( char* str ); + +/** + * Search a string for a positive number, convert the first + * number found to binary and return the binary number. + * returns the number or -1 if number too large or -2 if + * no number found. + * + * @param pointer to a null terminated string + * @return longlong returns: + * the number + * -1 = number too large + * -2 = no number found. + */ +u64 str_ascii_number_to_ll( char* ); + +#endif /* HPA_DCO_H_ */ diff --git a/src/version.c b/src/version.c index cb54c1b..b123443 100644 --- a/src/version.c +++ b/src/version.c @@ -4,7 +4,7 @@ * used by configure to dynamically assign those values * to documentation files. */ -const char* version_string = "0.34"; +const char* version_string = "0.34.1 Development code, not for production use!"; const char* program_name = "nwipe"; const char* author_name = "Martijn van Brummelen"; const char* email_address = "git@brumit.nl"; @@ -14,4 +14,4 @@ Modifications to original dwipe Copyright Andy Beverley \n\ This is free software; see the source for copying conditions.\n\ There is NO warranty; not even for MERCHANTABILITY or FITNESS\n\ FOR A PARTICULAR PURPOSE.\n"; -const char* banner = "nwipe 0.34"; +const char* banner = "nwipe 0.34.1 Development code, not for production use!";