TransWikia.com

cmd nested in backticks processed from sudo?

Unix & Linux Asked by alecxs on November 17, 2021

Are nested `commands` using (deprecated) backticks executed with root permissions (from sudo command line) while command substitution $(...) is not?

for example

sudo mount `blkid -u /dev/sda1` ...
sudo mount `sudo blkid -u /dev/sda1` ...

vs

sudo mount $(blkid -u /dev/sda1) ...
sudo mount $(sudo blkid -u /dev/sda1) ...

is there a difference between methods? i am not even quite sure which of the four lines is the right usage (regarding sudo, not blkid it’s just pseudo code). or is it shell dependent?

One Answer

`...` and $(...) are the same thing with different syntax, one if from the Bourne shell, the other from the Korn shell, the former is deprecated but still supported by Bourne-like shells for backward compatibility with the ancient Bourne shell.

Other shells have different syntax for that. For instance, fish has (...) and rc/es have `cmd or `{more complex cmd}, or `(sep){cmd} to specify different splitting behaviour. ksh93 and mksh also have ${ ...; } (no subshell) variant.

In any case, that's syntax in the shell language, so you need a shell to interpret it and perform the command substitution.

In:

sudo cmd1 `cmd2`

In Bourne-like shells, the shell

  • runs cmd2 in a child process with its output redirected to a pipe
  • reads the output of that cmd from the other end of the pipe,
  • removes the trailing newline characters,
  • splits the result according to $IFS
  • perform filename generation on the resulting words (except in zsh; some ksh variants also perform brace expansion)
  • and then passes the resulting words as separate arguments to a sudo command executed in another child process.

sudo then changes uids and runs the command it is being passed as argument.

If you wanted cmd2, to be run with the different uid, you'd need sudo to run a shell to interpret the shell code that performs the command substitution:

sudo sh -c 'cmd1 $(cmd2)'
sudo fish -c 'cmd1 (cmd2)'
sudo rc -c 'cmd1 `cmd2'

and so on.

Note that in Bourne-like shells, command substitution is still performed inside double quotes, so do not do:

sudo sh -c "cmd1 $(cmd2)"

First, that would not run cmd2 as root, but also the output of cmd2 (this time, not subjected to split+glob as it's within quotes), would be interpreted as sh code, so would typically constitute a command injection vulnerability. For instance, if cmd2 outputs $(reboot), the sh invoked by sudo would be asked to interpret cmd1 $(reboot) and reboot.

Similarly, don't do sudo sh -c 'cmd1 $(cmd2) '"$var" or sudo sh -c 'cmd1 $(cmd2 '"$var"')' if you want to pass the contents of variables of your shell (as opposed to the one started by sudo) to cmd1 or cmd2. Instead, pass the contents of those variables as extra arguments to sh (not inside the code argument), or via environment variables (so they become variables of the shell started by sudo as well):

sudo sh -c 'cmd1 $(cmd2) "$1"' sh "$var"
sudo VAR="$var" sh -c 'cmd1 $(cmd2) "$VAR"'

sudo sh -c 'cmd1 $(cmd2 "$1")' sh "$var"
sudo VAR="$var" sh -c 'cmd1 $(cmd2 "$VAR")'

Here, you can always also do:

sudo cmd1 $(sudo cmd2) "$var"
sudo cmd1 $(sudo cmd2 "$var")

That is have your shell run both commands through two separate invocations of sudo so both be run with elevated privileges.

As hinted above, in Bourne-like shells (but that also applies, though differently, to csh-like shells), unquoted command substitution is subject to split+glob, so you'd only leave $(cmd2) unquoted in cmd1 $(cmd2) if cmd2 is outputting a $IFS delimited list of wildcard patterns. If you want the output of cmd2 (without the trailing newline characters) to be passed as a whole as one argument to cmd1, you'd want cmd1 "$(cmd2)" or more likely cmd1 -- "$(cmd2)" to make sure that argument is not treated as an option (assuming cmd1 supports that -- end-of-option marker).

So for your particular use case, that would be:

sudo DEVICE="$device" sh -c 'mount -- "$(blkid -u -- "$DEVICE")"'

Or to call mount only if blkid succeeds:

sudo DEVICE="$device" sh -c '
  output=$(blkid -u -- "$DEVICE") &&
    mount -- "$output"
'

Answered by Stéphane Chazelas on November 17, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP