Skip to content

Commit

Permalink
don't put symlink bodies in pagecache into highmem
Browse files Browse the repository at this point in the history
kmap() in page_follow_link_light() needed to go - allowing to hold
an arbitrary number of kmaps for long is a great way to deadlocking
the system.

new helper (inode_nohighmem(inode)) needs to be used for pagecache
symlinks inodes; done for all in-tree cases.  page_follow_link_light()
instrumented to yell about anything missed.

Signed-off-by: Al Viro <[email protected]>
  • Loading branch information
Al Viro committed Dec 9, 2015
1 parent aa80dea commit 21fc61c
Show file tree
Hide file tree
Showing 57 changed files with 81 additions and 46 deletions.
5 changes: 5 additions & 0 deletions Documentation/filesystems/porting
Original file line number Diff line number Diff line change
Expand Up @@ -504,3 +504,8 @@ in your dentry operations instead.
[mandatory]
__fd_install() & fd_install() can now sleep. Callers should not
hold a spinlock or other resources that do not allow a schedule.
--
[mandatory]
any symlink that might use page_follow_link_light/page_put_link() must
have inode_nohighmem(inode) called before anything might start playing with
its pagecache.
1 change: 1 addition & 0 deletions fs/affs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ struct inode *affs_iget(struct super_block *sb, unsigned long ino)
break;
case ST_SOFTLINK:
inode->i_mode |= S_IFLNK;
inode_nohighmem(inode);
inode->i_op = &affs_symlink_inode_operations;
inode->i_data.a_ops = &affs_symlink_aops;
break;
Expand Down
1 change: 1 addition & 0 deletions fs/affs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
return -ENOSPC;

inode->i_op = &affs_symlink_inode_operations;
inode_nohighmem(inode);
inode->i_data.a_ops = &affs_symlink_aops;
inode->i_mode = S_IFLNK | 0777;
mode_to_prot(inode);
Expand Down
4 changes: 1 addition & 3 deletions fs/affs/symlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ static int affs_symlink_readpage(struct file *file, struct page *page)
{
struct buffer_head *bh;
struct inode *inode = page->mapping->host;
char *link = kmap(page);
char *link = page_address(page);
struct slink_front *lf;
int i, j;
char c;
Expand Down Expand Up @@ -57,12 +57,10 @@ static int affs_symlink_readpage(struct file *file, struct page *page)
link[i] = '\0';
affs_brelse(bh);
SetPageUptodate(page);
kunmap(page);
unlock_page(page);
return 0;
fail:
SetPageError(page);
kunmap(page);
unlock_page(page);
return -EIO;
}
Expand Down
1 change: 1 addition & 0 deletions fs/afs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ static int afs_inode_map_status(struct afs_vnode *vnode, struct key *key)
case AFS_FTYPE_SYMLINK:
inode->i_mode = S_IFLNK | vnode->status.mode;
inode->i_op = &page_symlink_inode_operations;
inode_nohighmem(inode);
break;
default:
printk("kAFS: AFS vnode with undefined type\n");
Expand Down
5 changes: 2 additions & 3 deletions fs/befs/linuxvfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ static struct inode *befs_iget(struct super_block *sb, unsigned long ino)
} else if (S_ISLNK(inode->i_mode)) {
if (befs_ino->i_flags & BEFS_LONG_SYMLINK) {
inode->i_op = &page_symlink_inode_operations;
inode_nohighmem(inode);
inode->i_mapping->a_ops = &befs_symlink_aops;
} else {
inode->i_link = befs_ino->i_data.symlink;
Expand Down Expand Up @@ -469,7 +470,7 @@ static int befs_symlink_readpage(struct file *unused, struct page *page)
struct befs_inode_info *befs_ino = BEFS_I(inode);
befs_data_stream *data = &befs_ino->i_data.ds;
befs_off_t len = data->size;
char *link = kmap(page);
char *link = page_address(page);

if (len == 0 || len > PAGE_SIZE) {
befs_error(sb, "Long symlink with illegal length");
Expand All @@ -483,12 +484,10 @@ static int befs_symlink_readpage(struct file *unused, struct page *page)
}
link[len - 1] = '\0';
SetPageUptodate(page);
kunmap(page);
unlock_page(page);
return 0;
fail:
SetPageError(page);
kunmap(page);
unlock_page(page);
return -EIO;
}
Expand Down
2 changes: 2 additions & 0 deletions fs/btrfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -3774,6 +3774,7 @@ static void btrfs_read_locked_inode(struct inode *inode)
break;
case S_IFLNK:
inode->i_op = &btrfs_symlink_inode_operations;
inode_nohighmem(inode);
inode->i_mapping->a_ops = &btrfs_symlink_aops;
break;
default:
Expand Down Expand Up @@ -9705,6 +9706,7 @@ static int btrfs_symlink(struct inode *dir, struct dentry *dentry,
btrfs_free_path(path);

inode->i_op = &btrfs_symlink_inode_operations;
inode_nohighmem(inode);
inode->i_mapping->a_ops = &btrfs_symlink_aops;
inode_set_bytes(inode, name_len);
btrfs_i_size_write(inode, name_len);
Expand Down
2 changes: 2 additions & 0 deletions fs/coda/cnode.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <linux/coda.h>
#include <linux/coda_psdev.h>
#include <linux/pagemap.h>
#include "coda_linux.h"

static inline int coda_fideq(struct CodaFid *fid1, struct CodaFid *fid2)
Expand Down Expand Up @@ -35,6 +36,7 @@ static void coda_fill_inode(struct inode *inode, struct coda_vattr *attr)
inode->i_fop = &coda_dir_operations;
} else if (S_ISLNK(inode->i_mode)) {
inode->i_op = &coda_symlink_inode_operations;
inode_nohighmem(inode);
inode->i_data.a_ops = &coda_symlink_aops;
inode->i_mapping = &inode->i_data;
} else
Expand Down
4 changes: 1 addition & 3 deletions fs/coda/symlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,19 @@ static int coda_symlink_filler(struct file *file, struct page *page)
int error;
struct coda_inode_info *cii;
unsigned int len = PAGE_SIZE;
char *p = kmap(page);
char *p = page_address(page);

cii = ITOC(inode);

error = venus_readlink(inode->i_sb, &cii->c_fid, p, &len);
if (error)
goto fail;
SetPageUptodate(page);
kunmap(page);
unlock_page(page);
return 0;

fail:
SetPageError(page);
kunmap(page);
unlock_page(page);
return error;
}
Expand Down
1 change: 1 addition & 0 deletions fs/cramfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ static struct inode *get_cramfs_inode(struct super_block *sb,
break;
case S_IFLNK:
inode->i_op = &page_symlink_inode_operations;
inode_nohighmem(inode);
inode->i_data.a_ops = &cramfs_aops;
break;
default:
Expand Down
1 change: 1 addition & 0 deletions fs/efs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ struct inode *efs_iget(struct super_block *super, unsigned long ino)
break;
case S_IFLNK:
inode->i_op = &page_symlink_inode_operations;
inode_nohighmem(inode);
inode->i_data.a_ops = &efs_symlink_aops;
break;
case S_IFCHR:
Expand Down
4 changes: 1 addition & 3 deletions fs/efs/symlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

static int efs_symlink_readpage(struct file *file, struct page *page)
{
char *link = kmap(page);
char *link = page_address(page);
struct buffer_head * bh;
struct inode * inode = page->mapping->host;
efs_block_t size = inode->i_size;
Expand All @@ -39,12 +39,10 @@ static int efs_symlink_readpage(struct file *file, struct page *page)
}
link[size] = '\0';
SetPageUptodate(page);
kunmap(page);
unlock_page(page);
return 0;
fail:
SetPageError(page);
kunmap(page);
unlock_page(page);
return err;
}
Expand Down
1 change: 1 addition & 0 deletions fs/exofs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,7 @@ struct inode *exofs_iget(struct super_block *sb, unsigned long ino)
inode->i_link = (char *)oi->i_data;
} else {
inode->i_op = &page_symlink_inode_operations;
inode_nohighmem(inode);
inode->i_mapping->a_ops = &exofs_aops;
}
} else {
Expand Down
1 change: 1 addition & 0 deletions fs/exofs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ static int exofs_symlink(struct inode *dir, struct dentry *dentry,
if (l > sizeof(oi->i_data)) {
/* slow symlink */
inode->i_op = &page_symlink_inode_operations;
inode_nohighmem(inode);
inode->i_mapping->a_ops = &exofs_aops;
memset(oi->i_data, 0, sizeof(oi->i_data));

Expand Down
1 change: 1 addition & 0 deletions fs/ext2/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -1420,6 +1420,7 @@ struct inode *ext2_iget (struct super_block *sb, unsigned long ino)
sizeof(ei->i_data) - 1);
} else {
inode->i_op = &ext2_symlink_inode_operations;
inode_nohighmem(inode);
if (test_opt(inode->i_sb, NOBH))
inode->i_mapping->a_ops = &ext2_nobh_aops;
else
Expand Down
1 change: 1 addition & 0 deletions fs/ext2/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ static int ext2_symlink (struct inode * dir, struct dentry * dentry,
if (l > sizeof (EXT2_I(inode)->i_data)) {
/* slow symlink */
inode->i_op = &ext2_symlink_inode_operations;
inode_nohighmem(inode);
if (test_opt(inode->i_sb, NOBH))
inode->i_mapping->a_ops = &ext2_nobh_aops;
else
Expand Down
1 change: 1 addition & 0 deletions fs/ext4/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -4283,6 +4283,7 @@ struct inode *ext4_iget(struct super_block *sb, unsigned long ino)
inode->i_op = &ext4_symlink_inode_operations;
ext4_set_aops(inode);
}
inode_nohighmem(inode);
} else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
inode->i_op = &ext4_special_inode_operations;
Expand Down
1 change: 1 addition & 0 deletions fs/ext4/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -3132,6 +3132,7 @@ static int ext4_symlink(struct inode *dir,
if ((disk_link.len > EXT4_N_BLOCKS * 4)) {
if (!encryption_required)
inode->i_op = &ext4_symlink_inode_operations;
inode_nohighmem(inode);
ext4_set_aops(inode);
/*
* We cannot call page_symlink() with transaction started
Expand Down
10 changes: 3 additions & 7 deletions fs/ext4/symlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ static const char *ext4_encrypted_follow_link(struct dentry *dentry, void **cook
cpage = read_mapping_page(inode->i_mapping, 0, NULL);
if (IS_ERR(cpage))
return ERR_CAST(cpage);
caddr = kmap(cpage);
caddr = page_address(cpage);
caddr[size] = 0;
}

Expand Down Expand Up @@ -75,16 +75,12 @@ static const char *ext4_encrypted_follow_link(struct dentry *dentry, void **cook
/* Null-terminate the name */
if (res <= plen)
paddr[res] = '\0';
if (cpage) {
kunmap(cpage);
if (cpage)
page_cache_release(cpage);
}
return *cookie = paddr;
errout:
if (cpage) {
kunmap(cpage);
if (cpage)
page_cache_release(cpage);
}
kfree(paddr);
return ERR_PTR(res);
}
Expand Down
1 change: 1 addition & 0 deletions fs/f2fs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ struct inode *f2fs_iget(struct super_block *sb, unsigned long ino)
inode->i_op = &f2fs_encrypted_symlink_inode_operations;
else
inode->i_op = &f2fs_symlink_inode_operations;
inode_nohighmem(inode);
inode->i_mapping->a_ops = &f2fs_dblock_aops;
} else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
Expand Down
5 changes: 2 additions & 3 deletions fs/f2fs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ static int f2fs_symlink(struct inode *dir, struct dentry *dentry,
inode->i_op = &f2fs_encrypted_symlink_inode_operations;
else
inode->i_op = &f2fs_symlink_inode_operations;
inode_nohighmem(inode);
inode->i_mapping->a_ops = &f2fs_dblock_aops;

f2fs_lock_op(sbi);
Expand Down Expand Up @@ -942,7 +943,7 @@ static const char *f2fs_encrypted_follow_link(struct dentry *dentry, void **cook
cpage = read_mapping_page(inode->i_mapping, 0, NULL);
if (IS_ERR(cpage))
return ERR_CAST(cpage);
caddr = kmap(cpage);
caddr = page_address(cpage);
caddr[size] = 0;

/* Symlink is encrypted */
Expand Down Expand Up @@ -982,13 +983,11 @@ static const char *f2fs_encrypted_follow_link(struct dentry *dentry, void **cook
/* Null-terminate the name */
paddr[res] = '\0';

kunmap(cpage);
page_cache_release(cpage);
return *cookie = paddr;
errout:
kfree(cstr.name);
f2fs_fname_crypto_free_buffer(&pstr);
kunmap(cpage);
page_cache_release(cpage);
return ERR_PTR(res);
}
Expand Down
1 change: 1 addition & 0 deletions fs/freevxfs/vxfs_inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ vxfs_iget(struct super_block *sbp, ino_t ino)
} else if (S_ISLNK(ip->i_mode)) {
if (!VXFS_ISIMMED(vip)) {
ip->i_op = &page_symlink_inode_operations;
inode_nohighmem(ip);
ip->i_mapping->a_ops = &vxfs_aops;
} else {
ip->i_op = &simple_symlink_inode_operations;
Expand Down
2 changes: 2 additions & 0 deletions fs/hfsplus/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ struct inode *hfsplus_new_inode(struct super_block *sb, umode_t mode)
} else if (S_ISLNK(inode->i_mode)) {
sbi->file_count++;
inode->i_op = &page_symlink_inode_operations;
inode_nohighmem(inode);
inode->i_mapping->a_ops = &hfsplus_aops;
hip->clump_blocks = 1;
} else
Expand Down Expand Up @@ -526,6 +527,7 @@ int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd)
inode->i_mapping->a_ops = &hfsplus_aops;
} else if (S_ISLNK(inode->i_mode)) {
inode->i_op = &page_symlink_inode_operations;
inode_nohighmem(inode);
inode->i_mapping->a_ops = &hfsplus_aops;
} else {
init_special_inode(inode, inode->i_mode,
Expand Down
1 change: 1 addition & 0 deletions fs/hpfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ void hpfs_read_inode(struct inode *i)
kfree(ea);
i->i_mode = S_IFLNK | 0777;
i->i_op = &page_symlink_inode_operations;
inode_nohighmem(i);
i->i_data.a_ops = &hpfs_symlink_aops;
set_nlink(i, 1);
i->i_size = ea_size;
Expand Down
5 changes: 2 additions & 3 deletions fs/hpfs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ static int hpfs_symlink(struct inode *dir, struct dentry *dentry, const char *sy
result->i_blocks = 1;
set_nlink(result, 1);
result->i_size = strlen(symlink);
inode_nohighmem(result);
result->i_op = &page_symlink_inode_operations;
result->i_data.a_ops = &hpfs_symlink_aops;

Expand Down Expand Up @@ -500,7 +501,7 @@ static int hpfs_rmdir(struct inode *dir, struct dentry *dentry)

static int hpfs_symlink_readpage(struct file *file, struct page *page)
{
char *link = kmap(page);
char *link = page_address(page);
struct inode *i = page->mapping->host;
struct fnode *fnode;
struct buffer_head *bh;
Expand All @@ -516,14 +517,12 @@ static int hpfs_symlink_readpage(struct file *file, struct page *page)
goto fail;
hpfs_unlock(i->i_sb);
SetPageUptodate(page);
kunmap(page);
unlock_page(page);
return 0;

fail:
hpfs_unlock(i->i_sb);
SetPageError(page);
kunmap(page);
unlock_page(page);
return err;
}
Expand Down
1 change: 1 addition & 0 deletions fs/hugetlbfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,7 @@ static struct inode *hugetlbfs_get_inode(struct super_block *sb,
break;
case S_IFLNK:
inode->i_op = &page_symlink_inode_operations;
inode_nohighmem(inode);
break;
}
lockdep_annotate_inode_mutex_key(inode);
Expand Down
6 changes: 6 additions & 0 deletions fs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -2028,3 +2028,9 @@ void inode_set_flags(struct inode *inode, unsigned int flags,
new_flags) != old_flags));
}
EXPORT_SYMBOL(inode_set_flags);

void inode_nohighmem(struct inode *inode)
{
mapping_set_gfp_mask(inode->i_mapping, GFP_USER);
}
EXPORT_SYMBOL(inode_nohighmem);
1 change: 1 addition & 0 deletions fs/isofs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -1417,6 +1417,7 @@ static int isofs_read_inode(struct inode *inode, int relocated)
inode->i_fop = &isofs_dir_operations;
} else if (S_ISLNK(inode->i_mode)) {
inode->i_op = &page_symlink_inode_operations;
inode_nohighmem(inode);
inode->i_data.a_ops = &isofs_symlink_aops;
} else
/* XXX - parse_rock_ridge_inode() had already set i_rdev. */
Expand Down
Loading

0 comments on commit 21fc61c

Please sign in to comment.