This is an automated email from the ASF dual-hosted git repository.

anchao pushed a commit to branch fix/fdlist-concurrent-leak
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit be28936547917cc5eb9ec80fdb439788163ff962
Author: chao an <[email protected]>
AuthorDate: Wed Nov 4 16:52:27 2026 +0800

    fs/inode: Fix descriptor-block leak on concurrent fdlist_extend()
    
    When fdlist_extend() grows the file-descriptor list, it allocates the
    new row array and per-row descriptor blocks while the list lock is
    dropped.  If another thread also grows the list in the meantime
    (range (orig_rows, row)), the old code ran memcpy() with the already
    published row pointers, overwriting - and permanently leaking - the
    descriptor blocks this thread just allocated for those rows.
    
    Merge the list row by row instead: release the buffers that the
    published list already supplies before copying in its pointers, and
    drop the redundant single request when fl_rows >= row.
    
    Signed-off-by: chao an <[email protected]>
---
 fs/inode/fs_files.c | 22 ++++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/fs/inode/fs_files.c b/fs/inode/fs_files.c
index 3dc715ce31e..6f17691330d 100644
--- a/fs/inode/fs_files.c
+++ b/fs/inode/fs_files.c
@@ -156,13 +156,18 @@ static int fdlist_extend(FAR struct fdlist *list, size_t 
row)
 
   flags = spin_lock_irqsave_notrace(&list->fl_lock);
 
-  /* To avoid race condition, if the file list is updated by other threads
-   * and list rows is greater or equal than temp list,
-   * release the obsolete buffers
+  /* Another thread may have extended the list while we were allocating the
+   * buffers above.  Instead of blindly memcpy()ing the whole list (which
+   * would overwrite - and leak - the buffers we allocated for those rows),
+   * merge row by row:
    */
 
-  if (orig_rows != list->fl_rows && list->fl_rows >= row)
+  if (list->fl_rows >= row)
     {
+      /* Another thread has already published a list with at least `row`
+       * rows -- keep it and release every buffer we allocated.
+       */
+
       spin_unlock_irqrestore_notrace(&list->fl_lock, flags);
 
       for (j = orig_rows; j < i; j++)
@@ -175,6 +180,15 @@ static int fdlist_extend(FAR struct fdlist *list, size_t 
row)
       return OK;
     }
 
+  /* Release the buffers we allocated for any rows that the current list
+   * already supplies (its pointers will be copied in below).
+   */
+
+  for (j = orig_rows; j < i && j < list->fl_rows; j++)
+    {
+      fs_heap_free(fds[j]);
+    }
+
   if (list->fl_fds != NULL)
     {
       memcpy(fds, list->fl_fds, list->fl_rows * sizeof(FAR struct fd *));

Reply via email to