2009-06-17 02:33:40 +04:00
/*
* Supplementary group IDs
*/
# include <linux/cred.h>
2011-05-23 22:51:41 +04:00
# include <linux/export.h>
2009-06-17 02:33:40 +04:00
# include <linux/slab.h>
# include <linux/security.h>
# include <linux/syscalls.h>
2014-12-06 03:01:11 +03:00
# include <linux/user_namespace.h>
2009-06-17 02:33:40 +04:00
# include <asm/uaccess.h>
struct group_info * groups_alloc ( int gidsetsize )
{
struct group_info * group_info ;
int nblocks ;
int i ;
nblocks = ( gidsetsize + NGROUPS_PER_BLOCK - 1 ) / NGROUPS_PER_BLOCK ;
/* Make sure we always allocate at least one indirect block pointer */
nblocks = nblocks ? : 1 ;
group_info = kmalloc ( sizeof ( * group_info ) + nblocks * sizeof ( gid_t * ) , GFP_USER ) ;
if ( ! group_info )
return NULL ;
group_info - > ngroups = gidsetsize ;
group_info - > nblocks = nblocks ;
atomic_set ( & group_info - > usage , 1 ) ;
if ( gidsetsize < = NGROUPS_SMALL )
group_info - > blocks [ 0 ] = group_info - > small_block ;
else {
for ( i = 0 ; i < nblocks ; i + + ) {
2011-11-15 03:56:38 +04:00
kgid_t * b ;
2009-06-17 02:33:40 +04:00
b = ( void * ) __get_free_page ( GFP_USER ) ;
if ( ! b )
goto out_undo_partial_alloc ;
group_info - > blocks [ i ] = b ;
}
}
return group_info ;
out_undo_partial_alloc :
while ( - - i > = 0 ) {
free_page ( ( unsigned long ) group_info - > blocks [ i ] ) ;
}
kfree ( group_info ) ;
return NULL ;
}
EXPORT_SYMBOL ( groups_alloc ) ;
void groups_free ( struct group_info * group_info )
{
if ( group_info - > blocks [ 0 ] ! = group_info - > small_block ) {
int i ;
for ( i = 0 ; i < group_info - > nblocks ; i + + )
free_page ( ( unsigned long ) group_info - > blocks [ i ] ) ;
}
kfree ( group_info ) ;
}
EXPORT_SYMBOL ( groups_free ) ;
/* export the group_info to a user-space array */
static int groups_to_user ( gid_t __user * grouplist ,
const struct group_info * group_info )
{
2011-11-15 03:56:38 +04:00
struct user_namespace * user_ns = current_user_ns ( ) ;
2009-06-17 02:33:40 +04:00
int i ;
unsigned int count = group_info - > ngroups ;
2011-11-15 03:56:38 +04:00
for ( i = 0 ; i < count ; i + + ) {
gid_t gid ;
gid = from_kgid_munged ( user_ns , GROUP_AT ( group_info , i ) ) ;
if ( put_user ( gid , grouplist + i ) )
2009-06-17 02:33:40 +04:00
return - EFAULT ;
}
return 0 ;
}
/* fill a group_info from a user-space array - it must be allocated already */
static int groups_from_user ( struct group_info * group_info ,
gid_t __user * grouplist )
{
2011-11-15 03:56:38 +04:00
struct user_namespace * user_ns = current_user_ns ( ) ;
2009-06-17 02:33:40 +04:00
int i ;
unsigned int count = group_info - > ngroups ;
2011-11-15 03:56:38 +04:00
for ( i = 0 ; i < count ; i + + ) {
gid_t gid ;
kgid_t kgid ;
if ( get_user ( gid , grouplist + i ) )
2009-06-17 02:33:40 +04:00
return - EFAULT ;
2011-11-15 03:56:38 +04:00
kgid = make_kgid ( user_ns , gid ) ;
if ( ! gid_valid ( kgid ) )
return - EINVAL ;
GROUP_AT ( group_info , i ) = kgid ;
2009-06-17 02:33:40 +04:00
}
return 0 ;
}
/* a simple Shell sort */
static void groups_sort ( struct group_info * group_info )
{
int base , max , stride ;
int gidsetsize = group_info - > ngroups ;
for ( stride = 1 ; stride < gidsetsize ; stride = 3 * stride + 1 )
; /* nothing */
stride / = 3 ;
while ( stride ) {
max = gidsetsize - stride ;
for ( base = 0 ; base < max ; base + + ) {
int left = base ;
int right = left + stride ;
2011-11-15 03:56:38 +04:00
kgid_t tmp = GROUP_AT ( group_info , right ) ;
2009-06-17 02:33:40 +04:00
2011-11-15 03:56:38 +04:00
while ( left > = 0 & & gid_gt ( GROUP_AT ( group_info , left ) , tmp ) ) {
2009-06-17 02:33:40 +04:00
GROUP_AT ( group_info , right ) =
GROUP_AT ( group_info , left ) ;
right = left ;
left - = stride ;
}
GROUP_AT ( group_info , right ) = tmp ;
}
stride / = 3 ;
}
}
/* a simple bsearch */
2011-11-15 03:56:38 +04:00
int groups_search ( const struct group_info * group_info , kgid_t grp )
2009-06-17 02:33:40 +04:00
{
unsigned int left , right ;
if ( ! group_info )
return 0 ;
left = 0 ;
right = group_info - > ngroups ;
while ( left < right ) {
unsigned int mid = ( left + right ) / 2 ;
2011-11-15 03:56:38 +04:00
if ( gid_gt ( grp , GROUP_AT ( group_info , mid ) ) )
2009-06-17 02:33:40 +04:00
left = mid + 1 ;
2011-11-15 03:56:38 +04:00
else if ( gid_lt ( grp , GROUP_AT ( group_info , mid ) ) )
2009-06-17 02:33:40 +04:00
right = mid ;
else
return 1 ;
}
return 0 ;
}
/**
* set_groups - Change a group subscription in a set of credentials
* @ new : The newly prepared set of credentials to alter
* @ group_info : The group list to install
*/
2014-04-04 01:48:26 +04:00
void set_groups ( struct cred * new , struct group_info * group_info )
2009-06-17 02:33:40 +04:00
{
put_group_info ( new - > group_info ) ;
groups_sort ( group_info ) ;
get_group_info ( group_info ) ;
new - > group_info = group_info ;
}
EXPORT_SYMBOL ( set_groups ) ;
/**
* set_current_groups - Change current ' s group subscription
* @ group_info : The group list to impose
*
* Validate a group subscription and , if valid , impose it upon current ' s task
* security record .
*/
int set_current_groups ( struct group_info * group_info )
{
struct cred * new ;
new = prepare_creds ( ) ;
if ( ! new )
return - ENOMEM ;
2014-04-04 01:48:26 +04:00
set_groups ( new , group_info ) ;
2009-06-17 02:33:40 +04:00
return commit_creds ( new ) ;
}
EXPORT_SYMBOL ( set_current_groups ) ;
SYSCALL_DEFINE2 ( getgroups , int , gidsetsize , gid_t __user * , grouplist )
{
const struct cred * cred = current_cred ( ) ;
int i ;
if ( gidsetsize < 0 )
return - EINVAL ;
/* no need to grab task_lock here; it cannot change */
i = cred - > group_info - > ngroups ;
if ( gidsetsize ) {
if ( i > gidsetsize ) {
i = - EINVAL ;
goto out ;
}
if ( groups_to_user ( grouplist , cred - > group_info ) ) {
i = - EFAULT ;
goto out ;
}
}
out :
return i ;
}
2014-12-06 02:19:27 +03:00
bool may_setgroups ( void )
{
struct user_namespace * user_ns = current_user_ns ( ) ;
2014-12-06 03:01:11 +03:00
return ns_capable ( user_ns , CAP_SETGID ) & &
userns_may_setgroups ( user_ns ) ;
2014-12-06 02:19:27 +03:00
}
2009-06-17 02:33:40 +04:00
/*
* SMP : Our groups are copy - on - write . We can set them safely
* without another task interfering .
*/
SYSCALL_DEFINE2 ( setgroups , int , gidsetsize , gid_t __user * , grouplist )
{
struct group_info * group_info ;
int retval ;
2014-12-06 02:19:27 +03:00
if ( ! may_setgroups ( ) )
2009-06-17 02:33:40 +04:00
return - EPERM ;
if ( ( unsigned ) gidsetsize > NGROUPS_MAX )
return - EINVAL ;
group_info = groups_alloc ( gidsetsize ) ;
if ( ! group_info )
return - ENOMEM ;
retval = groups_from_user ( group_info , grouplist ) ;
if ( retval ) {
put_group_info ( group_info ) ;
return retval ;
}
retval = set_current_groups ( group_info ) ;
put_group_info ( group_info ) ;
return retval ;
}
/*
* Check whether we ' re fsgid / egid or in the supplemental group . .
*/
2012-02-09 21:09:39 +04:00
int in_group_p ( kgid_t grp )
2009-06-17 02:33:40 +04:00
{
const struct cred * cred = current_cred ( ) ;
int retval = 1 ;
2012-02-09 21:09:39 +04:00
if ( ! gid_eq ( grp , cred - > fsgid ) )
retval = groups_search ( cred - > group_info , grp ) ;
2009-06-17 02:33:40 +04:00
return retval ;
}
EXPORT_SYMBOL ( in_group_p ) ;
2012-02-09 21:09:39 +04:00
int in_egroup_p ( kgid_t grp )
2009-06-17 02:33:40 +04:00
{
const struct cred * cred = current_cred ( ) ;
int retval = 1 ;
2012-02-09 21:09:39 +04:00
if ( ! gid_eq ( grp , cred - > egid ) )
retval = groups_search ( cred - > group_info , grp ) ;
2009-06-17 02:33:40 +04:00
return retval ;
}
EXPORT_SYMBOL ( in_egroup_p ) ;