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

Find fusermount using execvp #32

Closed
wants to merge 2 commits into from
Closed

Conversation

probonopd
Copy link
Member

@probonopd probonopd commented May 9, 2024

Closes #31

NOTE: The code in this PR is currently wrong. We should do what is described in #32 (comment) instead. Coding help is welcome.

UPDATE: Consider merging

instead.

@probonopd
Copy link
Member Author

Build for testing:
https://github.com/AppImage/type2-runtime/actions/runs/9014294337/artifacts/1487021420

Hi @Samueru-sama, can you confirm that this runtime fixes the issue at ivan-hc/Chrome-appimage#3?

Ping @TheAssassin, can you please do a review?

@Samueru-sama
Copy link

Samueru-sama commented Jun 27, 2024

image

It works! (htop2 is the one that I made manually by making the squashfsimage and then adding this runtime with cat).

However I don't think it is needed to reinstate the look in $PATH, what needs to be changed is instead looking for /bin/fusermount, /bin/fusermount3, etc because that location will always exist in all distros, just that some symlink it to /usr/bin

No distro will ever get rid of the /bin symlink, that would break millions of scripts.

@2011
Copy link

2011 commented Jun 28, 2024

However I don't think it is needed to reinstate the look in $PATH, what needs to be changed is instead looking for /bin/fusermount, /bin/fusermount3, etc because that location will always exist in all distros, just that some symlink it to /usr/bin

No distro will ever get rid of the /bin symlink, that would break millions of scripts.

Gentoo does not have /bin/fusermount or /bin/fusermount3. Those binariies exist only in /usr/bin. If I understand the problem (at least as it affects me), AppImages should test to see if they can execute /usr/bin/fusermount or /usr/bin/fusermount3, not test if they can read them.

@Samueru-sama
Copy link

However I don't think it is needed to reinstate the look in $PATH, what needs to be changed is instead looking for /bin/fusermount, /bin/fusermount3, etc because that location will always exist in all distros, just that some symlink it to /usr/bin
No distro will ever get rid of the /bin symlink, that would break millions of scripts.

Gentoo does not have /bin/fusermount or /bin/fusermount3. Those binariies exist only in /usr/bin. If I understand the problem (at least as it affects me), AppImages should test to see if they can execute /usr/bin/fusermount or /usr/bin/fusermount3, not test if they can read them.

You're right, when I said look I meant execute as I did the in the screenshot of alpine, sorry I didn't make that clear.

Do you still have the problem with the testing build? If you need the htop2 I made with it to test it let me know.

@probonopd
Copy link
Member Author

AppImages should test to see if they can execute /usr/bin/fusermount or /usr/bin/fusermount3, not test if they can read them.

If fusermount* is on the $PATH but cannot be executed, then I'd say it's a broken FUSE setup?

@Bqleine
Copy link

Bqleine commented Jul 3, 2024

This patch is odd, firstly it calls access() after already checking the access rights using fopen("r"), secondly the char fullPath[256] might work but there's no guarantee paths can't be longer than this. And lastly why are we not using execvp, which is guaranteed to search PATH correctly, and even more?

p - execlp(), execvp(), execvpe()
These functions duplicate the actions of the shell in searching for an executable file if
the specified filename does not contain a slash (/) character. The file is sought in the
colon-separated list of directory pathnames specified in the PATH environment variable.
If this variable isn't defined, the path list defaults to a list that includes the direc‐
tories returned by confstr(_CS_PATH) (which typically returns the value "/bin:/usr/bin")
and possibly also the current working directory; see NOTES for further details.

@probonopd
Copy link
Member Author

probonopd commented Jul 3, 2024

Thanks @Bqlein . fusermountXX is a setuid root program, so we must check whether it is executable by root, not by the current user.

So to my understanding we should:

  • Use execvp for executing programs, which handles setuid permissions and PATH searching correctly.

  • Avoid implementing custom checks like stat, findBinaryOnPath specifically for setuid execution verification if using execvp.

  • Ensure dynamic memory allocation or sufficient buffer sizes when constructing file paths to avoid issues with fixed path lengths. Especially we should not use char fullPath[256]

  • Focus on robust error handling around execvp calls to manage cases where execution fails due to permissions, missing binaries, or other errors.

Does this sound about right?

@Bqleine
Copy link

Bqleine commented Jul 3, 2024

Thanks @Bqlein . fusermountXX is a setuid root program, so we must check whether it is executable by root, not by the current user.

No, that is not how setuid works.

So to my understanding we should:

* Use `execvp` for executing programs, which handles setuid permissions and PATH searching correctly.

* Avoid implementing custom checks like `stat`, `findBinaryOnPath` specifically for setuid execution verification if using `execvp`.

* Ensure dynamic memory allocation or sufficient buffer sizes when constructing file paths to avoid issues with fixed path lengths. Especially we should not use `char fullPath[256]`

* Focus on robust error handling around `execvp` calls to manage cases where execution fails due to permissions, missing binaries, or other errors.

Does this sound about right?

Yes

@probonopd
Copy link
Member Author

@TheAssassin wdyt?

@TheAssassin
Copy link
Member

I think someone had some thoughts on the security of such an approach and advocated for hardcoding a list. I think you remember the discussion. I can't find it right now. Do you have a link?

+ if (fileExists(binaryPath)) {
+ return binaryPath;
+ }
+char* findBinaryOnPath(const char* binaryName) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we agreed on using execvp instead of manually tracing the PATH?

Copy link
Member Author

@probonopd probonopd Aug 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are spot on. We should use that instead. As I summarized in #32 (comment). If you agree with the points there, could you please help me implement them? That would really be a great contribution. Thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can provide the implementation, but it'll take a while until I'll get to it.

#32

If we choose to utilize execvp, those points need to be considered.

@probonopd
Copy link
Member Author

probonopd commented Aug 8, 2024

Please, lets not hardcode a list. Every time we do this there is at least one example where it doesn't work. Let's use execvp for executing programs, which (according to this discussion) handles setuid permissions and PATH searching correctly. Let's not worry about malicious binaries in the PATH, because if they are there I can imagine much, much easier attack vectors than anything related to AppImage. Really.

@TheAssassin
Copy link
Member

That does not really answer my question, though. Do you remember those concerns? I'd like to review them myself.

@probonopd
Copy link
Member Author

If I remember correctly, these were not concerns voiced by someone, but we deduced this from the fact that fuse compiles absolute paths in at compile time rather than searching at runtime.

@probonopd probonopd changed the title Find fusermount on $PATH Find fusermount using execvp Aug 8, 2024
@probonopd probonopd marked this pull request as draft August 8, 2024 00:28
@probonopd
Copy link
Member Author

Converted this PR to a draft because the code in this PR is currently wrong. We should do what is described in #32 (comment) instead. Coding help is welcome.

@probonopd
Copy link
Member Author

https://www.reddit.com/r/linux/comments/1d60t1r/comment/lh1qxf2/

on alpine fusermount is in /bin instead of /usr/bin, and currently the static runtime doesn't work as result.

Finding it dynamically using execvp should (hopefully) fix this.

@Samueru-sama
Copy link

Samueru-sama commented Aug 8, 2024

https://www.reddit.com/r/linux/comments/1d60t1r/comment/lh1qxf2/

on alpine fusermount is in /bin instead of /usr/bin, and currently the static runtime doesn't work as result.

Finding it dynamically using execvp should (hopefully) fix this.

Or you could just hardcode the path to fusermount to /bin instead of /usr/bin.

Because when fusermount is on /usr/bin then /bin would be a symlink to /usr/bin and everything will still work, like it does for the millions of scripts that have the #!/bin/sh shebang.

However because it is hardcoded to /usr/bin and that location is never symlinked to /bin it breaks as result in the very few distros that still keep the bin separation.

(I'm the person of the reddit comment btw).

@AppImage AppImage deleted a comment from github-actions bot Aug 8, 2024
@probonopd
Copy link
Member Author

probonopd commented Aug 8, 2024

Note to self: mount.c.zip (Editing diff files is not fun...)

@Samueru-sama
Copy link

Is it true or is it an urban myth that distrbutions symlink fusermount3 to fusermount? As far as I remember, libfuse3 is meant to be installable alongside libfuse2, and if that is true, wouldn't such a symlink create package conflicts?

Yes, you can actually see it in the fuse3 package in debian it contains a fusermount symlink to fusermount3.

Arch linux does not have it, but that is because archlinux has separate fuse2 and fuse3 packages that can be installed together.

In fact before there was this issue, that the appimages with the static runtime wouldn't work without the fusermount symlink to fusermount3

Also fun fact, on archlinux despite being a rolling release distro, fuse2 is still the "main" fuse used for ntfs-3g and mtpfs.

In any case, is there any guarantee that all distributions currently have and in the future will have such a symlink?
At least better than not even trying imho.

Okay, it is possible for the symlink to be missing on fuse4-only distros in the future, but that would be because they realized that it doesn't actually work and that it causes more problems with the applications that expect fusermount.

@probonopd
Copy link
Member Author

Arch linux does not have it, but that is because archlinux has separate fuse2 and fuse3 packages that can be installed together.

That's what I mean. We cannot rely on a symlink to be there. In fact, adding the symlink breaks being able to install multiple versions of libfuse alongside each other, something that libfuse is explicitly designed to do.

@Bqleine
Copy link

Bqleine commented Aug 10, 2024

Wouldn’t that mean a user in the future could just install fuse3 to run his appimage if he only has fuse4?

@mgord9518
Copy link

The issue is that AppImages are intended to just work. It's an uphill battle though, the Linux kernel is solid as a rock but userspace libraries don't give a rat's ass about binary compatibility

@probonopd
Copy link
Member Author

Wouldn’t that mean a user in the future could just install fuse3 to run his appimage if he only has fuse4?

Probably. But if distributions do what they did last time, they will drop fuse3 from the ISOs as soon as they have fuse4 packaged.

@probonopd probonopd removed the help wanted Extra attention is needed label Aug 11, 2024
@mgord9518
Copy link

Alright, I've been doing a little experimenting. I noticed that for some reason my libfuse-zig example project works on NixOS even with static linking. This initially only gave me more questions as my system doesn't even have /usr/local/bin, which is how I have it configured to build libfuse.

I took a peek at libfuse's code and found this. A fairly recent change that has it search the PATH if fusermount cannot be executed by the full pathname that's configured at compile time

@eli-schwartz
Copy link

@Samueru-sama,

I don't think such distro actually exists, if it did it would have bigger problems than just appimages not working. Also the writing is on the wall and every distro will eventually merge everything under /usr/bin (with the /bin symlink), Gentoo recently posted some updates about no longer supporting a separated bin not related to this issue.

Hi, I'm the author of that update. Gentoo is not going to support having /usr be un-mounted during early boot, and will no longer hack packages into a million pieces to replace their shared libraries with linker scripts that make it work by moving shared libraries to /lib instead (which wreaks havoc on the compiler as it cannot handle having the libfoo.a in one directory but libfoo.so in another directory).

The hacks that were tried ended up breaking userland (stuff like dlopen() of PAM, for example) and weren't worth the pain.

We continue to install /bin/bash and /bin/sh rather than /usr/bin/*sh. More generally we continue to install any binary that was historically in /bin on Gentoo, in the place that it was expected to be. This doesn't apply to every single binary, of course.

There are no plans at the current time for us to remove support for split /usr as long as you handle prompt mounting of the partition at early boot (e.g. by using an initramfs), so we are consequently committed to maintaining all existing infrastructure for ensuring that binaries which belong in /bin end up installed there.

I'm not sure off the top of my head what the history of fusermount on split-usr systems was, so I cannot answer the question of whether it is in /bin or /usr/bin on Gentoo, not can I answer the question of where it is "supposed to" be installed.

@TheAssassin
Copy link
Member

I took a peek at libfuse's code and found this. A fairly recent change that has it search the PATH if fusermount cannot be executed by the full pathname that's configured at compile time

So, I guess security wise that makes sense. I've been thinking about it in the past few days and I can't think of an attack vector that wouldn't require access much worse than installing a fake fusermount binary. I don't mind searching it on the $PATH either.

I'm a bit sad that unmounting requires privileged syscalls which in turn requires a setuid binary (or maybe PolKit). But then again, if FUSE even do it, it should be fine. Again, I cannot think of an attack vector. And I can't find that old discussion, so I can't judge how much sense it made.

@TheAssassin
Copy link
Member

TheAssassin commented Aug 16, 2024

P.S.: We probably don't even need to patch it in then, we just need to update libfuse. Have those changes been released yet?

Edit: nope. But I'm willing to include the upstream change as a patch in the repository until a new release is available.

Edit 2: one thing to consider: the libfuse patch expects the fusermount program to have a specific hardcoded name, but according to @probonopd it may or may not have a 3 version number suffix.

@eli-schwartz
Copy link

There is a common and obvious attack vector for a library that doesn't exist for an application. Libraries should be safe to link into a setuid binary.

Not a libfuse issue, actually a golang binding for FUSE issue, but... e.g. this is a thing that happened: https://keybase.io/docs/secadv/kb002

It's an attack scenario not because you could run a command in $PATH that you didn't expect, but because a setuid binary could run a command in $PATH when it thought it was running the one in /usr/bin or /bin. One solution for this is sanitizing $PATH to a known "safe default", such as /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin.

I don't think this matters to appimage anyway because appimage is not setuid, so PATH injection doesn't allow smuggling code past a permission boundary.

@eli-schwartz
Copy link

I'm not sure off the top of my head what the history of fusermount on split-usr systems was, so I cannot answer the question of whether it is in /bin or /usr/bin on Gentoo, not can I answer the question of where it is "supposed to" be installed.

The upstream libfuse codebase simply uses autoconf / meson --libdir to set the location of fusermount / fusermount3. On gentoo this installs to /usr/bin.

mount.fuse does get installed for fuse2 as MOUNT_FUSE_PATH=${EPREFIX}/sbin, which is a Gentoo Prefix compatible way to do what the builtin autotools makefiles did, namely, default MOUNT_FUSE_PATH to /sbin.

with fuse3, mount.fuse3 gets installed to the location of --sbindir rather than enforcing or even recommending /sbin. Gentoo doesn't override this.

So on Gentoo, you get:

  • /usr/bin/fusermount
  • /usr/bin/fusermount3
  • /sbin/mount.fuse
  • /usr/sbin/mount.fuse3

@mgord9518
Copy link

If the goal is maximum forwards-compatibility, maybe the runtime should have a baked-in way to fix itself in the (likely) case libfuse breaks compatibility again?

Let's say a user attempts to launch an AppImage and the mount fails due to "missing" fusermount, a popup then appears asking the user how they want to fix it. The options would be something like "Attempt to update runtime", "Extract and run" or "Quit"

For fixing the runtime, it would download a presumably maintained runtime version from GitHub or somewhere else stable then patch the AppImage. This would only ever happen if the AppImage wouldn't be able to start anyway, hopefully saving the user from a headache

@Samueru-sama
Copy link

@mgord9518 I was talking about that here but probono doesn't like the idea.

@TheAssassin
Copy link
Member

I'd rather avoid linking UI code into the runtime, this would introduce a lot of bloat. We already link to libnotify IIRC, we could show a notification instead. At least tell the user the AppImage couldn't launch.

Do we need fusermount just for unmounting or for mounting? I'd assume the latter.

@mgord9518
Copy link

@TheAssassin Just mounting. You can unmount a fuse FS using the umount command (unprivileged) on every distro I've tested (Arch, Pop, NixOS)

As far as I understand, using SIGINT on the fuse process also gracefully unmounts

@mgord9518
Copy link

Even without libnotify, I suppose a few commands like notify-send, zenity, kdialog could be tried

@mgord9518
Copy link

Been doing some digging, came across this very interesting article. Gonna mess around and see if I can get anywhere without fusermount

@mgord9518
Copy link

Sorry for the spam, but at least on first look this seems very promising:

┌┤ ~/Git/squashfuse-zig │ ok │
└─ nix-shell --pure -p util-linux

[nix-shell:~/Git/squashfuse-zig]$ unshare -pfr --user --mount --kill-child bash

[root@framework:~/Git/squashfuse-zig]# ./zig-out/bin/squashfuse test/tree_xz.sqfs mnt/

[root@framework:~/Git/squashfuse-zig]# ls mnt
1
2
block_device
...

[root@framework:~/Git/squashfuse-zig]# which fusermount fusermount3
which: no fusermount in (/home/mgord9518/.local/bin:/home...
which: no fusermount3 in (/home/mgord9518/.local/bin:/home...

[root@framework:~/Git/squashfuse-zig]# umount mnt

I know very little about namespaces, but assuming I'm not somehow incorrectly testing, this might be exactly what we need

@Samueru-sama
Copy link

@mgord9518 Is your idea related to this? I have bad news if that's the case.

@mgord9518
Copy link

@Samueru-sama Just about some distros disabling them or something else?

@Samueru-sama
Copy link

@Samueru-sama Just about some distros disabling them or something else?

Yes ubuntu and most forks have it disabled now. and that caused a bunch of bug reports on all electron based applications as result.

I think only solus and mint so far have disabled the ubuntu restriction.

@mgord9518
Copy link

@Samueru-sama Yeah, that does throw a wrench in this a bit. Even still, it's probably worth implementing even if most Ubuntu-based distros won't benefit by default

@Samueru-sama
Copy link

@Samueru-sama Yeah, that does throw a wrench in this a bit. Even still, it's probably worth implementing even if most Ubuntu-based distros won't benefit by default

I'm ok with the method, however if it is implemented I think there has to be something that would detect that the restriction is in place and ask the user with pkexec or similar to disable it.

@mgord9518
Copy link

mgord9518 commented Aug 19, 2024

I was more thinking having this as a fallback if fusermount can't be found/ executed. 100% future-proofing is essentially impossible, but shedding the need for an SUID binary on many distros should be a big help

@probonopd
Copy link
Member Author

probonopd commented Aug 19, 2024

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 @Mgord. 💯 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 👍 --> let's continue this topic at

@probonopd
Copy link
Member Author

Point in case why we cannot assume the binaries to be in certain directories - in Guix, they put fusermount somewhere else...

Hence we need to search on $PATH at runtime.

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

Successfully merging this pull request may close these issues.

Find fusermount on the $PATH
7 participants