From 3ca04acd75cfd0e80ce13849ae353cb8e436cfd6 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 26 Sep 2024 11:43:22 +0200 Subject: [PATCH] Add message table entry on binary verification failure Signed-off-by: DL6ER --- src/args.c | 2 +- src/database/message-table.c | 75 ++++++++++++++++++++++++++++++++++++ src/database/message-table.h | 1 + src/dnsmasq_interface.c | 6 +++ src/enums.h | 1 + src/files.c | 19 +++++---- src/files.h | 2 +- src/main.c | 2 + 8 files changed, 98 insertions(+), 10 deletions(-) diff --git a/src/args.c b/src/args.c index 085b633a..c9bcbe15 100644 --- a/src/args.c +++ b/src/args.c @@ -555,7 +555,7 @@ void parse_args(int argc, char* argv[]) { // Enable stdout printing cli_mode = true; - const bool match = verify_self_hash(true); + const bool match = verify_FTL(true); if(match) printf("%s SHA256 checksum matches\n", cli_tick()); else diff --git a/src/database/message-table.c b/src/database/message-table.c index 409d151b..a563a939 100644 --- a/src/database/message-table.c +++ b/src/database/message-table.c @@ -31,6 +31,8 @@ #include "database/query-table.h" // escape_html() #include "webserver/http-common.h" +// GIT_HASH, FTL_ARCH +#include "version.h" // Number of arguments in a variadic macro // Credit: https://stackoverflow.com/a/35693080/2087442 @@ -99,6 +101,8 @@ static const char *get_message_type_str(const enum message_type type) return "CONNECTION_ERROR"; case NTP_MESSAGE: return "NTP"; + case VERIFY_MESSAGE: + return "VERIFY"; case MAX_MESSAGE: default: return "UNKNOWN"; @@ -135,6 +139,8 @@ static enum message_type get_message_type_from_string(const char *typestr) return CONNECTION_ERROR_MESSAGE; else if (strcmp(typestr, "NTP") == 0) return NTP_MESSAGE; + else if (strcmp(typestr, "VERIFY") == 0) + return VERIFY_MESSAGE; else return MAX_MESSAGE; } @@ -242,6 +248,14 @@ static unsigned char message_blob_types[MAX_MESSAGE][5] = SQLITE_NULL, // not used SQLITE_NULL, // not used SQLITE_NULL // not used + }, + { + // VERIFY_MESSAGE: The message column contains the error + SQLITE_TEXT, // expected checksum + SQLITE_TEXT, // actual checksum + SQLITE_TEXT, // FTL commit hash + SQLITE_TEXT, // FTL architecture + SQLITE_NULL // not used } }; // Create message table in the database @@ -928,6 +942,39 @@ static void format_ntp_message(char *plain, const int sizeof_plain, char *html, log_warn("format_ntp_message(): Buffer too small to hold HTML message, warning truncated"); } +static void format_verify_message(char *plain, const int sizeof_plain, char *html, const int sizeof_html, + const char *message, const char *expected, const char *actual, + const char *commit, const char *arch) +{ + if(snprintf(plain, sizeof_plain, "%s - expected \"%s\", but got \"%s\" - FTL commit is %s on %s", + message, expected, actual, commit, arch) > sizeof_plain) + log_warn("format_verify_message(): Buffer too small to hold plain message, warning truncated"); + + // Return early if HTML text is not required + if(sizeof_html < 1 || html == NULL) + return; + + char *escaped_message = escape_html(message); + char *escaped_expected = escape_html(expected); + char *escaped_actual = escape_html(actual); + char *escaped_commit = escape_html(commit); + char *escaped_arch = escape_html(arch); + + // Return early if memory allocation failed + if(escaped_message == NULL || escaped_expected == NULL || escaped_actual == NULL || escaped_commit == NULL || escaped_arch == NULL) + return; + + if(snprintf(html, sizeof_html, "%s
Expected:
%s

Actual:
%s

FTL commit is %s on %s", + escaped_message, escaped_expected, escaped_actual, escaped_commit, escaped_arch) > sizeof_html) + log_warn("format_verify_message(): Buffer too small to hold HTML message, warning truncated"); + + free(escaped_message); + free(escaped_expected); + free(escaped_actual); + free(escaped_commit); + free(escaped_arch); +} + int count_messages(const bool filter_dnsmasq_warnings) { int count = 0; @@ -1187,6 +1234,20 @@ bool format_messages(cJSON *array) break; } + case VERIFY_MESSAGE: + { + const char *message = (const char*)sqlite3_column_text(stmt, 3); + const char *expected = (const char*)sqlite3_column_text(stmt, 4); + const char *actual = (const char*)sqlite3_column_text(stmt, 5); + const char *hash = (const char*)sqlite3_column_text(stmt, 6); + const char *arch = (const char*)sqlite3_column_text(stmt, 7); + + format_verify_message(plain, sizeof(plain), html, sizeof(html), + message, expected, actual, hash, arch); + + break; + } + case MAX_MESSAGE: // Fall through default: log_warn("format_messages() - Unknown message type: %s", mtypestr); @@ -1458,3 +1519,17 @@ void log_ntp_message(const bool error, const bool server, const char *message) add_message(NTP_MESSAGE, message, level, who); } + +void log_verify_message(const char *expected, const char *actual) +{ + // Create message + char buf[2048]; + snprintf(buf, sizeof(buf), "Corrupt binary detected - this may lead to unexpected behaviour!"); + + // Log to FTL.log + log_crit("%s", buf); + + // Log to database + add_message(VERIFY_MESSAGE, buf, expected, actual, GIT_HASH, FTL_ARCH); + +} diff --git a/src/database/message-table.h b/src/database/message-table.h index 5354af6f..d230f066 100644 --- a/src/database/message-table.h +++ b/src/database/message-table.h @@ -31,5 +31,6 @@ void logg_inaccessible_adlist(const int dbindex, const char *address); void log_certificate_domain_mismatch(const char *certfile, const char *domain); void log_connection_error(const char *server, const char *reason, const char *error); void log_ntp_message(const bool error, const bool server, const char *message); +void log_verify_message(const char *expected, const char *actual); #endif //MESSAGETABLE_H diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 362eb1bb..9eec8cda 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -3130,6 +3130,12 @@ void FTL_fork_and_bind_sockets(struct passwd *ent_pw, bool dnsmasq_start) // Flush messages stored in the long-term database flush_message_table(); + // Verify checksum of this binary early on to ensure that the binary is + // not corrupted and that the binary is not tampered with. We can only + // do this here as we need the database to be properly initialized + // in case we need to store the verification result + verify_FTL(false); + // Initialize in-memory database starting index update_disk_db_idx(); diff --git a/src/enums.h b/src/enums.h index e748562c..1dd8c929 100644 --- a/src/enums.h +++ b/src/enums.h @@ -264,6 +264,7 @@ enum message_type { CERTIFICATE_DOMAIN_MISMATCH_MESSAGE, CONNECTION_ERROR_MESSAGE, NTP_MESSAGE, + VERIFY_MESSAGE, MAX_MESSAGE, } __attribute__ ((packed)); diff --git a/src/files.c b/src/files.c index 9e934b13..98834f19 100644 --- a/src/files.c +++ b/src/files.c @@ -15,6 +15,8 @@ #include "log.h" // sha256_raw_to_hex() #include "config/password.h" +// log_verify_message() +#include "database/message-table.h" // opendir(), readdir() #include @@ -776,7 +778,7 @@ bool sha256sum(const char *path, uint8_t checksum[SHA256_DIGEST_SIZE], const boo * @return Returns true if the checksum matches the expected value, false * otherwise. */ -bool verify_self_hash(bool verbose) +bool verify_FTL(bool verbose) { // Get the filename of the current executable char filename[PATH_MAX] = { 0 }; @@ -820,11 +822,6 @@ bool verify_self_hash(bool verbose) // 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]; @@ -832,8 +829,14 @@ bool verify_self_hash(bool verbose) 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); + if(!verbose) // during startup + log_verify_message(expected_hex, actual_hex); + else // CLI verification + { + log_err("Checksum verification failed!"); + log_err("Expected: %s", expected_hex); + log_err("Actual: %s", actual_hex); + } } return success; diff --git a/src/files.h b/src/files.h index 6af94bd3..b1096e14 100644 --- a/src/files.h +++ b/src/files.h @@ -37,7 +37,7 @@ 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], const bool skip_end); -bool verify_self_hash(bool verbose); +bool verify_FTL(bool verbose); int parse_line(char *line, char **key, char **value); diff --git a/src/main.c b/src/main.c index c7f288c3..d6909495 100644 --- a/src/main.c +++ b/src/main.c @@ -27,6 +27,8 @@ #include "overTime.h" // export_queries_to_disk() #include "database/query-table.h" +// verify_FTL() +#include "files.h" char *username; bool needGC = false;