#include #include #include #include #include #include #include #include #include #include #include #include #ifdef NOISY # define DEBUG(...) fprintf( stderr, __VA_ARGS__ ) #else # define DEBUG(...) do { } while ( 0 ) #endif uid_t parse_user( const char *user ) { char *end; unsigned long tmp; DEBUG( "insecuresuexec parse_user( %s )\n", user ); tmp = strtoul( user, &end, 10 ); if ( end != user && ! *end ) { DEBUG( " which is the uid %lu\n", tmp ); return tmp; } else { DEBUG( " which is a username\n" ); errno = 0; struct passwd *pw = getpwnam( user ); assert_perror( errno ); assert( pw ); DEBUG( " corresponding to the uid %u\n", pw->pw_uid ); return pw->pw_uid; }; } gid_t parse_group( const char *group ) { char *end; unsigned long tmp; DEBUG( "insecuresuexec parse_group( %s )\n", group ); tmp = strtoul( group, &end, 10 ); if ( end != group && ! *end ) { DEBUG( " which is the gid %lu\n", tmp ); return tmp; } else { DEBUG( " which is a groupname\n" ); errno = 0; struct group *gr = getgrnam( group ); assert_perror( errno ); assert( gr ); DEBUG( " corresponding to the gid %u\n", gr->gr_gid ); 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 ) { DEBUG( "insecuresuexec permission( %u-%u, %u-%u )\n", min_uid, max_uid, min_gid, max_gid ); } const bool allows( uid_t uid, gid_t gid ) { return uid >= min_uid && uid <= max_uid && gid >= min_gid && gid <= max_gid; } }; class override { std::regex constraint; public: uid_t uid; gid_t gid; override( uid_t _uid, gid_t _gid, const char *ex ) : constraint( ex, std::regex_constants::basic ) , uid( _uid ) , gid( _gid ) { DEBUG( "insecuresuexec override( %u, %u, %s )\n", _uid, _gid, ex ); } const bool match( const char *str ) { return regex_match( str, constraint, std::regex_constants::match_continuous ); } }; 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_permission_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 ); } override parse_override_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 = strcspn( line + user_len + 1, " " ); assert( group_len != line_len - user_len - 1 ); size_t ex_len = line_len - user_len - 1 - group_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; char ex[ex_len + 1]; strncpy( ex, line + user_len + 1 + group_len + 1, ex_len ); ex[ex_len] = 0; uid_t uid_part = parse_user( user ); gid_t gid_part = parse_group( group ); return override( uid_part, gid_part, ex ); } 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_permission_line( line ) ); }; }; if ( line ) free( line ); return ret; } std::vector< override > * read_overrides( const char *file ) { FILE *fh = fopen( file, "r" ); if ( not fh ) assert_perror( errno ); auto *ret = new std::vector< override >( ); 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_override_line( line ) ); }; }; if ( line ) free( line ); return ret; } int main( int argc, char *argv[] ) { DEBUG( "insecuresuexec is parsing the stored permissions...\n" ); auto allowed = read_permissions( "/etc/insecuresuexec/permissions" ); DEBUG( "insecuresuexec is parsing the stored overrides...\n" ); auto override = read_overrides( "/etc/insecuresuexec/overrides" ); if ( argc < 4 ) { fprintf( stderr, "Usage: %s user group cmd [args..]\n", argv[0] ); return 1; }; char *cwd = getcwd( nullptr, 0 ); if ( not cwd ) assert_perror( errno ); char *user = argv[1]; char *group = argv[2]; char *relcmd = argv[3]; char **args = argv + 3; std::string cmd = cwd; cmd += '/'; cmd += relcmd; DEBUG( "insecuresuexec user=%s group=%s cwd=%s cmd=%s\n", user, group, cwd, cmd.c_str() ); uid_t uid; gid_t gid; DEBUG( "insecuresuexec is checking the overrides...\n" ); bool did_override = false; for ( auto i = override->begin( ); i != override->end( ); ++i ) { if ( i->match( cmd.c_str() ) ) { DEBUG( " cmd matched, now uid=%u gid=%u\n", i->uid, i->gid ); uid = i->uid; gid = i->gid; did_override = true; break; }; }; if ( ! did_override ) { DEBUG( " no matching override found\n" ); DEBUG( "insecuresuexec is parsing the command-line user and group...\n" ); 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 ); }; DEBUG( "insecuresuexec is running the configured security checks...\n" ); // 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 ); }; DEBUG( "insecuresuexec is going to go ahead with the exec...\n" ); 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.c_str(), args ); assert_perror( errno ); }