50 comments

[ 4.7 ms ] story [ 120 ms ] thread
`readlink -e /proc/$$/exe` would be a simple solution on linux -> /proc is such a fun treasure trove of process info :-)

(Not sure it macOS has as much info in /proc?)

> Not sure it macOS has as much info in /proc?

macOS does not have procfs

> 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?

Do you have any resources about /proc you'd recommend?
`man proc' is quite good. https://man7.org/linux/man-pages/man5/proc.5.html

/proc is Linux specific, but it's extremely useful. A lot of tools are actually built on top of it, for example ps.

  ngarvey@cheapo:~$ strace ps aux |& grep cmdline | head
  openat(AT_FDCWD, "/proc/1/cmdline", O_RDONLY) = 6
  openat(AT_FDCWD, "/proc/2/cmdline", O_RDONLY) = 6
  openat(AT_FDCWD, "/proc/3/cmdline", O_RDONLY) = 6
  openat(AT_FDCWD, "/proc/4/cmdline", O_RDONLY) = 6
  openat(AT_FDCWD, "/proc/9/cmdline", O_RDONLY) = 6
  openat(AT_FDCWD, "/proc/10/cmdline", O_RDONLY) = 6
  openat(AT_FDCWD, "/proc/11/cmdline", O_RDONLY) = 6
  openat(AT_FDCWD, "/proc/12/cmdline", O_RDONLY) = 6
  openat(AT_FDCWD, "/proc/13/cmdline", O_RDONLY) = 6
  openat(AT_FDCWD, "/proc/14/cmdline", O_RDONLY) = 6
does not work in Fish Shell.

  fish: $$ is not the pid. In fish, please use $fish_pid.

  readlink -e /proc/$$/exe

                     ^
plot twist: the binary was unlinked already
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
    # [...]
zsh doesn't use "what" strings? well there goes that idea.
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?

  % help
  zsh: command not found: help
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.)
Fish opens up file:///usr/share/doc/fish/index.html in Firefox with the following console output:

    ╰─>$ help
    
    (firefox:42227): Gtk-WARNING **: 11:46:58.100: Locale not supported by C library.
            Using the fallback 'C' locale
That looks like output from Firefox. The page itself should be the fish help page.
That's right, but the output of the help command doesn't provide any indication as to what shell I'm running still.
I would just pstree and see what the parent of pstree is. Or top/htop.
This should also do it: realpath /proc/$$/exe
(comment deleted)
On BSD, the approximate "lsof" equivalent, fstat, is part of the base system. Does macOS not have fstat?
Apparently it does not:

   ~ % which fstat
   fstat not found
I use 'echo $0' to see what shell I'm using.
the article is about finding the specific version and path
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!
Which is not guaranteed to be present. 'command -v' should work on any posix shell though.
But you might have customized your PATH after your shell was executed? So this does not seem accurate.
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.
Nah.

  $ which "$(echo $0)"
  which: illegal option -- b
  usage: which [-as] program ...

  $ echo $0
  -bash
OSX. True story.
Only for POSIX shells.

fish: $(...) is not supported. In fish, please use '(echo)'. which "$(echo $0)" ^

;)

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.
Let me introduce you to a few friends, only 3 for now. Hopefully these will answer your question:

* 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.

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":

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.)

  #!/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.

1: https://www.gnu.org/software/gnulib/manual/html_node/Support...

what happened to the simple invocation of `which "$(echo $0)"` ?
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.
> that only told me the location of zsh the executable which is only correct because I added homebrew's /usr/local/bin location to my PATH

Eh, why not remove it from PATH then ?

You can temporarily alter environment variables like this:

  PATH= /path/to/command
That will run /path/to/command with the PATH variable unset.
/usr/local/bin is on PATH by default on macOS.
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.