Add message table entry on binary verification failure

Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
DL6ER
2024-09-26 11:43:22 +02:00
parent bb93467442
commit 3ca04acd75
8 changed files with 98 additions and 10 deletions
+1 -1
View File
@@ -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
+75
View File
@@ -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<br>Expected: <pre>%s</pre><br>Actual: <pre>%s</pre><br>FTL commit is <code>%s</code> on <code>%s</code>",
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);
}
+1
View File
@@ -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
+6
View File
@@ -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();
+1
View File
@@ -264,6 +264,7 @@ enum message_type {
CERTIFICATE_DOMAIN_MISMATCH_MESSAGE,
CONNECTION_ERROR_MESSAGE,
NTP_MESSAGE,
VERIFY_MESSAGE,
MAX_MESSAGE,
} __attribute__ ((packed));
+11 -8
View File
@@ -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 <dirent.h>
@@ -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;
+1 -1
View File
@@ -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);
+2
View File
@@ -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;