AppImageCommunity / libappimage

Implements functionality for dealing with AppImage files
https://appimage.org
Other
46 stars 29 forks source link

Also use libfuse3, fusermount3 in addition to libfuse2, fusermount #168

Closed probonopd closed 2 years ago

probonopd commented 2 years ago

https://github.com/AppImage/libappimage/blob/1c2510ad2f385787e8fa5fdf88c0098f69095366/src/patches/squashfuse_dlopen.h#L24

Ubuntu 22.04 x64 (arriving next month -- note 22.04, not 20.04) does not ship libfuse.so.2 anymore but only libfuse.so.3 (upstream launchpad ticket; distribution policies may prevent them from shipping both by default indefinitely because fuse2 appears no longer actively maintaned by upstream). I think Fedora is doing the same. As a result, AppImages cannot run on Ubuntu 22.04 out of the box:

$ Downloads/Mu_Editor-x86_64.AppImage 
dlopen(): error loading libfuse.so.2

Hence, we should also try to dlopen() that one.

Like we are already doing for another library in the AppImageKit runtime:

https://github.com/AppImage/AppImageKit/blob/8bbf694455d00f48d835f56afaa1dabcd9178ba6/src/notify.c#L28-L45

Actually it may be a bit more involved than this, since the symbols inside libfuse may have changed in an incompatible way?

References:

@TheAssassin, @azubieta since I was never quite involved in the libappimage codebase, maybe one of you could have a look into this? I'd appreciate this very much, since having AppImages run out of the box on all supported mainstream distributions is quite important.

image

probonopd commented 2 years ago

@JamesHenstridge points out:

If they're using FUSE, then it is likely that they depend on the host system /usr/bin/fusermount setuid executable, even if they're statically linked.

The binary was renamed to fusermount3 in libfuse 3.x

probonopd commented 2 years ago

As an alternative, there is also the option of statically linking (and possibly implementing the required parts of fusermount or using Golang for the runtime like in this POC).

Yes, it would add some (~3 MB?) overhead to each AppImage. But the result would be much more robust and future-proof. For AppImages that bundle all dependency libraries (currently the exception rather than the rule; another ~10 MB overhead), the result would be almost immune to userland breaking changes, almost only relying on the kernel, which is fortunately very stable when it comes to the userland interface.

Maybe that is the way to go. Wdyt?

jhenstridge commented 2 years ago

As I said on Twitter, the fusermount binary is setuid. If you want to ship your own copy, you'll need root access to set the file permissions.

probonopd commented 2 years ago

Ouch. That's a bummer! Need to check whether https://github.com/orivej/static-appimage/ is also using fusermount.

probonopd commented 2 years ago

Need to check whether https://github.com/orivej/static-appimage/ is also using fusermount.

It is:

FreeBSD% ~/Desktop/Spotify.AppImage               
main.main (main.go:50): exec: "/bin/fusermount": stat /bin/fusermount: no such file or directory

https://github.com/hanwen/go-fuse/blob/74a933d6e856048cbff1a437bf3dbf79162e77de/fuse/api.go#L92-L97

// 1) go-fuse opens `/dev/fuse` and executes the `fusermount`
// setuid-root helper to call `mount(2)` for us. This is the default.
// Does not need root permissions but needs `fusermount` installed.
//
// 2) If `MountOptions.DirectMount` is set, go-fuse calls `mount(2)` itself.
// Needs root permissions, but works without `fusermount`.

So if we want AppImages to run without the need for the end user to have root rights (which we want), then it seems like whatever we do we will not get around using fusermount/fusermount3.

So it seems like statically linking libfuse2 or libfuse3 is not sufficient to ensure that things work on systems that come with a random libfuse version.

probonopd commented 2 years ago

Highly relevant discussion: https://github.com/libfuse/libfuse/issues/460

probonopd commented 2 years ago

Seems like go-fuse can work with both fusermount and fusermount3, which is a huge plus:

// fusermountBinary returns the path to the `fusermount3` binary, or, if not
// found, the `fusermount` binary.
func fusermountBinary() (string, error) {
    if path, err := lookPathFallback("fusermount3", "/bin"); err == nil {
        return path, nil
    }
    return lookPathFallback("fusermount", "/bin")
}

Now, there should be a guarantee that it will continue to work with fusermountN with N = 4...infinity.

Next step: Rebuild https://github.com/orivej/static-appimage/ with a recent https://github.com/hanwen/go-fuse/ version that includes the above, then test on systems with (only) libfuse2 and on systems with (only) libfuse3.

If it works, we could use this approach.

probonopd commented 2 years ago

Another question:

If we statically link the current runtime, maybe using musl libc, will it require fusermount? If yes, can it be changed so that it won't require fusermount anymore?

What does fusermount do, after all? Allow users without root permissions to unmount FUSE mounts? Well, we don't need that, because AppImages unmount themselves after the payload application exits.

@TheAssassin, @azubieta: What do you think about statically linking the AppImage runtime using musl libc?

azubieta commented 2 years ago

Random thought, can we make a composite elf that ships two runtimes one for fuse2 and other for fuse3 ?

The current AppRun has 25 Kb so this solution should be about 50 Kb.

probonopd commented 2 years ago

I've experimented a bit with static runtimes built in Alpine Linux with musl libc. Still need to sort some things out. But a promising start.

probonopd commented 2 years ago

Closing in favor of