diff --git a/libfwupdplugin/fu-common.c b/libfwupdplugin/fu-common.c index 62f1f6738..fc0eb55c3 100644 --- a/libfwupdplugin/fu-common.c +++ b/libfwupdplugin/fu-common.c @@ -1941,6 +1941,79 @@ fu_common_strnsplit(const gchar *str, gsize sz, const gchar *delimiter, gint max return g_strsplit(str, delimiter, max_tokens); } +/** + * fu_common_strnsplit_full: + * @str: a string to split + * @sz: size of @str, or -1 for unknown + * @delimiter: a string which specifies the places at which to split the string + * @callback: (scope call): a #FuCommonStrsplitFunc. + * @user_data: user data + * @error: (nullable): optional return location for an error + * + * Splits the string, calling the given function for each + * of the tokens found. If any @callback returns %FALSE scanning is aborted. + * + * Use this function in preference to fu_common_strnsplit() when the input file is untrusted, + * and you don't want to allocate a GStrv with billions of one byte items. + * + * Returns: %TRUE if no @callback returned FALSE + * + * Since: 1.7.0 + */ +gboolean +fu_common_strnsplit_full(const gchar *str, + gssize sz, + const gchar *delimiter, + FuCommonStrsplitFunc callback, + gpointer user_data, + GError **error) +{ + gsize delimiter_sz; + gsize str_sz; + guint found_idx = 0; + guint token_idx = 0; + + g_return_val_if_fail(str != NULL, FALSE); + g_return_val_if_fail(delimiter != NULL && delimiter[0] != '\0', FALSE); + g_return_val_if_fail(callback != NULL, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + /* make known */ + str_sz = sz != -1 ? (gsize)sz : strlen(str); + delimiter_sz = strlen(delimiter); + + /* cannot split */ + if (delimiter_sz > str_sz) { + g_autoptr(GString) token = g_string_new(str); + return callback(token, token_idx, user_data, error); + } + + /* start splittin' */ + for (gsize i = 0; i < (str_sz - delimiter_sz) + 1;) { + if (strncmp(str + i, delimiter, delimiter_sz) == 0) { + g_autoptr(GString) token = g_string_new(NULL); + g_string_append_len(token, str + found_idx, i - found_idx); + if (!callback(token, token_idx++, user_data, error)) + return FALSE; + i += delimiter_sz; + found_idx = i; + } else { + i++; + } + } + + /* any bits left over? */ + if (found_idx != str_sz) { + g_autoptr(GString) token = g_string_new(NULL); + g_string_append_len(token, str + found_idx, str_sz - found_idx); + if (!callback(token, token_idx, user_data, error)) + return FALSE; + } + + /* success */ + return TRUE; +} + /** * fu_common_strsafe: * @str: (nullable): a string to make safe for printing diff --git a/libfwupdplugin/fu-common.h b/libfwupdplugin/fu-common.h index 4a4d2ac9e..a4e73edeb 100644 --- a/libfwupdplugin/fu-common.h +++ b/libfwupdplugin/fu-common.h @@ -344,6 +344,28 @@ void fu_common_string_append_kb(GString *str, guint idt, const gchar *key, gboolean value); gchar ** fu_common_strnsplit(const gchar *str, gsize sz, const gchar *delimiter, gint max_tokens); + +/** + * FuCommonStrsplitFunc: + * @token: a #GString + * @token_idx: the token number + * @user_data: user data + * @error: a #GError or NULL + * + * The fu_common_strnsplit_full() iteration callback. + */ +typedef gboolean (*FuCommonStrsplitFunc)(GString *token, + guint token_idx, + gpointer user_data, + GError **error); +gboolean +fu_common_strnsplit_full(const gchar *str, + gssize sz, + const gchar *delimiter, + FuCommonStrsplitFunc callback, + gpointer user_data, + GError **error); + gchar * fu_common_strsafe(const gchar *str, gsize maxsz); gchar * diff --git a/libfwupdplugin/fu-ihex-firmware.c b/libfwupdplugin/fu-ihex-firmware.c index 8b9062003..57893ab30 100644 --- a/libfwupdplugin/fu-ihex-firmware.c +++ b/libfwupdplugin/fu-ihex-firmware.c @@ -30,6 +30,8 @@ typedef struct { G_DEFINE_TYPE_WITH_PRIVATE(FuIhexFirmware, fu_ihex_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_ihex_firmware_get_instance_private(o)) +#define FU_IHEX_FIRMWARE_TOKENS_MAX 100000 /* lines */ + /** * fu_ihex_firmware_get_records: * @self: A #FuIhexFirmware @@ -180,30 +182,60 @@ fu_ihex_firmware_record_type_to_string(guint8 record_type) return NULL; } +typedef struct { + FuIhexFirmware *self; + FwupdInstallFlags flags; +} FuIhexFirmwareTokenHelper; + +static gboolean +fu_ihex_firmware_tokenize_cb(GString *token, guint token_idx, gpointer user_data, GError **error) +{ + FuIhexFirmwareTokenHelper *helper = (FuIhexFirmwareTokenHelper *)user_data; + FuIhexFirmwarePrivate *priv = GET_PRIVATE(helper->self); + g_autoptr(FuIhexFirmwareRecord) rcd = NULL; + + /* sanity check */ + if (token_idx > FU_IHEX_FIRMWARE_TOKENS_MAX) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "file has too many lines"); + return FALSE; + } + + /* remove WIN32 line endings */ + g_strdelimit(token->str, "\r\x1a", '\0'); + token->len = strlen(token->str); + + /* ignore blank lines */ + if (token->len == 0) + return TRUE; + + /* ignore comments */ + if (g_str_has_prefix(token->str, ";")) + return TRUE; + + /* parse record */ + rcd = fu_ihex_firmware_record_new(token_idx + 1, token->str, helper->flags, error); + if (rcd == NULL) { + g_prefix_error(error, "invalid line %u: ", token_idx + 1); + return FALSE; + } + g_ptr_array_add(priv->records, g_steal_pointer(&rcd)); + return TRUE; +} + static gboolean fu_ihex_firmware_tokenize(FuFirmware *firmware, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuIhexFirmware *self = FU_IHEX_FIRMWARE(firmware); - FuIhexFirmwarePrivate *priv = GET_PRIVATE(self); - gsize sz = 0; - const gchar *data = g_bytes_get_data(fw, &sz); - g_auto(GStrv) lines = fu_common_strnsplit(data, sz, "\n", -1); - - for (guint ln = 0; lines[ln] != NULL; ln++) { - g_autoptr(FuIhexFirmwareRecord) rcd = NULL; - g_strdelimit(lines[ln], "\r\x1a", '\0'); - if (g_str_has_prefix(lines[ln], ";")) - continue; - if (lines[ln][0] == '\0') - continue; - rcd = fu_ihex_firmware_record_new(ln + 1, lines[ln], flags, error); - if (rcd == NULL) { - g_prefix_error(error, "invalid line %u: ", ln + 1); - return FALSE; - } - g_ptr_array_add(priv->records, g_steal_pointer(&rcd)); - } - return TRUE; + FuIhexFirmwareTokenHelper helper = {.self = self, .flags = flags}; + return fu_common_strnsplit_full(g_bytes_get_data(fw, NULL), + g_bytes_get_size(fw), + "\n", + fu_ihex_firmware_tokenize_cb, + &helper, + error); } static gboolean diff --git a/libfwupdplugin/fu-self-test.c b/libfwupdplugin/fu-self-test.c index fcbbcad72..cacc5e808 100644 --- a/libfwupdplugin/fu-self-test.c +++ b/libfwupdplugin/fu-self-test.c @@ -422,6 +422,52 @@ fu_smbios_class_func(void) g_assert_cmpuint(byte, ==, 16); } +static gboolean +_strnsplit_add_cb(GString *token, guint token_idx, gpointer user_data, GError **error) +{ + GPtrArray *array = (GPtrArray *)user_data; + g_debug("TOKEN: [%s] (%u)", token->str, token_idx); + g_ptr_array_add(array, g_strdup(token->str)); + return TRUE; +} + +static gboolean +_strnsplit_nop_cb(GString *token, guint token_idx, gpointer user_data, GError **error) +{ + guint *cnt = (guint *)user_data; + (*cnt)++; + return TRUE; +} + +static void +fu_common_strnsplit_func(void) +{ + const gchar *str = "123foo123bar123"; + const guint bigsz = 1024 * 1024; + gboolean ret; + guint cnt = 0; + g_autoptr(GError) error = NULL; + g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func(g_free); + g_autoptr(GString) bigstr = g_string_sized_new(bigsz * 2); + + /* works for me */ + ret = fu_common_strnsplit_full(str, -1, "123", _strnsplit_add_cb, array, &error); + g_assert_no_error(error); + g_assert_true(ret); + g_assert_cmpint(array->len, ==, 3); + g_assert_cmpstr(g_ptr_array_index(array, 0), ==, ""); + g_assert_cmpstr(g_ptr_array_index(array, 1), ==, "foo"); + g_assert_cmpstr(g_ptr_array_index(array, 2), ==, "bar"); + + /* lets try something insane */ + for (guint i = 0; i < bigsz; i++) + g_string_append(bigstr, "X\n"); + ret = fu_common_strnsplit_full(bigstr->str, -1, "\n", _strnsplit_nop_cb, &cnt, &error); + g_assert_no_error(error); + g_assert_true(ret); + g_assert_cmpint(cnt, ==, bigsz); +} + static void fu_common_strsafe_func(void) { @@ -3642,6 +3688,7 @@ main(int argc, char **argv) g_setenv("FWUPD_OFFLINE_TRIGGER", "/tmp/fwupd-self-test/system-update", TRUE); g_setenv("FWUPD_LOCALSTATEDIR", "/tmp/fwupd-self-test/var", TRUE); + g_test_add_func("/fwupd/common{strnsplit}", fu_common_strnsplit_func); g_test_add_func("/fwupd/progress", fu_progress_func); g_test_add_func("/fwupd/progress{child}", fu_progress_child_func); g_test_add_func("/fwupd/progress{parent-1-step}", fu_progress_parent_one_step_proxy_func); diff --git a/libfwupdplugin/fu-srec-firmware.c b/libfwupdplugin/fu-srec-firmware.c index 53ec64034..1382021b6 100644 --- a/libfwupdplugin/fu-srec-firmware.c +++ b/libfwupdplugin/fu-srec-firmware.c @@ -29,6 +29,8 @@ typedef struct { G_DEFINE_TYPE_WITH_PRIVATE(FuSrecFirmware, fu_srec_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_srec_firmware_get_instance_private(o)) +#define FU_SREC_FIRMWARE_TOKENS_MAX 100000 /* lines */ + /** * fu_srec_firmware_get_records: * @self: A #FuSrecFirmware @@ -117,189 +119,227 @@ fu_srec_firmware_record_get_type(void) return type_id; } +typedef struct { + FuSrecFirmware *self; + FwupdInstallFlags flags; + gboolean got_eof; +} FuSrecFirmwareTokenHelper; + +static gboolean +fu_srec_firmware_tokenize_cb(GString *token, guint token_idx, gpointer user_data, GError **error) +{ + FuSrecFirmwareTokenHelper *helper = (FuSrecFirmwareTokenHelper *)user_data; + FuSrecFirmwarePrivate *priv = GET_PRIVATE(helper->self); + g_autoptr(FuSrecFirmwareRecord) rcd = NULL; + guint32 rec_addr32; + guint16 rec_addr16; + guint8 addrsz = 0; /* bytes */ + guint8 rec_count; /* words */ + guint8 rec_kind; + + /* sanity check */ + if (token_idx > FU_SREC_FIRMWARE_TOKENS_MAX) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "file has too many lines"); + return FALSE; + } + + /* remove WIN32 line endings */ + g_strdelimit(token->str, "\r\x1a", '\0'); + token->len = strlen(token->str); + + /* ignore blank lines */ + if (token->len == 0) + return TRUE; + + /* check starting token */ + if (token->str[0] != 'S' || token->len < 3) { + g_autofree gchar *strsafe = fu_common_strsafe(token->str, 3); + if (strsafe != NULL) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "invalid starting token, got '%s' at line %u", + strsafe, + token_idx + 1); + return FALSE; + } + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "invalid starting token at line %u", + token_idx + 1); + return FALSE; + } + + /* kind, count, address, (data), checksum, linefeed */ + rec_kind = token->str[1] - '0'; + if (!fu_firmware_strparse_uint8_safe(token->str, token->len, 2, &rec_count, error)) + return FALSE; + if (rec_count * 2 != token->len - 4) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "count incomplete at line %u, " + "length %u, expected %u", + token_idx + 1, + (guint)token->len - 4, + (guint)rec_count * 2); + return FALSE; + } + + /* checksum check */ + if ((helper->flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { + guint8 rec_csum = 0; + guint8 rec_csum_expected; + for (guint8 i = 0; i < rec_count; i++) { + guint8 csum_tmp = 0; + if (!fu_firmware_strparse_uint8_safe(token->str, + token->len, + (i * 2) + 2, + &csum_tmp, + error)) + return FALSE; + rec_csum += csum_tmp; + } + rec_csum ^= 0xff; + if (!fu_firmware_strparse_uint8_safe(token->str, + token->len, + (rec_count * 2) + 2, + &rec_csum_expected, + error)) + return FALSE; + if (rec_csum != rec_csum_expected) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "checksum incorrect line %u, " + "expected %02x, got %02x", + token_idx + 1, + rec_csum_expected, + rec_csum); + return FALSE; + } + } + + /* set each command settings */ + switch (rec_kind) { + case FU_FIRMWARE_SREC_RECORD_KIND_S0_HEADER: + addrsz = 2; + break; + case FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16: + addrsz = 2; + break; + case FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24: + addrsz = 3; + break; + case FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32: + addrsz = 4; + break; + case FU_FIRMWARE_SREC_RECORD_KIND_S5_COUNT_16: + addrsz = 2; + helper->got_eof = TRUE; + break; + case FU_FIRMWARE_SREC_RECORD_KIND_S6_COUNT_24: + addrsz = 3; + break; + case FU_FIRMWARE_SREC_RECORD_KIND_S7_COUNT_32: + addrsz = 4; + helper->got_eof = TRUE; + break; + case FU_FIRMWARE_SREC_RECORD_KIND_S8_TERMINATION_24: + addrsz = 3; + helper->got_eof = TRUE; + break; + case FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16: + addrsz = 2; + helper->got_eof = TRUE; + break; + default: + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "invalid srec record type S%c at line %u", + token->str[1], + token_idx + 1); + return FALSE; + } + + /* parse address */ + switch (addrsz) { + case 2: + if (!fu_firmware_strparse_uint16_safe(token->str, + token->len, + 4, + &rec_addr16, + error)) + return FALSE; + rec_addr32 = rec_addr16; + break; + case 3: + if (!fu_firmware_strparse_uint24_safe(token->str, + token->len, + 4, + &rec_addr32, + error)) + return FALSE; + break; + case 4: + if (!fu_firmware_strparse_uint32_safe(token->str, + token->len, + 4, + &rec_addr32, + error)) + return FALSE; + break; + default: + g_assert_not_reached(); + } + if (g_getenv("FU_SREC_FIRMWARE_VERBOSE") != NULL) { + g_debug("line %03u S%u addr:0x%04x datalen:0x%02x", + token_idx + 1, + rec_kind, + rec_addr32, + (guint)rec_count - addrsz - 1); + } + + /* data */ + rcd = fu_srec_firmware_record_new(token_idx + 1, rec_kind, rec_addr32); + if (rec_kind == 1 || rec_kind == 2 || rec_kind == 3) { + for (gsize i = 4 + (addrsz * 2); i <= rec_count * 2; i += 2) { + guint8 tmp = 0; + if (!fu_firmware_strparse_uint8_safe(token->str, + token->len, + i, + &tmp, + error)) + return FALSE; + fu_byte_array_append_uint8(rcd->buf, tmp); + } + } + g_ptr_array_add(priv->records, g_steal_pointer(&rcd)); + return TRUE; +} + static gboolean fu_srec_firmware_tokenize(FuFirmware *firmware, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSrecFirmware *self = FU_SREC_FIRMWARE(firmware); - FuSrecFirmwarePrivate *priv = GET_PRIVATE(self); - const gchar *data; - gboolean got_eof = FALSE; - gsize sz = 0; - g_auto(GStrv) lines = NULL; + FuSrecFirmwareTokenHelper helper = {.self = self, .flags = flags, .got_eof = FALSE}; /* parse records */ - data = g_bytes_get_data(fw, &sz); - lines = fu_common_strnsplit(data, sz, "\n", -1); - for (guint ln = 0; lines[ln] != NULL; ln++) { - g_autoptr(FuSrecFirmwareRecord) rcd = NULL; - const gchar *line = lines[ln]; - gsize linesz; - guint32 rec_addr32; - guint16 rec_addr16; - guint8 addrsz = 0; /* bytes */ - guint8 rec_count; /* words */ - guint8 rec_kind; - - /* ignore blank lines */ - g_strdelimit(lines[ln], "\r", '\0'); - linesz = strlen(line); - if (linesz == 0) - continue; - - /* check starting token */ - if (line[0] != 'S') { - g_autofree gchar *strsafe = fu_common_strsafe(line, 3); - if (strsafe != NULL) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "invalid starting token, got '%s' at line %u", - strsafe, - ln + 1); - return FALSE; - } - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "invalid starting token at line %u", - ln + 1); - return FALSE; - } - - /* kind, count, address, (data), checksum, linefeed */ - rec_kind = line[1] - '0'; - if (!fu_firmware_strparse_uint8_safe(line, linesz, 2, &rec_count, error)) - return FALSE; - if (rec_count * 2 != linesz - 4) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "count incomplete at line %u, " - "length %u, expected %u", - ln + 1, - (guint)linesz - 4, - (guint)rec_count * 2); - return FALSE; - } - - /* checksum check */ - if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { - guint8 rec_csum = 0; - guint8 rec_csum_expected; - for (guint8 i = 0; i < rec_count; i++) { - guint8 csum_tmp = 0; - if (!fu_firmware_strparse_uint8_safe(line, - linesz, - (i * 2) + 2, - &csum_tmp, - error)) - return FALSE; - rec_csum += csum_tmp; - } - rec_csum ^= 0xff; - if (!fu_firmware_strparse_uint8_safe(line, - linesz, - (rec_count * 2) + 2, - &rec_csum_expected, - error)) - return FALSE; - if (rec_csum != rec_csum_expected) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "checksum incorrect line %u, " - "expected %02x, got %02x", - ln + 1, - rec_csum_expected, - rec_csum); - return FALSE; - } - } - - /* set each command settings */ - switch (rec_kind) { - case FU_FIRMWARE_SREC_RECORD_KIND_S0_HEADER: - addrsz = 2; - break; - case FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16: - addrsz = 2; - break; - case FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24: - addrsz = 3; - break; - case FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32: - addrsz = 4; - break; - case FU_FIRMWARE_SREC_RECORD_KIND_S5_COUNT_16: - addrsz = 2; - got_eof = TRUE; - break; - case FU_FIRMWARE_SREC_RECORD_KIND_S6_COUNT_24: - addrsz = 3; - break; - case FU_FIRMWARE_SREC_RECORD_KIND_S7_COUNT_32: - addrsz = 4; - got_eof = TRUE; - break; - case FU_FIRMWARE_SREC_RECORD_KIND_S8_TERMINATION_24: - addrsz = 3; - got_eof = TRUE; - break; - case FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16: - addrsz = 2; - got_eof = TRUE; - break; - default: - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INVALID_FILE, - "invalid srec record type S%c at line %u", - line[1], - ln + 1); - return FALSE; - } - - /* parse address */ - switch (addrsz) { - case 2: - if (!fu_firmware_strparse_uint16_safe(line, linesz, 4, &rec_addr16, error)) - return FALSE; - rec_addr32 = rec_addr16; - break; - case 3: - if (!fu_firmware_strparse_uint24_safe(line, linesz, 4, &rec_addr32, error)) - return FALSE; - break; - case 4: - if (!fu_firmware_strparse_uint32_safe(line, linesz, 4, &rec_addr32, error)) - return FALSE; - break; - default: - g_assert_not_reached(); - } - if (g_getenv("FU_SREC_FIRMWARE_VERBOSE") != NULL) { - g_debug("line %03u S%u addr:0x%04x datalen:0x%02x", - ln + 1, - rec_kind, - rec_addr32, - (guint)rec_count - addrsz - 1); - } - - /* data */ - rcd = fu_srec_firmware_record_new(ln + 1, rec_kind, rec_addr32); - if (rec_kind == 1 || rec_kind == 2 || rec_kind == 3) { - for (gsize i = 4 + (addrsz * 2); i <= rec_count * 2; i += 2) { - guint8 tmp = 0; - if (!fu_firmware_strparse_uint8_safe(line, linesz, i, &tmp, error)) - return FALSE; - fu_byte_array_append_uint8(rcd->buf, tmp); - } - } - g_ptr_array_add(priv->records, g_steal_pointer(&rcd)); - } + if (!fu_common_strnsplit_full(g_bytes_get_data(fw, NULL), + g_bytes_get_size(fw), + "\n", + fu_srec_firmware_tokenize_cb, + &helper, + error)) + return FALSE; /* no EOF */ - if (!got_eof) { + if (!helper.got_eof) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, diff --git a/libfwupdplugin/fwupdplugin.map b/libfwupdplugin/fwupdplugin.map index a7b4ffad1..15e6de1ee 100644 --- a/libfwupdplugin/fwupdplugin.map +++ b/libfwupdplugin/fwupdplugin.map @@ -861,6 +861,7 @@ LIBFWUPDPLUGIN_1.6.2 { LIBFWUPDPLUGIN_1.7.0 { global: + fu_common_strnsplit_full; fu_device_set_progress; fu_plugin_runner_attach; fu_plugin_runner_cleanup; diff --git a/plugins/ccgx/fu-ccgx-firmware.c b/plugins/ccgx/fu-ccgx-firmware.c index a0982482a..afffd2612 100644 --- a/plugins/ccgx/fu-ccgx-firmware.c +++ b/plugins/ccgx/fu-ccgx-firmware.c @@ -27,6 +27,8 @@ G_DEFINE_TYPE(FuCcgxFirmware, fu_ccgx_firmware, FU_TYPE_FIRMWARE) /* offset stored application version for CCGx */ #define CCGX_APP_VERSION_OFFSET 228 /* 128+64+32+4 */ +#define FU_CCGX_FIRMWARE_TOKENS_MAX 100000 /* lines */ + GPtrArray * fu_ccgx_firmware_get_records(FuCcgxFirmware *self) { @@ -79,11 +81,10 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCcgxFirmwareRecord, fu_ccgx_firmware_record_free static gboolean fu_ccgx_firmware_add_record(FuCcgxFirmware *self, - const gchar *line, + GString *token, FwupdInstallFlags flags, GError **error) { - guint16 linesz = strlen(line); guint16 buflen; guint8 checksum_calc = 0; g_autoptr(FuCcgxFirmwareRecord) rcd = NULL; @@ -91,26 +92,30 @@ fu_ccgx_firmware_add_record(FuCcgxFirmware *self, /* parse according to https://community.cypress.com/docs/DOC-10562 */ rcd = g_new0(FuCcgxFirmwareRecord, 1); - if (!fu_firmware_strparse_uint8_safe(line, linesz, 0, &rcd->array_id, error)) + if (!fu_firmware_strparse_uint8_safe(token->str, token->len, 0, &rcd->array_id, error)) return FALSE; - if (!fu_firmware_strparse_uint16_safe(line, linesz, 2, &rcd->row_number, error)) + if (!fu_firmware_strparse_uint16_safe(token->str, token->len, 2, &rcd->row_number, error)) return FALSE; - if (!fu_firmware_strparse_uint16_safe(line, linesz, 6, &buflen, error)) + if (!fu_firmware_strparse_uint16_safe(token->str, token->len, 6, &buflen, error)) return FALSE; - if (linesz != (buflen * 2) + 12) { + if (token->len != ((gsize)buflen * 2) + 12) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid record, expected %u chars, got %u", (guint)(buflen * 2) + 12, - linesz); + (guint)token->len); return FALSE; } /* parse payload, adding checksum */ for (guint i = 0; i < buflen; i++) { guint8 tmp = 0; - if (!fu_firmware_strparse_uint8_safe(line, linesz, 10 + (i * 2), &tmp, error)) + if (!fu_firmware_strparse_uint8_safe(token->str, + token->len, + 10 + (i * 2), + &tmp, + error)) return FALSE; fu_byte_array_append_uint8(data, tmp); checksum_calc += tmp; @@ -120,15 +125,19 @@ fu_ccgx_firmware_add_record(FuCcgxFirmware *self, /* verify 2s complement checksum */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint8 checksum_file; - if (!fu_firmware_strparse_uint8_safe(line, - linesz, + if (!fu_firmware_strparse_uint8_safe(token->str, + token->len, (buflen * 2) + 10, &checksum_file, error)) return FALSE; for (guint i = 0; i < 5; i++) { guint8 tmp = 0; - if (!fu_firmware_strparse_uint8_safe(line, linesz, i * 2, &tmp, error)) + if (!fu_firmware_strparse_uint8_safe(token->str, + token->len, + i * 2, + &tmp, + error)) return FALSE; checksum_calc += tmp; } @@ -287,6 +296,69 @@ fu_ccgx_firmware_parse_md_block(FuCcgxFirmware *self, FwupdInstallFlags flags, G return TRUE; } +typedef struct { + FuCcgxFirmware *self; + FwupdInstallFlags flags; +} FuCcgxFirmwareTokenHelper; + +static gboolean +fu_ccgx_firmware_tokenize_cb(GString *token, guint token_idx, gpointer user_data, GError **error) +{ + FuCcgxFirmwareTokenHelper *helper = (FuCcgxFirmwareTokenHelper *)user_data; + FuCcgxFirmware *self = FU_CCGX_FIRMWARE(helper->self); + + /* sanity check */ + if (token_idx > FU_CCGX_FIRMWARE_TOKENS_MAX) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "file has too many lines"); + return FALSE; + } + + /* remove WIN32 line endings */ + g_strdelimit(token->str, "\r\x1a", '\0'); + token->len = strlen(token->str); + + /* header */ + if (token_idx == 0) { + guint32 device_id = 0; + if (token->len != 12) { + g_autofree gchar *strsafe = fu_common_strsafe(token->str, 12); + if (strsafe != NULL) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "invalid header, expected == 12 chars -- got %s", + strsafe); + return FALSE; + } + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "invalid header, expected == 12 chars"); + return FALSE; + } + if (!fu_firmware_strparse_uint32_safe(token->str, token->len, 0, &device_id, error)) + return FALSE; + self->silicon_id = device_id >> 16; + return TRUE; + } + + /* ignore blank lines */ + if (token->len == 0) + return TRUE; + + /* parse record */ + if (!fu_ccgx_firmware_add_record(self, token, helper->flags, error)) { + g_prefix_error(error, "error on line %u: ", token_idx + 1); + return FALSE; + } + + /* success */ + return TRUE; +} + static gboolean fu_ccgx_firmware_parse(FuFirmware *firmware, GBytes *fw, @@ -296,52 +368,16 @@ fu_ccgx_firmware_parse(FuFirmware *firmware, GError **error) { FuCcgxFirmware *self = FU_CCGX_FIRMWARE(firmware); - gsize linesz; - gsize sz = 0; - guint32 device_id = 0; - const gchar *data = g_bytes_get_data(fw, &sz); - g_auto(GStrv) lines = fu_common_strnsplit(data, sz, "\n", -1); + FuCcgxFirmwareTokenHelper helper = {.self = self, .flags = flags}; - /* parse header */ - if (lines[0] == NULL) { - g_set_error_literal(error, - FWUPD_ERROR, - FWUPD_ERROR_NOT_SUPPORTED, - "invalid header, expected == 12 chars"); + /* tokenize */ + if (!fu_common_strnsplit_full(g_bytes_get_data(fw, NULL), + g_bytes_get_size(fw), + "\n", + fu_ccgx_firmware_tokenize_cb, + &helper, + error)) return FALSE; - } - g_strdelimit(lines[0], "\r\x1a", '\0'); - linesz = strlen(lines[0]); - if (linesz != 12) { - g_autofree gchar *strsafe = fu_common_strsafe(lines[0], 12); - if (strsafe != NULL) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_NOT_SUPPORTED, - "invalid header, expected == 12 chars -- got %s", - strsafe); - return FALSE; - } - g_set_error_literal(error, - FWUPD_ERROR, - FWUPD_ERROR_NOT_SUPPORTED, - "invalid header, expected == 12 chars"); - return FALSE; - } - if (!fu_firmware_strparse_uint32_safe(lines[0], linesz, 0, &device_id, error)) - return FALSE; - self->silicon_id = device_id >> 16; - - /* parse data */ - for (guint ln = 1; lines[ln] != NULL; ln++) { - g_strdelimit(lines[ln], "\r\x1a", '\0'); - if (lines[ln][0] == '\0') - continue; - if (!fu_ccgx_firmware_add_record(self, lines[ln] + 1, flags, error)) { - g_prefix_error(error, "error on line %u: ", ln + 1); - return FALSE; - } - } /* address is first data entry */ if (self->records->len > 0) { diff --git a/plugins/wacom-usb/fu-wac-firmware.c b/plugins/wacom-usb/fu-wac-firmware.c index d59ed9a5f..e4e89b32f 100644 --- a/plugins/wacom-usb/fu-wac-firmware.c +++ b/plugins/wacom-usb/fu-wac-firmware.c @@ -18,12 +18,234 @@ struct _FuWacFirmware { G_DEFINE_TYPE(FuWacFirmware, fu_wac_firmware, FU_TYPE_FIRMWARE) +#define FU_WAC_FIRMWARE_TOKENS_MAX 100000 /* lines */ + typedef struct { guint32 addr; guint32 sz; guint32 prog_start_addr; } FuFirmwareWacHeaderRecord; +typedef struct { + FuFirmware *firmware; + FwupdInstallFlags flags; + GPtrArray *header_infos; + GString *image_buffer; + guint8 images_cnt; +} FuWacFirmwareTokenHelper; + +static gboolean +fu_wac_firmware_tokenize_cb(GString *token, guint token_idx, gpointer user_data, GError **error) +{ + FuWacFirmwareTokenHelper *helper = (FuWacFirmwareTokenHelper *)user_data; + g_autofree gchar *cmd = NULL; + + /* sanity check */ + if (token_idx > FU_WAC_FIRMWARE_TOKENS_MAX) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "file has too many lines"); + return FALSE; + } + + /* remove WIN32 line endings */ + g_strdelimit(token->str, "\r\x1a", '\0'); + token->len = strlen(token->str); + + /* ignore blank lines */ + cmd = g_strndup(token->str, 2); + if (g_strcmp0(cmd, "") == 0) + return TRUE; + + /* Wacom-specific metadata */ + if (g_strcmp0(cmd, "WA") == 0) { + /* header info record */ + if (token->len > 3 && memcmp(token->str + 2, "COM", 3) == 0) { + guint8 header_image_cnt = 0; + if (token->len != 40) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "invalid header, got %" G_GSIZE_FORMAT " bytes", + token->len); + return FALSE; + } + if (!fu_firmware_strparse_uint4_safe(token->str, + token->len, + 5, + &header_image_cnt, + error)) + return FALSE; + for (guint j = 0; j < header_image_cnt; j++) { + g_autofree FuFirmwareWacHeaderRecord *hdr = NULL; + hdr = g_new0(FuFirmwareWacHeaderRecord, 1); + if (!fu_firmware_strparse_uint32_safe(token->str, + token->len, + (j * 16) + 6, + &hdr->addr, + error)) + return FALSE; + if (!fu_firmware_strparse_uint32_safe(token->str, + token->len, + (j * 16) + 14, + &hdr->sz, + error)) + return FALSE; + g_debug("header_fw%u_addr: 0x%x", j, hdr->addr); + g_debug("header_fw%u_sz: 0x%x", j, hdr->sz); + g_ptr_array_add(helper->header_infos, g_steal_pointer(&hdr)); + } + return TRUE; + } + + /* firmware headline record */ + if (token->len == 13) { + FuFirmwareWacHeaderRecord *hdr; + guint8 idx = 0; + if (!fu_firmware_strparse_uint4_safe(token->str, + token->len, + 2, + &idx, + error)) + return FALSE; + if (idx == 0) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "headline %u invalid", + idx); + return FALSE; + } + if (idx > helper->header_infos->len) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "headline %u exceeds header count %u", + idx, + helper->header_infos->len); + return FALSE; + } + if (idx - 1 != helper->images_cnt) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "headline %u is not in sorted order", + idx); + return FALSE; + } + hdr = g_ptr_array_index(helper->header_infos, idx - 1); + if (!fu_firmware_strparse_uint32_safe(token->str, + token->len, + 3, + &hdr->prog_start_addr, + error)) + return FALSE; + if (hdr->prog_start_addr != hdr->addr) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "programming address 0x%x != " + "base address 0x%0x for idx %u", + hdr->prog_start_addr, + hdr->addr, + idx); + return FALSE; + } + g_debug("programing-start-address: 0x%x", hdr->prog_start_addr); + return TRUE; + } + + g_debug("unknown Wacom-specific metadata"); + return TRUE; + } + + /* start */ + if (g_strcmp0(cmd, "S0") == 0) { + if (helper->image_buffer->len > 0) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "duplicate S0 without S7"); + return FALSE; + } + g_string_append_printf(helper->image_buffer, "%s\n", token->str); + return TRUE; + } + + /* these are things we want to include in the image */ + if (g_strcmp0(cmd, "S1") == 0 || g_strcmp0(cmd, "S2") == 0 || g_strcmp0(cmd, "S3") == 0 || + g_strcmp0(cmd, "S5") == 0 || g_strcmp0(cmd, "S7") == 0 || g_strcmp0(cmd, "S8") == 0 || + g_strcmp0(cmd, "S9") == 0) { + if (helper->image_buffer->len == 0) { + g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "%s without S0", cmd); + return FALSE; + } + g_string_append_printf(helper->image_buffer, "%s\n", token->str); + } else { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "invalid SREC command on line %u: %s", + token_idx + 1, + cmd); + return FALSE; + } + + /* end */ + if (g_strcmp0(cmd, "S7") == 0) { + g_autoptr(GBytes) blob = NULL; + g_autoptr(GBytes) fw_srec = NULL; + g_autoptr(FuFirmware) firmware_srec = fu_srec_firmware_new(); + g_autoptr(FuFirmware) img = fu_firmware_new(); + FuFirmwareWacHeaderRecord *hdr; + + /* get the correct relocated start address */ + if (helper->images_cnt >= helper->header_infos->len) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "%s without header", + cmd); + return FALSE; + } + hdr = g_ptr_array_index(helper->header_infos, helper->images_cnt); + + if (helper->image_buffer->len == 0) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "%s with missing image buffer", + cmd); + return FALSE; + } + + /* parse SREC file and add as image */ + blob = g_bytes_new(helper->image_buffer->str, helper->image_buffer->len); + if (!fu_firmware_parse_full(firmware_srec, + blob, + hdr->addr, + 0x0, + helper->flags, + error)) + return FALSE; + fw_srec = fu_firmware_get_bytes(firmware_srec, error); + if (fw_srec == NULL) + return FALSE; + fu_firmware_set_bytes(img, fw_srec); + fu_firmware_set_addr(img, fu_firmware_get_addr(firmware_srec)); + fu_firmware_set_idx(img, helper->images_cnt); + fu_firmware_add_image(helper->firmware, img); + helper->images_cnt++; + + /* clear the image buffer */ + g_string_set_size(helper->image_buffer, 0); + } + + /* success */ + return TRUE; +} + static gboolean fu_wac_firmware_parse(FuFirmware *firmware, GBytes *fw, @@ -32,16 +254,18 @@ fu_wac_firmware_parse(FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { - gsize len; - guint8 *data; - guint8 images_cnt = 0; - g_auto(GStrv) lines = NULL; + gsize sz = 0; + const gchar *data = g_bytes_get_data(fw, &sz); g_autoptr(GPtrArray) header_infos = g_ptr_array_new_with_free_func(g_free); - g_autoptr(GString) image_buffer = NULL; + g_autoptr(GString) image_buffer = g_string_new(NULL); + FuWacFirmwareTokenHelper helper = {.firmware = firmware, + .flags = flags, + .header_infos = header_infos, + .image_buffer = image_buffer, + .images_cnt = 0}; /* check the prefix (BE) */ - data = (guint8 *)g_bytes_get_data(fw, &len); - if (len < 5 || memcmp(data, "WACOM", 5) != 0) { + if (sz < 5 || memcmp(data, "WACOM", 5) != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, @@ -49,213 +273,12 @@ fu_wac_firmware_parse(FuFirmware *firmware, return FALSE; } - /* parse each line */ - lines = fu_common_strnsplit((const gchar *)data, len, "\n", -1); - for (guint i = 0; lines[i] != NULL; i++) { - g_autofree gchar *cmd = NULL; - - /* remove windows line endings */ - g_strdelimit(lines[i], "\r", '\0'); - cmd = g_strndup(lines[i], 2); - - /* ignore blank lines */ - if (g_strcmp0(cmd, "") == 0) - continue; - - /* Wacom-specific metadata */ - if (g_strcmp0(cmd, "WA") == 0) { - gsize cmdlen = strlen(lines[i]); - - /* header info record */ - if (cmdlen > 3 && memcmp(lines[i] + 2, "COM", 3) == 0) { - guint8 header_image_cnt = 0; - if (cmdlen != 40) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "invalid header, got %" G_GSIZE_FORMAT " bytes", - cmdlen); - return FALSE; - } - if (!fu_firmware_strparse_uint4_safe(lines[i], - cmdlen, - 5, - &header_image_cnt, - error)) - return FALSE; - for (guint j = 0; j < header_image_cnt; j++) { - g_autofree FuFirmwareWacHeaderRecord *hdr = NULL; - hdr = g_new0(FuFirmwareWacHeaderRecord, 1); - if (!fu_firmware_strparse_uint32_safe(lines[i], - cmdlen, - (j * 16) + 6, - &hdr->addr, - error)) - return FALSE; - if (!fu_firmware_strparse_uint32_safe(lines[i], - cmdlen, - (j * 16) + 14, - &hdr->sz, - error)) - return FALSE; - g_debug("header_fw%u_addr: 0x%x", j, hdr->addr); - g_debug("header_fw%u_sz: 0x%x", j, hdr->sz); - g_ptr_array_add(header_infos, g_steal_pointer(&hdr)); - } - continue; - } - - /* firmware headline record */ - if (cmdlen == 13) { - FuFirmwareWacHeaderRecord *hdr; - guint8 idx = 0; - if (!fu_firmware_strparse_uint4_safe(lines[i], - cmdlen, - 2, - &idx, - error)) - return FALSE; - if (idx == 0) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "headline %u invalid", - idx); - return FALSE; - } - if (idx > header_infos->len) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "headline %u exceeds header count %u", - idx, - header_infos->len); - return FALSE; - } - if (idx - 1 != images_cnt) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "headline %u is not in sorted order", - idx); - return FALSE; - } - hdr = g_ptr_array_index(header_infos, idx - 1); - if (!fu_firmware_strparse_uint32_safe(lines[i], - cmdlen, - 3, - &hdr->prog_start_addr, - error)) - return FALSE; - if (hdr->prog_start_addr != hdr->addr) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "programming address 0x%x != " - "base address 0x%0x for idx %u", - hdr->prog_start_addr, - hdr->addr, - idx); - return FALSE; - } - g_debug("programing-start-address: 0x%x", hdr->prog_start_addr); - continue; - } - - g_debug("unknown Wacom-specific metadata"); - continue; - } - - /* start */ - if (g_strcmp0(cmd, "S0") == 0) { - if (image_buffer != NULL) { - g_set_error_literal(error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "duplicate S0 without S7"); - return FALSE; - } - image_buffer = g_string_new(NULL); - } - - /* these are things we want to include in the image */ - if (g_strcmp0(cmd, "S0") == 0 || g_strcmp0(cmd, "S1") == 0 || - g_strcmp0(cmd, "S2") == 0 || g_strcmp0(cmd, "S3") == 0 || - g_strcmp0(cmd, "S5") == 0 || g_strcmp0(cmd, "S7") == 0 || - g_strcmp0(cmd, "S8") == 0 || g_strcmp0(cmd, "S9") == 0) { - if (image_buffer == NULL) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "%s without S0", - cmd); - return FALSE; - } - g_string_append_printf(image_buffer, "%s\n", lines[i]); - } else { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "invalid SREC command on line %u: %s", - i + 1, - cmd); - return FALSE; - } - - /* end */ - if (g_strcmp0(cmd, "S7") == 0) { - g_autoptr(GBytes) blob = NULL; - g_autoptr(GBytes) fw_srec = NULL; - g_autoptr(FuFirmware) firmware_srec = fu_srec_firmware_new(); - g_autoptr(FuFirmware) img = fu_firmware_new(); - FuFirmwareWacHeaderRecord *hdr; - - /* get the correct relocated start address */ - if (images_cnt >= header_infos->len) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "%s without header", - cmd); - return FALSE; - } - hdr = g_ptr_array_index(header_infos, images_cnt); - - if (image_buffer == NULL) { - g_set_error(error, - FWUPD_ERROR, - FWUPD_ERROR_INTERNAL, - "%s with missing image buffer", - cmd); - return FALSE; - } - - /* parse SREC file and add as image */ - blob = g_bytes_new(image_buffer->str, image_buffer->len); - if (!fu_firmware_parse_full(firmware_srec, - blob, - hdr->addr, - 0x0, - flags, - error)) - return FALSE; - fw_srec = fu_firmware_get_bytes(firmware_srec, error); - if (fw_srec == NULL) - return FALSE; - fu_firmware_set_bytes(img, fw_srec); - fu_firmware_set_addr(img, fu_firmware_get_addr(firmware_srec)); - fu_firmware_set_idx(img, images_cnt); - fu_firmware_add_image(firmware, img); - images_cnt++; - - /* clear the image buffer */ - g_string_free(image_buffer, TRUE); - image_buffer = NULL; - } - } + /* tokenize */ + if (!fu_common_strnsplit_full(data, sz, "\n", fu_wac_firmware_tokenize_cb, &helper, error)) + return FALSE; /* verify data is complete */ - if (image_buffer != NULL) { + if (helper.image_buffer != NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, @@ -264,12 +287,12 @@ fu_wac_firmware_parse(FuFirmware *firmware, } /* ensure this matched the header */ - if (header_infos->len != images_cnt) { + if (helper.header_infos->len != helper.images_cnt) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "not enough images %u for header count %u", - images_cnt, + helper.images_cnt, header_infos->len); return FALSE; }