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
This commit is contained in:
Richard Hughes 2021-09-15 12:09:10 +01:00
parent e50d911a59
commit 3162c8540d
8 changed files with 738 additions and 464 deletions

View File

@ -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

View File

@ -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 *

View File

@ -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

View File

@ -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);

View File

@ -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,

View File

@ -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;

View File

@ -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) {

View File

@ -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;
}