mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 23:09:29 +02:00
This is another run of the Coccinelle script for converting kmalloc() family of allocations to kmalloc_obj() via the existing rules in scripts/coccinelle/api/kmalloc_objs.cocci This catches both the set of kmalloc() uses added since the first kmalloc_obj() conversions in v7.0 and adds a large group missed in the first pass due to Coccinelle not interacting well with the cleanup.h scoped_...() family of macros[1]. I worked around this with spatch's "--macro-file" argument to a file with all the scoped_...() macros mapped to Coccinelle's YACFE_ITERATOR[2] as that was the closest viable control flow indicator I could find. Build tested allmodconfig on x86, arm64, arm, loongarch, mips, powerpc, riscv, and s390 with no new warnings. Link: https://lore.kernel.org/lkml/202609021314.8A9C0B8@keescook/ [1] Link: https://github.com/coccinelle/coccinelle/blob/master/standard.h [2] Signed-off-by: Kees Cook <kees+treewide@kernel.org>
278 lines
6.7 KiB
C
278 lines
6.7 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/* AFS filesystem symbolic link handling
|
|
*
|
|
* Copyright (C) 2026 Red Hat, Inc. All Rights Reserved.
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/namei.h>
|
|
#include <linux/pagemap.h>
|
|
#include <linux/iov_iter.h>
|
|
#include "internal.h"
|
|
|
|
static void afs_put_symlink(struct afs_symlink *symlink)
|
|
{
|
|
if (refcount_dec_and_test(&symlink->ref))
|
|
kfree_rcu(symlink, rcu);
|
|
}
|
|
|
|
static void afs_replace_symlink(struct afs_vnode *vnode, struct afs_symlink *symlink)
|
|
{
|
|
struct afs_symlink *old;
|
|
|
|
old = rcu_replace_pointer(vnode->symlink, symlink,
|
|
lockdep_is_held(&vnode->validate_lock));
|
|
if (old)
|
|
afs_put_symlink(old);
|
|
}
|
|
|
|
/*
|
|
* In the event that a third-party update of a symlink occurs, dispose of the
|
|
* copy of the old contents. Called under ->validate_lock.
|
|
*/
|
|
void afs_invalidate_symlink(struct afs_vnode *vnode)
|
|
{
|
|
afs_replace_symlink(vnode, NULL);
|
|
}
|
|
|
|
/*
|
|
* Dispose of a symlink copy during inode deletion.
|
|
*/
|
|
void afs_evict_symlink(struct afs_vnode *vnode)
|
|
{
|
|
struct afs_symlink *old;
|
|
|
|
old = rcu_replace_pointer(vnode->symlink, NULL, true);
|
|
if (old)
|
|
afs_put_symlink(old);
|
|
|
|
}
|
|
|
|
/*
|
|
* Set up a locally created symlink inode for immediate write to the cache.
|
|
*/
|
|
void afs_init_new_symlink(struct afs_vnode *vnode, struct afs_operation *op)
|
|
{
|
|
struct afs_symlink *symlink = op->create.symlink;
|
|
size_t dsize = 0;
|
|
size_t size = strlen(symlink->content) + 1;
|
|
char *p;
|
|
|
|
rcu_assign_pointer(vnode->symlink, symlink);
|
|
op->create.symlink = NULL;
|
|
|
|
if (!fscache_cookie_enabled(netfs_i_cookie(&vnode->netfs)))
|
|
return;
|
|
|
|
if (netfs_alloc_folioq_buffer(NULL, &vnode->directory, &dsize, size,
|
|
mapping_gfp_mask(vnode->netfs.inode.i_mapping)) < 0)
|
|
return;
|
|
|
|
vnode->directory_size = dsize;
|
|
p = kmap_local_folio(folioq_folio(vnode->directory, 0), 0);
|
|
memcpy(p, symlink->content, size);
|
|
kunmap_local(p);
|
|
netfs_single_mark_inode_dirty(&vnode->netfs.inode);
|
|
}
|
|
|
|
/*
|
|
* Read a symlink in a single download.
|
|
*/
|
|
static ssize_t afs_do_read_symlink(struct afs_vnode *vnode)
|
|
{
|
|
struct afs_symlink *symlink;
|
|
struct iov_iter iter;
|
|
ssize_t ret;
|
|
loff_t i_size;
|
|
|
|
i_size = i_size_read(&vnode->netfs.inode);
|
|
if (i_size > PAGE_SIZE - 1) {
|
|
trace_afs_file_error(vnode, -EFBIG, afs_file_error_dir_big);
|
|
return -EFBIG;
|
|
}
|
|
|
|
if (!vnode->directory) {
|
|
size_t cur_size = 0;
|
|
|
|
ret = netfs_alloc_folioq_buffer(NULL,
|
|
&vnode->directory, &cur_size, PAGE_SIZE,
|
|
mapping_gfp_mask(vnode->netfs.inode.i_mapping));
|
|
vnode->directory_size = PAGE_SIZE - 1;
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
iov_iter_folio_queue(&iter, ITER_DEST, vnode->directory, 0, 0, PAGE_SIZE);
|
|
|
|
/* AFS requires us to perform the read of a symlink as a single unit to
|
|
* avoid issues with the content being changed between reads.
|
|
*/
|
|
ret = netfs_read_single(&vnode->netfs.inode, NULL, &iter);
|
|
if (ret >= 0) {
|
|
i_size = ret;
|
|
if (i_size > PAGE_SIZE - 1) {
|
|
trace_afs_file_error(vnode, -EFBIG, afs_file_error_dir_big);
|
|
return -EFBIG;
|
|
}
|
|
vnode->directory_size = i_size;
|
|
|
|
/* Copy the symlink. */
|
|
symlink = kmalloc_flex(struct afs_symlink, content, i_size + 1);
|
|
if (!symlink)
|
|
return -ENOMEM;
|
|
|
|
refcount_set(&symlink->ref, 1);
|
|
symlink->content[i_size] = 0;
|
|
|
|
const char *s = kmap_local_folio(folioq_folio(vnode->directory, 0), 0);
|
|
|
|
memcpy(symlink->content, s, i_size);
|
|
kunmap_local(s);
|
|
|
|
afs_replace_symlink(vnode, symlink);
|
|
}
|
|
|
|
if (!fscache_cookie_enabled(netfs_i_cookie(&vnode->netfs))) {
|
|
netfs_free_folioq_buffer(vnode->directory);
|
|
vnode->directory = NULL;
|
|
vnode->directory_size = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t afs_read_symlink(struct afs_vnode *vnode)
|
|
{
|
|
ssize_t ret;
|
|
|
|
fscache_use_cookie(afs_vnode_cache(vnode), false);
|
|
ret = afs_do_read_symlink(vnode);
|
|
fscache_unuse_cookie(afs_vnode_cache(vnode), NULL, NULL);
|
|
return ret;
|
|
}
|
|
|
|
static void afs_put_link(void *arg)
|
|
{
|
|
afs_put_symlink(arg);
|
|
}
|
|
|
|
const char *afs_get_link(struct dentry *dentry, struct inode *inode,
|
|
struct delayed_call *callback)
|
|
{
|
|
struct afs_symlink *symlink;
|
|
struct afs_vnode *vnode = AFS_FS_I(inode);
|
|
ssize_t ret;
|
|
|
|
if (!dentry) {
|
|
/* RCU pathwalk. */
|
|
symlink = rcu_dereference(vnode->symlink);
|
|
if (!symlink || !afs_check_validity(vnode))
|
|
return ERR_PTR(-ECHILD);
|
|
set_delayed_call(callback, NULL, NULL);
|
|
return symlink->content;
|
|
}
|
|
|
|
if (vnode->symlink) {
|
|
ret = afs_validate(vnode, NULL);
|
|
if (ret < 0)
|
|
return ERR_PTR(ret);
|
|
|
|
down_read(&vnode->validate_lock);
|
|
if (vnode->symlink)
|
|
goto good;
|
|
up_read(&vnode->validate_lock);
|
|
}
|
|
|
|
if (down_write_killable(&vnode->validate_lock) < 0)
|
|
return ERR_PTR(-ERESTARTSYS);
|
|
if (!vnode->symlink) {
|
|
ret = afs_read_symlink(vnode);
|
|
if (ret < 0) {
|
|
up_write(&vnode->validate_lock);
|
|
return ERR_PTR(ret);
|
|
}
|
|
}
|
|
|
|
downgrade_write(&vnode->validate_lock);
|
|
|
|
good:
|
|
symlink = rcu_dereference_protected(vnode->symlink,
|
|
lockdep_is_held(&vnode->validate_lock));
|
|
refcount_inc(&symlink->ref);
|
|
up_read(&vnode->validate_lock);
|
|
|
|
set_delayed_call(callback, afs_put_link, symlink);
|
|
return symlink->content;
|
|
}
|
|
|
|
int afs_readlink(struct dentry *dentry, char __user *buffer, int buflen)
|
|
{
|
|
DEFINE_DELAYED_CALL(done);
|
|
const char *content;
|
|
int len;
|
|
|
|
content = afs_get_link(dentry, d_inode(dentry), &done);
|
|
if (IS_ERR(content)) {
|
|
do_delayed_call(&done);
|
|
return PTR_ERR(content);
|
|
}
|
|
|
|
len = umin(strlen(content), buflen);
|
|
if (copy_to_user(buffer, content, len))
|
|
len = -EFAULT;
|
|
do_delayed_call(&done);
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* Write the symlink contents to the cache as a single blob. We then throw
|
|
* away the page we used to receive it.
|
|
*/
|
|
int afs_symlink_writepages(struct address_space *mapping,
|
|
struct writeback_control *wbc)
|
|
{
|
|
struct afs_vnode *vnode = AFS_FS_I(mapping->host);
|
|
struct iov_iter iter;
|
|
int ret = 0;
|
|
|
|
if (!down_read_trylock(&vnode->validate_lock)) {
|
|
if (wbc->sync_mode == WB_SYNC_NONE) {
|
|
/* The VFS will have undirtied the inode. */
|
|
netfs_single_mark_inode_dirty(&vnode->netfs.inode);
|
|
return 0;
|
|
}
|
|
down_read(&vnode->validate_lock);
|
|
}
|
|
|
|
if (vnode->directory &&
|
|
atomic64_read(&vnode->cb_expires_at) != AFS_NO_CB_PROMISE) {
|
|
iov_iter_folio_queue(&iter, ITER_SOURCE, vnode->directory, 0, 0,
|
|
i_size_read(&vnode->netfs.inode));
|
|
ret = netfs_writeback_single(mapping, wbc, &iter);
|
|
}
|
|
|
|
if (ret == 0) {
|
|
netfs_wb_begin(&vnode->netfs, false);
|
|
netfs_free_folioq_buffer(vnode->directory);
|
|
vnode->directory = NULL;
|
|
vnode->directory_size = 0;
|
|
netfs_wb_end(&vnode->netfs);
|
|
} else if (ret == 1) {
|
|
ret = 0; /* Skipped write due to lock conflict. */
|
|
}
|
|
|
|
up_read(&vnode->validate_lock);
|
|
return ret;
|
|
}
|
|
|
|
const struct inode_operations afs_symlink_inode_operations = {
|
|
.get_link = afs_get_link,
|
|
.readlink = afs_readlink,
|
|
};
|
|
|
|
const struct address_space_operations afs_symlink_aops = {
|
|
.writepages = afs_symlink_writepages,
|
|
};
|