Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mount a FUSE filesystem without use of root or fusermount (suid) #71

Open
probonopd opened this issue Aug 19, 2024 · 26 comments
Open

Mount a FUSE filesystem without use of root or fusermount (suid) #71

probonopd opened this issue Aug 19, 2024 · 26 comments

Comments

@probonopd
Copy link
Member

probonopd commented Aug 19, 2024

As @mgord9518 pointed out:

#32 (comment)

It’s possible to mount a FUSE filesystem without use of root permissions or SUID binaries by doing the mount inside of a user namespace.

VERY interesting @mgord9518. 💯 I think you are up to something. That suid helper binary always bothered be to begin with.

Does anyone know how to actually implement this, in code? Any help appreciated 👍

@mgord9518
Copy link

For a trivial example, you can just use unshare --mount --user -r, which will create a fake root environment which FUSE can be mounted from. An issue I see is that it's almost like a sandbox, so the behavior won't just be 1:1 with using fusermount.

Maybe someone should experiment with launching AppImages from this shell environment to see how differently the applications behave before implementing anything in C

@TheAssassin
Copy link
Member

user namespace

For security reasons, user namespaces have been restricted resp. locked down more again on many OSes. I could imagine that this would cause issues with rootless FUSE. Plus, they've never been enabled on a lot of popular distros.

@probonopd
Copy link
Member Author

Isn't Bubblewrap using this, too? Which would mean that at least all distributions that support Flatpak should have this enabled by default?

@mgord9518
Copy link

Isn't Bubblewrap using this, too? Which would mean that at least all distributions that support Flatpak should have this enabled by default?

As far as I know. I believe it also has an SUID version but normal bubblewrap uses unprivileged namespaces. Even with the distros that disable them (like Ubuntu), many distros still support them out of the box. Nix, Arch and LM right off the top of my head, so assuming we can actually get it to feel like there's no difference it's probably worth it

I've started on making a new AppImage runtime in Zig and I'll try to implement mounting with namespaces

@Samueru-sama
Copy link

For a trivial example, you can just use unshare --mount --user -r, which will create a fake root environment which FUSE can be mounted from. An issue I see is that it's almost like a sandbox, so the behavior won't just be 1:1 with using fusermount.

Maybe someone should experiment with launching AppImages from this shell environment to see how differently the applications behave before implementing anything in C

I tested this with the nvtop appimage, it has an odd issue that I can no longer see the active processes, it also breaks the cpu-x daemon.

@dleggo
Copy link

dleggo commented Jan 3, 2025

Any news on this?

@Samueru-sama
Copy link

Any news on this?

I don't think this will be implemented, the ideal solution for now is to set APPIMAGE_EXTRACT_AND_RUN when fuse isn't available.

see this for example

Would be great if the runtime actually did it without having to set the env variable though.

@TheAssassin
Copy link
Member

Would be great if the runtime actually did it without having to set the env variable though.

The reason it doesn't is that it's a very inefficient method and we would rather have people set up FUSE. There is a significant share of people running into this who can actually fix FUSE. The few who don't most likely know about the flag.

What I am open to is to check if FUSE does work, then check whether we're in a Docker environment (or other environments which cannot really be fixed) and then print a warning and extract the files. I was objected to that in the past, but given the fact that enabling FUSE in namespaces is a bad idea security wise, it'd be a small quality-of-life improvement to do auto extract.

For the record, the extracting could be optimized a lot to, e.g., cache more reliably.

@Samueru-sama
Copy link

Yeah I'm fine with it only being done automatically when we are on some container that doesn't allow SUID binaries.

The uruntime actually cleans up the extracted files when the application stops being used when APPIMAGE_EXTRACT_AND_RUN is used while the type2-runtime leaves them on /tmp.

@TheAssassin
Copy link
Member

The uruntime actually cleans up the extracted files when the application stops being used when APPIMAGE_EXTRACT_AND_RUN is used while the type2-runtime leaves them on /tmp.

I think I initially implemented a $NO_CLEANUP and had it always clean up, but I think that was changed at some point to clean up only when asked to because you might end up running two instances at the same time (they both extract/cache in the same path) and then one instance might end up deleting the other's files. There are ways around this, e.g., spinning forking off a small daemon that tracks the instances.

@dleggo
Copy link

dleggo commented Jan 3, 2025

For cases where FUSE isn't wanted it would be nice to have a user-namespaces option. Personally, to run AppImages, I extract them even though I have FUSE installed just because it is much faster, (1.5x - 2x) but user namespaces can utilize the kernel mounts which are much faster. Additionally, for some users, FUSE is a big barier to entry, and it would be nice to eliminate that for people who have user namespaces enabled in their kernel.

@Samueru-sama
Copy link

Personally, to run AppImages, I extract them even though I have FUSE installed just because it is much faster, (1.5x - 2x)

In what AppImages do you see this? With the type2-runtime and zstd compression the overhead I see is usually less than 1 second for most applications. And when it is more than 1 second it is usually apps that have high launch times anyway without being appimage.

There is room for improvement, I personally use the uruntime because it supports dwarfs, which reduces the launch time even more and even with squashfs the uruntime is still slightly faster.

@dleggo
Copy link

dleggo commented Jan 4, 2025

In what AppImages do you see this?

Time in seconds for applications to launch. (Not perfectly accurate, but I tried to get it as accurate as possible.)
Brave AppImage launch: 3.210
Brave Extracted: 1.960
OnlyOffice AppImage: 3.213
OnlyOffice Extracted: 1.943
VSCode AppImage: 6.754
VSCode Extracted: 6.004
Inkscape AppImage: 3.094
Inkscape Extracted: 1.994

And when it is more than 1 second it is usually apps that have high launch times anyway without being appimage.

This kinda sounds multiplicative to me. :)

I would love to see more work in the area of user namespaces as they have the opportunity to speed up many things that are currently using FUSE! Performance is actually a relatively common complaint against AppImage IIRC, and this would help migate that.

@TheAssassin
Copy link
Member

Have you used the flag or pre-extracted the AppImages?

@dleggo
Copy link

dleggo commented Jan 4, 2025

Pre-extracted

@Samueru-sama
Copy link

Samueru-sama commented Jan 4, 2025

Extracted: 1.960
OnlyOffice AppImage: 3.213
OnlyOffice Extracted: 1.943
VSCode AppImage: 6.754

Brave actually uses the static runtime, I made the PR adding that a while ago lol.

image

Welp, it was more than 1 second 🤣 It would not be if the uruntime was being used since it doesn't have the 25% degradation from the static musl.

Before the dev switched to linking glibc statically, the uruntime was actually slower than the type2-runtime, if the type2-runtime has the same issue the fix would bring the time down to ~1.5 seconds in this example further reducing the overhead.


Inkscape is an interesting case, they actually use go-appimage to deploy the appimage and bundle all the libraries, meaning this is one of the few appimages that would work on musl systems if it wasn't for the fact that they are still using the old dynamic runtime.

The developer is still using the old appimagetool from appimagekit because of issues with go-appimage appstream verification, this can actually be fixed by switching to the updated appimagetool and iirc I already mentioned that but was ignored it seems lol

Their AppImage uses gzip compression right now which is slower:

image

Takes ~2.5 seconds while zstd level 15 takes ~1.9 seconds.

And for reference the extracted AppImage takes 1.2 seconds.


Anyways don't get me wrong, if namespaces are much faster then that change is more than welcomed, but I need to see/do some tests, is it enough to do the unshare trick that was suggested here before to test or that doesn't count?

@Samueru-sama
Copy link

Just tested with unshare anyway. Made two wrapper scripts since I have to pass the --no-sandbox in this case because otherwise Brave won't open and the script that I use to benchmark only takes the window class as an argument:

image

👀 well it is actually ~20% faster indeed.

@dleggo
Copy link

dleggo commented Jan 4, 2025

I am not actually certain how big the preformance gap is between FUSE and kernel mounting. ( Could be of reference apptainer/apptainer#665 ) Although it doesn't appear very significant.

Those tests you did have quite interesting results though. That is still with FUSE but just inside a user namespace? I actually don't know how that made it faster 😆 . I was more advocating for the usage of kernel mounting inside the namespace, but know I see I misread probonopd's post and the title! Whoops! 😆 It is quite fascinating that FUSE inside a user namespace is faster though... Sorry for my probably irrelevent posts.

@Samueru-sama
Copy link

I also don't know how it is faster, would be nice if anyone could try to replicate what I did though.

@dleggo
Copy link

dleggo commented Jan 4, 2025

The method I got my rough numbers was to run
time ./Brave-*.AppImage and Alt+F4 it the moment content in the window appeared, so it was much less scientific then the tests you did. 😆 Would you mind posting the contents of your benchmark-startup.sh script and I could test of I can replicate the results, if you want?

@Samueru-sama
Copy link

Samueru-sama commented Jan 4, 2025

The method I got my rough numbers was to run time ./Brave-*.AppImage and Alt+F4 it the moment content in the window appeared, so it was much less scientific then the tests you did. 😆 Would you mind posting the contents of your benchmark-startup.sh script and I could test of I can replicate the results, if you want?

#!/bin/bash

# This script is used to determines the startup time of applications
# It only works if your WM is configured to focus on new windows automatically

# Start the application
"$1" >/dev/null 2>&1 &

# START TIME (nanoseconds)
START=$(date +%s%N)

# Loop until the window is found by xdotool
while true; do
    window_class="$(xdotool getactivewindow getwindowname 2>/dev/null)"
    if echo "$window_class" | grep -qi "$2"; then
        break
    fi
    sleep 0.001
done

# END TIME
END=$(date +%s%N)
TIME=$((($END - $START) / 1000000))
echo "Time taken: $TIME miliseconds"

All it does is launch the app and count until the window name matches $2 which I just use brave in this case.

This script will only work if you are on X11 and you have your window manager configured to focus on new windows automatically.

@dleggo
Copy link

dleggo commented Jan 4, 2025

Thanks for the script! Unfortunately I use Wayland... I did 10 tests with my previous method inside and outside user namespaces with Inkscape, and there was no difference. (2.5775s outside namespace, 2.5636s inside namespace) It definitely isn't slower though. I can log into an X11 session if needed though?

@Samueru-sama
Copy link

image

Seems there is no difference with inkscape, but I did have two instances where the time was slightly lower when using unshare 🤔

There is a diference with Brave though:

image

@dleggo
Copy link

dleggo commented Jan 4, 2025

Interesting... If it doesn't appear to have any performance downsides then perhaps this could be useful for the sole purpose of not having to have a SUID helper binary?

I tested this with the nvtop appimage, it has an odd issue that I can no longer see the active processes, it also breaks the cpu-x daemon.

After some testing, i.e.

$ unshare -Urm
# ps aux
# top

I can still see all outside processes... Are you accidentally unsharing the PID namespace @Samueru-sama ?

@Samueru-sama
Copy link

Samueru-sama commented Jan 4, 2025

Interesting... If it doesn't appear to have any performance downsides then perhaps this could be useful for the sole purpose of not having to have a SUID helper binary?

I tested this with the nvtop appimage, it has an odd issue that I can no longer see the active processes, it also breaks the cpu-x daemon.

After some testing, i.e.

$ unshare -Urm
# ps aux
# top

I can still see all outside processes... Are you accidentally unsharing the PID namespace @Samueru-sama ?

I originally just used the unshare --mount --user -r ./nvtop.AppImage and that results in not being able to see processes.

I tried unshare -Urm and then nvtop and I get the same issue:

With unshare

image

Without unshare

image

@dleggo
Copy link

dleggo commented Jan 4, 2025

I tried it myself and it had the same behavior, quite peculiar... I tested top htop ps aux and they show the correct results. I tested both the AppImage and non-AppImage and they both failed to function, so maybe it is a bug with nvtop then? Maybe it reads the process list in a different way? I tried strace nvtop inside the namespace and came across some permission issues, but after trying sudo unshare -Um nothing changed, so my guess is that this is some strange nvtop behaviour.

I originally just used the unshare --mount --user -r ./nvtop.AppImage and that results in not being able to see processes.

I tried unshare -Urm and then nvtop and I get the same issue:

unshare --mount --user -r and unshare -Urm are actually the same command, I am just used to using the abbreviated version. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants