mirror of
https://github.com/DreamMaoMao/maomaowm.git
synced 2026-05-02 06:46:29 -04:00
security: fix command execution and null-termination issues
Closes security vulnerabilities and documentation gaps: 1. Remove shell expansion from config-driven exec/exec-once - Eliminate wordexp() usage in spawn() - Add split_argv_noexpand() for safe argument parsing - Change run_exec() and run_exec_once() to use spawn() instead of spawn_shell() - Prevents shell injection and expansion-based DoS 2. Fix null-termination in chvt_backup_selmon - Add explicit null-terminator after strncpy() in chvt() - Prevents out-of-bounds read when used in regex_match() 3. Add regression test - New test_chvt_backup_selmon unit test to verify null-termination logic - Integrate tests into meson.build 4. Translate Chinese comments to English - Update IMPLEMENTATION_SUMMARY.md to remove Chinese text - Improves accessibility for international contributors 5. Update documentation - Update REVIEW_FINDINGS.md with English versions of examples - Remove wordexp include from meson.c headers (no longer needed)
This commit is contained in:
parent
5d2f052886
commit
5597a5ab80
8 changed files with 128 additions and 49 deletions
|
|
@ -61,37 +61,27 @@ shell expansion features.
|
||||||
|
|
||||||
#### Changes Made
|
#### Changes Made
|
||||||
|
|
||||||
Translated 10 Chinese comment lines to English:
|
Translated comment lines to English (originals removed):
|
||||||
|
|
||||||
1. Line 18: `"如果 sysconfdir 以 prefix 开头,去掉 prefix"`
|
1. Line 18: "If sysconfdir starts with prefix, remove prefix"
|
||||||
→ `"If sysconfdir starts with prefix, remove prefix"`
|
|
||||||
|
|
||||||
2. Line 21: `"确保 sysconfdir 是绝对路径"`
|
2. Line 21: "Ensure sysconfdir is an absolute path"
|
||||||
→ `"Ensure sysconfdir is an absolute path"`
|
|
||||||
|
|
||||||
3. Line 27: `"打印调试信息,确认 sysconfdir 的值"`
|
3. Line 27: "Print debug information to confirm sysconfdir value"
|
||||||
→ `"Print debug information to confirm sysconfdir value"`
|
|
||||||
|
|
||||||
4. Line 44: `"获取版本信息"`
|
4. Line 44: "Get version information"
|
||||||
→ `"Get version information"`
|
|
||||||
|
|
||||||
5. Line 48: `"检查当前目录是否是 Git 仓库"`
|
5. Line 48: "Check if current directory is a Git repository"
|
||||||
→ `"Check if current directory is a Git repository"`
|
|
||||||
|
|
||||||
6. Line 57: `"如果是 Git 目录,获取 Commit Hash 和最新的 tag"`
|
6. Line 57: "If in Git directory, get Commit Hash and latest tag"
|
||||||
→ `"If in Git directory, get Commit Hash and latest tag"`
|
|
||||||
|
|
||||||
7. Line 62: `"如果不是 Git 目录,使用项目版本号和 'release' 字符串"`
|
7. Line 62: "If not in Git directory, use project version number and 'release' string"
|
||||||
→ `"If not in Git directory, use project version number and 'release' string"`
|
|
||||||
|
|
||||||
8. Line 68: `"定义编译参数"`
|
8. Line 68: "Define compilation arguments"
|
||||||
→ `"Define compilation arguments"`
|
|
||||||
|
|
||||||
9. Line 78: `"仅在 debug 选项启用时添加调试参数"`
|
9. Line 78: "Only add debug arguments when debug option is enabled"
|
||||||
→ `"Only add debug arguments when debug option is enabled"`
|
|
||||||
|
|
||||||
10. Line 91: `"链接参数(根据 debug 状态添加 ASAN)"`
|
10. Line 91: "Link arguments (add ASAN based on debug state)"
|
||||||
→ `"Link arguments (add ASAN based on debug state)"`
|
|
||||||
|
|
||||||
#### Impact
|
#### Impact
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -330,10 +330,10 @@ for common cases (e.g., < 32 windows).
|
||||||
|
|
||||||
**Examples:**
|
**Examples:**
|
||||||
|
|
||||||
- Line 18: `# 如果 sysconfdir 以 prefix 开头,去掉 prefix`
|
- Line 18: `# If sysconfdir starts with prefix, remove prefix`
|
||||||
- Line 22: `# 确保 sysconfdir 是绝对路径`
|
- Line 22: `# Ensure sysconfdir is an absolute path`
|
||||||
- Line 27-29: Debug output comments
|
- Line 27-29: Debug output comments
|
||||||
- Line 44: `# 获取版本信息`
|
- Line 44: `# Get version information`
|
||||||
|
|
||||||
**Impact:** Reduces accessibility for international contributors
|
**Impact:** Reduces accessibility for international contributors
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -151,3 +151,5 @@ portal_install_dir = join_paths(prefix, 'share/xdg-desktop-portal')
|
||||||
install_data('assets/mango.desktop', install_dir : desktop_install_dir)
|
install_data('assets/mango.desktop', install_dir : desktop_install_dir)
|
||||||
install_data('assets/mango-portals.conf', install_dir : portal_install_dir)
|
install_data('assets/mango-portals.conf', install_dir : portal_install_dir)
|
||||||
install_data('assets/config.conf', install_dir : join_paths(sysconfdir, 'mango'))
|
install_data('assets/config.conf', install_dir : join_paths(sysconfdir, 'mango'))
|
||||||
|
|
||||||
|
subdir('tests')
|
||||||
|
|
|
||||||
|
|
@ -1232,7 +1232,7 @@ void run_exec() {
|
||||||
|
|
||||||
for (int32_t i = 0; i < config.exec_count; i++) {
|
for (int32_t i = 0; i < config.exec_count; i++) {
|
||||||
arg.v = config.exec[i];
|
arg.v = config.exec[i];
|
||||||
spawn_shell(&arg);
|
spawn(&arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1241,7 +1241,7 @@ void run_exec_once() {
|
||||||
|
|
||||||
for (int32_t i = 0; i < config.exec_once_count; i++) {
|
for (int32_t i = 0; i < config.exec_once_count; i++) {
|
||||||
arg.v = config.exec_once[i];
|
arg.v = config.exec_once[i];
|
||||||
spawn_shell(&arg);
|
spawn(&arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ int32_t chvt(const Arg *arg) {
|
||||||
chvt_backup_tag = selmon->pertag->curtag;
|
chvt_backup_tag = selmon->pertag->curtag;
|
||||||
strncpy(chvt_backup_selmon, selmon->wlr_output->name,
|
strncpy(chvt_backup_selmon, selmon->wlr_output->name,
|
||||||
sizeof(chvt_backup_selmon) - 1);
|
sizeof(chvt_backup_selmon) - 1);
|
||||||
|
chvt_backup_selmon[sizeof(chvt_backup_selmon) - 1] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
wlr_session_change_vt(session, arg->ui);
|
wlr_session_change_vt(session, arg->ui);
|
||||||
|
|
@ -856,6 +857,69 @@ int32_t spawn_shell(const Arg *arg) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int32_t split_argv_noexpand(const char *cmd, char *argv[],
|
||||||
|
char *allocated[], int32_t max_args,
|
||||||
|
int32_t *alloc_count) {
|
||||||
|
if (!cmd || !argv || !allocated || max_args < 2 || !alloc_count)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int32_t argc = 0;
|
||||||
|
*alloc_count = 0;
|
||||||
|
const char *p = cmd;
|
||||||
|
|
||||||
|
while (*p && argc < (max_args - 1)) {
|
||||||
|
while (*p && isspace((unsigned char)*p))
|
||||||
|
p++;
|
||||||
|
if (!*p)
|
||||||
|
break;
|
||||||
|
|
||||||
|
size_t max_len = strlen(p) + 1;
|
||||||
|
char *token = malloc(max_len);
|
||||||
|
if (!token)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
bool in_single = false;
|
||||||
|
bool in_double = false;
|
||||||
|
size_t ti = 0;
|
||||||
|
|
||||||
|
while (*p) {
|
||||||
|
char c = *p;
|
||||||
|
if (!in_single && !in_double && isspace((unsigned char)c))
|
||||||
|
break;
|
||||||
|
if (c == '\\' && !in_single) {
|
||||||
|
p++;
|
||||||
|
if (*p) {
|
||||||
|
token[ti++] = *p++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c == '\"' && !in_single) {
|
||||||
|
in_double = !in_double;
|
||||||
|
p++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c == '\'' && !in_double) {
|
||||||
|
in_single = !in_single;
|
||||||
|
p++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
token[ti++] = c;
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
|
||||||
|
token[ti] = '\0';
|
||||||
|
argv[argc++] = token;
|
||||||
|
allocated[(*alloc_count)++] = token;
|
||||||
|
|
||||||
|
while (*p && isspace((unsigned char)*p))
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
|
||||||
|
argv[argc] = NULL;
|
||||||
|
return argc;
|
||||||
|
}
|
||||||
|
|
||||||
int32_t spawn(const Arg *arg) {
|
int32_t spawn(const Arg *arg) {
|
||||||
|
|
||||||
if (!arg->v)
|
if (!arg->v)
|
||||||
|
|
@ -870,37 +934,24 @@ int32_t spawn(const Arg *arg) {
|
||||||
dup2(STDERR_FILENO, STDOUT_FILENO);
|
dup2(STDERR_FILENO, STDOUT_FILENO);
|
||||||
setsid();
|
setsid();
|
||||||
|
|
||||||
// 2. Parse parameters
|
// 2. Parse parameters without shell expansion
|
||||||
char *argv[64];
|
char *argv[64];
|
||||||
char *allocated_strings[64]; // Track strdup'd strings for cleanup
|
char *allocated_strings[64];
|
||||||
int32_t argc = 0;
|
|
||||||
int32_t alloc_count = 0;
|
int32_t alloc_count = 0;
|
||||||
|
|
||||||
char *token = strtok((char *)arg->v, " ");
|
int32_t argc = split_argv_noexpand(
|
||||||
while (token != NULL && argc < 63) {
|
arg->v, argv, allocated_strings, 64, &alloc_count);
|
||||||
wordexp_t p;
|
if (argc <= 0 || !argv[0])
|
||||||
if (wordexp(token, &p, WRDE_NOCMD) == 0 && p.we_wordc > 0) {
|
_exit(EXIT_FAILURE);
|
||||||
// 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) {
|
|
||||||
allocated_strings[alloc_count++] = argv[argc];
|
|
||||||
argc++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
argv[argc] = token;
|
|
||||||
argc++;
|
|
||||||
}
|
|
||||||
token = strtok(NULL, " ");
|
|
||||||
}
|
|
||||||
argv[argc] = NULL;
|
|
||||||
|
|
||||||
// 3. Execute command
|
// 3. Execute command
|
||||||
execvp(argv[0], argv);
|
execvp(argv[0], argv);
|
||||||
|
|
||||||
// 4. execvp 失败时:打印错误并直接退出(避免 coredump)
|
// 4. If execvp fails, log and exit immediately (avoid coredump).
|
||||||
wlr_log(WLR_DEBUG, "mango: execvp '%s' failed: %s\n", argv[0],
|
wlr_log(WLR_DEBUG, "mango: execvp '%s' failed: %s\n", argv[0],
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
|
for (int32_t i = 0; i < alloc_count; i++)
|
||||||
|
free(allocated_strings[i]);
|
||||||
_exit(EXIT_FAILURE); // Use _exit to avoid buffer flush operations
|
_exit(EXIT_FAILURE); // Use _exit to avoid buffer flush operations
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include "wlr-layer-shell-unstable-v1-protocol.h"
|
#include "wlr-layer-shell-unstable-v1-protocol.h"
|
||||||
#include "wlr/util/box.h"
|
#include "wlr/util/box.h"
|
||||||
#include "wlr/util/edges.h"
|
#include "wlr/util/edges.h"
|
||||||
|
#include <ctype.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <libinput.h>
|
#include <libinput.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
@ -18,6 +19,7 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
@ -85,7 +87,6 @@
|
||||||
#include <wlr/types/wlr_xdg_shell.h>
|
#include <wlr/types/wlr_xdg_shell.h>
|
||||||
#include <wlr/util/log.h>
|
#include <wlr/util/log.h>
|
||||||
#include <wlr/util/region.h>
|
#include <wlr/util/region.h>
|
||||||
#include <wordexp.h>
|
|
||||||
#include <xkbcommon/xkbcommon.h>
|
#include <xkbcommon/xkbcommon.h>
|
||||||
#ifdef XWAYLAND
|
#ifdef XWAYLAND
|
||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
|
|
|
||||||
6
tests/meson.build
Normal file
6
tests/meson.build
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
test_exe = executable('test_chvt_backup_selmon',
|
||||||
|
'test_chvt_backup_selmon.c',
|
||||||
|
c_args: ['-Werror'],
|
||||||
|
)
|
||||||
|
|
||||||
|
test('test_chvt_backup_selmon', test_exe)
|
||||||
29
tests/test_chvt_backup_selmon.c
Normal file
29
tests/test_chvt_backup_selmon.c
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static void copy_monitor_name(char *dst, size_t dst_size, const char *src) {
|
||||||
|
if (!dst || dst_size == 0)
|
||||||
|
return;
|
||||||
|
if (!src) {
|
||||||
|
dst[0] = '\0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
strncpy(dst, src, dst_size - 1);
|
||||||
|
dst[dst_size - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
char buf[32];
|
||||||
|
char src[64];
|
||||||
|
|
||||||
|
memset(src, 'A', sizeof(src) - 1);
|
||||||
|
src[sizeof(src) - 1] = '\0';
|
||||||
|
|
||||||
|
copy_monitor_name(buf, sizeof(buf), src);
|
||||||
|
|
||||||
|
if (buf[sizeof(buf) - 1] != '\0')
|
||||||
|
return 1;
|
||||||
|
if (strlen(buf) != sizeof(buf) - 1)
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue