2005-04-17 02:20:36 +04:00
/*
* fs / isofs / export . c
*
* ( C ) 2004 Paul Serice - The new inode scheme requires switching
* from iget ( ) to iget5_locked ( ) which means
* the NFS export operations have to be hand
* coded because the default routines rely on
* iget ( ) .
*
* The following files are helpful :
*
2009-10-27 21:41:35 +03:00
* Documentation / filesystems / nfs / Exporting
2005-04-17 02:20:36 +04:00
* fs / exportfs / expfs . c .
*/
2005-04-26 05:32:12 +04:00
# include "isofs.h"
2005-04-17 02:20:36 +04:00
static struct dentry *
isofs_export_iget ( struct super_block * sb ,
unsigned long block ,
unsigned long offset ,
__u32 generation )
{
struct inode * inode ;
2008-08-11 17:49:04 +04:00
2005-04-17 02:20:36 +04:00
if ( block = = 0 )
return ERR_PTR ( - ESTALE ) ;
inode = isofs_iget ( sb , block , offset ) ;
2008-02-07 11:15:41 +03:00
if ( IS_ERR ( inode ) )
return ERR_CAST ( inode ) ;
if ( generation & & inode - > i_generation ! = generation ) {
2005-04-17 02:20:36 +04:00
iput ( inode ) ;
return ERR_PTR ( - ESTALE ) ;
}
2008-08-11 17:49:04 +04:00
return d_obtain_alias ( inode ) ;
2005-04-17 02:20:36 +04:00
}
/* This function is surprisingly simple. The trick is understanding
* that " child " is always a directory . So , to find its parent , you
* simply need to find its " .. " entry , normalize its block and offset ,
* and return the underlying inode . See the comments for
* isofs_normalize_block_and_offset ( ) . */
static struct dentry * isofs_export_get_parent ( struct dentry * child )
{
unsigned long parent_block = 0 ;
unsigned long parent_offset = 0 ;
2015-03-18 01:25:59 +03:00
struct inode * child_inode = d_inode ( child ) ;
2005-04-17 02:20:36 +04:00
struct iso_inode_info * e_child_inode = ISOFS_I ( child_inode ) ;
struct iso_directory_record * de = NULL ;
struct buffer_head * bh = NULL ;
struct dentry * rv = NULL ;
/* "child" must always be a directory. */
if ( ! S_ISDIR ( child_inode - > i_mode ) ) {
printk ( KERN_ERR " isofs: isofs_export_get_parent(): "
" child is not a directory! \n " ) ;
rv = ERR_PTR ( - EACCES ) ;
goto out ;
}
/* It is an invariant that the directory offset is zero. If
* it is not zero , it means the directory failed to be
* normalized for some reason . */
if ( e_child_inode - > i_iget5_offset ! = 0 ) {
printk ( KERN_ERR " isofs: isofs_export_get_parent(): "
" child directory not normalized! \n " ) ;
rv = ERR_PTR ( - EACCES ) ;
goto out ;
}
/* The child inode has been normalized such that its
* i_iget5_block value points to the " . " entry . Fortunately ,
* the " .. " entry is located in the same block . */
parent_block = e_child_inode - > i_iget5_block ;
/* Get the block in question. */
bh = sb_bread ( child_inode - > i_sb , parent_block ) ;
if ( bh = = NULL ) {
rv = ERR_PTR ( - EACCES ) ;
goto out ;
}
/* This is the "." entry. */
de = ( struct iso_directory_record * ) bh - > b_data ;
/* The ".." entry is always the second entry. */
parent_offset = ( unsigned long ) isonum_711 ( de - > length ) ;
de = ( struct iso_directory_record * ) ( bh - > b_data + parent_offset ) ;
/* Verify it is in fact the ".." entry. */
if ( ( isonum_711 ( de - > name_len ) ! = 1 ) | | ( de - > name [ 0 ] ! = 1 ) ) {
printk ( KERN_ERR " isofs: Unable to find the \" .. \" "
" directory for NFS. \n " ) ;
rv = ERR_PTR ( - EACCES ) ;
goto out ;
}
/* Normalize */
isofs_normalize_block_and_offset ( de , & parent_block , & parent_offset ) ;
2008-08-11 17:49:04 +04:00
rv = d_obtain_alias ( isofs_iget ( child_inode - > i_sb , parent_block ,
parent_offset ) ) ;
2005-04-17 02:20:36 +04:00
out :
2008-08-11 17:49:04 +04:00
if ( bh )
2005-04-17 02:20:36 +04:00
brelse ( bh ) ;
return rv ;
}
static int
2012-04-02 22:34:06 +04:00
isofs_export_encode_fh ( struct inode * inode ,
2005-04-17 02:20:36 +04:00
__u32 * fh32 ,
int * max_len ,
2012-04-02 22:34:06 +04:00
struct inode * parent )
2005-04-17 02:20:36 +04:00
{
struct iso_inode_info * ei = ISOFS_I ( inode ) ;
int len = * max_len ;
int type = 1 ;
__u16 * fh16 = ( __u16 * ) fh32 ;
/*
* WARNING : max_len is 5 for NFSv2 . Because of this
* limitation , we use the lower 16 bits of fh32 [ 1 ] to hold the
* offset of the inode and the upper 16 bits of fh32 [ 1 ] to
* hold the offset of the parent .
*/
2012-04-02 22:34:06 +04:00
if ( parent & & ( len < 5 ) ) {
2011-01-29 16:13:25 +03:00
* max_len = 5 ;
2013-02-17 10:48:11 +04:00
return FILEID_INVALID ;
2011-01-29 16:13:25 +03:00
} else if ( len < 3 ) {
* max_len = 3 ;
2013-02-17 10:48:11 +04:00
return FILEID_INVALID ;
2011-01-29 16:13:25 +03:00
}
2005-04-17 02:20:36 +04:00
len = 3 ;
fh32 [ 0 ] = ei - > i_iget5_block ;
fh16 [ 2 ] = ( __u16 ) ei - > i_iget5_offset ; /* fh16 [sic] */
2012-07-12 10:46:54 +04:00
fh16 [ 3 ] = 0 ; /* avoid leaking uninitialized data */
2005-04-17 02:20:36 +04:00
fh32 [ 2 ] = inode - > i_generation ;
2012-04-02 22:34:06 +04:00
if ( parent ) {
2005-04-17 02:20:36 +04:00
struct iso_inode_info * eparent ;
eparent = ISOFS_I ( parent ) ;
fh32 [ 3 ] = eparent - > i_iget5_block ;
fh16 [ 3 ] = ( __u16 ) eparent - > i_iget5_offset ; /* fh16 [sic] */
fh32 [ 4 ] = parent - > i_generation ;
len = 5 ;
type = 2 ;
}
* max_len = len ;
return type ;
}
2007-10-22 03:42:12 +04:00
struct isofs_fid {
u32 block ;
u16 offset ;
u16 parent_offset ;
u32 generation ;
u32 parent_block ;
u32 parent_generation ;
} ;
2005-04-17 02:20:36 +04:00
2007-10-22 03:42:12 +04:00
static struct dentry * isofs_fh_to_dentry ( struct super_block * sb ,
struct fid * fid , int fh_len , int fh_type )
2005-04-17 02:20:36 +04:00
{
2007-10-22 03:42:12 +04:00
struct isofs_fid * ifid = ( struct isofs_fid * ) fid ;
2005-04-17 02:20:36 +04:00
2007-10-22 03:42:12 +04:00
if ( fh_len < 3 | | fh_type > 2 )
2005-04-17 02:20:36 +04:00
return NULL ;
2007-10-22 03:42:12 +04:00
return isofs_export_iget ( sb , ifid - > block , ifid - > offset ,
ifid - > generation ) ;
2005-04-17 02:20:36 +04:00
}
2007-10-22 03:42:12 +04:00
static struct dentry * isofs_fh_to_parent ( struct super_block * sb ,
struct fid * fid , int fh_len , int fh_type )
{
struct isofs_fid * ifid = ( struct isofs_fid * ) fid ;
tmpfs,ceph,gfs2,isofs,reiserfs,xfs: fix fh_len checking
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 <levinsasha928@gmail.com>
Signed-off-by: Hugh Dickins <hughd@google.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Sage Weil <sage@inktank.com>
Cc: Steven Whitehouse <swhiteho@redhat.com>
Cc: Christoph Hellwig <hch@infradead.org>
Cc: stable@vger.kernel.org
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2012-10-08 07:32:51 +04:00
if ( fh_len < 2 | | fh_type ! = 2 )
2007-10-22 03:42:12 +04:00
return NULL ;
return isofs_export_iget ( sb ,
fh_len > 2 ? ifid - > parent_block : 0 ,
ifid - > parent_offset ,
fh_len > 4 ? ifid - > parent_generation : 0 ) ;
}
2005-04-17 02:20:36 +04:00
2007-10-22 03:42:17 +04:00
const struct export_operations isofs_export_ops = {
2005-04-17 02:20:36 +04:00
. encode_fh = isofs_export_encode_fh ,
2007-10-22 03:42:12 +04:00
. fh_to_dentry = isofs_fh_to_dentry ,
. fh_to_parent = isofs_fh_to_parent ,
2005-04-17 02:20:36 +04:00
. get_parent = isofs_export_get_parent ,
} ;