From a2b1c34b0f1f036e876b25628036f30674a43b13 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 08:50:55 +0000 Subject: [PATCH 1/9] Initial plan From 9d2f852ec29cb7c7e4250437b369e820507560b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 08:54:42 +0000 Subject: [PATCH 2/9] Fix critical buffer overflow and memory leak vulnerabilities - Replace unsafe strcpy() with strncpy() in fetch/common.h - Replace unsafe strcpy() with strncpy() in config parsing - Fix buffer overflow from strcat() by adding bounds checking - Fix memory leak by adding wordfree() for wordexp results - Add integer overflow validation for strtol() calls - Add errno checking for all strtol conversions Co-authored-by: squassina <8495707+squassina@users.noreply.github.com> --- src/config/parse_config.h | 43 ++++++++++++++++++++++++++++++++------ src/dispatch/bind_define.h | 9 +++++++- src/fetch/common.h | 6 ++++-- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index b7f89d59..5d7cbfae 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -537,10 +538,19 @@ int32_t parse_fold_state(const char *str) { } int64_t parse_color(const char *hex_str) { char *endptr; + errno = 0; int64_t hex_num = strtol(hex_str, &endptr, 16); - if (*endptr != '\0') { + + // Check for conversion errors + if (*endptr != '\0' || errno == ERANGE) { return -1; } + + // Validate range for color values (0x00000000 to 0xFFFFFFFF) + if (hex_num < 0 || hex_num > 0xFFFFFFFF) { + return -1; + } + return hex_num; } @@ -588,11 +598,20 @@ static char *combine_args_until_empty(char *values[], int count) { } combined[0] = '\0'; + size_t current_len = 0; for (int i = 0; i < first_empty; i++) { if (i > 0) { - strcat(combined, ","); + size_t remaining = total_len - current_len; + if (remaining > 0) { + strncat(combined, ",", remaining); + current_len += 1; + } + } + size_t remaining = total_len - current_len; + if (remaining > 0) { + strncat(combined, values[i], remaining); + current_len += strlen(values[i]); } - strcat(combined, values[i]); } return combined; @@ -626,8 +645,9 @@ uint32_t parse_mod(const char *mod_str) { if (strncmp(token, "code:", 5) == 0) { // 处理 code: 形式 char *endptr; + errno = 0; long keycode = strtol(token + 5, &endptr, 10); - if (endptr != token + 5 && (*endptr == '\0' || *endptr == ' ')) { + if (endptr != token + 5 && (*endptr == '\0' || *endptr == ' ') && errno != ERANGE) { switch (keycode) { case 133: case 134: @@ -777,7 +797,16 @@ KeySymCode parse_key(const char *key_str, bool isbindsym) { // 处理 code: 前缀的情况 if (strncmp(key_str, "code:", 5) == 0) { char *endptr; + errno = 0; xkb_keycode_t keycode = (xkb_keycode_t)strtol(key_str + 5, &endptr, 10); + + // Validate conversion + if (errno == ERANGE || *endptr != '\0') { + kc.type = KEY_TYPE_SYM; + kc.keysym = XKB_KEY_NoSymbol; + return kc; + } + kc.type = KEY_TYPE_CODE; kc.keycode.keycode1 = keycode; // 只设置第一个 kc.keycode.keycode2 = 0; @@ -2283,7 +2312,8 @@ bool parse_option(Config *config, char *key, char *value) { trim_whitespace(arg_value4); trim_whitespace(arg_value5); - strcpy(binding->mode, config->keymode); + strncpy(binding->mode, config->keymode, sizeof(binding->mode) - 1); + binding->mode[sizeof(binding->mode) - 1] = '\0'; if (strcmp(binding->mode, "common") == 0) { binding->iscommonmode = true; binding->isdefaultmode = false; @@ -3474,7 +3504,8 @@ bool parse_config(void) { config.tag_rules = NULL; config.tag_rules_count = 0; config.cursor_theme = NULL; - strcpy(config.keymode, "default"); + strncpy(config.keymode, "default", sizeof(config.keymode) - 1); + config.keymode[sizeof(config.keymode) - 1] = '\0'; create_config_keymap(); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 0bfab158..c653a7ba 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -837,11 +837,15 @@ int32_t spawn(const Arg *arg) { // 2. 解析参数 char *argv[64]; int32_t argc = 0; + wordexp_t wordexp_results[63]; // Track all wordexp results for cleanup + int32_t wordexp_count = 0; + char *token = strtok((char *)arg->v, " "); while (token != NULL && argc < 63) { wordexp_t p; if (wordexp(token, &p, 0) == 0) { argv[argc++] = p.we_wordv[0]; + wordexp_results[wordexp_count++] = p; // Store for cleanup } else { argv[argc++] = token; } @@ -852,7 +856,10 @@ int32_t spawn(const Arg *arg) { // 3. 执行命令 execvp(argv[0], argv); - // 4. execvp 失败时:打印错误并直接退出(避免 coredump) + // 4. execvp 失败时:清理并打印错误 + for (int i = 0; i < wordexp_count; i++) { + wordfree(&wordexp_results[i]); + } wlr_log(WLR_ERROR, "mango: execvp '%s' failed: %s\n", argv[0], strerror(errno)); _exit(EXIT_FAILURE); // 使用 _exit 避免缓冲区刷新等操作 diff --git a/src/fetch/common.h b/src/fetch/common.h index 58e69dc1..de5131a6 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -33,7 +33,8 @@ void get_layout_abbr(char *abbr, const char *full_name) { // 1. 尝试在映射表中查找 for (int32_t i = 0; layout_mappings[i].full_name != NULL; i++) { if (strcmp(full_name, layout_mappings[i].full_name) == 0) { - strcpy(abbr, layout_mappings[i].abbr); + strncpy(abbr, layout_mappings[i].abbr, 4); + abbr[4] = '\0'; return; } } @@ -73,7 +74,8 @@ void get_layout_abbr(char *abbr, const char *full_name) { abbr[2] = '\0'; } else { // 5. 最终回退:返回 "xx" - strcpy(abbr, "xx"); + strncpy(abbr, "xx", 4); + abbr[4] = '\0'; } } From d017fc4837240b62253fb70996f31df2996ce70f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 08:57:08 +0000 Subject: [PATCH 3/9] Address code review feedback on security fixes - Fix buffer size for strncpy to match actual buffer (32 bytes) - Use strtoul instead of strtol for unsigned color values - Improve strncat bounds checking with accurate length tracking - Free wordexp results immediately after use instead of batching - Add strdup for wordexp strings to avoid use-after-free Co-authored-by: squassina <8495707+squassina@users.noreply.github.com> --- _codeql_detected_source_root | 1 + src/config/parse_config.h | 27 ++++++++++++++++----------- src/dispatch/bind_define.h | 19 ++++++++++--------- src/fetch/common.h | 8 ++++---- 4 files changed, 31 insertions(+), 24 deletions(-) create mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 00000000..945c9b46 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 5d7cbfae..19a30d3e 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -539,7 +539,7 @@ int32_t parse_fold_state(const char *str) { int64_t parse_color(const char *hex_str) { char *endptr; errno = 0; - int64_t hex_num = strtol(hex_str, &endptr, 16); + uint64_t hex_num = strtoul(hex_str, &endptr, 16); // Check for conversion errors if (*endptr != '\0' || errno == ERANGE) { @@ -547,11 +547,11 @@ int64_t parse_color(const char *hex_str) { } // Validate range for color values (0x00000000 to 0xFFFFFFFF) - if (hex_num < 0 || hex_num > 0xFFFFFFFF) { + if (hex_num > 0xFFFFFFFF) { return -1; } - return hex_num; + return (int64_t)hex_num; } // 辅助函数:检查字符串是否以指定的前缀开头(忽略大小写) @@ -600,17 +600,22 @@ static char *combine_args_until_empty(char *values[], int count) { combined[0] = '\0'; size_t current_len = 0; for (int i = 0; i < first_empty; i++) { - if (i > 0) { + if (i > 0 && current_len < total_len) { size_t remaining = total_len - current_len; - if (remaining > 0) { - strncat(combined, ",", remaining); - current_len += 1; + size_t to_copy = (remaining < 1) ? 0 : 1; + if (to_copy > 0) { + strncat(combined, ",", to_copy); + current_len += to_copy; } } - size_t remaining = total_len - current_len; - if (remaining > 0) { - strncat(combined, values[i], remaining); - current_len += strlen(values[i]); + if (current_len < total_len) { + size_t remaining = total_len - current_len; + size_t val_len = strlen(values[i]); + size_t to_copy = (val_len < remaining) ? val_len : remaining; + if (to_copy > 0) { + strncat(combined, values[i], to_copy); + current_len += to_copy; + } } } diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index c653a7ba..e4463c31 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -837,15 +837,17 @@ int32_t spawn(const Arg *arg) { // 2. 解析参数 char *argv[64]; int32_t argc = 0; - wordexp_t wordexp_results[63]; // Track all wordexp results for cleanup - int32_t wordexp_count = 0; char *token = strtok((char *)arg->v, " "); while (token != NULL && argc < 63) { wordexp_t p; - if (wordexp(token, &p, 0) == 0) { - argv[argc++] = p.we_wordv[0]; - wordexp_results[wordexp_count++] = p; // Store for cleanup + if (wordexp(token, &p, 0) == 0 && p.we_wordc > 0) { + // Duplicate the string since we'll free the wordexp result + argv[argc] = strdup(p.we_wordv[0]); + wordfree(&p); // Free immediately after copying + if (argv[argc] != NULL) { + argc++; + } } else { argv[argc++] = token; } @@ -856,10 +858,9 @@ int32_t spawn(const Arg *arg) { // 3. 执行命令 execvp(argv[0], argv); - // 4. execvp 失败时:清理并打印错误 - for (int i = 0; i < wordexp_count; i++) { - wordfree(&wordexp_results[i]); - } + // 4. execvp 失败时:清理分配的字符串并打印错误 + // Note: We only need to free strings that were strdup'd from wordexp + // The original tokens from arg->v don't need to be freed wlr_log(WLR_ERROR, "mango: execvp '%s' failed: %s\n", argv[0], strerror(errno)); _exit(EXIT_FAILURE); // 使用 _exit 避免缓冲区刷新等操作 diff --git a/src/fetch/common.h b/src/fetch/common.h index de5131a6..28645969 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -33,8 +33,8 @@ void get_layout_abbr(char *abbr, const char *full_name) { // 1. 尝试在映射表中查找 for (int32_t i = 0; layout_mappings[i].full_name != NULL; i++) { if (strcmp(full_name, layout_mappings[i].full_name) == 0) { - strncpy(abbr, layout_mappings[i].abbr, 4); - abbr[4] = '\0'; + strncpy(abbr, layout_mappings[i].abbr, 31); + abbr[31] = '\0'; return; } } @@ -74,8 +74,8 @@ void get_layout_abbr(char *abbr, const char *full_name) { abbr[2] = '\0'; } else { // 5. 最终回退:返回 "xx" - strncpy(abbr, "xx", 4); - abbr[4] = '\0'; + strncpy(abbr, "xx", 31); + abbr[31] = '\0'; } } From e2649dd84f36b9fd76c28e072fcbcb7a0e5e1abd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 08:58:36 +0000 Subject: [PATCH 4/9] Final security improvements based on code review - Add LAYOUT_ABBR_SIZE constant to avoid magic numbers - Track allocated argv entries to properly free on error - Simplify strncat bounds checking using strlen for accuracy - Ensure all allocated memory is freed in error paths Co-authored-by: squassina <8495707+squassina@users.noreply.github.com> --- src/config/parse_config.h | 20 +++----------------- src/dispatch/bind_define.h | 13 ++++++++++--- src/fetch/common.h | 11 +++++++---- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 19a30d3e..a94686ee 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -598,25 +598,11 @@ static char *combine_args_until_empty(char *values[], int count) { } combined[0] = '\0'; - size_t current_len = 0; for (int i = 0; i < first_empty; i++) { - if (i > 0 && current_len < total_len) { - size_t remaining = total_len - current_len; - size_t to_copy = (remaining < 1) ? 0 : 1; - if (to_copy > 0) { - strncat(combined, ",", to_copy); - current_len += to_copy; - } - } - if (current_len < total_len) { - size_t remaining = total_len - current_len; - size_t val_len = strlen(values[i]); - size_t to_copy = (val_len < remaining) ? val_len : remaining; - if (to_copy > 0) { - strncat(combined, values[i], to_copy); - current_len += to_copy; - } + if (i > 0) { + strncat(combined, ",", total_len - strlen(combined)); } + strncat(combined, values[i], total_len - strlen(combined)); } return combined; diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index e4463c31..c845b4b9 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -836,6 +836,7 @@ int32_t spawn(const Arg *arg) { // 2. 解析参数 char *argv[64]; + bool argv_allocated[64] = {false}; // Track which argv entries were allocated int32_t argc = 0; char *token = strtok((char *)arg->v, " "); @@ -846,10 +847,13 @@ int32_t spawn(const Arg *arg) { argv[argc] = strdup(p.we_wordv[0]); wordfree(&p); // Free immediately after copying if (argv[argc] != NULL) { + argv_allocated[argc] = true; argc++; } } else { - argv[argc++] = token; + argv[argc] = token; + argv_allocated[argc] = false; + argc++; } token = strtok(NULL, " "); } @@ -859,8 +863,11 @@ int32_t spawn(const Arg *arg) { execvp(argv[0], argv); // 4. execvp 失败时:清理分配的字符串并打印错误 - // Note: We only need to free strings that were strdup'd from wordexp - // The original tokens from arg->v don't need to be freed + for (int i = 0; i < argc; i++) { + if (argv_allocated[i]) { + free(argv[i]); + } + } wlr_log(WLR_ERROR, "mango: execvp '%s' failed: %s\n", argv[0], strerror(errno)); _exit(EXIT_FAILURE); // 使用 _exit 避免缓冲区刷新等操作 diff --git a/src/fetch/common.h b/src/fetch/common.h index 28645969..072b4e0b 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -26,6 +26,9 @@ int32_t isdescprocess(pid_t p, pid_t c) { return (int32_t)c; } +// Buffer size for layout abbreviations (must match kb_layout buffer in dwl-ipc.h) +#define LAYOUT_ABBR_SIZE 32 + void get_layout_abbr(char *abbr, const char *full_name) { // 清空输出缓冲区 abbr[0] = '\0'; @@ -33,8 +36,8 @@ void get_layout_abbr(char *abbr, const char *full_name) { // 1. 尝试在映射表中查找 for (int32_t i = 0; layout_mappings[i].full_name != NULL; i++) { if (strcmp(full_name, layout_mappings[i].full_name) == 0) { - strncpy(abbr, layout_mappings[i].abbr, 31); - abbr[31] = '\0'; + strncpy(abbr, layout_mappings[i].abbr, LAYOUT_ABBR_SIZE - 1); + abbr[LAYOUT_ABBR_SIZE - 1] = '\0'; return; } } @@ -74,8 +77,8 @@ void get_layout_abbr(char *abbr, const char *full_name) { abbr[2] = '\0'; } else { // 5. 最终回退:返回 "xx" - strncpy(abbr, "xx", 31); - abbr[31] = '\0'; + strncpy(abbr, "xx", LAYOUT_ABBR_SIZE - 1); + abbr[LAYOUT_ABBR_SIZE - 1] = '\0'; } } From 12fe0abca1282015582b5a53b41f719269677379 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 09:00:02 +0000 Subject: [PATCH 5/9] Polish security fixes with minor improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove redundant null termination for short strings - Use descriptive variable names in cleanup loop - Cache strlen results to avoid O(n²) complexity in string concatenation - Add bounds checks before string operations Co-authored-by: squassina <8495707+squassina@users.noreply.github.com> --- src/config/parse_config.h | 11 ++++++++--- src/dispatch/bind_define.h | 6 +++--- src/fetch/common.h | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index a94686ee..9226fb53 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -598,11 +598,16 @@ static char *combine_args_until_empty(char *values[], int count) { } combined[0] = '\0'; + size_t current_len = 0; for (int i = 0; i < first_empty; i++) { - if (i > 0) { - strncat(combined, ",", total_len - strlen(combined)); + if (i > 0 && current_len < total_len) { + strncat(combined, ",", total_len - current_len); + current_len = strlen(combined); + } + if (current_len < total_len) { + strncat(combined, values[i], total_len - current_len); + current_len = strlen(combined); } - strncat(combined, values[i], total_len - strlen(combined)); } return combined; diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index c845b4b9..c522ff54 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -863,9 +863,9 @@ int32_t spawn(const Arg *arg) { execvp(argv[0], argv); // 4. execvp 失败时:清理分配的字符串并打印错误 - for (int i = 0; i < argc; i++) { - if (argv_allocated[i]) { - free(argv[i]); + for (int arg_idx = 0; arg_idx < argc; arg_idx++) { + if (argv_allocated[arg_idx]) { + free(argv[arg_idx]); } } wlr_log(WLR_ERROR, "mango: execvp '%s' failed: %s\n", argv[0], diff --git a/src/fetch/common.h b/src/fetch/common.h index 072b4e0b..dde1c800 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -77,8 +77,8 @@ void get_layout_abbr(char *abbr, const char *full_name) { abbr[2] = '\0'; } else { // 5. 最终回退:返回 "xx" + // strncpy will null-terminate since source is shorter than buffer strncpy(abbr, "xx", LAYOUT_ABBR_SIZE - 1); - abbr[LAYOUT_ABBR_SIZE - 1] = '\0'; } } From 21088fe86a606ebc3293ff0a4293869bdff7ad9c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 09:01:20 +0000 Subject: [PATCH 6/9] Final refinements for security and performance - Add explicit null termination for consistency - Optimize string concatenation to avoid redundant strlen calls - Track actual characters added to maintain O(n) complexity - Improve code clarity with better comments Co-authored-by: squassina <8495707+squassina@users.noreply.github.com> --- src/config/parse_config.h | 17 +++++++++++++---- src/fetch/common.h | 3 ++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 9226fb53..cb1cb36f 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -601,12 +601,21 @@ static char *combine_args_until_empty(char *values[], int count) { size_t current_len = 0; for (int i = 0; i < first_empty; i++) { if (i > 0 && current_len < total_len) { - strncat(combined, ",", total_len - current_len); - current_len = strlen(combined); + size_t remaining = total_len - current_len; + size_t to_add = (remaining < 1) ? 0 : 1; + if (to_add > 0) { + strncat(combined, ",", remaining); + current_len += to_add; // We know we added 1 character + } } if (current_len < total_len) { - strncat(combined, values[i], total_len - current_len); - current_len = strlen(combined); + size_t remaining = total_len - current_len; + size_t val_len = strlen(values[i]); + size_t to_add = (val_len < remaining) ? val_len : remaining; + if (to_add > 0) { + strncat(combined, values[i], remaining); + current_len += to_add; + } } } diff --git a/src/fetch/common.h b/src/fetch/common.h index dde1c800..18d90232 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -77,8 +77,9 @@ void get_layout_abbr(char *abbr, const char *full_name) { abbr[2] = '\0'; } else { // 5. 最终回退:返回 "xx" - // strncpy will null-terminate since source is shorter than buffer + // Explicit null termination for consistency strncpy(abbr, "xx", LAYOUT_ABBR_SIZE - 1); + abbr[LAYOUT_ABBR_SIZE - 1] = '\0'; } } From 5d145cc80fc681b5ac89a6b5849a4094c9c7c9b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 09:02:22 +0000 Subject: [PATCH 7/9] Simplify code based on final review feedback - Use direct character assignment for constant strings - Remove unnecessary tracking array and cleanup code - Simplify string concatenation logic - Fix length calculation to match actual strncat behavior - Code is cleaner and more maintainable Co-authored-by: squassina <8495707+squassina@users.noreply.github.com> --- src/config/parse_config.h | 16 +++++----------- src/dispatch/bind_define.h | 12 +++--------- src/fetch/common.h | 6 +++--- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index cb1cb36f..114e19e8 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -601,21 +601,15 @@ static char *combine_args_until_empty(char *values[], int count) { size_t current_len = 0; for (int i = 0; i < first_empty; i++) { if (i > 0 && current_len < total_len) { - size_t remaining = total_len - current_len; - size_t to_add = (remaining < 1) ? 0 : 1; - if (to_add > 0) { - strncat(combined, ",", remaining); - current_len += to_add; // We know we added 1 character - } + strncat(combined, ",", total_len - current_len); + current_len++; } if (current_len < total_len) { size_t remaining = total_len - current_len; size_t val_len = strlen(values[i]); - size_t to_add = (val_len < remaining) ? val_len : remaining; - if (to_add > 0) { - strncat(combined, values[i], remaining); - current_len += to_add; - } + size_t will_add = (val_len < remaining) ? val_len : remaining; + strncat(combined, values[i], remaining); + current_len += will_add; } } diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index c522ff54..4407f981 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -836,7 +836,6 @@ int32_t spawn(const Arg *arg) { // 2. 解析参数 char *argv[64]; - bool argv_allocated[64] = {false}; // Track which argv entries were allocated int32_t argc = 0; char *token = strtok((char *)arg->v, " "); @@ -847,12 +846,10 @@ int32_t spawn(const Arg *arg) { argv[argc] = strdup(p.we_wordv[0]); wordfree(&p); // Free immediately after copying if (argv[argc] != NULL) { - argv_allocated[argc] = true; argc++; } } else { argv[argc] = token; - argv_allocated[argc] = false; argc++; } token = strtok(NULL, " "); @@ -862,12 +859,9 @@ int32_t spawn(const Arg *arg) { // 3. 执行命令 execvp(argv[0], argv); - // 4. execvp 失败时:清理分配的字符串并打印错误 - for (int arg_idx = 0; arg_idx < argc; arg_idx++) { - if (argv_allocated[arg_idx]) { - free(argv[arg_idx]); - } - } + // 4. execvp 失败时打印错误并退出 + // Note: We don't need to free here since we're about to _exit + // The OS will clean up when the process exits wlr_log(WLR_ERROR, "mango: execvp '%s' failed: %s\n", argv[0], strerror(errno)); _exit(EXIT_FAILURE); // 使用 _exit 避免缓冲区刷新等操作 diff --git a/src/fetch/common.h b/src/fetch/common.h index 18d90232..b04f090d 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -77,9 +77,9 @@ void get_layout_abbr(char *abbr, const char *full_name) { abbr[2] = '\0'; } else { // 5. 最终回退:返回 "xx" - // Explicit null termination for consistency - strncpy(abbr, "xx", LAYOUT_ABBR_SIZE - 1); - abbr[LAYOUT_ABBR_SIZE - 1] = '\0'; + abbr[0] = 'x'; + abbr[1] = 'x'; + abbr[2] = '\0'; } } From 969c68b66df37febebd6dc13f051e0ae0c9c7c55 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 09:03:32 +0000 Subject: [PATCH 8/9] Fix remaining memory and buffer issues - Use memcpy with pointer arithmetic for safe string concatenation - Track and free allocated strings in spawn error path - Properly account for null terminator in all buffer operations - Eliminate potential buffer overflows from strncat edge cases Co-authored-by: squassina <8495707+squassina@users.noreply.github.com> --- src/config/parse_config.h | 24 +++++++++++++----------- src/dispatch/bind_define.h | 12 +++++++++--- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 114e19e8..a5f2391b 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -591,27 +591,29 @@ static char *combine_args_until_empty(char *values[], int count) { // plus the number of commas (first_empty-1 commas) total_len += (first_empty - 1); - // allocate memory and concatenate + // allocate memory and concatenate safely char *combined = malloc(total_len + 1); if (combined == NULL) { return strdup(""); } - combined[0] = '\0'; - size_t current_len = 0; + char *ptr = combined; + size_t remaining = total_len + 1; // Include space for null terminator + for (int i = 0; i < first_empty; i++) { - if (i > 0 && current_len < total_len) { - strncat(combined, ",", total_len - current_len); - current_len++; + if (i > 0 && remaining > 1) { + *ptr++ = ','; + remaining--; } - if (current_len < total_len) { - size_t remaining = total_len - current_len; + if (remaining > 1) { size_t val_len = strlen(values[i]); - size_t will_add = (val_len < remaining) ? val_len : remaining; - strncat(combined, values[i], remaining); - current_len += will_add; + size_t to_copy = (val_len < remaining - 1) ? val_len : remaining - 1; + memcpy(ptr, values[i], to_copy); + ptr += to_copy; + remaining -= to_copy; } } + *ptr = '\0'; // Null terminate return combined; } diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 4407f981..7a24417b 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -836,7 +836,9 @@ int32_t spawn(const Arg *arg) { // 2. 解析参数 char *argv[64]; + char *allocated_strings[64]; // Track strdup'd strings for cleanup int32_t argc = 0; + int32_t alloc_count = 0; char *token = strtok((char *)arg->v, " "); while (token != NULL && argc < 63) { @@ -846,6 +848,7 @@ int32_t spawn(const Arg *arg) { argv[argc] = strdup(p.we_wordv[0]); wordfree(&p); // Free immediately after copying if (argv[argc] != NULL) { + allocated_strings[alloc_count++] = argv[argc]; argc++; } } else { @@ -859,9 +862,12 @@ int32_t spawn(const Arg *arg) { // 3. 执行命令 execvp(argv[0], argv); - // 4. execvp 失败时打印错误并退出 - // Note: We don't need to free here since we're about to _exit - // The OS will clean up when the process exits + // 4. execvp 失败时:清理并退出 + // If execvp succeeds, this code never runs (process replaced) + // If it fails, clean up allocated strings before exiting + for (int i = 0; i < alloc_count; i++) { + free(allocated_strings[i]); + } wlr_log(WLR_ERROR, "mango: execvp '%s' failed: %s\n", argv[0], strerror(errno)); _exit(EXIT_FAILURE); // 使用 _exit 避免缓冲区刷新等操作 From 1350b7787ab4d159dd5ced5983010c7da25d7138 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 09:04:20 +0000 Subject: [PATCH 9/9] Final code polish for consistency and correctness - Use int32_t consistently in all loops - Add zero-length check before memcpy - Improve error detection in strtol (comment clarification) - Ensure null terminator always has space reserved Co-authored-by: squassina <8495707+squassina@users.noreply.github.com> --- src/config/parse_config.h | 10 +++++++--- src/dispatch/bind_define.h | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index a5f2391b..0ce6d1ea 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -607,10 +607,13 @@ static char *combine_args_until_empty(char *values[], int count) { } if (remaining > 1) { size_t val_len = strlen(values[i]); + // Always leave space for null terminator size_t to_copy = (val_len < remaining - 1) ? val_len : remaining - 1; - memcpy(ptr, values[i], to_copy); - ptr += to_copy; - remaining -= to_copy; + if (to_copy > 0) { + memcpy(ptr, values[i], to_copy); + ptr += to_copy; + remaining -= to_copy; + } } } *ptr = '\0'; // Null terminate @@ -648,6 +651,7 @@ uint32_t parse_mod(const char *mod_str) { char *endptr; errno = 0; long keycode = strtol(token + 5, &endptr, 10); + // Check for conversion errors: overflow or no conversion if (endptr != token + 5 && (*endptr == '\0' || *endptr == ' ') && errno != ERANGE) { switch (keycode) { case 133: diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 7a24417b..c54f9a53 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -865,7 +865,7 @@ int32_t spawn(const Arg *arg) { // 4. execvp 失败时:清理并退出 // If execvp succeeds, this code never runs (process replaced) // If it fails, clean up allocated strings before exiting - for (int i = 0; i < alloc_count; i++) { + for (int32_t i = 0; i < alloc_count; i++) { free(allocated_strings[i]); } wlr_log(WLR_ERROR, "mango: execvp '%s' failed: %s\n", argv[0],