From 03a4cf22dea8c5ab6197e62a201338cf44a1c7ce Mon Sep 17 00:00:00 2001 From: Pavan Kumar Reddy B <57708013+pavankumar464@users.noreply.github.com> Date: Tue, 14 Jul 2026 18:01:05 +0530 Subject: [PATCH 1/5] RDKB-65677 eliminate session_create leaks and strengthen regression coverage (#24) --- source/jst_session.c | 17 +++++++++ tests/CMakeLists.txt | 1 + tests/parser_test.cpp | 84 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) diff --git a/source/jst_session.c b/source/jst_session.c index ceec720..69f542f 100644 --- a/source/jst_session.c +++ b/source/jst_session.c @@ -164,6 +164,12 @@ static duk_ret_t session_create(duk_context *ctx) char* session_id = NULL; session_id = (char*)malloc(SESSION_ID_BYTES_LENGTH+1); + if(!session_id) + { + CosaPhpExtLog("Failed to allocate session_id!\n"); + RETURN_FALSE; + } + n = syscall(SYS_getrandom, bytes, SESSION_ID_BYTES_LENGTH, 0); if(n != SESSION_ID_BYTES_LENGTH) { @@ -177,16 +183,27 @@ static duk_ret_t session_create(duk_context *ctx) session_id[i] = BYTE_TO_PRINTABLE_HEX_CODE(bytes[i]); } + if(session_identifier) + { + char filename[SESSION_FILE_MAX_PATH]; + snprintf(filename, SESSION_FILE_MAX_PATH, "%s/%s", SESSION_TMP_DIR, session_identifier); + unlink(filename); + free(session_identifier); + session_identifier = NULL; + } + session_identifier = (char*)malloc(SESSION_ID_LENGTH+1); if(!session_identifier) { CosaPhpExtLog("Failed to allocate session_identifier!\n"); + free(session_id); RETURN_FALSE; } memset(session_identifier, 0, SESSION_ID_LENGTH+1); session_id[SESSION_ID_BYTES_LENGTH] = '\0'; snprintf(session_identifier, SESSION_ID_LENGTH+1, "%s%s", SESSION_PREFIX, session_id); + free(session_id); RETURN_TRUE; return 1; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2628f71..013f6ef 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -73,6 +73,7 @@ add_executable( parser_test ../tests/parser_test.cpp ../source/jst_parser.c + ../source/jst_session.c ../source/jst_internal.c ../source/duktape/duktape.c) target_link_libraries(parser_test libgtest libgmock -pthread) diff --git a/tests/parser_test.cpp b/tests/parser_test.cpp index cd97b54..a3bc427 100644 --- a/tests/parser_test.cpp +++ b/tests/parser_test.cpp @@ -20,12 +20,17 @@ #include #include #include +#include #include "jst.h" #include #include #include #include +extern "C" { + duk_ret_t ccsp_session_module_open(duk_context *ctx); +} + using namespace std; class BufferFreer @@ -106,6 +111,85 @@ TEST(general, parser) { } } +TEST(general, session_create_multiple_calls_succeed) +{ + duk_context* ctx = duk_create_heap_default(); + ASSERT_NE(ctx, nullptr); + + duk_push_c_function(ctx, ccsp_session_module_open, 0); + ASSERT_EQ(duk_pcall(ctx, 0), DUK_EXEC_SUCCESS); + duk_put_global_string(ctx, "ccsp_session"); + + duk_get_global_string(ctx, "ccsp_session"); + duk_get_prop_string(ctx, -1, "create"); + ASSERT_EQ(duk_pcall(ctx, 0), DUK_EXEC_SUCCESS); + EXPECT_TRUE(duk_get_boolean(ctx, -1)); + duk_pop_2(ctx); + + duk_get_global_string(ctx, "ccsp_session"); + duk_get_prop_string(ctx, -1, "create"); + ASSERT_EQ(duk_pcall(ctx, 0), DUK_EXEC_SUCCESS); + EXPECT_TRUE(duk_get_boolean(ctx, -1)); + duk_pop_2(ctx); + + duk_get_global_string(ctx, "ccsp_session"); + duk_get_prop_string(ctx, -1, "destroy"); + ASSERT_EQ(duk_pcall(ctx, 0), DUK_EXEC_SUCCESS); + EXPECT_TRUE(duk_get_boolean(ctx, -1)); + duk_pop_2(ctx); + + duk_destroy_heap(ctx); +} + +TEST(general, session_create_destroy_cycle_and_id_format) +{ + duk_context* ctx = duk_create_heap_default(); + ASSERT_NE(ctx, nullptr); + + duk_push_c_function(ctx, ccsp_session_module_open, 0); + ASSERT_EQ(duk_pcall(ctx, 0), DUK_EXEC_SUCCESS); + duk_put_global_string(ctx, "ccsp_session"); + + duk_get_global_string(ctx, "ccsp_session"); + duk_get_prop_string(ctx, -1, "create"); + ASSERT_EQ(duk_pcall(ctx, 0), DUK_EXEC_SUCCESS); + EXPECT_TRUE(duk_get_boolean(ctx, -1)); + duk_pop_2(ctx); + + duk_get_global_string(ctx, "ccsp_session"); + duk_get_prop_string(ctx, -1, "getId"); + ASSERT_EQ(duk_pcall(ctx, 0), DUK_EXEC_SUCCESS); + const char* first_id = duk_get_string(ctx, -1); + ASSERT_NE(first_id, nullptr); + EXPECT_EQ(strlen(first_id), 40u); + EXPECT_EQ(strncmp(first_id, "jst_sess", 8), 0); + + char first_session_file[128] = {0}; + snprintf(first_session_file, sizeof(first_session_file), "/tmp/%s", first_id); + duk_pop_2(ctx); + + FILE* stale = fopen(first_session_file, "w"); + ASSERT_NE(stale, nullptr); + fclose(stale); + ASSERT_EQ(access(first_session_file, F_OK), 0); + + duk_get_global_string(ctx, "ccsp_session"); + duk_get_prop_string(ctx, -1, "create"); + ASSERT_EQ(duk_pcall(ctx, 0), DUK_EXEC_SUCCESS); + EXPECT_TRUE(duk_get_boolean(ctx, -1)); + duk_pop_2(ctx); + + EXPECT_NE(access(first_session_file, F_OK), 0); + + duk_get_global_string(ctx, "ccsp_session"); + duk_get_prop_string(ctx, -1, "destroy"); + ASSERT_EQ(duk_pcall(ctx, 0), DUK_EXEC_SUCCESS); + EXPECT_TRUE(duk_get_boolean(ctx, -1)); + duk_pop_2(ctx); + + duk_destroy_heap(ctx); +} + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); From 2228424454210db2c428c230f76528e39a429dfc Mon Sep 17 00:00:00 2001 From: Pavan Kumar Reddy B <57708013+pavankumar464@users.noreply.github.com> Date: Tue, 14 Jul 2026 19:46:15 +0530 Subject: [PATCH 2/5] RDKB-64256 fix OOB access in log_syntax_error for malformed include parsing (#26) Guard against null pointers in log_syntax_error. Avoid reading/writing at end by using bounded line scanning. Replace temporary in-place buffer mutation with length-limited logging (%.*s). Prevent potential SIGSEGV/SIGABRT when malformed include syntax reaches EOF. --- source/jst_parser.c | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/source/jst_parser.c b/source/jst_parser.c index 6422995..c3cce5c 100644 --- a/source/jst_parser.c +++ b/source/jst_parser.c @@ -129,15 +129,43 @@ static int template_process(char** buf, size_t* buflen, int top); static void log_syntax_error(char* err, char* s1, char* cur, char* end) { - char ch; + const char* err_msg; + char* line_end; + char* src_end; + int line_len; + int src_len; - while(cur != end && *cur != '\n' && *cur != '\r') - cur++; + err_msg = err ? err : "unknown"; + + if(!cur || !end) + { + log_debug_message("syntax error. malformed include: %s\n", err_msg); + return; + } + + if(cur > end) + cur = end; + + if(!s1) + s1 = cur; + + line_end = s1; + while(line_end < end && *line_end != '\n' && *line_end != '\r') + line_end++; + + src_end = cur; + while(src_end < end && *src_end != '\n' && *src_end != '\r') + src_end++; + + line_len = (int)(line_end - s1); + src_len = (int)(src_end - cur); + + if(line_len < 0) + line_len = 0; + if(src_len < 0) + src_len = 0; - ch = *cur; - *cur = 0; - log_debug_message("syntax error. malformed include: %s. line: %s src:%s\n", err, s1, cur); - *cur = ch; + log_debug_message("syntax error. malformed include: %s. line: %.*s src:%.*s\n", err_msg, line_len, s1, src_len, cur); } static void template_write_block(growing_buffer* bufout, template_block* block) From fcbbb7e25ecaed2a09b571dce711e1e108715f3f Mon Sep 17 00:00:00 2001 From: Pavan Kumar Reddy B <57708013+pavankumar464@users.noreply.github.com> Date: Tue, 14 Jul 2026 20:07:57 +0530 Subject: [PATCH 3/5] RDKB-64641 sets post_data = NULL to keep getPost() unset for file-only multipart bodies (#27) - In process_multipart_form_data, replace post_data = content_data with post_data = strdup(content_data) when there are no non-file fields. - content_data is freed by caller (ccsp_post_module_open), so aliasing caused double-free/heap corruption. - Prevents crash pattern seen as SIGSEGV/SIGABRT in core dumps. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- source/jst_post.c | 5 +- tests/CMakeLists.txt | 1 + tests/parser_test.cpp | 151 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 1 deletion(-) diff --git a/source/jst_post.c b/source/jst_post.c index 8383dde..7396437 100644 --- a/source/jst_post.c +++ b/source/jst_post.c @@ -849,7 +849,10 @@ static void process_multipart_form_data(char* content_data, int content_len, cha } else { - post_data = content_data; + /* Multipart requests with only file parts have no _POST fields. Keep + post_data unset so ccsp_post.getPost() continues to mean name=value&... + rather than returning the raw multipart body. */ + post_data = NULL; } for(i=0; i #include #include +#include +#include #include +#include #include "jst.h" #include #include @@ -28,6 +31,7 @@ #include extern "C" { + duk_ret_t ccsp_post_module_open(duk_context *ctx); duk_ret_t ccsp_session_module_open(duk_context *ctx); } @@ -46,6 +50,105 @@ class BufferFreer char* buffer_; }; +class EnvVarGuard +{ +public: + explicit EnvVarGuard(const char* name) + : name_(name), had_value_(false) + { + const char* value = getenv(name_); + if (value) + { + old_value_ = value; + had_value_ = true; + } + } + + ~EnvVarGuard() + { + if (had_value_) + setenv(name_, old_value_.c_str(), 1); + else + unsetenv(name_); + } + + void set(const char* value) + { + if (value) + setenv(name_, value, 1); + else + unsetenv(name_); + } + +private: + const char* name_; + std::string old_value_; + bool had_value_; +}; + +class StdinRedirectGuard +{ +public: + StdinRedirectGuard(const char* data, size_t length) + : temp_(tmpfile()), original_fd_(-1), active_(false) + { + if (!temp_) + return; + + if (fwrite(data, 1, length, temp_) != length) + return; + + fflush(temp_); + rewind(temp_); + + original_fd_ = dup(fileno(stdin)); + if (original_fd_ < 0) + return; + + if (dup2(fileno(temp_), fileno(stdin)) < 0) + return; + + active_ = true; + } + + ~StdinRedirectGuard() + { + if (original_fd_ >= 0) + { + dup2(original_fd_, fileno(stdin)); + close(original_fd_); + } + + if (temp_) + fclose(temp_); + } + + bool is_active() const + { + return active_; + } + +private: + FILE* temp_; + int original_fd_; + bool active_; +}; + +static string getFieldValue(const string& input, const string& key) +{ + string pattern = key + "="; + size_t start = input.find(pattern); + if (start == string::npos) + return ""; + + start += pattern.length(); + size_t end = input.find('&', start); + if (end == string::npos) + return input.substr(start); + + return input.substr(start, end - start); +} + int recurseDirectory(const string& path, vector& files, const string& match) { DIR *dir; @@ -141,6 +244,54 @@ TEST(general, session_create_multiple_calls_succeed) duk_destroy_heap(ctx); } +TEST(general, multipart_file_only_request_leaves_post_unset) +{ + const string boundary = "----jstBoundary123"; + const string body = + string("--") + boundary + "\r\n" + + "Content-Disposition: form-data; name=\"upload\"; filename=\"config.bin\"\r\n" + + "Content-Type: application/octet-stream\r\n" + + "\r\n" + + "abc123\r\n" + + string("--") + boundary + "--\r\n"; + const string content_type = "multipart/form-data; boundary=" + boundary; + const string content_length = to_string(body.size()); + + EnvVarGuard content_type_guard("CONTENT_TYPE"); + EnvVarGuard content_length_guard("CONTENT_LENGTH"); + content_type_guard.set(content_type.c_str()); + content_length_guard.set(content_length.c_str()); + + StdinRedirectGuard stdin_guard(body.c_str(), body.size()); + ASSERT_TRUE(stdin_guard.is_active()); + + duk_context* ctx = duk_create_heap_default(); + ASSERT_NE(ctx, nullptr); + + duk_push_c_function(ctx, ccsp_post_module_open, 0); + ASSERT_EQ(duk_pcall(ctx, 0), DUK_EXEC_SUCCESS); + duk_put_global_string(ctx, "ccsp_post"); + + duk_get_global_string(ctx, "ccsp_post"); + duk_get_prop_string(ctx, -1, "getFiles"); + ASSERT_EQ(duk_pcall(ctx, 0), DUK_EXEC_SUCCESS); + ASSERT_TRUE(duk_is_string(ctx, -1)); + string files_value = duk_get_string(ctx, -1); + duk_pop_2(ctx); + + const string tmp_name = getFieldValue(files_value, "tmp_name"); + if (!tmp_name.empty()) + remove(tmp_name.c_str()); + + duk_get_global_string(ctx, "ccsp_post"); + duk_get_prop_string(ctx, -1, "getPost"); + ASSERT_EQ(duk_pcall(ctx, 0), DUK_EXEC_SUCCESS); + EXPECT_FALSE(duk_get_boolean(ctx, -1)); + duk_pop_2(ctx); + + duk_destroy_heap(ctx); +} + TEST(general, session_create_destroy_cycle_and_id_format) { duk_context* ctx = duk_create_heap_default(); From d20859c4518578860cc4b64177c168ddd2db4c90 Mon Sep 17 00:00:00 2001 From: bunnam988 <107185904+bunnam988@users.noreply.github.com> Date: Wed, 15 Jul 2026 11:52:10 +0530 Subject: [PATCH 4/5] RDKB-66032 : Add PR Format Check workflow (#29) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Adds a caller workflow that runs the shared PR format check from `rdkcentral/build_tools_workflows`. ## What it does Automatically checks every new/edited PR (including fork PRs) for: - Title format: `TICKET-123 : description` (multiple tickets supported) - Required description fields: Reason for change, Test Procedure, Risks, Priority Fails the check and posts a reminder comment if format is incorrect. Deletes the comment automatically when fixed. ## Files changed - `.github/workflows/pr-lint.yml` (9 lines — caller only, no logic) --- .github/workflows/pr-lint.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/pr-lint.yml diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml new file mode 100644 index 0000000..7b2c503 --- /dev/null +++ b/.github/workflows/pr-lint.yml @@ -0,0 +1,18 @@ +name: PR Format Check + +on: + pull_request_target: + types: [opened, edited, synchronize, reopened] + +permissions: + contents: read + issues: write + pull-requests: write + +concurrency: + group: pr-format-check-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + lint: + uses: rdkcentral/build_tools_workflows/.github/workflows/pr-lint.yml@develop From 6d3c2ca36a06cc878c9b125b4053dd66d4be3f25 Mon Sep 17 00:00:00 2001 From: bunnam988 Date: Wed, 22 Jul 2026 09:28:07 +0000 Subject: [PATCH 5/5] Add changelog for release 2.3.0 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 485b806..6a023c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,20 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [2.3.0](https://github.com/rdkcentral/javascript-templates/compare/2.2.0...2.3.0) + +- RDKB-66032 : Add PR Format Check workflow [`#29`](https://github.com/rdkcentral/javascript-templates/pull/29) +- RDKB-64641 sets post_data = NULL to keep getPost() unset for file-only multipart bodies [`#27`](https://github.com/rdkcentral/javascript-templates/pull/27) +- RDKB-64256 fix OOB access in log_syntax_error for malformed include parsing [`#26`](https://github.com/rdkcentral/javascript-templates/pull/26) +- RDKB-65677 eliminate session_create leaks and strengthen regression coverage [`#24`](https://github.com/rdkcentral/javascript-templates/pull/24) +- Merge tag '2.2.0' into develop [`d73972d`](https://github.com/rdkcentral/javascript-templates/commit/d73972dcf3281b29682f4561a32904d0eb9611dc) + #### [2.2.0](https://github.com/rdkcentral/javascript-templates/compare/2.1.0...2.2.0) +> 15 June 2026 + - RDKB-65466 : sso validation token [`#19`](https://github.com/rdkcentral/javascript-templates/pull/19) +- Add changelog for release 2.2.0 [`7323c07`](https://github.com/rdkcentral/javascript-templates/commit/7323c0772b5f6c7f573093bbeca0f4b65bb1e1ef) - Merge tag '2.1.0' into develop [`6246ada`](https://github.com/rdkcentral/javascript-templates/commit/6246adaaa950bded6b962e748c7f4058285f0a6f) #### [2.1.0](https://github.com/rdkcentral/javascript-templates/compare/2.0.0...2.1.0)