diff --git a/CMakeLists.txt b/CMakeLists.txt index 0419051..dc0523d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.42.1 LANGUAGES C) +project(fun VERSION 0.42.2 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) @@ -521,7 +521,7 @@ install(DIRECTORY lib/ # Optionally install example scripts option(FUN_INSTALL_EXAMPLES "Install example .fun scripts" ON) if(FUN_INSTALL_EXAMPLES) - install(DIRECTORY examples/ + install(DIRECTORY examples/ DESTINATION /usr/share/fun/examples FILES_MATCHING PATTERN "*.fun" ) diff --git a/src/fun.c b/src/fun.c index 127af48..12869de 100644 --- a/src/fun.c +++ b/src/fun.c @@ -142,11 +142,26 @@ int main(int argc, char **argv) { } char *joined = (char *)malloc(total); if (joined) { - joined[0] = '\0'; + size_t off = 0; for (int i = 0; i < sargc; ++i) { - strcat(joined, argv[sargi + i]); - if (i + 1 < sargc) strcat(joined, " "); + int written = snprintf(joined + off, (off < total ? total - off : 0), + "%s%s", + argv[sargi + i], + (i + 1 < sargc) ? " " : ""); + if (written < 0) { /* encoding error */ + off = total; /* force stop */ + break; + } + size_t w = (size_t)written; + if (off + w >= total) { /* ensure we don't advance beyond buffer */ + off = total ? total - 1 : 0; + joined[off] = '\0'; + break; + } + off += w; } + /* Ensure NUL termination even if loop didn't run */ + if (total > 0) joined[(off < total) ? off : (total - 1)] = '\0'; setenv("FUN_ARGS", joined, 1); free(joined); } diff --git a/src/repl.c b/src/repl.c index 458886f..4d2ccb7 100644 --- a/src/repl.c +++ b/src/repl.c @@ -259,14 +259,16 @@ static int complete_load_path(char *buf, size_t *len_io) { if (slash) { size_t dlen = (size_t)(slash - expanded); if (dlen == 0) { - strcpy(dirpart, "/"); + /* use bounded copy to avoid potential overflow (even though "/" fits) */ + snprintf(dirpart, sizeof(dirpart), "%s", "/"); } else { memcpy(dirpart, expanded, dlen); dirpart[dlen] = '\0'; } snprintf(base, sizeof(base), "%s", slash + 1); } else { - strcpy(dirpart, "."); + /* use bounded copy to avoid potential overflow (even though "." fits) */ + snprintf(dirpart, sizeof(dirpart), "%s", "."); snprintf(base, sizeof(base), "%s", expanded); } diff --git a/src/vm/os/proc_system.c b/src/vm/os/proc_system.c index 238b18f..90bc8db 100644 --- a/src/vm/os/proc_system.c +++ b/src/vm/os/proc_system.c @@ -27,6 +27,24 @@ case OP_PROC_SYSTEM: { push_value(vm, make_int(-1)); break; } + /* Security hardening: reject commands containing shell metacharacters or control chars + to reduce risk of command injection when using system(3). This preserves simple + command execution like "ls -l" but blocks dangerous constructs like pipes, redirects, + command substitution, etc. */ + const char *bad = "&;|$<>`\\\"'()*?[]{}~"; + int unsafe = 0; + for (const unsigned char *p = (const unsigned char *)cmd; *p; ++p) { + if (*p < 0x20 || strchr(bad, (int)*p)) { /* control or meta */ + unsafe = 1; + break; + } + } + if (unsafe) { + /* refuse to execute potentially unsafe shell command */ + push_value(vm, make_int(-1)); + free(cmd); + break; + } int status = system(cmd); int code = -1; #ifdef __unix__