Junbo-Zheng opened a new pull request, #20128:
URL: https://github.com/apache/nuttx/pull/20128

   
   
   ## Summary
   
   The documented caller contract for `prctl(PR_GET_NAME)` is a buffer of 
`CONFIG_TASK_NAME_SIZE` bytes, NUL included (include/sys/prctl.h shows `char 
myname[CONFIG_TASK_NAME_SIZE]` as the usage example). However, the 
implementation passed `sizeof(tcb->name)`, i.e. `CONFIG_TASK_NAME_SIZE + 1`, to 
`strlcpy()`:
   
       strlcpy(name, tcb->name, sizeof(tcb->name));
       name[CONFIG_TASK_NAME_SIZE - 1] = '\0';
   
   `tcb->name` is `char[CONFIG_TASK_NAME_SIZE + 1]` and normally holds exactly 
`CONFIG_TASK_NAME_SIZE` characters after `nxtask_setup_name()` truncation. In 
that case `strlcpy()` copies all of them and writes its terminating NUL at 
`name[CONFIG_TASK_NAME_SIZE]` -- one byte past the caller's buffer. The 
forced-NUL line below it is a leftover from the `strncpy` era and ran after the 
overflow had already happened.
   
   The fix passes `CONFIG_TASK_NAME_SIZE` to `strlcpy()` so the copy is 
truncated in bounds (at most `CONFIG_TASK_NAME_SIZE - 1` characters plus NUL), 
and drops the stale forced-NUL line.
   
   ## Impact
   
   - **Users**: fixes a 1-byte out-of-bounds stack write for any caller that 
follows the documented buffer size when querying a task whose name was 
truncated to exactly `CONFIG_TASK_NAME_SIZE` characters. The returned name is 
now truncated to `CONFIG_TASK_NAME_SIZE - 1` characters plus NUL, matching the 
documented contract and Linux behavior.
   - **Build**: None.
   - **Hardware**: None -- plain libc-level fix, no board specifics.
   - **Documentation**: None -- the code now matches the existing contract 
documented in include/sys/prctl.h.
   - **Security & Compatibility**: closes a memory-corruption defect (single 
NUL byte written past a caller-provided buffer). No API change.
   
   ## Testing
   
   Simulated (sim:nsh host build on Ubuntu x86-64, `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 PR); its diff:
   
   ```diff
   --- a/examples/hello/hello_main.c
   +++ b/examples/hello/hello_main.c
   @@ -24,6 +24,8 @@
   
    #include <nuttx/config.h>
    #include <stdio.h>
   +#include <string.h>
   +#include <sys/prctl.h>
   
    
/****************************************************************************
     * 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: CONFIG_TASK_NAME_SIZE=31
   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: CONFIG_TASK_NAME_SIZE=31
   prctl test: guard=0xaa (expected 0xaa), name len=30, last char=0x00
   prctl test: PASS - caller buffer intact
   ```
   
   The before/after outputs were reproduced independently in this session on 
the unfixed base (origin/master) and on this branch.
   
   Signed-off-by: Junbo Zheng <[email protected]>


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to