From 9d12c81fccf962d9d5a410658076c007f62d2451 Mon Sep 17 00:00:00 2001 From: IgnorantGuru Date: Sun, 16 Dec 2012 05:36:09 -0700 Subject: [PATCH] close potential race conditions file mount prevent race conditions also disallow remount of file due to potential race conditions duplicate realpath checks --- ChangeLog | 3 +- etc/udevil.conf | 2 + src/udevil.c | 295 +++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 270 insertions(+), 30 deletions(-) diff --git a/ChangeLog b/ChangeLog index bc85ab0..0df32ca 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ 0.3.5+ - + file mount prevent race conditions + disallow remount of file due to potential race conditions 0.3.5 2012-11-24: ftpfs limit attempts on none #14 handle mount point trailing space #12 diff --git a/etc/udevil.conf b/etc/udevil.conf index e7ee827..e508de2 100644 --- a/etc/udevil.conf +++ b/etc/udevil.conf @@ -117,6 +117,8 @@ allowed_media_dirs = /media, /run/media/$USER # Note: Wildcards may be used, but a wildcard will never match a /, except # for "allowed_devices=*" which allows any device. The recommended setting is # allowed_devices = /dev/* +# WARNING: ALLOWING USERS TO MOUNT DEVICES OUTSIDE OF /dev CAN CAUSE SERIOUS +# SECURITY PROBLEMS. DO NOT ALLOW DEVICES IN /dev/shm allowed_devices = /dev/* diff --git a/src/udevil.c b/src/udevil.c index 51762a6..f3fbda7 100644 --- a/src/udevil.c +++ b/src/udevil.c @@ -1409,6 +1409,16 @@ static gboolean get_realpath( char** path ) } } +static gboolean check_realpath( const char* path ) +{ // verify realpath hasn't changed + if ( !( path && path[0] != '\0' ) ) + return FALSE; + char* res = canonicalize_path( path ); + gboolean ret = !g_strcmp0( res, path ); + g_free( res ); + return ret; +} + /* int user_on_tty() { // ( source code taken from pmount - but this doesn't seem to find tty ) @@ -1443,6 +1453,148 @@ int user_on_tty() } */ +static void detach_loop( const char* loopdev ) +{ + char* stdout = NULL; + char* str; + int status = 0; + int exit_status = 1; + gchar *argv[4] = { NULL }; + + int a = 0; + argv[a++] = g_strdup( read_config( "losetup_program", NULL ) ); + if ( !argv[0] ) + return; + argv[a++] = g_strdup( "-d" ); + argv[a++] = g_strdup( loopdev ); + + // print + char* allarg = g_strjoinv( " ", argv ); + wlog( "ROOT: %s\n", allarg, 0 ); + g_free( allarg ); + + restore_privileges(); + if ( g_spawn_sync( NULL, argv, NULL, + 0, + NULL, NULL, &stdout, NULL, &status, NULL ) ) + { + if ( status && WIFEXITED( status ) ) + exit_status = WEXITSTATUS( status ); + else + exit_status = 0; + g_free( stdout ); + } + else + wlog( _("udevil: warning 9: unable to run losetup (%s)\n"), + read_config( "losetup_program", NULL ), 1 ); + drop_privileges( 0 ); +} + +static char* get_free_loop() +{ + char* stdout = NULL; + char* ret = NULL; + char* str; + int status = 0; + int exit_status = 1; + gchar *argv[4] = { NULL }; + + int a = 0; + argv[a++] = g_strdup( read_config( "losetup_program", NULL ) ); + if ( !argv[0] ) + return NULL; + argv[a++] = g_strdup( "-f" ); + restore_privileges(); + if ( g_spawn_sync( NULL, argv, NULL, + G_SPAWN_STDERR_TO_DEV_NULL, + NULL, NULL, &stdout, NULL, &status, NULL ) ) + { + drop_privileges( 0 ); + if ( status && WIFEXITED( status ) ) + exit_status = WEXITSTATUS( status ); + else + exit_status = 0; + + if ( !exit_status && stdout && g_str_has_prefix( stdout, "/dev/loop" ) ) + { + str = strchr( stdout, '\n' ); + if ( str ) + { + str[0] = '\0'; + ret = g_strdup( stdout ); + } + } + g_free( stdout ); + } + else + wlog( _("udevil: warning 9: unable to run losetup (%s)\n"), + read_config( "losetup_program", NULL ), 1 ); + drop_privileges( 0 ); + return ret; +} + +static char* attach_fd_to_loop( const char* device_file, int fd ) +{ + if ( fd == -1 ) + return NULL; + char* loopdev = get_free_loop(); + if ( !loopdev ) + { + wlog( _("udevil: error 147: unable to get free loop device\n"), NULL, 2 ); + return NULL; + } + // use /dev/fd to prevent race condition exploit + char* fdpath = g_strdup_printf( "/dev/fd/%d", fd ); + if ( !get_realpath( &fdpath ) || g_strcmp0( fdpath, device_file ) ) + { + g_free( loopdev ); + g_free( fdpath ); + wlog( _("udevil: error 150: path changed\n"), NULL, 2 ); + return NULL; + } + + char* stdout = NULL; + char* str; + int status = 0; + int exit_status = 1; + gchar *argv[4] = { NULL }; + + int a = 0; + argv[a++] = g_strdup( read_config( "losetup_program", NULL ) ); + if ( !argv[0] ) + return NULL; + argv[a++] = g_strdup( loopdev ); + argv[a++] = fdpath; + + // print + char* allarg = g_strjoinv( " ", argv ); + wlog( "ROOT: %s\n", allarg, 0 ); + g_free( allarg ); + + restore_privileges(); + if ( g_spawn_sync( NULL, argv, NULL, + 0, + NULL, NULL, &stdout, NULL, &status, NULL ) ) + { + if ( status && WIFEXITED( status ) ) + exit_status = WEXITSTATUS( status ); + else + exit_status = 0; + + if ( exit_status ) + { + g_free( loopdev ); + loopdev = NULL; + } + g_free( stdout ); + } + else + wlog( _("udevil: warning 9: unable to run losetup (%s)\n"), + read_config( "losetup_program", NULL ), 1 ); + drop_privileges( 0 ); + return loopdev; +} + static char* get_loop_from_file( const char* path ) { char* stdout = NULL; @@ -1926,7 +2078,7 @@ static int umount_path( const char* path, gboolean force, gboolean lazy ) // setup command int status = 0; int exit_status = 1; - gchar *argv[6] = { NULL }; + gchar *argv[7] = { NULL }; int a = 0; argv[a++] = g_strdup( read_config( "umount_program", NULL ) ); if ( !argv[0] ) @@ -1936,7 +2088,8 @@ static int umount_path( const char* path, gboolean force, gboolean lazy ) if ( force ) argv[a++] = g_strdup( "-f" ); if ( lazy ) - argv[a++] = g_strdup( "-l" ); + argv[a++] = g_strdup( "-l" ); + argv[a++] = g_strdup( "-d" ); argv[a++] = g_strdup( path ); // print @@ -1950,7 +2103,13 @@ static int umount_path( const char* path, gboolean force, gboolean lazy ) setregid( 0, -1 ); // run - if ( g_spawn_sync( NULL, argv, NULL, 0, NULL, NULL, NULL, NULL, &status, NULL ) ) + if ( !check_realpath( path ) && + g_file_test( path, G_FILE_TEST_EXISTS ) /* not net url */ ) + { + wlog( _("udevil: error 144: invalid path\n"), NULL, 2 ); + g_strfreev( argv ); + } + else if ( g_spawn_sync( NULL, argv, NULL, 0, NULL, NULL, NULL, NULL, &status, NULL ) ) { if ( status && WIFEXITED( status ) ) exit_status = WEXITSTATUS( status ); @@ -2015,8 +2174,14 @@ static int mount_device( const char* device_file, const char* fstype, setregid( 0, -1 ); } - // run - if ( g_spawn_sync( NULL, argv, NULL, 0, NULL, NULL, NULL, NULL, &status, NULL ) ) + // check existing device path and run mount + if ( !check_realpath( device_file ) && + g_file_test( device_file, G_FILE_TEST_EXISTS ) /* not net url */ ) + { + wlog( _("udevil: error 144: invalid path\n"), NULL, 2 ); + g_strfreev( argv ); + } + else if ( g_spawn_sync( NULL, argv, NULL, 0, NULL, NULL, NULL, NULL, &status, NULL ) ) { if ( status && WIFEXITED( status ) ) exit_status = WEXITSTATUS( status ); @@ -2044,6 +2209,27 @@ static int mount_device( const char* device_file, const char* fstype, return exit_status; } +static int mount_file( int fd, const char* device_file, const char* fstype, + const char* options, const char* point ) +{ + char* loopdev = attach_fd_to_loop( device_file, fd ); + if ( !loopdev || fd == -1 ) + { + g_free( loopdev ); + wlog( _("udevil: error 148: unable to attach file to loop device\n"), + NULL, 2 ); + return 1; + } + char* loopopts = g_strdup_printf( "%s%sro", options ? options : "", + options ? "," : "" ); + int exit_status = mount_device( loopdev, fstype, loopopts, point, TRUE ); + if ( exit_status ) + detach_loop( loopdev ); + g_free( loopdev ); + g_free( loopopts ); + return exit_status; +} + static gboolean mount_knows( const char* device_file ) { int status = 0; @@ -2510,6 +2696,8 @@ static int command_mount( CommandData* data ) MOUNT_MISSING }; struct stat64 statbuf; + struct stat64 statfd; + int fd = -1; char* str; char* str2; char* parent_dir; @@ -2651,7 +2839,8 @@ _get_type: g_free( str ); if ( orig_euid == 0 ) command_clean(); - return 0; + ret = 0; + goto _finish; } } else if ( data->cmd_type == CMD_MOUNT @@ -2702,7 +2891,7 @@ _get_type: g_free( str ); } } - return ret; + goto _finish; } } ret = 0; @@ -3203,22 +3392,47 @@ _get_type: { if ( !g_file_test( data->device_file, G_FILE_TEST_IS_DIR ) ) { - if ( !validate_in_list( "allowed_files", "file", data->device_file ) - || validate_in_list( "forbidden_files", "file", data->device_file ) ) - { - wlog( _("udevil: denied 82: '%s' is not an allowed file\n"), - data->device_file, 2 ); - ret = 2; - goto _finish; - } if ( g_strcmp0( data->device_file, "tmpfs" ) && - g_strcmp0( data->device_file, "ramfs" ) && - g_access( data->device_file, R_OK ) != 0 ) + g_strcmp0( data->device_file, "ramfs" ) ) { - wlog( _("udevil: denied 83: you don't have read permission for file '%s'\n"), - data->device_file, 2 ); - ret = 2; - goto _finish; + if ( !validate_in_list( "allowed_files", "file", data->device_file ) + || validate_in_list( "forbidden_files", "file", data->device_file ) ) + { + wlog( _("udevil: denied 82: '%s' is not an allowed file\n"), + data->device_file, 2 ); + ret = 2; + goto _finish; + } + if ( g_access( data->device_file, R_OK ) != 0 ) + { + wlog( _("udevil: denied 83: you don't have read permission for file '%s'\n"), + data->device_file, 2 ); + ret = 2; + goto _finish; + } + // test for race conditions + restore_privileges(); + fd = open( data->device_file, O_RDWR ); + drop_privileges( 0 ); + if ( fd == -1 ) + { + wlog( _("udevil: denied 145: cannot open '%s'\n"), + data->device_file, 2 ); + ret = 2; + goto _finish; + } + if ( stat64( data->device_file, &statbuf ) != 0 || + fstat64( fd, &statfd ) != 0 || + !S_ISREG( statbuf.st_mode ) || + !S_ISREG( statfd.st_mode ) || + statbuf.st_ino != statfd.st_ino || + statbuf.st_dev != statfd.st_dev || + !check_realpath( data->device_file ) ) + { + wlog( _("udevil: error 146: path changed\n"), NULL, 2 ); + ret = 1; + goto _finish; + } } } else if ( data->cmd_type == CMD_MOUNT && data->point ) @@ -3510,6 +3724,15 @@ _get_type: goto _finish; } + // check for file remount + if ( remount && type == MOUNT_FILE ) + { + wlog( _("udevil: denied 149: cannot use remount option with file\n"), + NULL, 2 ); + ret = 1; + goto _finish; + } + // replace fuse fstype if ( type == MOUNT_NET && ( !strcmp( fstype, "curlftpfs" ) || !strcmp( fstype, "sshfs" ) ) ) @@ -3606,7 +3829,7 @@ _get_type: wlog( _("udevil: denied 98: '%s' is already mounted (or specify mount point)\n"), data->device_file, 2 ); else - wlog( _("udevil: denied 99: can't mount '%s' (not in fstab?) (or specify mount point)\n"), + wlog( _("udevil: denied 99: can't mount '%s' (not in fstab?)\n"), data->device_file, 2 ); ret = 2; goto _finish; @@ -3869,10 +4092,15 @@ _get_type: else ret = mount_device( netmount->url, fstype, options, point, TRUE ); } + else if ( type == MOUNT_FILE && g_strcmp0( data->device_file, "tmpfs" ) && + g_strcmp0( data->device_file, "ramfs" ) ) + ret = mount_file( fd, data->device_file, + g_strcmp0( fstype, "file" ) ? fstype : NULL, + options, point ); else - ret = mount_device( data->device_file, - g_strcmp0( fstype, "file" ) ? fstype : NULL, - options, point, TRUE ); + ret = mount_device( data->device_file, + g_strcmp0( fstype, "file" ) ? fstype : NULL, + options, point, TRUE ); // result if ( ret ) @@ -3942,6 +4170,12 @@ _finish: device_free( device ); g_free( options ); g_free( point ); + if ( fd != -1 ) + { + restore_privileges(); + close( fd ); + drop_privileges( 0 ); + } return ret; } @@ -4538,9 +4772,6 @@ finish_: void command_interrupt() { - //if (signal == SIGINT || signal == SIGTERM) - //printf( "\nudevil: SIGINT || SIGTERM\n"); - if ( udev ) { udev_unref( udev ); @@ -4600,6 +4831,7 @@ static void show_help() printf( " %s\n", _("requires sshfs") ); printf( " udevil mount -t sshfs user@sys.domain # %s\n", _("sshfs with user") ); printf( " udevil mount tmpfs # %s\n", _("make a ram drive") ); + printf( _("\n WARNING !!! a password on the command line is UNSAFE - see filesystem docs\n\n") ); printf( _("UNMOUNT - Unmount DEVICE or DIR with UNMOUNT-OPTIONS:\n") ); printf( _(" udevil umount|unmount|--unmount|--umount [UNMOUNT-OPTIONS] \n") ); printf( _(" {[-b|--block-device] DEVICE}|DIR\n") ); @@ -4654,6 +4886,9 @@ int main( int argc, char **argv ) signal( SIGTERM, command_interrupt ); signal( SIGINT, command_interrupt ); + signal( SIGHUP, command_interrupt ); + signal( SIGSTOP, SIG_IGN ); + /* printf("\n-----------------------PRE-SANITIZE\n"); int i = 0; @@ -4772,7 +5007,9 @@ printf("\n-----------------------\n"); arg_next = NULL; if ( ( arg && !g_utf8_validate( arg, -1, NULL ) ) || - ( arg_next && !g_utf8_validate( arg_next, -1, NULL ) ) ) + ( arg_next && !g_utf8_validate( arg_next, -1, NULL ) ) || + ( arg && strchr( arg, '\n' ) ) || + ( arg_next && strchr( arg_next, '\n' ) ) ) { wlog( _("udevil: error 138: argument is not valid UTF-8\n"), NULL, 2 ); goto _exit;