Repository: borgbackup/borg

Issue:
fuse: pass default_options
llfuse's documentation recommends using `llfuse.default_options`.

I've tried this, and it appears to work.

In particular "default_permissions" might be useful for performance reasons.

I'd be happy to provide a PR for this, if you agree that it makes sense.


Discussion:
```
>>> import llfuse
>>> llfuse.default_options
frozenset({'default_permissions', 'no_splice_read', 'nonempty', 'splice_move', 'splice_write', 'big_writes'}) 
```

Not sure what they all mean, whether they make sense for us or since when default_options is available in llfuse.

So, double check that please and then do a PR. :)
`default_options` is there since release-0.42~61(1d8c8e0): https://github.com/python-llfuse/python-llfuse/commit/1d8c8e0b40a09496074f76e6d0da036422eec10b.  Do you want to bump the required version, or should be duplicate it for older versions?

See https://github.com/python-llfuse/python-llfuse/pull/12 about the documentation for its values.

`default_permissions` seems to be crucial: without it, I can access a mount with `-o uid=1001,umask=077` as user 1000!
Hmm, somehow embarassing that the default_options must be explicitly given and are not the default(*)...

Same for the default_permissions.

(*) in fact, they are the default value for the init argument. but that's not practically helpful as in many cases one needs to pass other options there, so the argument default is not used.
@blueyed can you evaluate whether this means a security or performance issue for:
- borg 1.1.x (x < 6) or borg 1.0.y - no uid, gid, umask options implemented there
- borg >= 1.1.6 (with new uid, gid, umask options)
related PRs: #3769 (#3843 is the fwd port to master, no backport to 1.0 yet)
@blueyed do not bump the required version, but use something like:

```
default_options = frozenset(...)  # copied from llfuse, to support pre-0.42 llfuse
default_options = getattr(llfuse, 'default_options', default_options)
...
```
@blueyed you said `In particular "default_permissions" might be useful for performance reasons.`.

Did you mean security reasons? If not, can you explain the performance impact?
By now I would say "security reasons", but initially I've read something along that otherwise the program would have to do checks, which might be slower then.
borg's fuse driver does not do any checks however, so it would be slower probably, but for good reasons.  The other options should improve performance though I guess.
considering the PRs have been merged into master and 1.1-maint, guess we can close this?
ehrm, looks like I misinterpreted my own comment https://github.com/borgbackup/borg/issues/3903#issuecomment-397272669 - "related" did not mean this issue has been fixed there, just related stuff...

reopening...

Resolving patch:
diff --git a/borg/archiver.py b/borg/archiver.py
--- a/borg/archiver.py
+++ b/borg/archiver.py
@@ -1613,12 +1613,15 @@ def build_parser(self, args=None, prog=None):
         memory usage can be up to ~8 MiB times this number. The default is the number
         of CPU cores.
 
-        For mount options, see the fuse(8) manual page. Additional mount options
-        supported by borg:
+        For FUSE configuration and mount options, see the mount.fuse(8) manual page.
 
+        Additional mount options supported by borg:
         - allow_damaged_files: by default damaged files (where missing chunks were
           replaced with runs of zeros by borg check --repair) are not readable and
           return EIO (I/O error). Set this option to read such files.
+        - ignore_permissions: for security reasons the "default_permissions" mount
+          option is internally enforced by borg. "ignore_permissions" can be given
+          to not enforce "default_permissions".
 
         When the daemonized process receives a signal or crashes, it does not unmount.
         Unmounting in these cases could cause an active rsync or similar process
diff --git a/borg/fuse.py b/borg/fuse.py
--- a/borg/fuse.py
+++ b/borg/fuse.py
@@ -84,7 +84,12 @@ def __init__(self, key, repository, manifest, archive, cached_repo):
 
     def mount(self, mountpoint, mount_options, foreground=False):
         """Mount filesystem on *mountpoint* with *mount_options*."""
-        options = ['fsname=borgfs', 'ro']
+        # default_permissions enables permission checking by the kernel. Without
+        # this, any umask (or uid/gid) would not have an effect and this could
+        # cause security issues if used with allow_other mount option.
+        # When not using allow_other or allow_root, access is limited to the
+        # mounting user anyway.
+        options = ['fsname=borgfs', 'ro', 'default_permissions']
         if mount_options:
             options.extend(mount_options.split(','))
         try:
@@ -92,6 +97,16 @@ def mount(self, mountpoint, mount_options, foreground=False):
             self.allow_damaged_files = True
         except ValueError:
             pass
+        try:
+            options.remove('ignore_permissions')
+            # if above does not raise ValueError (meaning: ignore_permissions is present),
+            # we remove default_permissions again.
+            # in case users have a use-case that requires NOT giving "default_permissions",
+            # this is enabled by the custom "ignore_permissions" mount option which just
+            # removes "default_permissions" again:
+            options.remove('default_permissions')
+        except ValueError:
+            pass
         llfuse.init(self, mountpoint, options)
         if not foreground:
             old_id, new_id = daemonize()


Test patch: