From 36d2494b0e8d6a623d4b0249a986bc6d2e8d9fd4 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Reddy B Date: Wed, 5 Aug 2026 22:27:42 +0530 Subject: [PATCH 1/4] RDKB-65595 : [Risk-Critical] JST (Generic) Security Fuzzing Report Fixed Heap Buffer Overflow in `do_openssl_verify_with_cert` Recommendation - Check `strlen(filepath) >= 7` before calling `memcmp`, or use `strncmp` which handles short strings safely MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed Command Injection via `popen()` Recommendation - Never pass untrusted input to `popen()` — use `execve()` with argument arrays or sanitize input --- source/jst_functions.c | 142 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 5 deletions(-) diff --git a/source/jst_functions.c b/source/jst_functions.c index a09e7ad..3822636 100644 --- a/source/jst_functions.c +++ b/source/jst_functions.c @@ -18,8 +18,12 @@ */ #include #include +#include +#include #include #include +#include +#include #include #include "jst_internal.h" #include "jst.h" @@ -42,6 +46,83 @@ typedef struct { size_t memsize; // allocated memory size (if applicable) } MemData; +static int is_exec_command_safe(const char* command) +{ + const char* p = NULL; + + if (!command || !*command) + return 0; + + for (p = command; *p; ++p) + { + if (isalnum((unsigned char)*p) || *p == '/' || *p == '.' || *p == '_' || + *p == '-' || *p == ':' || *p == '=' || *p == '+' || *p == '@' || + isspace((unsigned char)*p)) + { + continue; + } + + return 0; + } + + return 1; +} + +static void free_exec_argv(char** argv) +{ + if (argv) + { + free(argv[0]); + free(argv); + } +} + +static char** build_exec_argv(const char* command) +{ + char* command_copy = NULL; + char* scan_ctx = NULL; + char* token = NULL; + int argc = 0; + int i = 0; + char** argv = NULL; + + command_copy = strdup(command); + if (!command_copy) + return NULL; + + token = strtok_r(command_copy, " \t\r\n", &scan_ctx); + while (token) + { + argc++; + token = strtok_r(NULL, " \t\r\n", &scan_ctx); + } + + if (argc == 0) + { + free(command_copy); + return NULL; + } + + argv = calloc((size_t)argc + 1, sizeof(char*)); + if (!argv) + { + free(command_copy); + return NULL; + } + + argv[0] = command_copy; + scan_ctx = NULL; + token = strtok_r(command_copy, " \t\r\n", &scan_ctx); + while (token && i < argc) + { + argv[i++] = token; + token = strtok_r(NULL, " \t\r\n", &scan_ctx); + } + argv[i] = NULL; + + return argv; +} + static duk_ret_t do_getenv(duk_context *ctx) { @@ -148,28 +229,77 @@ static duk_ret_t do_gettext(duk_context *ctx) static duk_ret_t do_exec(duk_context *ctx) { char* command; + char** argv = NULL; + int pipefd[2] = {-1, -1}; + pid_t child_pid; + int child_status; char *line = NULL; size_t len = 0; ssize_t nread; duk_idx_t idx; int index = 0; + FILE* output_pipe = NULL; idx = duk_push_array(ctx); if (!parse_parameter(__FUNCTION__, ctx, "s", &command)) return 1; + if (!is_exec_command_safe(command)) + { + CosaPhpExtLog("exec rejected unsafe command input\n"); + return 1; + } + + argv = build_exec_argv(command); + if (!argv) + { + CosaPhpExtLog("exec failed to parse command arguments\n"); + return 1; + } + CosaPhpExtLog("exec command=%s\n", command); - FILE* pipe = popen(command, "r"); - if (!pipe) + if (pipe(pipefd) != 0) + { + CosaPhpExtLog("exec failed to create pipe error=%s\n", strerror(errno)); + free_exec_argv(argv); + return 1; + } + + child_pid = fork(); + if (child_pid < 0) + { + CosaPhpExtLog("exec failed to fork error=%s\n", strerror(errno)); + close(pipefd[0]); + close(pipefd[1]); + free_exec_argv(argv); + return 1; + } + + if (child_pid == 0) + { + close(pipefd[0]); + dup2(pipefd[1], STDOUT_FILENO); + dup2(pipefd[1], STDERR_FILENO); + close(pipefd[1]); + execvp(argv[0], argv); + _exit(127); + } + + close(pipefd[1]); + output_pipe = fdopen(pipefd[0], "r"); + if (!output_pipe) { CosaPhpExtLog("exec failed to open pipe\n"); + close(pipefd[0]); + waitpid(child_pid, &child_status, 0); + free_exec_argv(argv); duk_pop(ctx); return 1; } - while((nread = getline(&line, &len, pipe)) != -1) + while((nread = getline(&line, &len, output_pipe)) != -1) { CosaPhpExtLog("exec line: %s\n", line); duk_push_string(ctx, line); @@ -177,7 +307,9 @@ static duk_ret_t do_exec(duk_context *ctx) } free(line); - pclose(pipe); + fclose(output_pipe); + waitpid(child_pid, &child_status, 0); + free_exec_argv(argv); return 1; } @@ -615,7 +747,7 @@ static duk_ret_t do_openssl_verify_with_cert(duk_context *ctx) /* === NOW PROCEED WITH SIGNATURE VERIFICATION === */ //open certificate file - if(memcmp(filepath, "file://", sizeof("file://")-1) != 0) + if(strncmp(filepath, "file://", sizeof("file://")-1) != 0) { CosaPhpExtLog("openssl_verify_with_cert: file %s doesn't begin with 'file://'\n", filepath); free(sig_bytes); From 8b61868054caa2dd5836349af7ddfcc0a86f31f3 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Reddy B Date: Thu, 6 Aug 2026 16:43:21 +0530 Subject: [PATCH 2/4] prevent invalid free in argv parsing with leading whitespace --- source/jst_functions.c | 49 ++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/source/jst_functions.c b/source/jst_functions.c index 3822636..4ce1859 100644 --- a/source/jst_functions.c +++ b/source/jst_functions.c @@ -46,6 +46,11 @@ typedef struct { size_t memsize; // allocated memory size (if applicable) } MemData; +typedef struct { + char** argv; + char* command_copy; +} ExecArgv; + static int is_exec_command_safe(const char* command) { const char* p = NULL; @@ -68,16 +73,18 @@ static int is_exec_command_safe(const char* command) return 1; } -static void free_exec_argv(char** argv) +static void free_exec_argv(ExecArgv* exec_argv) { - if (argv) + if (exec_argv) { - free(argv[0]); - free(argv); + free(exec_argv->command_copy); + free(exec_argv->argv); + exec_argv->command_copy = NULL; + exec_argv->argv = NULL; } } -static char** build_exec_argv(const char* command) +static int build_exec_argv(const char* command, ExecArgv* exec_argv) { char* command_copy = NULL; char* scan_ctx = NULL; @@ -86,9 +93,15 @@ static char** build_exec_argv(const char* command) int i = 0; char** argv = NULL; + if (!exec_argv) + return 0; + + exec_argv->argv = NULL; + exec_argv->command_copy = NULL; + command_copy = strdup(command); if (!command_copy) - return NULL; + return 0; token = strtok_r(command_copy, " \t\r\n", &scan_ctx); while (token) @@ -100,17 +113,16 @@ static char** build_exec_argv(const char* command) if (argc == 0) { free(command_copy); - return NULL; + return 0; } argv = calloc((size_t)argc + 1, sizeof(char*)); if (!argv) { free(command_copy); - return NULL; + return 0; } - argv[0] = command_copy; scan_ctx = NULL; token = strtok_r(command_copy, " \t\r\n", &scan_ctx); while (token && i < argc) @@ -120,7 +132,9 @@ static char** build_exec_argv(const char* command) } argv[i] = NULL; - return argv; + exec_argv->argv = argv; + exec_argv->command_copy = command_copy; + return 1; } @@ -229,7 +243,7 @@ static duk_ret_t do_gettext(duk_context *ctx) static duk_ret_t do_exec(duk_context *ctx) { char* command; - char** argv = NULL; + ExecArgv exec_argv = {0}; int pipefd[2] = {-1, -1}; pid_t child_pid; int child_status; @@ -251,8 +265,7 @@ static duk_ret_t do_exec(duk_context *ctx) return 1; } - argv = build_exec_argv(command); - if (!argv) + if (!build_exec_argv(command, &exec_argv)) { CosaPhpExtLog("exec failed to parse command arguments\n"); return 1; @@ -263,7 +276,7 @@ static duk_ret_t do_exec(duk_context *ctx) if (pipe(pipefd) != 0) { CosaPhpExtLog("exec failed to create pipe error=%s\n", strerror(errno)); - free_exec_argv(argv); + free_exec_argv(&exec_argv); return 1; } @@ -273,7 +286,7 @@ static duk_ret_t do_exec(duk_context *ctx) CosaPhpExtLog("exec failed to fork error=%s\n", strerror(errno)); close(pipefd[0]); close(pipefd[1]); - free_exec_argv(argv); + free_exec_argv(&exec_argv); return 1; } @@ -283,7 +296,7 @@ static duk_ret_t do_exec(duk_context *ctx) dup2(pipefd[1], STDOUT_FILENO); dup2(pipefd[1], STDERR_FILENO); close(pipefd[1]); - execvp(argv[0], argv); + execvp(exec_argv.argv[0], exec_argv.argv); _exit(127); } @@ -294,7 +307,7 @@ static duk_ret_t do_exec(duk_context *ctx) CosaPhpExtLog("exec failed to open pipe\n"); close(pipefd[0]); waitpid(child_pid, &child_status, 0); - free_exec_argv(argv); + free_exec_argv(&exec_argv); duk_pop(ctx); return 1; } @@ -309,7 +322,7 @@ static duk_ret_t do_exec(duk_context *ctx) free(line); fclose(output_pipe); waitpid(child_pid, &child_status, 0); - free_exec_argv(argv); + free_exec_argv(&exec_argv); return 1; } From 2939391b89533f766d642630f42069c2842b59d9 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Reddy B Date: Thu, 6 Aug 2026 16:45:59 +0530 Subject: [PATCH 3/4] preserve argv ownership and full token parsing --- source/jst_functions.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/source/jst_functions.c b/source/jst_functions.c index 4ce1859..c799826 100644 --- a/source/jst_functions.c +++ b/source/jst_functions.c @@ -87,6 +87,7 @@ static void free_exec_argv(ExecArgv* exec_argv) static int build_exec_argv(const char* command, ExecArgv* exec_argv) { char* command_copy = NULL; + char* command_scan_copy = NULL; char* scan_ctx = NULL; char* token = NULL; int argc = 0; @@ -103,13 +104,23 @@ static int build_exec_argv(const char* command, ExecArgv* exec_argv) if (!command_copy) return 0; - token = strtok_r(command_copy, " \t\r\n", &scan_ctx); + command_scan_copy = strdup(command); + if (!command_scan_copy) + { + free(command_copy); + return 0; + } + + token = strtok_r(command_scan_copy, " \t\r\n", &scan_ctx); while (token) { argc++; token = strtok_r(NULL, " \t\r\n", &scan_ctx); } + free(command_scan_copy); + command_scan_copy = NULL; + if (argc == 0) { free(command_copy); From 04e2ab94dd4975a4240e2aa16ac9b444362ee88c Mon Sep 17 00:00:00 2001 From: Pavan Kumar Reddy B Date: Thu, 6 Aug 2026 16:53:00 +0530 Subject: [PATCH 4/4] keep empty array on stack when fdopen fails --- source/jst_functions.c | 1 - 1 file changed, 1 deletion(-) diff --git a/source/jst_functions.c b/source/jst_functions.c index c799826..7f504c3 100644 --- a/source/jst_functions.c +++ b/source/jst_functions.c @@ -319,7 +319,6 @@ static duk_ret_t do_exec(duk_context *ctx) close(pipefd[0]); waitpid(child_pid, &child_status, 0); free_exec_argv(&exec_argv); - duk_pop(ctx); return 1; }