From 3162c8540d3d0e875cba7d3869c34d4d2992b5de Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Wed, 15 Sep 2021 12:09:10 +0100 Subject: [PATCH] Add new API for splitting an untrusted string Using fu_common_strnsplit() has the drawback that a malicious user (or a fuzzer!) could create a file with 5,000,000 newlines, and then pass that into any parser that tokenizes into lines. This causes millions of tiny allocations and quickly dirties hundreds of megabytes of RSS due to heap overheads. Rather than splitting a huge array and then processing each line, set up a callback to process each line and only allocate the next string if the token was parsed correctly. This means that we don't even dup the buffer before we start parsing, rather than allocating everything and then failing at the first hurdle. Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=38696 --- libfwupdplugin/fu-common.c | 73 +++++ libfwupdplugin/fu-common.h | 22 ++ libfwupdplugin/fu-ihex-firmware.c | 72 +++-- libfwupdplugin/fu-self-test.c | 47 +++ libfwupdplugin/fu-srec-firmware.c | 390 +++++++++++++----------- libfwupdplugin/fwupdplugin.map | 1 + plugins/ccgx/fu-ccgx-firmware.c | 146 +++++---- plugins/wacom-usb/fu-wac-firmware.c | 451 +++++++++++++++------------- 8 files changed, 738 insertions(+), 464 deletions(-) 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; }