diff --git a/block/export/fuse.c b/block/export/fuse.c index d0b88e8f80..4068250241 100644 --- a/block/export/fuse.c +++ b/block/export/fuse.c @@ -46,6 +46,8 @@ typedef struct FuseExport { char *mountpoint; bool writable; bool growable; + /* Whether allow_other was used as a mount option or not */ + bool allow_other; } FuseExport; static GHashTable *exports; @@ -57,7 +59,7 @@ static void fuse_export_delete(BlockExport *exp); static void init_exports_table(void); static int setup_fuse_export(FuseExport *exp, const char *mountpoint, - Error **errp); + bool allow_other, Error **errp); static void read_from_fuse_export(void *opaque); static bool is_regular_file(const char *path, Error **errp); @@ -118,7 +120,22 @@ static int fuse_export_create(BlockExport *blk_exp, exp->writable = blk_exp_args->writable; exp->growable = args->growable; - ret = setup_fuse_export(exp, args->mountpoint, errp); + /* set default */ + if (!args->has_allow_other) { + args->allow_other = FUSE_EXPORT_ALLOW_OTHER_AUTO; + } + + if (args->allow_other == FUSE_EXPORT_ALLOW_OTHER_AUTO) { + /* Ignore errors on our first attempt */ + ret = setup_fuse_export(exp, args->mountpoint, true, NULL); + exp->allow_other = ret == 0; + if (ret < 0) { + ret = setup_fuse_export(exp, args->mountpoint, false, errp); + } + } else { + exp->allow_other = args->allow_other == FUSE_EXPORT_ALLOW_OTHER_ON; + ret = setup_fuse_export(exp, args->mountpoint, exp->allow_other, errp); + } if (ret < 0) { goto fail; } @@ -146,7 +163,7 @@ static void init_exports_table(void) * Create exp->fuse_session and mount it. */ static int setup_fuse_export(FuseExport *exp, const char *mountpoint, - Error **errp) + bool allow_other, Error **errp) { const char *fuse_argv[4]; char *mount_opts; @@ -157,8 +174,9 @@ static int setup_fuse_export(FuseExport *exp, const char *mountpoint, * max_read needs to match what fuse_init() sets. * max_write need not be supplied. */ - mount_opts = g_strdup_printf("max_read=%zu,default_permissions", - FUSE_MAX_BOUNCE_BYTES); + mount_opts = g_strdup_printf("max_read=%zu,default_permissions%s", + FUSE_MAX_BOUNCE_BYTES, + allow_other ? ",allow_other" : ""); fuse_argv[0] = ""; /* Dummy program name */ fuse_argv[1] = "-o"; diff --git a/qapi/block-export.json b/qapi/block-export.json index e819e70cac..0ed63442a8 100644 --- a/qapi/block-export.json +++ b/qapi/block-export.json @@ -120,6 +120,23 @@ '*logical-block-size': 'size', '*num-queues': 'uint16'} } +## +# @FuseExportAllowOther: +# +# Possible allow_other modes for FUSE exports. +# +# @off: Do not pass allow_other as a mount option. +# +# @on: Pass allow_other as a mount option. +# +# @auto: Try mounting with allow_other first, and if that fails, retry +# without allow_other. +# +# Since: 6.1 +## +{ 'enum': 'FuseExportAllowOther', + 'data': ['off', 'on', 'auto'] } + ## # @BlockExportOptionsFuse: # @@ -132,11 +149,25 @@ # @growable: Whether writes beyond the EOF should grow the block node # accordingly. (default: false) # +# @allow-other: If this is off, only qemu's user is allowed access to +# this export. That cannot be changed even with chmod or +# chown. +# Enabling this option will allow other users access to +# the export with the FUSE mount option "allow_other". +# Note that using allow_other as a non-root user requires +# user_allow_other to be enabled in the global fuse.conf +# configuration file. +# In auto mode (the default), the FUSE export driver will +# first attempt to mount the export with allow_other, and +# if that fails, try again without. +# (since 6.1; default: auto) +# # Since: 6.0 ## { 'struct': 'BlockExportOptionsFuse', 'data': { 'mountpoint': 'str', - '*growable': 'bool' }, + '*growable': 'bool', + '*allow-other': 'FuseExportAllowOther' }, 'if': 'defined(CONFIG_FUSE)' } ## diff --git a/tests/qemu-iotests/308 b/tests/qemu-iotests/308 index 11c28a75f2..d13a9a969c 100755 --- a/tests/qemu-iotests/308 +++ b/tests/qemu-iotests/308 @@ -58,6 +58,9 @@ _supported_os Linux # We need /dev/urandom # $4: Node to export (defaults to 'node-format') fuse_export_add() { + # The grep -v is a filter for errors when /etc/fuse.conf does not contain + # user_allow_other. (The error is benign, but it is printed by fusermount + # on the first mount attempt, so our export code cannot hide it.) _send_qemu_cmd $QEMU_HANDLE \ "{'execute': 'block-export-add', 'arguments': { @@ -67,7 +70,8 @@ fuse_export_add() $2 } }" \ "${3:-return}" \ - | _filter_imgfmt + | _filter_imgfmt \ + | grep -v 'option allow_other only allowed if' } # $1: Export ID diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc index cbbf6d7c7f..609d82de89 100644 --- a/tests/qemu-iotests/common.rc +++ b/tests/qemu-iotests/common.rc @@ -512,9 +512,13 @@ _make_test_img() # Usually, users would export formatted nodes. But we present fuse as a # protocol-level driver here, so we have to leave the format to the # client. + # Switch off allow-other, because in general we do not need it for + # iotests. The default allow-other=auto has the downside of printing a + # fusermount error on its first attempt if allow_other is not + # permissible, which we would need to filter. QSD_NEED_PID=y $QSD \ --blockdev file,node-name=export-node,filename=$img_name,discard=unmap \ - --export fuse,id=fuse-export,node-name=export-node,mountpoint="$export_mp",writable=on,growable=on \ + --export fuse,id=fuse-export,node-name=export-node,mountpoint="$export_mp",writable=on,growable=on,allow-other=off \ & pidfile="$QEMU_TEST_DIR/qemu-storage-daemon.pid"