Re: [PATCH RFC 19/38] proc: resume readdir after the last name it returned
From: Jann Horn
Date: Thu Nov 05 2026 - 12:17:49 EST
On Mon, Nov 2, 2026 at 3:26 PM Christian Brauner <brauner@xxxxxxxxxx> wrote:
> proc_readdir_de() counts the entries of a directory and resumes a
> listing at the number getdents64() stopped at. The entries are sorted
> by name, so one that is added or removed in between shifts everything
> behind it by one. Listing /proc/net/dev_snmp6 with a buffer that holds
> ".", ".." and "lo" and adding a device whose name sorts before "lo"
> makes the next call return "lo" a second time. Removing one that sorted
> before the position makes the next call skip an entry instead.
>
> Since commit 895b4c0c79b0 ("fs/proc: fix uaf in proc_readdir_de()") an
> entry that is removed while it is being emitted also ends the listing
> right there. pde_subdir_next() finds nothing after an erased entry, so
> proc_readdir_de() reports the end of the directory and the next call
> resumes by number, skipping another entry. For /proc itself
> proc_root_readdir() takes that as the end of the static entries and
> moves on to the pids, so the rest of the static entries is missing.
> And if the position is behind the last entry when a call starts
> proc_readdir_de() returns 0, so /proc then lists no pids at all.
>
> So remember the name of the last entry a call emitted in the file and
> resume at the first entry after it, like kernfs resumes after the node
> it stopped at. An entry that was removed while it was emitted is
> continued after by its name as well. If the position was moved by a
> seek in between the listing counts from the start like before.
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Fixes: 895b4c0c79b0 ("fs/proc: fix uaf in proc_readdir_de()")
> Cc: stable@xxxxxxxxxxxxxxx
> Signed-off-by: Christian Brauner (Amutable) <brauner@xxxxxxxxxx>
Reviewed-by: Jann Horn <jannh@xxxxxxxxxx>
> +static void proc_dir_cursor_set(struct file *file, struct proc_dir_entry *de,
> + loff_t pos)
> +{
> + struct proc_dir_cursor *cursor = file->private_data;
> +
> + if (!cursor) {
> + cursor = kmalloc(sizeof(*cursor), GFP_KERNEL);
> + if (!cursor)
> + return;
(For both this and the proc_sys version, you could instead do the
cursor allocation at the start of readdir() so that you can return a
proper error if it fails, but I guess it doesn't really matter and
that would complicate the code a bit more...)
> + file->private_data = cursor;
> + }
> + cursor->pos = pos;
> + cursor->len = de->namelen;
> + memcpy(cursor->name, de->name, de->namelen);
> +}