From bb9346744228a83e1b40782516e1c59cd9a756bb Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 26 Sep 2024 11:02:39 +0200 Subject: [PATCH] Add new binary integrity verification function Signed-off-by: DL6ER --- src/CMakeLists.txt | 7 +++ src/args.c | 30 ++++++++++-- src/config/config.c | 2 +- src/config/toml_writer.c | 4 +- src/files.c | 103 ++++++++++++++++++++++++++++++++++++--- src/files.h | 3 +- 6 files changed, 134 insertions(+), 15 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 008e6d7d..778764a6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -371,6 +371,13 @@ else() target_compile_definitions(civetweb PRIVATE NO_SSL) endif() +# After finishing building the FTL binary, we append the sha256sum of the binary in raw form to itself +add_custom_command(TARGET pihole-FTL POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ $/pihole-FTL.tmp + COMMAND sha256sum $.tmp | cut -d ' ' -f 1 | xxd -r -p >> $.tmp + COMMAND mv $.tmp $ + ) + find_program(SETCAP setcap) install(TARGETS pihole-FTL RUNTIME DESTINATION bin diff --git a/src/args.c b/src/args.c index bf2713d8..085b633a 100644 --- a/src/args.c +++ b/src/args.c @@ -532,12 +532,13 @@ void parse_args(int argc, char* argv[]) } // sha256sum mode - if(argc == 3 && strcmp(argv[1], "sha256sum") == 0) + if((argc == 3 || (argc == 4 && strcmp(argv[2], "--skip-end"))) && strcmp(argv[1], "sha256sum") == 0) { + const bool skip_end = argc == 4; // Enable stdout printing cli_mode = true; uint8_t checksum[SHA256_DIGEST_SIZE]; - if(!sha256sum(argv[2], checksum)) + if(!sha256sum(argv[skip_end ? 3 : 2], checksum, skip_end)) exit(EXIT_FAILURE); // Convert checksum to hex string @@ -545,10 +546,23 @@ void parse_args(int argc, char* argv[]) sha256_raw_to_hex(checksum, hex); // Print result - printf("%s %s\n", hex, argv[2]); + printf("%s %s\n", hex, argv[skip_end ? 3 : 2]); exit(EXIT_SUCCESS); } + // Checksum verification mode + if(argc == 2 && strcmp(argv[1], "verify") == 0) + { + // Enable stdout printing + cli_mode = true; + const bool match = verify_self_hash(true); + if(match) + printf("%s SHA256 checksum matches\n", cli_tick()); + else + printf("%s SHA256 checksum does not match\n", cli_cross()); + exit(match ? EXIT_SUCCESS : EXIT_FAILURE); + } + // Local reverse name resolver if((argc == 3 || argc == 4) && strcasecmp(argv[1], "ptr") == 0) { @@ -1082,10 +1096,18 @@ void parse_args(int argc, char* argv[]) printf(" %s--update%s flag is given.\n\n", purple, normal); printf(" Usage: %spihole-FTL ntp %s[server]%s %s[--update]%s\n\n", green, cyan, normal, purple, normal); + printf("%sSHA256 checksum tools:%s\n", yellow, normal); + printf(" Calculates the SHA256 checksum of a file.\n\n"); + printf(" Usage: %spihole-FTL sha256sum %sfile%s\n\n", green, cyan, normal); + printf(" The special flag %s--skip-end%s can be used to skip the last 32\n", purple, normal); + printf(" bytes of the file. This is useful for files which have their\n"); + printf(" checksum appended at the end of the file, e.g., pihole-FTL:\n\n"); + printf(" %spihole-FTL sha256sum %s--skip_end %sfile%s\n\n", green, purple, cyan, normal); + printf("%sOther:%s\n", yellow, normal); + printf("\t%sverify%s Verify the integrity of the FTL binary\n", green, normal); printf("\t%sptr %sIP%s %s[tcp]%s Resolve IP address to hostname\n", green, cyan, normal, purple, normal); printf("\t Append %stcp%s to use TCP instead of UDP\n", purple, normal); - printf("\t%ssha256sum %sfile%s Calculate SHA256 checksum of a file\n", green, cyan, normal); printf("\t%sdhcp-discover%s Discover DHCP servers in the local\n", green, normal); printf("\t network\n"); printf("\t%sarp-scan %s[-a/-x]%s Use ARP to scan local network for\n", green, cyan, normal); diff --git a/src/config/config.c b/src/config/config.c index b984af70..2331adbd 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -1831,7 +1831,7 @@ void reread_config(void) // Create checksum of config file uint8_t checksum[SHA256_DIGEST_SIZE]; - if(!sha256sum(GLOBALTOMLPATH, checksum)) + if(!sha256sum(GLOBALTOMLPATH, checksum, false)) { log_err("Unable to create checksum of %s, not re-reading config file", GLOBALTOMLPATH); return; diff --git a/src/config/toml_writer.c b/src/config/toml_writer.c index 8361c324..57a2090a 100644 --- a/src/config/toml_writer.c +++ b/src/config/toml_writer.c @@ -35,7 +35,7 @@ bool writeFTLtoml(const bool verbose) // We need to (re-)calculate the checksum here as it'd otherwise // be outdated (in non-read-only mode, it's calculated at the // end of this function) - if(!sha256sum(GLOBALTOMLPATH, last_checksum)) + if(!sha256sum(GLOBALTOMLPATH, last_checksum, false)) log_err("Unable to create checksum of %s", GLOBALTOMLPATH); return true; } @@ -209,7 +209,7 @@ bool writeFTLtoml(const bool verbose) log_debug(DEBUG_CONFIG, "pihole.toml unchanged"); } - if(!sha256sum(GLOBALTOMLPATH, last_checksum)) + if(!sha256sum(GLOBALTOMLPATH, last_checksum, false)) log_err("Unable to create checksum of %s", GLOBALTOMLPATH); return true; diff --git a/src/files.c b/src/files.c index 8158f60a..9e934b13 100644 --- a/src/files.c +++ b/src/files.c @@ -13,6 +13,8 @@ #include "config/config.h" #include "config/setupVars.h" #include "log.h" +// sha256_raw_to_hex() +#include "config/password.h" // opendir(), readdir() #include @@ -27,10 +29,8 @@ // sendfile() #include #include - // PRIu64 #include - //basename() #include @@ -714,7 +714,7 @@ bool files_different(const char *pathA, const char* pathB, unsigned int from) } // Create SHA256 checksum of a file -bool sha256sum(const char *path, uint8_t checksum[SHA256_DIGEST_SIZE]) +bool sha256sum(const char *path, uint8_t checksum[SHA256_DIGEST_SIZE], const bool skip_end) { // Open file FILE *fp = fopen(path, "rb"); @@ -728,14 +728,30 @@ bool sha256sum(const char *path, uint8_t checksum[SHA256_DIGEST_SIZE]) struct sha256_ctx ctx; sha256_init(&ctx); - // Read file in chunks of bytes - const size_t pagesize = getpagesize(); - unsigned char *buf = calloc(pagesize, sizeof(char)); + // Get size of file + fseek(fp, 0, SEEK_END); + size_t filesize = ftell(fp); + fseek(fp, 0, SEEK_SET); + + // Determine chunk size + size_t chunksize = getpagesize(); + + // Read file in chunks + unsigned char *buf = calloc(chunksize, sizeof(char)); size_t len; - while((len = fread(buf, sizeof(char), pagesize, fp)) > 0) + while((len = fread(buf, sizeof(char), chunksize, fp)) > 0) { // Update SHA256 context sha256_update(&ctx, len, buf); + + // Reduce filesize by the number of bytes read + filesize -= len; + + // If we want to skip the end of the file, we have to adjust the + // chunk size to the remaining bytes minus the size of the SHA256 + // checksum itself + if(skip_end && filesize <= chunksize + SHA256_DIGEST_SIZE) + chunksize = filesize - SHA256_DIGEST_SIZE; } // Finalize SHA256 context @@ -749,3 +765,76 @@ bool sha256sum(const char *path, uint8_t checksum[SHA256_DIGEST_SIZE]) return true; } + +/** + * @brief Verifies the integrity of the current executable file by comparing its + * SHA256 checksum with a pre-computed hash stored in the last 8 bytes of the + * binary. + * + * @param verbose A boolean value indicating whether verbose output should be + * enabled. + * @return Returns true if the checksum matches the expected value, false + * otherwise. + */ +bool verify_self_hash(bool verbose) +{ + // Get the filename of the current executable + char filename[PATH_MAX] = { 0 }; + if(readlink("/proc/self/exe", filename, sizeof(filename)) == -1) + { + log_err("Failed to read self filename: %s", strerror(errno)); + return -1; + } + + // Read the pre-computed hash - it is stored in the last 8 bytes of the + // binary itself + uint8_t self_hash[SHA256_DIGEST_SIZE]; + FILE *f = fopen(filename, "r"); + if(f == NULL) + { + log_err("Failed to open self file \"%s\": %s", filename, strerror(errno)); + return -1; + } + if(fseek(f, -SHA256_DIGEST_SIZE, SEEK_END) != 0) + { + log_err("Failed to seek to hash: %s", strerror(errno)); + fclose(f); + return -1; + } + if(fread(self_hash, SHA256_DIGEST_SIZE, 1, f) != 1) + { + log_err("Failed to read hash: %s", strerror(errno)); + fclose(f); + return -1; + } + fclose(f); + + // Calculate the hash of the binary + uint8_t checksum[SHA256_DIGEST_SIZE]; + if(!sha256sum(filename, checksum, true)) + { + log_err("Failed to calculate SHA256 checksum of %s", filename); + return false; + } + + // Compare the checksums + bool success = memcmp(checksum, self_hash, SHA256_DIGEST_SIZE) == 0; + if(!success) + log_err("SHA256 checksum of %s does not match the expected value", filename); + + // Log the checksums if the verification failed or if verbose output is + // requested + if(!success || verbose) + { + // Convert checksums to human-readable hex strings + char expected_hex[SHA256_DIGEST_SIZE*2+1]; + sha256_raw_to_hex(self_hash, expected_hex); + char actual_hex[SHA256_DIGEST_SIZE*2+1]; + sha256_raw_to_hex(checksum, actual_hex); + + log_info("Expected: %s", expected_hex); + log_info("Actual: %s", actual_hex); + } + + return success; +} diff --git a/src/files.h b/src/files.h index fc10ad23..6af94bd3 100644 --- a/src/files.h +++ b/src/files.h @@ -36,7 +36,8 @@ bool directory_exists(const char *path); bool chown_pihole(const char *path, struct passwd *pwd); void rotate_files(const char *path, char **first_file); bool files_different(const char *pathA, const char* pathB, unsigned int from); -bool sha256sum(const char *path, uint8_t checksum[SHA256_DIGEST_SIZE]); +bool sha256sum(const char *path, uint8_t checksum[SHA256_DIGEST_SIZE], const bool skip_end); +bool verify_self_hash(bool verbose); int parse_line(char *line, char **key, char **value);