From c89c55122116a829fc1442e784b8026be9868239 Mon Sep 17 00:00:00 2001 From: John McNamara Date: Fri, 24 May 2024 07:39:40 +0100 Subject: [PATCH] worksheet: fix heap buffer overflows from empty strings Closes #446 --- include/xlsxwriter/common.h | 4 +- include/xlsxwriter/utility.h | 3 +- src/chart.c | 2 +- src/utility.c | 12 ++++- src/workbook.c | 54 +++++++++++++++---- src/worksheet.c | 12 +++++ .../test_workbook_validate_worksheet_name.c | 4 +- 7 files changed, 74 insertions(+), 17 deletions(-) diff --git a/include/xlsxwriter/common.h b/include/xlsxwriter/common.h index 0610d959..21253943 100644 --- a/include/xlsxwriter/common.h +++ b/include/xlsxwriter/common.h @@ -107,8 +107,8 @@ typedef enum lxw_error { /** Function parameter validation error. */ LXW_ERROR_PARAMETER_VALIDATION, - /** Worksheet name cannot be blank. */ - LXW_ERROR_SHEETNAME_IS_BLANK, + /** Function string parameter is empty. */ + LXW_ERROR_PARAMETER_IS_EMPTY, /** Worksheet name exceeds Excel's limit of 31 characters. */ LXW_ERROR_SHEETNAME_LENGTH_EXCEEDED, diff --git a/include/xlsxwriter/utility.h b/include/xlsxwriter/utility.h index a885f610..5853d100 100644 --- a/include/xlsxwriter/utility.h +++ b/include/xlsxwriter/utility.h @@ -221,10 +221,9 @@ double lxw_unixtime_to_excel_date_epoch(int64_t unixtime, uint8_t date_1904); char *lxw_strdup(const char *str); char *lxw_strdup_formula(const char *formula); - size_t lxw_utf8_strlen(const char *str); - void lxw_str_tolower(char *str); +uint8_t lxw_str_is_empty(const char *str); /* Define a portable version of strcasecmp(). */ #ifdef _MSC_VER diff --git a/src/chart.c b/src/chart.c index 8abb8f50..ee8bcfb5 100644 --- a/src/chart.c +++ b/src/chart.c @@ -480,7 +480,7 @@ _chart_axis_set_default_num_format(lxw_chart_axis *axis, char *num_format) } /* - * Verify that a X/Y error bar property is support for the chart type. + * Verify that a X/Y error bar property is supported for the chart type. * All chart types, except Bar have Y error bars. Only Bar and Scatter * support X error bars. */ diff --git a/src/utility.c b/src/utility.c index 88110a16..31f4fb2d 100644 --- a/src/utility.c +++ b/src/utility.c @@ -40,7 +40,7 @@ char *error_strings[LXW_MAX_ERRNO + 1] = { "Feature is not currently supported in this configuration.", "NULL function parameter ignored.", "Function parameter validation error.", - "Worksheet name cannot be blank.", + "Function string parameter is empty.", "Worksheet name exceeds Excel's limit of 31 characters.", "Worksheet name cannot contain invalid characters: '[ ] : * ? / \\'", "Worksheet name cannot start or end with an apostrophe.", @@ -516,6 +516,16 @@ lxw_str_tolower(char *str) str[i] = tolower(str[i]); } +/* Simple check for empty strings. */ +uint8_t +lxw_str_is_empty(const char *str) +{ + if (str[0] == '\0') + return 1; + else + return 0; +} + /* Create a quoted version of the worksheet name, or return an unmodified * copy if it doesn't required quoting. */ char * diff --git a/src/workbook.c b/src/workbook.c index 4b94629a..0c8b22b3 100644 --- a/src/workbook.c +++ b/src/workbook.c @@ -678,8 +678,8 @@ _store_defined_name(lxw_workbook *self, const char *name, if (!name || !formula) return LXW_ERROR_NULL_PARAMETER_IGNORED; - if (strlen(name) == 0 || strlen(formula) == 0) - return LXW_ERROR_PARAMETER_VALIDATION; + if (lxw_str_is_empty(name) || lxw_str_is_empty(formula)) + return LXW_ERROR_PARAMETER_IS_EMPTY; if (lxw_utf8_strlen(name) > LXW_DEFINED_NAME_LENGTH || lxw_utf8_strlen(formula) > LXW_DEFINED_NAME_LENGTH) { @@ -713,7 +713,7 @@ _store_defined_name(lxw_workbook *self, const char *name, tmp_str++; worksheet_name = name_copy; - if (strlen(tmp_str) == 0 || strlen(worksheet_name) == 0) + if (lxw_str_is_empty(tmp_str) || lxw_str_is_empty(worksheet_name)) goto mem_error; /* Remove any worksheet quoting. */ @@ -939,8 +939,8 @@ _populate_range_dimensions(lxw_workbook *self, lxw_series_range *range) return; } else { - /* Peek forward to check for empty string. */ - if (tmp_str[1] == '\0') { + /* Check for empty string. */ + if (lxw_str_is_empty(tmp_str)) { range->ignore_cache = LXW_TRUE; return; } @@ -950,7 +950,7 @@ _populate_range_dimensions(lxw_workbook *self, lxw_series_range *range) tmp_str++; sheetname = formula; - if (strlen(tmp_str) == 0 || strlen(sheetname) == 0) { + if (lxw_str_is_empty(tmp_str) || lxw_str_is_empty(sheetname)) { range->ignore_cache = LXW_TRUE; return; } @@ -2374,6 +2374,12 @@ workbook_set_custom_property_string(lxw_workbook *self, const char *name, return LXW_ERROR_NULL_PARAMETER_IGNORED; } + if (lxw_str_is_empty(name)) { + LXW_WARN_FORMAT("workbook_set_custom_property_string(): " + "parameter 'name' cannot be an empty string."); + return LXW_ERROR_PARAMETER_IS_EMPTY; + } + if (!value) { LXW_WARN_FORMAT("workbook_set_custom_property_string(): " "parameter 'value' cannot be NULL."); @@ -2421,6 +2427,12 @@ workbook_set_custom_property_number(lxw_workbook *self, const char *name, return LXW_ERROR_NULL_PARAMETER_IGNORED; } + if (lxw_str_is_empty(name)) { + LXW_WARN_FORMAT("workbook_set_custom_property_number(): parameter " + "'name' cannot be an empty string."); + return LXW_ERROR_PARAMETER_IS_EMPTY; + } + if (lxw_utf8_strlen(name) > 255) { LXW_WARN_FORMAT("workbook_set_custom_property_number(): parameter " "'name' exceeds Excel length limit of 255."); @@ -2456,6 +2468,12 @@ workbook_set_custom_property_integer(lxw_workbook *self, const char *name, return LXW_ERROR_NULL_PARAMETER_IGNORED; } + if (lxw_str_is_empty(name)) { + LXW_WARN_FORMAT("workbook_set_custom_property_integer(): parameter " + "'name' cannot be an empty string."); + return LXW_ERROR_PARAMETER_IS_EMPTY; + } + if (strlen(name) > 255) { LXW_WARN_FORMAT("workbook_set_custom_property_integer(): parameter " "'name' exceeds Excel length limit of 255."); @@ -2491,6 +2509,12 @@ workbook_set_custom_property_boolean(lxw_workbook *self, const char *name, return LXW_ERROR_NULL_PARAMETER_IGNORED; } + if (lxw_str_is_empty(name)) { + LXW_WARN_FORMAT("workbook_set_custom_property_boolean(): parameter " + "'name' cannot be an empty string."); + return LXW_ERROR_PARAMETER_IS_EMPTY; + } + if (lxw_utf8_strlen(name) > 255) { LXW_WARN_FORMAT("workbook_set_custom_property_boolean(): parameter " "'name' exceeds Excel length limit of 255."); @@ -2526,6 +2550,12 @@ workbook_set_custom_property_datetime(lxw_workbook *self, const char *name, return LXW_ERROR_NULL_PARAMETER_IGNORED; } + if (lxw_str_is_empty(name)) { + LXW_WARN_FORMAT("workbook_set_custom_property_datetime(): parameter " + "'name' cannot be an empty string."); + return LXW_ERROR_PARAMETER_IS_EMPTY; + } + if (lxw_utf8_strlen(name) > 255) { LXW_WARN_FORMAT("workbook_set_custom_property_datetime(): parameter " "'name' exceeds Excel length limit of 255."); @@ -2627,9 +2657,9 @@ workbook_validate_sheet_name(lxw_workbook *self, const char *sheetname) if (sheetname == NULL) return LXW_ERROR_NULL_PARAMETER_IGNORED; - /* Check for blank worksheet name. */ - if (strlen(sheetname) == 0) - return LXW_ERROR_SHEETNAME_IS_BLANK; + /* Check for empty worksheet name. */ + if (lxw_str_is_empty(sheetname)) + return LXW_ERROR_PARAMETER_IS_EMPTY; /* Check the UTF-8 length of the worksheet name. */ if (lxw_utf8_strlen(sheetname) > LXW_SHEETNAME_MAX) @@ -2729,6 +2759,12 @@ workbook_set_vba_name(lxw_workbook *self, const char *name) return LXW_ERROR_NULL_PARAMETER_IGNORED; } + if (lxw_str_is_empty(name)) { + LXW_WARN_FORMAT("workbook_set_vba_name(): parameter " + "'name' cannot be an empty string."); + return LXW_ERROR_PARAMETER_IS_EMPTY; + } + self->vba_codename = lxw_strdup(name); return LXW_NO_ERROR; diff --git a/src/worksheet.c b/src/worksheet.c index f2f6312c..10e13b65 100644 --- a/src/worksheet.c +++ b/src/worksheet.c @@ -7944,6 +7944,9 @@ worksheet_write_formula_num(lxw_worksheet *self, if (!formula) return LXW_ERROR_NULL_PARAMETER_IGNORED; + if (lxw_str_is_empty(formula)) + return LXW_ERROR_PARAMETER_IS_EMPTY; + err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); if (err) return err; @@ -7979,6 +7982,9 @@ worksheet_write_formula_str(lxw_worksheet *self, if (!formula) return LXW_ERROR_NULL_PARAMETER_IGNORED; + if (lxw_str_is_empty(formula)) + return LXW_ERROR_PARAMETER_IS_EMPTY; + err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); if (err) return err; @@ -8044,6 +8050,9 @@ _store_array_formula(lxw_worksheet *self, if (!formula) return LXW_ERROR_NULL_PARAMETER_IGNORED; + if (lxw_str_is_empty(formula)) + return LXW_ERROR_PARAMETER_IS_EMPTY; + /* Check that row and col are valid and store max and min values. */ err = _check_dimensions(self, first_row, first_col, LXW_FALSE, LXW_FALSE); if (err) @@ -8670,6 +8679,9 @@ worksheet_write_comment_opt(lxw_worksheet *self, if (!text) return LXW_ERROR_NULL_PARAMETER_IGNORED; + if (lxw_str_is_empty(text)) + return LXW_ERROR_PARAMETER_IS_EMPTY; + if (lxw_utf8_strlen(text) > LXW_STR_MAX) return LXW_ERROR_MAX_STRING_LENGTH_EXCEEDED; diff --git a/test/unit/workbook/test_workbook_validate_worksheet_name.c b/test/unit/workbook/test_workbook_validate_worksheet_name.c index 7140b8f5..ce1f60b6 100644 --- a/test/unit/workbook/test_workbook_validate_worksheet_name.c +++ b/test/unit/workbook/test_workbook_validate_worksheet_name.c @@ -147,13 +147,13 @@ CTEST(workbook, validate_worksheet_name09) { lxw_workbook_free(workbook); } -/* Test for blank sheet name. */ +/* Test for empty sheet name. */ CTEST(workbook, validate_worksheet_name10) { const char* sheetname = ""; lxw_workbook *workbook = workbook_new(NULL); - lxw_error exp = LXW_ERROR_SHEETNAME_IS_BLANK; + lxw_error exp = LXW_ERROR_PARAMETER_IS_EMPTY; lxw_error got = workbook_validate_sheet_name(workbook, sheetname); ASSERT_EQUAL(exp, got);