[Pkg-shadow-devel] Bug#317264: login: Current su breaks pbuilder

Helmut Waitzmann (Debian Bug Tracking System) "Helmut Waitzmann" (Debian Bug Tracking System) <Helmut.Waitzmann@web.de>, 317264@bugs.debian.org
Wed, 13 Jul 2005 08:22:25 +0200


Junichi Uekawa <dancer@netfort.gr.jp> writes:

>Apart from being unhappy about a core component of=20
>Debian dramatically changing at all; to answer your
>question:
>
>> >I have concerns wrt shell quoting,
>>=20
>> Could you please explain more detailed?
>
>The implications of needing quoting means that previous=20
>quoting conventions will need to change.
>
>Applications which used to pass quoted text to su, and=20
>needs to quote differently now need to change dramatically.

That would be intolerable, indeed.  But see below.

>  su username command A $B "C $D"
>used to work, but it will now need to be=20
>  su username -c "command A \"$B\" \"C $D\""

No.  With Debian's new su one would have to do (suppose one is using
a bourne shell or compatible) to achieve the same result:

$ (set x command A $B "C $D" && shift && exec su username -c "$*")

>and specific semantics of how the contents of $B and $D is expanded is=20
>going to change.

No.  $B and $D are expanded by the shell invoking su, not by the shell
invoked by su.  So there will be no change w.r.t. to $B and $D.

There will be a change in concatenating:  Debian's new su will not
concatenate the parameters to make up a command string.  So you have to
do it yourself.  That's what the 'set' command in conjunction with the
special parameter "$*" does.

But this feature of Debian's old su (i.e. concatenating the command
arguments), is an undocumented feature:  It is not documented in Debian's
old su's manual page.  So every invocation of su that adheres to the
specification of Debian's old su won't use it anyway but concatenate all
command string components by itself.

The feature of Debian's old su to implicit pass a "-c" parameter to the
shell if there are any arguments following the username, is undocumented,
too.  So every conforming invocation of su would supply "-c" by itself
and not relay on su doing it implicitly.

And what about quoting?  No.  There will be no change in quoting, because
old su (like every su) does no quoting by itself, it simply concatenates
the arguments.

You may call this a bug of su, but it is not:  Su does not know anything
about quoting for username's shell it is going to start.  As there are
different shells (sh, bash, csh, tcsh, ksh, ash, some people use even
emacs, ...)  with different ways to quote, su cannot know how to quote
the parameters before constructing a command to be executed by username's
shell.

So what does Debian's old su do, when it constructs the command to be
executed by the username's shell?

It does'nt do any quoting.  It simply concatenates all the arguments into
one string with spaces in between and supplies that string as the command
string to the shell to be called.

An example may illustrate this.  In the following, I assume, that the
shell to enter this commands into is a bash and that the username's shell
(that will be invoked by su) is a bash, too:

Don't do this at home!

$ su -- username 'ls' '--' 'white     space' 'a fancy name; rm -rf ~'

Here, su is called with 6 parameters: '--', 'username', 'ls', '--',
'white     space' and 'a fancy name; rm -rf ~'.

Note, that the single quotes are not part of the parameters, as seen by
su.  I write them in this text to indicate the start and the end of each
of the parameters (which may contain white space).  Su will get the 6
parameters without the quotes as positional parameters.  The single
quotes in the command line are necessary, to indicate to the bash
invoking su what the parameters to pass to su are to be.

Debian's old su will notice, that the first parameter after the username
(i.e. 'ls') is not a su option (see manual page su), so it assumes, that
it is one of the words to be concatenated to make up the command string
to pass to username's shell to be invoked.

Therfore, Debian's old su will invoke the username's bash as follows
(note, that the parameters to su are simply concatenated with spaces in
between, no additional level of quoting is done by su):

$ bash -c 'ls -- white     space a fancy name; rm -rf ~'

What will that bash, that is invoked by su, do?  To understand, what will
happen, take a look at the manual page bash(1):

   BASH(1)

   NAME
          bash - GNU Bourne-Again SHell

   SYNOPSIS
          bash [options] [file]

[...]

   OPTIONS
          In addition to the single-character shell options documented in
          the description of the set builtin command, bash interprets the
          following options when it is invoked:

          -c string If the -c option is present, then commands are read
                    from string.  If there are arguments after the
                    string, they are assigned to the positional
                    parameters, starting with $0.

So, username's bash will execute the following two commands (note the
semicolon!):

$ ls -- white     space a fancy name; rm -rf ~

Do I have to comment, what will happen?

To achieve the same (bad) result using Debian's both old or new su, Linux
Standard Base's su, etc, one could enter the following command,
i.e. construct the parameters that su will use to invoke username's bash
by oneself and pass them to su.

W.r.t. to the command string, that would be: concatenate the arguments by
yourself and pass the resulting command string to su:

$ su -- username -c 'ls -- white     space a fancy name; rm -rf ~'

Here, Debian's new su, Linux Standard Base's su, etc, would just pass the
remaining (here: two) parameters (if any) following the username to
username's shell.

Debian's old su would notice, that there are parameters following the
username.  It assumes, that they should make up a command to be passed to
username's shell.  As the first parameter is '-c', it takes this as the
parameter '-c' to be passed to username's shell, indicating to it, that a
command string will follow.  Therefore, Debian's old su concatenates all
the parameters following the '-c' with spaces in between (here is nothing
to concatenate: only one parameter is remaining).

Let me summarize:

If you construct the command line by yourself and pass to Debian's old su
only 2 command parameters: '-c', and the command line, then it will behave
like Debian's new su, Linux Standard Base's su, etc.

If you pass more than 1 command line fragment, then Debian's old su will
concatenate the command line fragments with spaces in
between (but without quoting!) to make up the command line.

This "concatenating a command to be executed by username's shell" is a
misleading feature of Debian's old su:  It pretends to do proper command
construction (including quoting) whereas it simply concatenates the
parameters.

Now, what could one do to avoid that mess?  (1) Either construct a
command string by yourself with proper quoting or (2) use username's
shell (here: bash) to handle the unquoted arguments to ls by itself.

To do (1), there is AFAIK no utility that does that in an easy way.  One
could operate with the 'printf' bash-builtin command using the format
string '%q', but that's not easy (and would only work, if username's shell
is one that accepts bash's way of quoting).

To do (2), one can use the shell's "positional parameters".

How to use the positional parameters?  Again, bash(1) helps:

   PARAMETERS
          A parameter is an entity that stores values.  It can be a name,
          a number, or one of the special characters listed below under
          Special Parameters.

[...]

      Positional Parameters
          A positional parameter is a parameter denoted by one or more
          digits, other than the single digit 0.  Positional parameters
          are assigned from the shell=E2=80=99s arguments when it is invoke=
d, and
          may be reassigned using the set builtin command.

      Special Parameters
          The shell treats several parameters specially.

[...]

          @      Expands to the positional parameters, starting from one.
                 When the expansion occurs within double quotes, each
                 parameter expands to a separate word.  That is, "$@" is
                 equivalent to "$1" "$2" ...  When there are no
                 positional parameters, "$@" and $@ expand to nothing
                 (i.e., they are removed).


Now, we can use the shell invocation parameters '-c' 'string':

The command

$ bash -c 'ls -- "$@"' 'bash' 'white     space' 'a fancy name; rm -rf ~'

invokes a bash, giving it 5 parameters: '-c', 'ls -- "$@"' 'bash',
'white     space' and 'a fancy name; rm -rf ~'.

The first parameter, '-c', indicates to bash (see manual page above),
that bash should execute a command string (the second parameter
'ls -- "$@"') rather than be an interactive shell.

The 3 remaining parameters after the string following '-c', i.e. the
parameters 'bash', 'white     space' and 'a fancy name; rm -rf ~', are
assigned to the bash's positional parameters $0, $1 and $2, i.e. in that
bash, executing the commands

$ test "$0" =3D bash
$ test "$1" =3D 'white     space'
$ test "$2" =3D 'a fancy name; rm -rf ~'

would yield the exit code zero.

When executing the command

$ ls -- "$@"

in that bash, bash expands the expression "$@" to the positional
parameters no. 1 and 2:  The program ls gets called with the three
parameters '--', 'white     space' and 'a fancy name; rm -rf ~':

$ ls -- 'white     space' 'a fancy name; rm -rf ~'


To make a long story short:  To achieve proper parameter propagation
through su, there are two ways to go:  Either quote all arguments by
yourself (that's not easy!) when constructing the command string or pass
all arguments as separate parameters to username's shell and let that
shell construct the command by itself.

So, clearly the second way is the preferable way to do it.

Debian's new su, Linux Standard Base's su, etc, would just pass the
parameters given to it unchanged (i.e. not concatenated) to the
username's shell it invokes, thus let the user of su pass positional
parameters to the shell to be expanded using '"$@"'.

With Debian's old su, it is impossible to pass positional parameters to a
shell following the parameters '-c' 'ls -- "$@"', because it concatenates
all remaining parameters to the command string.  Therefore, Debian's old
su thwarts the second way to achieve proper parameter passing!

That's one of the reasons why I want Debian's su behavior to change to
Debian's old su's specification (i.e. manual page) and Linux Standard
Base', Fedora's, HPUX's and Solaris' su's semantics and specification.

Let me drop some words about (using Debian's old su)

>  su username command A $B "C $D"

and (using Debian's new su)

>  su username -c "command A \"$B\" \"C $D\""

.  As Debian's both old an new su don't do any quoting, there is no way
to construct an invocation of su without using the positional parameters
or explicit quoting (e.g. using printf ' %q') by simply inserting
quotation marks into the command string:

For example:

$ A=3D"\"':; cd; rm -r .;'; cd; rm -r .;\":; cd; rm -r .;:"

With Debian's old su, all four commands

$ su username ls "\"$A\""
$ su username ls "'$A'"
$ su username ls "$A"
$ su username -c "ls \"\$@\"" bash "$A"

would empty username's home directory, whereas Debian's new su, Linux
Standard Base's su, etc, when invoked by

$ su username -c "ls \"\$@\"" bash "$A"

would do what it is told to do.

Best regards,

Helmut
--=20
Wenn Sie mir E-Mail schreiben, stellen |  When writing me e-mail, please
Sie bitte vor meine E-Mail-Adresse     |  precede my e-mail address with
meinen Vor- und Nachnamen, etwa so:    |  my full name, like
Helmut Waitzmann <xxx@example.net>, (Helmut Waitzmann) xxx@example.net