1

Why does the code

date
bash -c "date"
declare -x date='() { echo today; }' #aka export date='() { echo today; }'
date
bash -c "date"

print

Wed Sep 24 22:01:50 CEST 2014
Wed Sep 24 22:01:50 CEST 2014
Wed Sep 24 22:01:50 CEST 2014
today

?

Where (and why) does the evaluation

 date$date

happen and getting

 date() {echo today; }

Ad: @Etan Reisner

  1. I exporting a variable - not a function. Bash makes a function from it. The
export date='someting'

is still a variable regardless of its content. So, why is

export date='() { echo something; }' #Note, it is a variable, not function.

converted to an function?

  1. The mentioned security advisory talks about the execution of the command following the variable, for example,
x='() { echo I do nothing; }; echo vulnerable' bash -c ':'
                              ^^^^^^^^^^^^^^^
                              This is executed - this vunerability is CLOSED in version 4.3.25(1).

The command after the env-definition isn't executed in the latest Bash.

But the question remains - Why does Bash convert the exported variable to a function?

It is a bug ;) Full demo, based on @chepner's answer:

#Define three variables
foo='() { echo variable foo; }'    # ()crafted
qux='() { echo variable qux; }'    # ()crafted
bar='variable bar'                 # Normal
export foo qux bar                 # Export

#Define the same name functions (but not qux!)
foo() { echo "function foo"; }
bar() { echo "function bar"; }
declare -fx foo bar                 #Export

#printouts
echo "current shell foo variable:=$foo="
echo "current shell foo function:=$(foo)="
echo "current shell bar variable:=$bar="
echo "current shell bar function:=$(bar)="
echo "current shell qux variable:=$qux="
echo "current shell qux function:=$(qux)="

#subshell
bash -c 'echo subshell foo variable:=$foo='
bash -c 'echo subshell foo command :=$(foo)='
bash -c 'echo subshell bar variable:=$bar='
bash -c 'echo subshell bar command :=$(bar)='
bash -c 'echo subshell qux variable:=$qux='
bash -c 'echo subshell qux command :=$(qux)='

prints

current shell foo variable:=() { echo variable foo; }=
current shell foo function:=function foo=
current shell bar variable:=variable bar=
current shell bar function:=function bar=
current shell qux variable:=() { echo variable qux; }=
tt: line 20: qux: command not found
current shell qux function:==
subshell foo variable:==                   #<-- LOST the exported foo variable
subshell foo command :=function foo=
subshell bar variable:=variable bar=
subshell bar command :=function bar=
subshell qux variable:==                   #<-- And the variable qux got converted to
subshell qux command :=variable qux=       #<-- function qux in the subshell (!!!).

Avoiding the long comments, here is code from the Bash sources:

 if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4))
                                                           ^^^^^^^^ THE PROBLEM
    {
      string_length = strlen (string);
      temp_string = (char *)xmalloc (3 + string_length + char_index);

      strcpy (temp_string, name);
      temp_string[char_index] = ' ';
      strcpy (temp_string + char_index + 1, string);

      if (posixly_correct == 0 || legal_identifier (name))
        parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);

      /* Ancient backwards compatibility.  Old versions of bash exported
         functions like name()=() {...} */

The "ancient" (seems) was better... :)

      if (name[char_index - 1] == ')' && name[char_index - 2] == '(')
        name[char_index - 2] = '\0';
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
clt60
  • 62,119
  • 17
  • 107
  • 194
  • 1
    Hopefully, my answer addresses your edit. Either way, I think the question will be worth keeping. The fact that `bash` somewhat abuses the environment for passing functions to child `bash` processes is a bit confusing and worth knowing about. – chepner Sep 24 '14 at 20:47
  • Hmmm...Is this related to [CVE-2014-6271](http://nvd.nist.gov/view/vuln/detail?vulnid=CVE-2014-6271)? – Jonathan Leffler Sep 24 '14 at 22:59
  • @JonathanLeffler the 6271 is closed in the lastest bash. (eg. the execution of the command after the env-variable setting. But the strange conversion of the variable export to function still exists as a bug... – clt60 Sep 24 '14 at 23:10
  • This looks to me very much like a duplicate of a prior question asking for an explanation of the bug's mechanism. – Charles Duffy Sep 25 '14 at 13:31
  • @jm666, Ahh. Frankly, this doesn't look like a bug to me at all, but an entirely expected consequence of behavior-as-designed. – Charles Duffy Sep 25 '14 at 13:36
  • @jm666, I'm curious as to how exactly that could or would work. One could, perhaps, have a separate environment variable listing exported functions -- that's the only implementation that comes to mind. In any event, it would take a breaking change -- a *design revision* -- to the protocol used to pass data between shells over the environment. – Charles Duffy Sep 25 '14 at 13:47
  • *shrug*. Nobody could possibly design this implementation without knowing that that "bug" was one of the consequences. The thing we're thinking was a bad idea in hindsight was deciding not to care. – Charles Duffy Sep 25 '14 at 13:58

3 Answers3

4

The key point to remember is that

foo='() { echo 5; }'

only defines a string parameter with a string that looks a lot like a function. It's still a regular string:

$ echo $foo
() { echo 5; }

And not a function:

$ foo
bash: foo: command not found

Once foo is marked for export,

$ export foo

any child Bash will see the following string in its environment:

foo=() { echo 5; }

Normally, such strings become shell variables, using the part preceding the = as the name and the part following the value. However, Bash treats such strings specially by defining a function instead:

$ echo $foo

$ foo
5

You can see that the environment itself is not changed by examining it with something other than Bash:

$ perl -e 'print $ENV{foo}\n"'
() { echo 5
}

(The parent Bash replaces the semicolon with a newline when creating the child's environment, apparently). It's only the child Bash that creates a function instead of a shell variable from such a string.

The fact that foo could be both a parameter and a function within the same shell;

$ foo=5
$ foo () { echo 9; }
$ echo $foo
5
$ foo
9

explains why -f is needed with export. export foo would cause the string foo=5 to be added to the environment of a child; export -f foo is used to add the string foo=() { echo 9; }.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
chepner
  • 497,756
  • 71
  • 530
  • 681
0

You are essentially manually exporting a function with the name date. (Since that is the format that bash uses internally to export functions. Which is suggested by Barmar in his answer. This mechanism is mentioned here at the very least.)

Then when you run bash it sees that exported function and uses it when you tell it to run date.

Is the question then where is that mechanism specified? My guess is it isn't since it is an internal detail.

This should show the merging of the behaviours if that helps anything.

$ bar() { echo automatic; }; export -f bar
$ declare -x foo='() { echo manual; }'
$ declare -p foo bar
declare -x foo="() { echo manual; }"
-bash: declare: bar: not found
$ type foo bar
-bash: type: foo: not found
bar is a function
bar ()
{
    echo automatic
}
$ bash -c 'type foo bar'
foo is a function
foo ()
{
    echo manual
}
bar is a function
bar ()
{
    echo automatic
}
Etan Reisner
  • 77,877
  • 8
  • 106
  • 148
  • 3
    Just to add to this, functions and parameters exist in different namespaces, so you can have both `bar () { echo automatic; }` and `bar=5`. The assignment `foo='() { echo manual; }'` in the parent defines a parameter named `foo` without defining a function. Marking `foo` for export adds it to the environment of any children, and if that child is another `bash` shell, it creates a function using the value of `foo` as the body, but does *not* retain a string parameter named `foo`. – chepner Sep 24 '14 at 20:36
  • @chepner Exactly, that's why I had the `declare -p` and `type` output in the snippet to attempt to show that off (though your prose explanation is also useful). – Etan Reisner Sep 24 '14 at 20:51
-1

The answer to your question comes directly from man bash:

The export and declare -x commands allow parameters and functions to be added to and deleted from the environment. If the value of a parameter in the environment is modified, the new value becomes part of the environment, replacing the old.

Thus

declare -x date='() { echo today; }'

replaces date in the environment. The next immediate call to date gives date as it exists in the script (which is unchanged). The call to bash -c "date" creates a new shell and executes date as defined by declare -x.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Note that your declare creates a parameter named `date` which is independent of any function which can exist by the same name. `bash` shells that inherit such values automatically convert the name from a parameter to a function. – chepner Sep 24 '14 at 20:37