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

Fish Running as Root Fails to Suggest All Executable Commands in PATH #9699

Closed
timkite opened this issue Mar 31, 2023 · 7 comments · Fixed by #9931
Closed

Fish Running as Root Fails to Suggest All Executable Commands in PATH #9699

timkite opened this issue Mar 31, 2023 · 7 comments · Fixed by #9931
Labels
bug Something that's not working as intended
Milestone

Comments

@timkite
Copy link

timkite commented Mar 31, 2023

In fish 3.6.1 as well as 3.3.1 at least, when running as root, commands (scripts or binaries) that are in $PATH, not owned by user root and/or group root, and are not world-executable will not be offered as completions, even though they are executable by root. This appears to affect Enterprise Linux (RHEL, CentOS, and Oracle Linux) 7 and 8, but does not affect macOS (any version). This happens whether ssh-ing to the Linux system via Apple Terminal or connecting directly to the machine's console. Running without third-party customizations does not affect the behavior.

How to reproduce:

  1. Ensure your user account on the Linux system can sudo to root and that /usr/local/bin is in your $PATH
  2. Create /usr/local/bin/testprog, using sudo if needed, containing something that could execute. An example:
#!/usr/bin/env fish
echo "Hello World"
  1. Set the owner and group of testprog to something OTHER than root:root. For full effect, make sure your user account is either the owner or belongs to the group.
  2. Set the permissions of testprog to 0550, 0750, or 0770
  3. Attempt to auto-complete or tab-complete the command testprog (without typing it fully) and notice that fish will happily
  4. Change to a root session running fish and ensure that /usr/local/bin is in your path as root
  5. Attempt to auto-complete or tab-complete the command testprog (without typing it fully) and notice that fish will not
  6. Run the command testprog (notice that fish will color the command as valid once it's typed fully)
  7. Attempt to auto-complete or tab-complete the command testprog (without typing it fully) and notice that fish will now suggest it, since it's in your command history, but will not tab-complete it.
  8. Switch to a root shell running bash
  9. Attempt to tab-complete the command testprog (without typing it fully) and notice that bash will happily
  10. Do any one of the following: change the owner of testprog to root, change the group of testprog to root, or set testprog's permissions to 0555, 0750, 0775, or 0777
  11. Switching back to a fish root prompt, try tab-completing testprog again. Note that fish will happily tab-complete it now.

What should happen:

  • If a command is executable at all, it's executable by root, and should be offered as a completion

What does happen:

  • Fish only seems to offer commands for completion when the following are true: it is executable by anyone OR it is executable specifically by you OR it is executable by a group you belong to OR it's in your command history
@ridiculousfish
Copy link
Member

Thank you for the detailed reproduction steps. I was able to reproduce this on Linux.

This bisects to 0b55f08. The issue is replacing the call to waccess with a call to fast_waccess which does a bunch of somewhat sketchy looking stuff, but mostly it tries to figure out if access would succeed without actually invoking access. I am tempted to revert.

ridiculousfish added a commit that referenced this issue Apr 1, 2023
This reverts commit 0b55f08.

This was found to have caused regressions in completions in #9699
@ridiculousfish
Copy link
Member

Upon reflection I think 0b55f08 cannot be saved; for example it doesn't consider ACLs, plus the "Cache a list of our group memberships" is suspect as this may change during a shell session. We should not be second-guessing the kernel in this way.

Reverted in c67d77f. Thank you for the outstanding bug report.

cc @mqudsi ; I don't think 0b55f08 can be saved but maybe you have other ideas.

@ridiculousfish ridiculousfish added this to the fish 3.7.0 milestone Apr 1, 2023
ridiculousfish added a commit that referenced this issue Apr 1, 2023
@mqudsi
Copy link
Contributor

mqudsi commented Apr 4, 2023

Thanks for cc'ing me, @ridiculousfish

for example it doesn't consider ACLs,

Yes, fair point. To be fair, no one has complained showing just how little real-world ACLs (continue to) get.

plus the "Cache a list of our group memberships" is suspect as this may change during a shell session.

I remember considering that but in practice I believe group membership is always cached anyway until you log out and log back in. You can try it yourself by creating a group and giving it permissions to access a file/directory and then add yourself to the group. You won't have permissions until you log back in after being made a member of the group (or start a new session over ssh or on another tty, but those scenarios necessarily involve a new shell instance).

I'll think over this and see if I can come up with any alternatives. The speed gain was very tangible, iirc.

@timkite
Copy link
Author

timkite commented Apr 4, 2023

for example it doesn't consider ACLs,

Yes, fair point. To be fair, no one has complained showing just how little real-world ACLs (continue to) get.

On our large, shared web environments we use ACLs extensively on the development tier, but almost exclusively for developer team file permissions within sites/hosted applications. I've honestly never run into an issue with fish not checking them during completions.

@mqudsi
Copy link
Contributor

mqudsi commented Apr 5, 2023

There are two options that I can see. The first step in both cases is to just add an euid == 0 check to fast_waccess() to address the issue in this PR - quite easily solved.

Given that code in question sonly suggests completions and doesn't affect any core functionality of the shell, I think that's the way to go. Just add the euid check and call it a day. Making 4 extra syscalls per potential file completion is expensive, especially with all the various security mitigations enabled that make the syscall boundary so much more expensive than it was.

The second option is to add the the code back as-is, add the euid check as above, but additionally fall back to an actual waccess() call when fast_waccess() returns access denied - i.e. only ask the kernel if fast_waccess() determines that a file can't be run (which should be the less common case). This will catch cases where ACL gives permission or any other kind of situation we didn't catch. The shell will never erroneously not suggest a completion, though a negative ACL rule could possibly make it suggest a non-viable completion (but no harm done).

Given that the fast_waccess() code has been in-place since fish 3.2.0 and this is literally the first regression flagged as a result, and given that workaround for this actual issue as reported is quite simple, I don't think we should be so quick to toss out that code.

@ridiculousfish
Copy link
Member

I'm uncomfortable second-guessing the kernel here but I may be convinced. Still it's true that ACLs can also deny privileges, plus there may be other mechanisms in play such as sandboxing.

But why is it four extra syscalls per file, and not just one?

@faho
Copy link
Member

faho commented Aug 3, 2023

But why is it four extra syscalls per file, and not just one?

Best I can see is that we check waccess for the executables_only flag, and then again for the description. We can of course skip the latter by passing on the information that it's definitely executable.

faho added a commit to faho/fish-shell that referenced this issue Aug 3, 2023
We have already run waccess with X_OK. We already *know* the file is
executable.

There is no reason to check again.

Restores some of the speedup from the fast_waccess hack that was
removed to fix fish-shell#9699.
faho added a commit to faho/fish-shell that referenced this issue Aug 3, 2023
We have already run waccess with X_OK. We already *know* the file is
executable.

There is no reason to check again.

Restores some of the speedup from the fast_waccess hack that was
removed to fix fish-shell#9699.
faho added a commit that referenced this issue Aug 3, 2023
We have already run waccess with X_OK. We already *know* the file is
executable.

There is no reason to check again.

Restores some of the speedup from the fast_waccess hack that was
removed to fix #9699.
@zanchey zanchey modified the milestones: fish 3.7.0, fish 3.6.2 Aug 4, 2023
faho pushed a commit that referenced this issue Sep 8, 2023
This reverts commit 0b55f08.

This was found to have caused regressions in completions in #9699

(cherry picked from commit c67d77f)
faho added a commit that referenced this issue Oct 6, 2023
We have already run waccess with X_OK. We already *know* the file is
executable.

There is no reason to check again.

Restores some of the speedup from the fast_waccess hack that was
removed to fix #9699.

(cherry picked from commit ee75b45)
faho added a commit to faho/fish-shell that referenced this issue Oct 9, 2023
This checks the st_mode, which we need for other reasons. If it has
the user, group *and* other exec bits set, i.e. a mode of 777 or 755
or similar, we assume it is executable and skip the waccess.

This can have false-positives in some cases:

1. Capabilities
2. noexec filesystems
3. MAC like SELinux?

Of these, capabilities also aren't reflected in access. On a debian
system:

```
sudo capsh --drop=CAP_NET_RAW --shell=/usr/bin/fish -- -c 'test -x /usr/bin/ping && echo is executable; ping 8.8.8.8'
```

prints

> is executable
> exec: Failed to execute process '/usr/bin/ping': No permission. Either suid/sgid is forbidden or you lack capabilities.

and strace confirms:

> faccessat(AT_FDCWD, "/usr/bin/ping", X_OK) = 0

Bash fails similarly, except the error is a more generic "Operation
not permitted"

This leaves noexec filesystems and MAC.

Since this is for completions, it's probably okay to possibly be wrong
in those cases - if it is, the user can just try to execute and will
get an error.

Especially noexec filesystems shouldn't really be in completions
because they'd have to be in $PATH.

I have been unable to test MAC, and from a cursory googling those
would mostly fail the `stat` already.

This speeds up the common case by removing *most* access calls here -
of the 2869 binaries in my $PATH, *1* (one) does not have a mode of
777, 555 or 755. It has 750.

That means completing an empty command will have 2868 fewer access() calls.

This regains all of the speed loss of fish-shell#9699.
@faho faho added the bug Something that's not working as intended label Oct 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something that's not working as intended
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants