From 96e2d48457caf06ac1ef54249e83f99207539cb7 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Wed, 22 Nov 2023 10:15:17 +0000 Subject: [PATCH] Hide HTTP passwords in fwupd debugging logs This keeps being pasted to public places like GitHub -- copy what curl does. --- libfwupdplugin/fu-self-test.c | 21 +++++++++++++++ libfwupdplugin/fu-string.c | 48 +++++++++++++++++++++++++++++++++++ libfwupdplugin/fu-string.h | 2 ++ src/fu-debug.c | 24 +++++++++++------- 4 files changed, 86 insertions(+), 9 deletions(-) diff --git a/libfwupdplugin/fu-self-test.c b/libfwupdplugin/fu-self-test.c index dae3cabd51c..d8cd9a775d0 100644 --- a/libfwupdplugin/fu-self-test.c +++ b/libfwupdplugin/fu-self-test.c @@ -566,6 +566,26 @@ fu_common_memmem_func(void) g_assert_false(ret); } +static void +fu_strpassmask_func(void) +{ + struct { + const gchar *in; + const gchar *op; + } strs[] = {{"foo https://test.com/auth bar", "foo https://test.com/auth bar"}, + {"foo https://user%40host:SECRET@test.com/auth bar", + "foo https://user%40host:XXXXXX@test.com/auth bar"}, + {"foo https://user1%40host:SECRET@test.com/auth " + "https://user2%40host:SECRET2@test.com/auth bar", + "foo https://user1%40host:XXXXXX@test.com/auth " + "https://user2%40host:XXXXXXX@test.com/auth bar"}, + {NULL, NULL}}; + for (guint i = 0; strs[i].in != NULL; i++) { + g_autofree gchar *tmp = fu_strpassmask(strs[i].in); + g_assert_cmpstr(tmp, ==, strs[i].op); + } +} + static void fu_strsplit_func(void) { @@ -4497,6 +4517,7 @@ main(int argc, char **argv) g_test_add_func("/fwupd/struct", fu_plugin_struct_func); g_test_add_func("/fwupd/struct{wrapped}", fu_plugin_struct_wrapped_func); g_test_add_func("/fwupd/plugin{quirks-append}", fu_plugin_quirks_append_func); + g_test_add_func("/fwupd/string{password-mask}", fu_strpassmask_func); g_test_add_func("/fwupd/common{strnsplit}", fu_strsplit_func); g_test_add_func("/fwupd/common{olson-timezone-id}", fu_common_olson_timezone_id_func); g_test_add_func("/fwupd/common{memmem}", fu_common_memmem_func); diff --git a/libfwupdplugin/fu-string.c b/libfwupdplugin/fu-string.c index 1a4cd5d10d0..0ea1e4c8e2e 100644 --- a/libfwupdplugin/fu-string.c +++ b/libfwupdplugin/fu-string.c @@ -580,6 +580,54 @@ fu_strjoin(const gchar *separator, GPtrArray *array) return g_strjoinv(separator, (gchar **)strv); } +/** + * fu_strpassmask: + * @str: (nullable): a string to make safe for printing + * + * Hides password strings encoded in HTTP requests. + * + * Returns: a string + * + * Since: 1.9.10 + **/ +gchar * +fu_strpassmask(const gchar *str) +{ + g_autoptr(GString) tmp = g_string_new(str); + if (tmp->str != NULL && g_strstr_len(tmp->str, -1, "@") != NULL && + g_strstr_len(tmp->str, -1, ":") != NULL) { + gboolean is_password = FALSE; + gboolean is_url = FALSE; + for (guint i = 0; i < tmp->len; i++) { + const gchar *url_prefixes[] = {"http://", "https://", NULL}; + for (guint j = 0; url_prefixes[j] != NULL; j++) { + if (g_str_has_prefix(tmp->str + i, url_prefixes[j])) { + is_url = TRUE; + i += strlen(url_prefixes[j]); + break; + } + } + if (tmp->str[i] == ' ' || tmp->str[i] == '@' || tmp->str[i] == '/') { + is_url = FALSE; + is_password = FALSE; + continue; + } + if (is_url && tmp->str[i] == ':') { + is_password = TRUE; + continue; + } + if (is_url && is_password) { + if (tmp->str[i] == '@') { + is_password = FALSE; + continue; + } + tmp->str[i] = 'X'; + } + } + } + return g_string_free(g_steal_pointer(&tmp), FALSE); +} + /** * fu_utf16_to_utf8_byte_array: * @array: a #GByteArray diff --git a/libfwupdplugin/fu-string.h b/libfwupdplugin/fu-string.h index d9d6800975f..1c6137d24ad 100644 --- a/libfwupdplugin/fu-string.h +++ b/libfwupdplugin/fu-string.h @@ -19,6 +19,8 @@ fu_string_append_kb(GString *str, guint idt, const gchar *key, gboolean value); gchar * fu_strsafe(const gchar *str, gsize maxsz); +gchar * +fu_strpassmask(const gchar *str); gboolean fu_strtoull(const gchar *str, guint64 *value, guint64 min, guint64 max, GError **error); gboolean diff --git a/src/fu-debug.c b/src/fu-debug.c index 051b12c8274..fe492f96b77 100644 --- a/src/fu-debug.c +++ b/src/fu-debug.c @@ -8,6 +8,8 @@ #include "config.h" +#include + #include #include #include @@ -124,17 +126,21 @@ fu_debug_handler_cb(const gchar *log_domain, { FuDebug *self = (FuDebug *)user_data; g_autofree gchar *timestamp = NULL; + g_autofree gchar *message_safe = NULL; g_autoptr(GString) domain = NULL; -#ifdef _WIN32 - /* use Windows event log */ - fu_debug_handler_win32(self, log_level, message); -#endif - /* should ignore */ if (!fu_debug_filter_cb(self, log_domain, log_level)) return; + /* make sure passwords never appear in logs */ + message_safe = fu_strpassmask(message); + +#ifdef _WIN32 + /* use Windows event log */ + fu_debug_handler_win32(self, log_level, message_safe); +#endif + /* time header */ if (!self->no_timestamp) { g_autoptr(GDateTime) dt = g_date_time_new_now_utc(); @@ -157,7 +163,7 @@ fu_debug_handler_cb(const gchar *log_domain, /* to file */ if (!self->console) { - g_autofree gchar *ascii_message = g_str_to_ascii(message, NULL); + g_autofree gchar *ascii_message = g_str_to_ascii(message_safe, NULL); if (timestamp != NULL) g_printerr("%s ", timestamp); if (domain != NULL) @@ -172,7 +178,7 @@ fu_debug_handler_cb(const gchar *log_domain, g_printerr("%s ", timestamp); if (domain != NULL) g_printerr("%s ", domain->str); - g_printerr("%s\n", message); + g_printerr("%s\n", message_safe); return; } @@ -186,7 +192,7 @@ fu_debug_handler_cb(const gchar *log_domain, g_printerr("%c[%dm%s ", 0x1B, 32, timestamp); if (domain != NULL) g_printerr("%s ", domain->str); - g_printerr("%c[%dm%s\n%c[%dm", 0x1B, 31, message, 0x1B, 0); + g_printerr("%c[%dm%s\n%c[%dm", 0x1B, 31, message_safe, 0x1B, 0); break; default: /* debug in blue */ @@ -194,7 +200,7 @@ fu_debug_handler_cb(const gchar *log_domain, g_printerr("%c[%dm%s ", 0x1B, 32, timestamp); if (domain != NULL) g_printerr("%s ", domain->str); - g_printerr("%c[%dm%s\n%c[%dm", 0x1B, 34, message, 0x1B, 0); + g_printerr("%c[%dm%s\n%c[%dm", 0x1B, 34, message_safe, 0x1B, 0); break; } }