Re: [PATCH] fs: do not cache a symlink length that disagrees with the string

Mateusz Guzik <[email protected]>
Newsgroups gmane.linux.file-systems,gmane.linux.kernel
Message-ID <CAGudoHEgPLtqzcsgJp5oVGWhTST0ZMVLA=kVDiLooh85N+LVWg@mail.gmail.com>
On Mon, Aug 17, 2026 at 10:17 AM Narek Jilavyan <[email protected]> wrote:
>
> inode_set_cached_link() stores a caller-supplied length in i_linklen and
> sets IOP_CACHED_LINK. vfs_readlink() then uses that length directly:
>
>         if (inode->i_opflags & IOP_CACHED_LINK)
>                 return readlink_copy(buffer, buflen, inode->i_link,
>                                      inode->i_linklen);
>
> and readlink_copy() clamps only against the user buffer, not against
> the string, so a length larger than the symlink body becomes a
> copy_to_user() of adjacent kernel memory - reachable by any process
> calling readlink() on such a symlink.
>
> The only thing standing behind the invariant is
>
>         VFS_WARN_ON_INODE(strlen(link) != linklen, inode);
>
> which expands to BUILD_BUG_ON_INVALID() unless CONFIG_DEBUG_VFS is set.
> On a production kernel it type-checks the expression and evaluates
> nothing, so the value is stored unvalidated.
>
> All four in-tree callers are correct today, and notably the two whose
> length comes from on-disk metadata (fs/ext4/inode.c, fs/erofs/inode.c)
> both re-derive it and reject the inode rather than relying on this
> helper.  The API should not require that of the next caller.
>

I'm confused why you don't expect filesystems to guarantee correct
value here, but I can agree issuing the strlen is not a big deal
during inode setup and I'm not going to insist on NOT having it in
prod kernels.

However, if going that route, I think the proposed WARN_ON is too terse.

inode_set_cached_link() at some point had the following:
       testlen = strlen(link);
       if (testlen != linklen) {
               WARN_ONCE(1, "bad length passed for symlink [%s] (got
%d, expected %d)",
                         link, linklen, testlen);
               linklen = testlen;
       }

As in, it used the correct value to fix up the caller.

I think it is ok to refrain from caching.

The important bit is the extra information as to what's going on,
namely what's the len disparity. I guess the message would be nicer if
the it also printed the filesystem name. You can borrow code from
dump_inode to do it.


> Validate unconditionally and fail safe: if the length disagrees, warn
> and leave IOP_CACHED_LINK clear.  i_linklen has exactly one reader in
> the tree and it is gated on that flag, and vfs_readlink() falls back to
> i_link with a strlen() of its own, so the inode degrades to the
> behaviour that predates the cached length instead of disclosing memory.
>
> The check runs once per symlink inode setup, not once per readlink(),
> which is what the cache was for.
>
> Fixes: ea3821990719 ("vfs: support caching symlink lengths in inodes")
> Signed-off-by: Narek Jilavyan <[email protected]>
> ---
>  include/linux/fs.h | 12 +++++++++++-
>  1 file changed, 11 insertions(+), 1 deletion(-)
>
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index 50ce731a2..e1d8f2614 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -946,9 +946,19 @@ static inline void inode_state_replace(struct inode *inode,
>
>  static inline void inode_set_cached_link(struct inode *inode, char *link, int linklen)
>  {
> -       VFS_WARN_ON_INODE(strlen(link) != linklen, inode);
>         VFS_WARN_ON_INODE(inode->i_opflags & IOP_CACHED_LINK, inode);
>         inode->i_link = link;
> +
> +       /*
> +        * i_linklen is used as a copy_to_user() length by vfs_readlink(), so it
> +        * must not be taken on trust. If it disagrees with the string, leave
> +        * IOP_CACHED_LINK clear: vfs_readlink() then falls back to i_link and
> +        * recomputes the length with strlen(), which is what it did before the
> +        * cached length was introduced.
> +        */
> +       if (WARN_ON_ONCE(strlen(link) != linklen))
> +               return;
> +
>         inode->i_linklen = linklen;
>         inode->i_opflags |= IOP_CACHED_LINK;
>  }
> --
> 2.43.0
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.