From a8f0ad5cc455ff18e5848a164778dfa66c039c40 Mon Sep 17 00:00:00 2001 From: Johannes Truschnigg <5927519+jtru@users.noreply.github.com> Date: Thu, 19 Feb 2026 18:11:49 +0100 Subject: [PATCH] mount: check for more requisite mountpoint conditions (#5718) * mount: check for more requisite mountpoint conditions In order to be able to mount a repository over a mountpoint target directory via FUSE, that target directory needs to be both writeable and executable for the UID performing the mount. Without this patch, `restic mount` only checks for the target pathname's existence, which can lead to a lot of data transfer and/or computation for large repos to be performed before eventually croaking with a fatal "fusermount: failed to chdir to mountpoint: Permission denied" (or similar) error. FUSE does allow for mounting over a target path that refers to a regular (writeable) file, but the result is not accessible via chdir(), so we prevent that as well, and accept only directory inodes as the intended target mountpoint path. * Don't use snake_case identifiers * Add changelog entry * tweak changelog summary --------- Co-authored-by: Michael Eischer --- changelog/unreleased/pull-5718 | 9 +++++++++ cmd/restic/cmd_mount.go | 19 +++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 changelog/unreleased/pull-5718 diff --git a/changelog/unreleased/pull-5718 b/changelog/unreleased/pull-5718 new file mode 100644 index 000000000..f349bb744 --- /dev/null +++ b/changelog/unreleased/pull-5718 @@ -0,0 +1,9 @@ +Enhancement: stricter early mountpoint validation in `mount` + +`restic mount` accepted parameters that would lead to a FUSE mount operation +failing after having done computationally intensive work to prepare the mount. +The `mountpoint` argument supplied must now refer to the name of a directory +that the current user can access and write to, otherwise `restic mount` will +exit with an error before interacting with the repository. + +https://github.com/restic/restic/pull/5718 diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 202109fa6..eb86cbada 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -11,6 +11,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" + "golang.org/x/sys/unix" "github.com/restic/restic/internal/data" "github.com/restic/restic/internal/debug" @@ -35,8 +36,8 @@ func newMountCommand(globalOptions *global.Options) *cobra.Command { Use: "mount [flags] mountpoint", Short: "Mount the repository", Long: ` -The "mount" command mounts the repository via fuse to a directory. This is a -read-only mount. +The "mount" command mounts the repository via fuse over a writeable directory. +The repository will be mounted read-only. Snapshot Directories ==================== @@ -133,9 +134,19 @@ func runMount(ctx context.Context, opts MountOptions, gopts global.Options, args // Check the existence of the mount point at the earliest stage to // prevent unnecessary computations while opening the repository. - if _, err := os.Stat(mountpoint); errors.Is(err, os.ErrNotExist) { + stat, err := os.Stat(mountpoint) + if errors.Is(err, os.ErrNotExist) { printer.P("Mountpoint %s doesn't exist", mountpoint) - return err + return errors.Fatal("invalid mountpoint") + } else if !stat.IsDir() { + printer.P("Mountpoint %s is not a directory", mountpoint) + return errors.Fatal("invalid mountpoint") + } + + err = unix.Access(mountpoint, unix.W_OK|unix.X_OK) + if err != nil { + printer.P("Mountpoint %s is not writeable or not excutable", mountpoint) + return errors.Fatal("inaccessible mountpoint") } debug.Log("start mount")