Skip to content

Commit

Permalink
tmpfs,ceph,gfs2,isofs,reiserfs,xfs: fix fh_len checking
Browse files Browse the repository at this point in the history
Fuzzing with trinity oopsed on the 1st instruction of shmem_fh_to_dentry(),
	u64 inum = fid->raw[2];
which is unhelpfully reported as at the end of shmem_alloc_inode():

BUG: unable to handle kernel paging request at ffff880061cd3000
IP: [<ffffffff812190d0>] shmem_alloc_inode+0x40/0x40
Oops: 0000 [#1] PREEMPT SMP DEBUG_PAGEALLOC
Call Trace:
 [<ffffffff81488649>] ? exportfs_decode_fh+0x79/0x2d0
 [<ffffffff812d77c3>] do_handle_open+0x163/0x2c0
 [<ffffffff812d792c>] sys_open_by_handle_at+0xc/0x10
 [<ffffffff83a5f3f8>] tracesys+0xe1/0xe6

Right, tmpfs is being stupid to access fid->raw[2] before validating that
fh_len includes it: the buffer kmalloc'ed by do_sys_name_to_handle() may
fall at the end of a page, and the next page not be present.

But some other filesystems (ceph, gfs2, isofs, reiserfs, xfs) are being
careless about fh_len too, in fh_to_dentry() and/or fh_to_parent(), and
could oops in the same way: add the missing fh_len checks to those.

Reported-by: Sasha Levin <[email protected]>
Signed-off-by: Hugh Dickins <[email protected]>
Cc: Al Viro <[email protected]>
Cc: Sage Weil <[email protected]>
Cc: Steven Whitehouse <[email protected]>
Cc: Christoph Hellwig <[email protected]>
Cc: [email protected]
Signed-off-by: Al Viro <[email protected]>
Hugh Dickins authored and Al Viro committed Oct 10, 2012
1 parent 8e22cc8 commit 35c2a7f
Showing 6 changed files with 31 additions and 8 deletions.
18 changes: 14 additions & 4 deletions fs/ceph/export.c
Original file line number Diff line number Diff line change
@@ -99,14 +99,17 @@ static int ceph_encode_fh(struct inode *inode, u32 *rawfh, int *max_len,
* FIXME: we should try harder by querying the mds for the ino.
*/
static struct dentry *__fh_to_dentry(struct super_block *sb,
struct ceph_nfs_fh *fh)
struct ceph_nfs_fh *fh, int fh_len)
{
struct ceph_mds_client *mdsc = ceph_sb_to_client(sb)->mdsc;
struct inode *inode;
struct dentry *dentry;
struct ceph_vino vino;
int err;

if (fh_len < sizeof(*fh) / 4)
return ERR_PTR(-ESTALE);

dout("__fh_to_dentry %llx\n", fh->ino);
vino.ino = fh->ino;
vino.snap = CEPH_NOSNAP;
@@ -150,14 +153,17 @@ static struct dentry *__fh_to_dentry(struct super_block *sb,
* convert connectable fh to dentry
*/
static struct dentry *__cfh_to_dentry(struct super_block *sb,
struct ceph_nfs_confh *cfh)
struct ceph_nfs_confh *cfh, int fh_len)
{
struct ceph_mds_client *mdsc = ceph_sb_to_client(sb)->mdsc;
struct inode *inode;
struct dentry *dentry;
struct ceph_vino vino;
int err;

if (fh_len < sizeof(*cfh) / 4)
return ERR_PTR(-ESTALE);

dout("__cfh_to_dentry %llx (%llx/%x)\n",
cfh->ino, cfh->parent_ino, cfh->parent_name_hash);

@@ -207,9 +213,11 @@ static struct dentry *ceph_fh_to_dentry(struct super_block *sb, struct fid *fid,
int fh_len, int fh_type)
{
if (fh_type == 1)
return __fh_to_dentry(sb, (struct ceph_nfs_fh *)fid->raw);
return __fh_to_dentry(sb, (struct ceph_nfs_fh *)fid->raw,
fh_len);
else
return __cfh_to_dentry(sb, (struct ceph_nfs_confh *)fid->raw);
return __cfh_to_dentry(sb, (struct ceph_nfs_confh *)fid->raw,
fh_len);
}

/*
@@ -230,6 +238,8 @@ static struct dentry *ceph_fh_to_parent(struct super_block *sb,

if (fh_type == 1)
return ERR_PTR(-ESTALE);
if (fh_len < sizeof(*cfh) / 4)
return ERR_PTR(-ESTALE);

pr_debug("fh_to_parent %llx/%d\n", cfh->parent_ino,
cfh->parent_name_hash);
4 changes: 4 additions & 0 deletions fs/gfs2/export.c
Original file line number Diff line number Diff line change
@@ -161,6 +161,8 @@ static struct dentry *gfs2_fh_to_dentry(struct super_block *sb, struct fid *fid,
case GFS2_SMALL_FH_SIZE:
case GFS2_LARGE_FH_SIZE:
case GFS2_OLD_FH_SIZE:
if (fh_len < GFS2_SMALL_FH_SIZE)
return NULL;
this.no_formal_ino = ((u64)be32_to_cpu(fh[0])) << 32;
this.no_formal_ino |= be32_to_cpu(fh[1]);
this.no_addr = ((u64)be32_to_cpu(fh[2])) << 32;
@@ -180,6 +182,8 @@ static struct dentry *gfs2_fh_to_parent(struct super_block *sb, struct fid *fid,
switch (fh_type) {
case GFS2_LARGE_FH_SIZE:
case GFS2_OLD_FH_SIZE:
if (fh_len < GFS2_LARGE_FH_SIZE)
return NULL;
parent.no_formal_ino = ((u64)be32_to_cpu(fh[4])) << 32;
parent.no_formal_ino |= be32_to_cpu(fh[5]);
parent.no_addr = ((u64)be32_to_cpu(fh[6])) << 32;
2 changes: 1 addition & 1 deletion fs/isofs/export.c
Original file line number Diff line number Diff line change
@@ -175,7 +175,7 @@ static struct dentry *isofs_fh_to_parent(struct super_block *sb,
{
struct isofs_fid *ifid = (struct isofs_fid *)fid;

if (fh_type != 2)
if (fh_len < 2 || fh_type != 2)
return NULL;

return isofs_export_iget(sb,
6 changes: 5 additions & 1 deletion fs/reiserfs/inode.c
Original file line number Diff line number Diff line change
@@ -1573,8 +1573,10 @@ struct dentry *reiserfs_fh_to_dentry(struct super_block *sb, struct fid *fid,
reiserfs_warning(sb, "reiserfs-13077",
"nfsd/reiserfs, fhtype=%d, len=%d - odd",
fh_type, fh_len);
fh_type = 5;
fh_type = fh_len;
}
if (fh_len < 2)
return NULL;

return reiserfs_get_dentry(sb, fid->raw[0], fid->raw[1],
(fh_type == 3 || fh_type >= 5) ? fid->raw[2] : 0);
@@ -1583,6 +1585,8 @@ struct dentry *reiserfs_fh_to_dentry(struct super_block *sb, struct fid *fid,
struct dentry *reiserfs_fh_to_parent(struct super_block *sb, struct fid *fid,
int fh_len, int fh_type)
{
if (fh_type > fh_len)
fh_type = fh_len;
if (fh_type < 4)
return NULL;

3 changes: 3 additions & 0 deletions fs/xfs/xfs_export.c
Original file line number Diff line number Diff line change
@@ -189,6 +189,9 @@ xfs_fs_fh_to_parent(struct super_block *sb, struct fid *fid,
struct xfs_fid64 *fid64 = (struct xfs_fid64 *)fid;
struct inode *inode = NULL;

if (fh_len < xfs_fileid_length(fileid_type))
return NULL;

switch (fileid_type) {
case FILEID_INO32_GEN_PARENT:
inode = xfs_nfs_get_inode(sb, fid->i32.parent_ino,
6 changes: 4 additions & 2 deletions mm/shmem.c
Original file line number Diff line number Diff line change
@@ -2220,12 +2220,14 @@ static struct dentry *shmem_fh_to_dentry(struct super_block *sb,
{
struct inode *inode;
struct dentry *dentry = NULL;
u64 inum = fid->raw[2];
inum = (inum << 32) | fid->raw[1];
u64 inum;

if (fh_len < 3)
return NULL;

inum = fid->raw[2];
inum = (inum << 32) | fid->raw[1];

inode = ilookup5(sb, (unsigned long)(inum + fid->raw[0]),
shmem_match, fid->raw);
if (inode) {

0 comments on commit 35c2a7f

Please sign in to comment.