> The security issues are long-standing and there have been many over the years, but the real reason is something that's less evident (or at least less directly apparent):
> Simply put, procfs on FreeBSD has been neglected. There isn't a lot of attention being given to it, and the only modifications in recent months/years have been generally minor compared to the rest of the tree. You can review some of the commits yourself:
A quick and dirty way to detect a modified executable on Linux, is to compare /proc/$pid/exe and its target.
$ readlink /proc/$$/exe
/bin/bash (deleted)
$ sha256sum /proc/$$/exe $(readlink /proc/$$/exe)
16a94220a1cf204076640914f09cf0af59da07771819e7eb9671216643296d73 /proc/665119/exe
16a94220a1cf204076640914f09cf0af59da07771819e7eb9671216643296d73 /bin/bash
sha256sum: '(deleted)': No such file or directory
If the unlinked executable has been replaced by a new executable with the same content, they will have the same checksum, this could possibly happen if the package has been reinstalled, which is unlikely to occur. If the original executable is nowhere to be found on the disk, at least you can...
$ cp /proc/$$/exe shell_bin_under_analysis
$ strings shell_bin_under_analysis | grep version
GNU bash, version %s-(%s)
GNU bash, version %s (%s)
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
@(#)Bash version 5.0.16(1) release GNU
$ strings shell_bin_under_analysis | grep zsh
# [...]
zsh-5.8-0-g77d203f
# [...]
For bash, the easiest way would be to just type help', which will report the currently executing shell's version number (e.g. 'GNU bash, version 5.0.18(1)-release (x86_64-apple-darwin19.5.0)'
I'm not familiar with zsh, perhaps it also has some built-in commands that would report the release version?
Well, it kinda works. Just not in the way you might have hoped :P But the output lets you know that you are running zsh.
And to find out the version:
% echo $ZSH_VERSION
5.7.1
(Assuming no-one has been silly and overridden the value of said environment variable in their zshenv file or something. But if we assumed that to be likely then we should also assume that someone using bash had aliased 'help' to something else too, so I think it is fair to assume that no-one had done that.)
Yeah, it's a little meta. I was afraid that what I was trying to say wouldn't quite come across. It's why I mentioned "the shell I'm currently typing in" so many times.
echo $0 will tell you the exact path if it was so executed as a login shell (or otherwise with a full path). If the shell was executed otherwise, then 'which $0' will tell you where it came from, because it must have come from $PATH.
> If the shell was executed otherwise, then 'which $0' will tell you where it came from, because it must have come from $PATH.
Not true: you can be in a shell not in $PATH, and if you executed it from a relative path, which $0 will not find it if you cd to a different location.
which "$(echo $0)"
should work no matter the invokation ;p
if full path, it still returns full path, if just "bash" or such it finds it from $PATH, badoom!
The only reason anyone would want to say "$($echo $0)", echoing the variable through a subshell and substituting it with itself, is to rely on the subshell parameter splitting to remove duplicate field separators from the variable.
That is likely not the case there, and any such file names are unlikely to exist. Just say: which "$0"
As the author notes in the 2nd section, that will return the location of the shell binary in the user path, which is not necessarily the one that is currently running.
Something I’ve never understood about Unix: what’s the point of the shell field in passwd(5), /etc/shells and chsh(1) when one can just “exec /my/shell” in a standard init file?
Is it a hangover from the days when mainframes charged $0.12 per command?
It's a bit of the same idea as "init", but for users.
When a user logs in, you somehow need to know what to do next. What does it even mean "to log in" if not "run a program with my uid".
Typically,you want to spawn a process that would allow the user to in turn run other processes. That's what the user default shell is.
Trivia: changing the user default shell to /bin/false has been used quite a bit as a way to disallow a user from ssh into a machine (there are specific other steps to take into account when doing that which I won't cover here)
Some users got a curses menu of stuff they may do, not a shell that requires knowing how to type a command (and that can run anything the filesystem lets them see). I've even seen users whose shell was pine, because email was all they used.
If your policy is instead to fire up a full blown unrestricted bash shell, then hope the user doesn't exit your script before you swap it with a restricted shell, I've got some surprises for you.
Even if you trust your users, every one of them is a potential doormat for a malicious user to wipe their feet on as they use leaked credentials.
Moral of the story: the stuff is there for a reason and it's easy to use. Might as well do it. Most of the vintage useless stuff is long gone, if it's there it's probably still important.
The ability to specify a shell in /etc/passwd is extremely long-standing: it is documented in the passwd(5) manpage of 1st Edition UNIX dated 1971 (https://www.tuhs.org/cgi-bin/utree.pl?file=V1/man/man5/passw...). My guess is that in 1971 you really didn't want to spawn a shell process unnecessarily on a multi-user pdp-11 (not because you're charged for it but because the machine is small and you don't want to waste ram or cpu). It also means you can say "use my locally compiled bugfixed shell binary that reads the same startup/init files as the standard system one", and you can have 'system' users with non-standard special-purpose shells -- 7th Ed's /etc/passwd (https://www.tuhs.org/cgi-bin/utree.pl?file=V7/etc/passwd) has a uucp user whose shell is the uucico program, so a remote system can dial in, log in as 'uucp' and trigger file transfer.
These days we'd probably also think about reducing 'attack surface' by not giving that kind of special purpose user a real shell even in passing (if a bug let them write to the shell's init files that turns write-file-as-user into remote-shell), but they wouldn't have been considering that in the early 70s...
But, no, one cannot just "exec /my/shell" if the shell in the password file is a program that doesn't offer such a command.
Being able to assign a login shell to an account is an important security feature. It's not simply a user preference mechanism. It can be used to bind an account to a "restricted shell":
When people use remote execution tools like (historically) rsh or ssh, the remote account's login shell is used.
You can replace an account's login shell with a program that parses the commands and allows only very limited access.
Here is an example such a script which I used for an account that accepted SSH tunnel connections for a remote desktop. (The remote Windows machine contained a "service" that called back to this machine via SSH, setting up a tunnel for accessing that Windows machine in the reverse direction.)
#!/bin/bash
if [ $# -ne 2 ] || [ "$1" != "-c" ] ; then
echo interactive login not permitted
echo "$@" >> ~/.log
exit 1
fi
case "$2" in
rdp )
while true ; do sleep 3600 ; done
;;
* )
echo that command is not allowed
exit 1
;;
esac
The only command allowed is "rdp", and what it does is put this "shell" to sleep, to keep the connection open. It's implemented as a built-in, right in the above case statement.
If this script exits, the login session or remote command is terminated.
The reason I did this is that the Windows machine held an unprotected SSH private key to be able to perform a password-less login to that Linux box. (This ran as a service, so even if the Windows machine were power-cycled, it would reconnect.)
If someone got into the Windows box and discovered and understood the setup, and the Linux account were not protected by this script, they would be able to use the unprotected private key to ssh into it, or run ssh commands, or scp files, etc.
This sort of thing has other applications; for instance, it could be used to give people real SSH git access, without shell access.
fish shell discovers its own filesystem path, and then looks for resources (completions, functions, etc) relative to that path. You can move a fish directory hierarchy to someplace else and it will still work.
One advantage is testing. To test fish, it first must be installed into a directory tree, but relative paths are preferred over build-time paths, so there is no special "build for test" mode. Also this makes it easier for fish to invoke itself as part of a test.
A second advantage is embedding. GNU encourages supporting relocation [1], and fish also provides a Mac app which can be run from anywhere, no installation.
Anyways there's a lot of benefits if a shell can find itself.
As the author notes in the 2nd section, that will return the location of the binary in the user path, which is not necessarily the one that is currently running.
I think you're missing the point. The author wants to know the location of the binary of the shell he's currently running. Changing PATH doesn't help answering this question: it only changes what `which` displays, but the point of the author is that `which` is not the correct tool to help answering this question.
50 comments
[ 4.7 ms ] story [ 120 ms ] thread(Not sure it macOS has as much info in /proc?)
macOS does not have procfs
https://en.wikipedia.org/wiki/Procfs
It also got dropped from OpenBSD:
> It was removed from OpenBSD in version 5.7, which was released in May 2015, because it "always suffered from race conditions and is now unused".[2]
The OpenBSD people don't say what you should replace it with, but backing up in the paragraph, the FreeBSD people do:
> As of February 2011, procfs is gradually becoming phased out in FreeBSD.[1]
Chasing that reference:
https://lists.freebsd.org/pipermail/freebsd-fs/2011-February...
> Why is procfs deprecated in favor of procstat?
[snip]
> The security issues are long-standing and there have been many over the years, but the real reason is something that's less evident (or at least less directly apparent):
> Simply put, procfs on FreeBSD has been neglected. There isn't a lot of attention being given to it, and the only modifications in recent months/years have been generally minor compared to the rest of the tree. You can review some of the commits yourself:
> http://www.freebsd.org/cgi/cvsweb.cgi/src/sys/fs/procfs/
> Others like yourself have asked what the state of procfs is, going back as far as 2005. Be sure to read the reply as well:
> http://unix.derkeiler.com/Mailing-Lists/FreeBSD/questions/20...
Here's the FreeBSD procstat manpage:
https://www.freebsd.org/cgi/man.cgi?procstat
Aside from bitrot, is there a reason to prefer procstat over procfs?
/proc is Linux specific, but it's extremely useful. A lot of tools are actually built on top of it, for example ps.
I'm not familiar with zsh, perhaps it also has some built-in commands that would report the release version?
And to find out the version:
(Assuming no-one has been silly and overridden the value of said environment variable in their zshenv file or something. But if we assumed that to be likely then we should also assume that someone using bash had aliased 'help' to something else too, so I think it is fair to assume that no-one had done that.)Not true: you can be in a shell not in $PATH, and if you executed it from a relative path, which $0 will not find it if you cd to a different location.
That is likely not the case there, and any such file names are unlikely to exist. Just say: which "$0"
fish: $(...) is not supported. In fish, please use '(echo)'. which "$(echo $0)" ^
;)
Is it a hangover from the days when mainframes charged $0.12 per command?
When a user logs in, you somehow need to know what to do next. What does it even mean "to log in" if not "run a program with my uid".
Typically,you want to spawn a process that would allow the user to in turn run other processes. That's what the user default shell is.
Trivia: changing the user default shell to /bin/false has been used quite a bit as a way to disallow a user from ssh into a machine (there are specific other steps to take into account when doing that which I won't cover here)
* nologin: https://man7.org/linux/man-pages/man8/nologin.8.html
* scponly: https://linux.die.net/man/8/scponly
* git-shell: https://git-scm.com/docs/git-shell
If your policy is instead to fire up a full blown unrestricted bash shell, then hope the user doesn't exit your script before you swap it with a restricted shell, I've got some surprises for you.
Even if you trust your users, every one of them is a potential doormat for a malicious user to wipe their feet on as they use leaked credentials.
Moral of the story: the stuff is there for a reason and it's easy to use. Might as well do it. Most of the vintage useless stuff is long gone, if it's there it's probably still important.
These days we'd probably also think about reducing 'attack surface' by not giving that kind of special purpose user a real shell even in passing (if a bug let them write to the shell's init files that turns write-file-as-user into remote-shell), but they wouldn't have been considering that in the early 70s...
Being able to assign a login shell to an account is an important security feature. It's not simply a user preference mechanism. It can be used to bind an account to a "restricted shell":
https://en.wikipedia.org/wiki/Restricted_shell
When people use remote execution tools like (historically) rsh or ssh, the remote account's login shell is used.
You can replace an account's login shell with a program that parses the commands and allows only very limited access.
Here is an example such a script which I used for an account that accepted SSH tunnel connections for a remote desktop. (The remote Windows machine contained a "service" that called back to this machine via SSH, setting up a tunnel for accessing that Windows machine in the reverse direction.)
The only command allowed is "rdp", and what it does is put this "shell" to sleep, to keep the connection open. It's implemented as a built-in, right in the above case statement.If this script exits, the login session or remote command is terminated.
The reason I did this is that the Windows machine held an unprotected SSH private key to be able to perform a password-less login to that Linux box. (This ran as a service, so even if the Windows machine were power-cycled, it would reconnect.)
If someone got into the Windows box and discovered and understood the setup, and the Linux account were not protected by this script, they would be able to use the unprotected private key to ssh into it, or run ssh commands, or scp files, etc.
This sort of thing has other applications; for instance, it could be used to give people real SSH git access, without shell access.
One advantage is testing. To test fish, it first must be installed into a directory tree, but relative paths are preferred over build-time paths, so there is no special "build for test" mode. Also this makes it easier for fish to invoke itself as part of a test.
A second advantage is embedding. GNU encourages supporting relocation [1], and fish also provides a Mac app which can be run from anywhere, no installation.
Anyways there's a lot of benefits if a shell can find itself.
1: https://www.gnu.org/software/gnulib/manual/html_node/Support...
Eh, why not remove it from PATH then ?