]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
proc: only bump parent nlink when registering directories
authorKrzysztof Wilczyński <kwilczynski@kernel.org>
Sat, 13 Jun 2026 21:10:05 +0000 (21:10 +0000)
committerChristian Brauner <brauner@kernel.org>
Wed, 1 Jul 2026 13:26:24 +0000 (15:26 +0200)
proc_register() increments the parent directory's link count for every
entry it registers, while remove_proc_entry() and remove_proc_subtree()
decrement it only when the removed entry is a directory.  Regular files
thus inflate the parent's count while they exist, and leak one link
permanently on every create and remove cycle.

For example, /proc/bus/pci/00 with twenty-two device files and no
subdirectories reports nlink 24 instead of 2, and SR-IOV VF enable
and disable cycles, each creating and removing the VF config space
entries under /proc/bus/pci/<bus>, inflate the link count of that
directory without bound.

Before commit e06689bf5701 ("proc: change ->nlink under
proc_subdir_lock"), the increment lived in proc_mkdir_data() and
proc_create_mount_point(), and was therefore applied only to
directories.  Moving it into proc_register() to bring it under
proc_subdir_lock dropped the S_ISDIR check.

Thus, move the nlink accounting into pde_subdir_insert() and
pde_erase(), only updating it for directories in both, so the link
count is always changed together with the directory entry itself.

Fixes: e06689bf5701 ("proc: change ->nlink under proc_subdir_lock")
Cc: stable@vger.kernel.org # v5.5+
Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
Link: https://patch.msgid.link/20260613211005.921692-1-kwilczynski@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
fs/proc/generic.c

index adc9b9a092b03dd77f8694e4bf2893c4155a7fee..26086a283672bf1e65f4807e9bfc84285bee85c9 100644 (file)
@@ -112,6 +112,8 @@ static bool pde_subdir_insert(struct proc_dir_entry *dir,
        /* Add new node and rebalance tree. */
        rb_link_node(&de->subdir_node, parent, new);
        rb_insert_color(&de->subdir_node, root);
+       if (S_ISDIR(de->mode))
+               dir->nlink++;
        return true;
 }
 
@@ -404,7 +406,6 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
                write_unlock(&proc_subdir_lock);
                goto out_free_inum;
        }
-       dir->nlink++;
        write_unlock(&proc_subdir_lock);
 
        return dp;
@@ -706,6 +707,8 @@ static void pde_erase(struct proc_dir_entry *pde, struct proc_dir_entry *parent)
 {
        rb_erase(&pde->subdir_node, &parent->subdir);
        RB_CLEAR_NODE(&pde->subdir_node);
+       if (S_ISDIR(pde->mode))
+               parent->nlink--;
 }
 
 /*
@@ -731,8 +734,6 @@ void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
                        de = NULL;
                } else {
                        pde_erase(de, parent);
-                       if (S_ISDIR(de->mode))
-                               parent->nlink--;
                }
        }
        write_unlock(&proc_subdir_lock);
@@ -791,8 +792,6 @@ int remove_proc_subtree(const char *name, struct proc_dir_entry *parent)
                        continue;
                }
                next = de->parent;
-               if (S_ISDIR(de->mode))
-                       next->nlink--;
                write_unlock(&proc_subdir_lock);
 
                proc_entry_rundown(de);