2005-04-17 02:20:36 +04:00
/*
* dcookies . c
*
* Copyright 2002 John Levon < levon @ movementarian . org >
*
* Persistent cookie - path mappings . These are used by
* profilers to convert a per - task EIP value into something
* non - transitory that can be processed at a later date .
* This is done by locking the dentry / vfsmnt pair in the
* kernel until released by the tasks needing the persistent
* objects . The tag is simply an unsigned long that refers
* to the pair and can be looked up from userspace .
*/
# include <linux/syscalls.h>
2011-11-17 08:57:37 +04:00
# include <linux/export.h>
2005-04-17 02:20:36 +04:00
# include <linux/slab.h>
# include <linux/list.h>
# include <linux/mount.h>
2006-01-11 23:17:46 +03:00
# include <linux/capability.h>
2005-04-17 02:20:36 +04:00
# include <linux/dcache.h>
# include <linux/mm.h>
2007-07-30 02:36:13 +04:00
# include <linux/err.h>
2005-04-17 02:20:36 +04:00
# include <linux/errno.h>
# include <linux/dcookies.h>
2006-03-26 13:37:12 +04:00
# include <linux/mutex.h>
2008-02-15 06:38:36 +03:00
# include <linux/path.h>
2013-02-26 03:42:04 +04:00
# include <linux/compat.h>
2016-12-24 22:46:01 +03:00
# include <linux/uaccess.h>
2005-04-17 02:20:36 +04:00
/* The dcookies are allocated from a kmem_cache and
* hashed onto a small number of lists . None of the
* code here is particularly performance critical
*/
struct dcookie_struct {
2008-02-15 06:38:36 +03:00
struct path path ;
2005-04-17 02:20:36 +04:00
struct list_head hash_list ;
} ;
static LIST_HEAD ( dcookie_users ) ;
2006-03-26 13:37:12 +04:00
static DEFINE_MUTEX ( dcookie_mutex ) ;
2006-12-07 07:33:20 +03:00
static struct kmem_cache * dcookie_cache __read_mostly ;
2006-03-26 13:37:24 +04:00
static struct list_head * dcookie_hashtable __read_mostly ;
static size_t hash_size __read_mostly ;
2005-04-17 02:20:36 +04:00
static inline int is_live ( void )
{
return ! ( list_empty ( & dcookie_users ) ) ;
}
/* The dentry is locked, its address will do for the cookie */
static inline unsigned long dcookie_value ( struct dcookie_struct * dcs )
{
2008-02-15 06:38:36 +03:00
return ( unsigned long ) dcs - > path . dentry ;
2005-04-17 02:20:36 +04:00
}
static size_t dcookie_hash ( unsigned long dcookie )
{
return ( dcookie > > L1_CACHE_SHIFT ) & ( hash_size - 1 ) ;
}
static struct dcookie_struct * find_dcookie ( unsigned long dcookie )
{
struct dcookie_struct * found = NULL ;
struct dcookie_struct * dcs ;
struct list_head * pos ;
struct list_head * list ;
list = dcookie_hashtable + dcookie_hash ( dcookie ) ;
list_for_each ( pos , list ) {
dcs = list_entry ( pos , struct dcookie_struct , hash_list ) ;
if ( dcookie_value ( dcs ) = = dcookie ) {
found = dcs ;
break ;
}
}
return found ;
}
static void hash_dcookie ( struct dcookie_struct * dcs )
{
struct list_head * list = dcookie_hashtable + dcookie_hash ( dcookie_value ( dcs ) ) ;
list_add ( & dcs - > hash_list , list ) ;
}
2016-11-21 03:30:18 +03:00
static struct dcookie_struct * alloc_dcookie ( const struct path * path )
2005-04-17 02:20:36 +04:00
{
2008-02-15 06:38:36 +03:00
struct dcookie_struct * dcs = kmem_cache_alloc ( dcookie_cache ,
GFP_KERNEL ) ;
2008-12-01 11:33:43 +03:00
struct dentry * d ;
2005-04-17 02:20:36 +04:00
if ( ! dcs )
return NULL ;
2008-12-01 11:33:43 +03:00
d = path - > dentry ;
spin_lock ( & d - > d_lock ) ;
d - > d_flags | = DCACHE_COOKIE ;
spin_unlock ( & d - > d_lock ) ;
2008-02-15 06:38:36 +03:00
dcs - > path = * path ;
path_get ( path ) ;
2005-04-17 02:20:36 +04:00
hash_dcookie ( dcs ) ;
return dcs ;
}
/* This is the main kernel-side routine that retrieves the cookie
* value for a dentry / vfsmnt pair .
*/
2016-11-21 03:30:18 +03:00
int get_dcookie ( const struct path * path , unsigned long * cookie )
2005-04-17 02:20:36 +04:00
{
int err = 0 ;
struct dcookie_struct * dcs ;
2006-03-26 13:37:12 +04:00
mutex_lock ( & dcookie_mutex ) ;
2005-04-17 02:20:36 +04:00
if ( ! is_live ( ) ) {
err = - EINVAL ;
goto out ;
}
2008-12-01 11:33:43 +03:00
if ( path - > dentry - > d_flags & DCACHE_COOKIE ) {
dcs = find_dcookie ( ( unsigned long ) path - > dentry ) ;
} else {
2008-02-15 06:38:36 +03:00
dcs = alloc_dcookie ( path ) ;
2008-12-01 11:33:43 +03:00
if ( ! dcs ) {
err = - ENOMEM ;
goto out ;
}
2005-04-17 02:20:36 +04:00
}
* cookie = dcookie_value ( dcs ) ;
out :
2006-03-26 13:37:12 +04:00
mutex_unlock ( & dcookie_mutex ) ;
2005-04-17 02:20:36 +04:00
return err ;
}
/* And here is where the userspace process can look up the cookie value
* to retrieve the path .
*/
2018-03-17 16:53:38 +03:00
static int do_lookup_dcookie ( u64 cookie64 , char __user * buf , size_t len )
2005-04-17 02:20:36 +04:00
{
unsigned long cookie = ( unsigned long ) cookie64 ;
int err = - EINVAL ;
char * kbuf ;
char * path ;
size_t pathlen ;
struct dcookie_struct * dcs ;
/* we could leak path information to users
* without dir read permission without this
*/
if ( ! capable ( CAP_SYS_ADMIN ) )
return - EPERM ;
2006-03-26 13:37:12 +04:00
mutex_lock ( & dcookie_mutex ) ;
2005-04-17 02:20:36 +04:00
if ( ! is_live ( ) ) {
err = - EINVAL ;
goto out ;
}
if ( ! ( dcs = find_dcookie ( cookie ) ) )
goto out ;
err = - ENOMEM ;
kbuf = kmalloc ( PAGE_SIZE , GFP_KERNEL ) ;
if ( ! kbuf )
goto out ;
/* FIXME: (deleted) ? */
2008-02-15 06:38:44 +03:00
path = d_path ( & dcs - > path , kbuf , PAGE_SIZE ) ;
2005-04-17 02:20:36 +04:00
2011-05-31 14:35:41 +04:00
mutex_unlock ( & dcookie_mutex ) ;
2005-04-17 02:20:36 +04:00
if ( IS_ERR ( path ) ) {
err = PTR_ERR ( path ) ;
goto out_free ;
}
err = - ERANGE ;
pathlen = kbuf + PAGE_SIZE - path ;
if ( pathlen < = len ) {
err = pathlen ;
if ( copy_to_user ( buf , path , pathlen ) )
err = - EFAULT ;
}
out_free :
kfree ( kbuf ) ;
2011-05-31 14:35:41 +04:00
return err ;
2005-04-17 02:20:36 +04:00
out :
2006-03-26 13:37:12 +04:00
mutex_unlock ( & dcookie_mutex ) ;
2005-04-17 02:20:36 +04:00
return err ;
}
2018-03-17 16:53:38 +03:00
SYSCALL_DEFINE3 ( lookup_dcookie , u64 , cookie64 , char __user * , buf , size_t , len )
{
return do_lookup_dcookie ( cookie64 , buf , len ) ;
}
2013-02-26 03:42:04 +04:00
# ifdef CONFIG_COMPAT
2014-01-30 02:05:46 +04:00
COMPAT_SYSCALL_DEFINE4 ( lookup_dcookie , u32 , w0 , u32 , w1 , char __user * , buf , compat_size_t , len )
2013-02-26 03:42:04 +04:00
{
# ifdef __BIG_ENDIAN
2018-03-17 16:53:38 +03:00
return do_lookup_dcookie ( ( ( u64 ) w0 < < 32 ) | w1 , buf , len ) ;
2013-02-26 03:42:04 +04:00
# else
2018-03-17 16:53:38 +03:00
return do_lookup_dcookie ( ( ( u64 ) w1 < < 32 ) | w0 , buf , len ) ;
2013-02-26 03:42:04 +04:00
# endif
}
# endif
2005-04-17 02:20:36 +04:00
static int dcookie_init ( void )
{
struct list_head * d ;
unsigned int i , hash_bits ;
int err = - ENOMEM ;
dcookie_cache = kmem_cache_create ( " dcookie_cache " ,
sizeof ( struct dcookie_struct ) ,
2007-07-20 05:11:58 +04:00
0 , 0 , NULL ) ;
2005-04-17 02:20:36 +04:00
if ( ! dcookie_cache )
goto out ;
dcookie_hashtable = kmalloc ( PAGE_SIZE , GFP_KERNEL ) ;
if ( ! dcookie_hashtable )
goto out_kmem ;
err = 0 ;
/*
* Find the power - of - two list - heads that can fit into the allocation . .
* We don ' t guarantee that " sizeof(struct list_head) " is necessarily
* a power - of - two .
*/
hash_size = PAGE_SIZE / sizeof ( struct list_head ) ;
hash_bits = 0 ;
do {
hash_bits + + ;
} while ( ( hash_size > > hash_bits ) ! = 0 ) ;
hash_bits - - ;
/*
* Re - calculate the actual number of entries and the mask
* from the number of bits we can fit .
*/
hash_size = 1UL < < hash_bits ;
/* And initialize the newly allocated array */
d = dcookie_hashtable ;
i = hash_size ;
do {
INIT_LIST_HEAD ( d ) ;
d + + ;
i - - ;
} while ( i ) ;
out :
return err ;
out_kmem :
kmem_cache_destroy ( dcookie_cache ) ;
goto out ;
}
static void free_dcookie ( struct dcookie_struct * dcs )
{
2008-12-01 11:33:43 +03:00
struct dentry * d = dcs - > path . dentry ;
spin_lock ( & d - > d_lock ) ;
d - > d_flags & = ~ DCACHE_COOKIE ;
spin_unlock ( & d - > d_lock ) ;
2008-02-15 06:38:36 +03:00
path_put ( & dcs - > path ) ;
2005-04-17 02:20:36 +04:00
kmem_cache_free ( dcookie_cache , dcs ) ;
}
static void dcookie_exit ( void )
{
struct list_head * list ;
struct list_head * pos ;
struct list_head * pos2 ;
struct dcookie_struct * dcs ;
size_t i ;
for ( i = 0 ; i < hash_size ; + + i ) {
list = dcookie_hashtable + i ;
list_for_each_safe ( pos , pos2 , list ) {
dcs = list_entry ( pos , struct dcookie_struct , hash_list ) ;
list_del ( & dcs - > hash_list ) ;
free_dcookie ( dcs ) ;
}
}
kfree ( dcookie_hashtable ) ;
kmem_cache_destroy ( dcookie_cache ) ;
}
struct dcookie_user {
struct list_head next ;
} ;
struct dcookie_user * dcookie_register ( void )
{
struct dcookie_user * user ;
2006-03-26 13:37:12 +04:00
mutex_lock ( & dcookie_mutex ) ;
2005-04-17 02:20:36 +04:00
user = kmalloc ( sizeof ( struct dcookie_user ) , GFP_KERNEL ) ;
if ( ! user )
goto out ;
if ( ! is_live ( ) & & dcookie_init ( ) )
goto out_free ;
list_add ( & user - > next , & dcookie_users ) ;
out :
2006-03-26 13:37:12 +04:00
mutex_unlock ( & dcookie_mutex ) ;
2005-04-17 02:20:36 +04:00
return user ;
out_free :
kfree ( user ) ;
user = NULL ;
goto out ;
}
void dcookie_unregister ( struct dcookie_user * user )
{
2006-03-26 13:37:12 +04:00
mutex_lock ( & dcookie_mutex ) ;
2005-04-17 02:20:36 +04:00
list_del ( & user - > next ) ;
kfree ( user ) ;
if ( ! is_live ( ) )
dcookie_exit ( ) ;
2006-03-26 13:37:12 +04:00
mutex_unlock ( & dcookie_mutex ) ;
2005-04-17 02:20:36 +04:00
}
EXPORT_SYMBOL_GPL ( dcookie_register ) ;
EXPORT_SYMBOL_GPL ( dcookie_unregister ) ;
EXPORT_SYMBOL_GPL ( get_dcookie ) ;