#include #include #include #include #include #include #include #include #include #include #include uid_t parse_user( const char *user ) { char *end; unsigned long tmp; tmp = strtoul( user, &end, 10 ); if ( end != user && ! *end ) { return tmp; } else { struct passwd *pw = getpwnam( user ); assert( pw ); return pw->pw_uid; }; } gid_t parse_group( const char *group ) { char *end; unsigned long tmp; tmp = strtoul( group, &end, 10 ); if ( end != group && ! *end ) { return tmp; } else { struct group *gr = getgrnam( group ); assert( gr ); return gr->gr_gid; }; } class permission { uid_t min_uid, max_uid; gid_t min_gid, max_gid; public: permission( std::pair< uid_t, uid_t > uids, std::pair< gid_t, gid_t > gids ) : min_uid( uids.first ) , max_uid( uids.second ) , min_gid( gids.first ) , max_gid( gids.second ) { } const bool allows( uid_t uid, gid_t gid ) { return uid >= min_uid && uid <= max_uid && gid >= min_gid && gid <= max_gid; } }; template< typename thing > std::pair< thing, thing > parse_pair( const char *line, thing (*parse_one)( const char *part ) ) { std::pair< thing, thing > ret( 0, -1 ); size_t part_len = strlen( line ); assert( part_len > 0 ); char part[part_len + 1]; strncpy( part, line, part_len ); part[part_len] = 0; size_t comma = strcspn( line, "," ); if ( comma == part_len ) { thing it = parse_one( part ); ret.first = it; ret.second = it; } else { size_t first_len = comma; size_t second_len = part_len - comma - 1; char first[first_len + 1]; strncpy( first, part, first_len ); first[first_len] = 0; char second[second_len + 1]; strncpy( second, part + comma + 1, second_len ); second[second_len] = 0; if ( first_len ) ret.first = parse_one( first ); if ( second_len ) ret.second = parse_one( second ); }; return ret; } permission parse_line( const char *line ) { size_t line_len = strcspn( line, "\n" ); assert( line_len > 0 ); size_t user_len = strcspn( line, ":" ); assert( user_len != line_len ); size_t group_len = line_len - user_len - 1; char user[user_len + 1]; strncpy( user, line, user_len ); user[user_len] = 0; char group[group_len + 1]; strncpy( group, line + user_len + 1, group_len ); group[group_len] = 0; auto uid_part = parse_pair< uid_t >( user, parse_user ); auto gid_part = parse_pair< gid_t >( group, parse_group ); return permission( uid_part, gid_part ); } std::vector< permission > * read_permissions( const char *file ) { FILE *fh = fopen( file, "r" ); if ( not fh ) assert_perror( errno ); auto *ret = new std::vector< permission >( ); char *line = 0; size_t size = 0; for ( ; getline( &line, &size, fh ) != -1; ) { size_t line_len = strcspn( line, "\n" ); if ( line_len ) { ret->push_back( parse_line( line ) ); }; }; if ( line ) free( line ); return ret; } int main( int argc, char *argv[] ) { if ( argc < 4 ) { fprintf( stderr, "Usage: %s user group cmd [args..]\n", argv[0] ); return 1; }; char *user = argv[1]; char *group = argv[2]; char *cmd = argv[3]; char **args = argv + 3; uid_t uid; gid_t gid; uid = parse_user( user ); gid = parse_group( group ); // literally the only hard-coded security check if ( not uid || not gid ) { fprintf( stderr, "Neither uid nor gid may be zero!\n" ); _exit( 1 ); }; auto allowed = read_permissions( "/etc/insecuresuexec/permissions" ); // the configurable security checks bool ok = false; for ( auto i = allowed->begin( ); i != allowed->end( ); ++i ) { ok |= i->allows( uid, gid ); if ( ok ) break; }; if ( not ok ) { fprintf( stderr, "The uid/gid pair %u/%u is not allowed!\n", uid, gid ); _exit( 1 ); }; if ( setgroups( 0, NULL ) != 0 ) assert_perror( errno ); if ( setregid( gid, gid ) != 0 ) assert_perror( errno ); if ( setreuid( uid, uid ) != 0 ) assert_perror( errno ); execv( cmd, args ); assert_perror( errno ); }