From a2c260bfce09757cb439de0f945439108712b58c Mon Sep 17 00:00:00 2001 From: Junbo Zheng Date: Sun, 13 Sep 2026 17:38:40 +0800 Subject: [PATCH] sched: fix 1-byte overflow in prctl(PR_GET_NAME) strlcpy() was given sizeof(tcb->name), i.e. CONFIG_TASK_NAME_SIZE + 1, but the documented caller contract is a buffer of CONFIG_TASK_NAME_SIZE bytes (include/sys/prctl.h). When a task name is exactly CONFIG_TASK_NAME_SIZE chars (the normal result of nxtask_setup_name() truncation), the terminating NUL lands one byte past the caller buffer. Pass CONFIG_TASK_NAME_SIZE to strlcpy() so the copy is truncated in-bounds, and drop the stale forced-NUL line left over from the strncpy era (it ran after the overflow had already happened). Before: ``` guard byte placed right after a CONFIG_TASK_NAME_SIZE caller buffer reads 0x00 (expected 0xAA) after the call: strlcpy writes its terminating NUL one byte past the buffer when the task name is exactly CONFIG_TASK_NAME_SIZE chars. ``` After: ``` strlcpy(name, tcb->name, CONFIG_TASK_NAME_SIZE) writes at most CONFIG_TASK_NAME_SIZE bytes; the caller buffer stays intact. ``` Testing: Simulated (sim:nsh, CONFIG_TASK_NAME_SIZE=31). Build and run: ``` cmake -B build -DBOARD_CONFIG=sim:nsh -GNinja cmake --build build -j$(nproc) echo hello | ./build/nuttx ``` then run "hello" at the NSH prompt. The test was carried by apps/examples/hello/hello_main.c (scratch only, not part of this commit); its diff: ``` --- a/examples/hello/hello_main.c +++ b/examples/hello/hello_main.c @@ -24,6 +24,8 @@ #include #include +#include +#include /**************************************************************************** * Public Functions @@ -35,6 +37,55 @@ int main(int argc, FAR char *argv[]) { + /* Longest-legal task name: exactly CONFIG_TASK_NAME_SIZE chars, the + * normal result of nxtask_setup_name() truncation. + */ + + static const char longname[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + /* Caller buffer per the documented prctl(PR_GET_NAME) contract, with a + * guard byte immediately after it to detect the 1-byte overflow. + */ + + struct + { + char buf[CONFIG_TASK_NAME_SIZE]; + volatile unsigned char guard; + } s; + + _Static_assert(sizeof(longname) - 1 > CONFIG_TASK_NAME_SIZE, + "test name must exceed CONFIG_TASK_NAME_SIZE"); + printf("Hello, World!!\n"); + printf("prctl test: CONFIG_TASK_NAME_SIZE=%d\n", CONFIG_TASK_NAME_SIZE); + + s.guard = 0xaa; + s.buf[0] = '\0'; + + if (prctl(PR_SET_NAME, (unsigned long)longname) != 0) + { + printf("prctl test: PR_SET_NAME failed\n"); + return 1; + } + + if (prctl(PR_GET_NAME, (unsigned long)s.buf) != 0) + { + printf("prctl test: PR_GET_NAME failed\n"); + return 1; + } + + printf("prctl test: guard=0x%02x (expected 0xaa), name len=%zu, " + "last char=0x%02x\n", s.guard, strlen(s.buf), (unsigned char)s.buf[strlen(s.buf)]); + + if (s.guard != 0xaa) + { + printf("prctl test: FAIL - terminating NUL written 1 byte past " + "the caller buffer\n"); + return 1; + } + + printf("prctl test: PASS - caller buffer intact\n"); return 0; } ``` Before the fix: ``` prctl test: guard=0x00 (expected 0xaa), name len=30, last char=0x00 prctl test: FAIL - terminating NUL written 1 byte past the caller buffer ``` After the fix: ``` prctl test: guard=0xaa (expected 0xaa), name len=30, last char=0x00 prctl test: PASS - caller buffer intact ``` Assisted-by: Claude Code (GLM-5.3) Signed-off-by: Junbo Zheng --- sched/task/task_prctl.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sched/task/task_prctl.c b/sched/task/task_prctl.c index 8b41979e89d98..b065eeae833d0 100644 --- a/sched/task/task_prctl.c +++ b/sched/task/task_prctl.c @@ -137,8 +137,7 @@ int prctl(int option, ...) * necessary. */ - strlcpy(name, tcb->name, sizeof(tcb->name)); - name[CONFIG_TASK_NAME_SIZE - 1] = '\0'; + strlcpy(name, tcb->name, CONFIG_TASK_NAME_SIZE); } } break;