GHSA-wrwf-jf85-4549: Cleanup path unconditionally calls fx_file_close() on unopened file in CVE-2025-0728 fix
GHSA ID |
|
|---|---|
CVE ID |
CVE‑2026‑11576 |
Severity |
High |
CVSS score |
7.5 ( |
CWE |
|
Published |
2026‑06‑19 |
Last updated |
2026‑06‑19 |
Affected packages
| Package | Vulnerable versions | Patched versions |
|---|---|---|
eclipse-threadx/netxduo |
>= 6.4.2 && ⇐ 6.5.0.202601 |
6.5.1.202602 |
Description
Summary
The security fix for CVE-2025-0728 in eclipse-threadx NetX Duo refactors error handling in the HTTP server PUT process to use a shared cleanup label, but this unified cleanup path unconditionally calls fx_file_close() even when the file was never successfully opened. Multiple error branches jump to the shared cleanup label before any file open operation has occurred, causing fx_file_close() to operate on an uninitialized file handle, leading to undefined behavior, double-close issues, or memory corruption.
Details
The initial patch refactored error handling in the HTTP server PUT process to use a shared put_process_end cleanup label. However, several error branches that occur before the file is opened now jump to this label, which unconditionally calls fx_file_close().
The specific problem is that early error conditions (e.g., malformed request, authentication failure) trigger a goto put_process_end before fx_file_open() is ever called. At the cleanup label, fx_file_close() is called on server_ptr→nx_web_http_server_file, which at that point contains uninitialized or zeroed memory, leading to undefined behavior in the filesystem layer.
Vulnerable pattern:
/* Early error before file is opened */
if (early_error) {
goto put_process_end; // File never opened!
}
/* ... file open happens here ... */
status = fx_file_open(&(server_ptr->nx_web_http_server_file), ...);
/* ... normal processing ... */
put_process_end:
fx_file_close(&(server_ptr->nx_web_http_server_file)); // BUG: unconditional close
return;
Correct fix — track whether the file was successfully opened:
UINT status = NX_SUCCESS;
UINT file_opened = NX_FALSE;
/* ... error checking before file open ... */
if (early_error) {
goto put_process_end;
}
/* Open the file */
status = fx_file_open(&(server_ptr->nx_web_http_server_file), ...);
if (status != NX_SUCCESS) {
goto put_process_end;
}
file_opened = NX_TRUE;
/* ... normal processing ... */
put_process_end:
/* Only close if file was actually opened */
if (file_opened == NX_TRUE) {
fx_file_close(&(server_ptr->nx_web_http_server_file));
}
return;
PoC
Environment: - OS: Linux (for simulation) - Compiler: GCC with AddressSanitizer/UBSan - Target: eclipse-threadx NetX Duo HTTP Server
Source (test_double_close.c):
/*
* PoC for CVE-2025-0728 - Double/Invalid Close in NetX Duo HTTP Server
*
* The vulnerability: The security fix refactors error handling to use a shared
* cleanup label "put_process_end" which unconditionally calls fx_file_close().
* However, several error branches jump to this label BEFORE the file is opened,
* causing fx_file_close() to be called on an uninitialized file handle.
*
* This can cause:
* 1. Double-close if the file handle was previously used
* 2. Use of uninitialized memory
* 3. Undefined behavior in the filesystem layer
*
* Compile with:
* gcc -fsanitize=address,undefined -g -o test_double_close test_double_close.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
/* Simulate NetX Duo types */
typedef unsigned int UINT;
typedef unsigned long ULONG;
#define NX_SUCCESS 0
#define NX_WEB_HTTP_ERROR 1
#define NX_WEB_HTTP_NOT_FOUND 2
/* Simulate file handle structure */
typedef struct {
int fd;
char* filename;
bool is_open;
} FX_FILE;
/* Simulate server structure */
typedef struct {
FX_FILE nx_web_http_server_file;
ULONG nx_web_http_server_total_bytes_received;
int request_chunked;
} NX_WEB_HTTP_SERVER;
/* Track close calls for detection */
int g_close_count = 0;
bool g_file_was_opened = false;
/* Simulated fx_file_open */
UINT fx_file_open(FX_FILE* file_ptr, const char* name, UINT mode)
{
printf("[FX] fx_file_open('%s') called\n", name);
/* Simulate file not found error */
if (strcmp(name, "nonexistent.txt") == 0) {
printf("[FX] File not found, returning error\n");
return NX_WEB_HTTP_NOT_FOUND;
}
file_ptr->fd = 42; /* Simulated FD */
file_ptr->filename = strdup(name);
file_ptr->is_open = true;
g_file_was_opened = true;
printf("[FX] File opened successfully, fd=%d\n", file_ptr->fd);
return NX_SUCCESS;
}
/* Simulated fx_file_close - detects invalid closes */
UINT fx_file_close(FX_FILE* file_ptr)
{
g_close_count++;
printf("[FX] fx_file_close() called (count=%d)\n", g_close_count);
if (!g_file_was_opened) {
printf("[FX] ERROR: Closing file that was never opened!\n");
printf("[FX] This is UNDEFINED BEHAVIOR - file handle contains garbage!\n");
/* In real code, this would access uninitialized memory */
return NX_WEB_HTTP_ERROR;
}
if (!file_ptr->is_open) {
printf("[FX] ERROR: Double close detected!\n");
return NX_WEB_HTTP_ERROR;
}
file_ptr->is_open = false;
free(file_ptr->filename);
file_ptr->filename = NULL;
printf("[FX] File closed successfully\n");
return NX_SUCCESS;
}
/* Simulated response send */
void _nx_web_http_server_response_send(NX_WEB_HTTP_SERVER* server, const char* status)
{
printf("[HTTP] Sending response: %s\n", status);
}
/*
* VULNERABLE put_process function (after the security fix)
* Shows how early errors jump to cleanup that always closes file
*/
void _nx_web_http_server_put_process_VULNERABLE(NX_WEB_HTTP_SERVER* server_ptr,
const char* filename,
bool simulate_early_error)
{
UINT status = NX_SUCCESS;
ULONG length = 100;
printf("\n=== PUT Process (VULNERABLE) ===\n");
printf("simulate_early_error = %s\n\n", simulate_early_error ? "true" : "false");
/* Simulate an early error BEFORE file open */
if (simulate_early_error) {
printf("[HTTP] Early error detected (before file open)\n");
_nx_web_http_server_response_send(server_ptr, "400 Bad Request");
/* BUG: The fix changes 'return;' to 'goto put_process_end;' */
/* But file was never opened! */
goto put_process_end;
}
/* Open the file */
status = fx_file_open(&(server_ptr->nx_web_http_server_file), filename, 0);
if (status != NX_SUCCESS) {
printf("[HTTP] File open failed\n");
_nx_web_http_server_response_send(server_ptr, "404 Not Found");
/* BUG: Jump to cleanup even though file wasn't opened */
goto put_process_end;
}
/* ... normal processing would happen here ... */
printf("[HTTP] Processing file...\n");
/* Success path */
printf("[HTTP] Processing complete\n");
_nx_web_http_server_response_send(server_ptr, "200 OK");
put_process_end:
/* BUG: Always attempts cleanup by closing the file */
/* Even if file was never opened! */
printf("[CLEANUP] Executing cleanup label...\n");
fx_file_close(&(server_ptr->nx_web_http_server_file));
return;
}
int main()
{
printf("=== CVE-2025-0728 Double/Invalid Close PoC ===\n\n");
NX_WEB_HTTP_SERVER server;
memset(&server, 0, sizeof(server));
/* Test 1: Early error triggers close on unopened file */
printf("========================================\n");
printf("TEST 1: Early error (before file open)\n");
printf("========================================\n");
g_close_count = 0;
g_file_was_opened = false;
_nx_web_http_server_put_process_VULNERABLE(&server, "test.txt", true);
printf("\nResult: fx_file_close called %d time(s) on NEVER-OPENED file!\n", g_close_count);
printf("This is UNDEFINED BEHAVIOR.\n");
/* Test 2: File not found error triggers close on unopened file */
printf("\n========================================\n");
printf("TEST 2: File not found error\n");
printf("========================================\n");
g_close_count = 0;
g_file_was_opened = false;
memset(&server, 0, sizeof(server));
_nx_web_http_server_put_process_VULNERABLE(&server, "nonexistent.txt", false);
printf("\nResult: fx_file_close called %d time(s) on NEVER-OPENED file!\n", g_close_count);
printf("\n========================================\n");
printf("SUMMARY\n");
printf("========================================\n");
printf("The VULNERABLE version unconditionally calls fx_file_close()\n");
printf("at the cleanup label, even when errors occur BEFORE the file\n");
printf("is opened. This causes undefined behavior by operating on\n");
printf("uninitialized file handle memory.\n");
return 0;
}
Steps:
1\. Compile the PoC:
gcc -fsanitize=address,undefined -g -o test_double_close test_double_close.c
2\. Run the PoC:
./test_double_close
Output:
=== CVE-2025-0728 Double/Invalid Close PoC ===
========================================
TEST 1: Early error (before file open)
========================================
=== PUT Process (VULNERABLE) ===
simulate_early_error = true
[HTTP] Early error detected (before file open)
[HTTP] Sending response: 400 Bad Request
[CLEANUP] Executing cleanup label...
[FX] fx_file_close() called (count=1)
[FX] ERROR: Closing file that was never opened!
[FX] This is UNDEFINED BEHAVIOR - file handle contains garbage!
Result: fx_file_close called 1 time(s) on NEVER-OPENED file!
This is UNDEFINED BEHAVIOR.
========================================
TEST 2: File not found error
========================================
=== PUT Process (VULNERABLE) ===
simulate_early_error = false
[FX] fx_file_open('nonexistent.txt') called
[FX] File not found, returning error
[HTTP] File open failed
[HTTP] Sending response: 404 Not Found
[CLEANUP] Executing cleanup label...
[FX] fx_file_close() called (count=1)
[FX] ERROR: Closing file that was never opened!
Result: fx_file_close called 1 time(s) on NEVER-OPENED file!
Impact
An attacker can send malformed HTTP PUT requests to a NetX Duo HTTP server to trigger early error paths that reach the cleanup label before the file has been opened. The unconditional fx_file_close() call on an uninitialized file handle causes undefined behavior in the filesystem layer, which on embedded systems can lead to crashes, memory corruption, or denial of service. Since this targets embedded RTOS devices, a crash may require physical intervention to recover, making this particularly impactful in IoT and industrial deployments.
References
-
Related CVE: CVE-2025-0728
-
Related CVEs in same commit: CVE-2025-0726, CVE-2025-0727 (multiple CVEs assigned to same incomplete fix)
-
Incomplete fix commit: https://github.com/eclipse-threadx/netxduo/commit/c78d650be7377aae1a8704bc0ce5cc6f9f189014
-
PoC:
poc_testing/threadx_poc/test_double_close.c