By now, you’ve probably seen this magic incantation, or variations, sent all around as a quick test for vulnerability to CVE-2014-6271, known as “Shellshock”, because in this post-Heartbleed world, apparently all security flaws will have cute over-dramatic names.

env x='() { :;}; echo OOPS' bash -c :

This will print “OOPS” on a vulnerable system, but exit silently if bash has been patched.

And you’ve probably heard that it has something to do with environment variables. But, why is code in environment variables getting executed? Well, it’s not supposed to be — but, because of a feature which I’m tempted to call a bit too clever for its own good, there’s some room for a flaw. Bash is what you see as a terminal prompt, but it also is a scripting language, and has the ability to define functions. You do that like this:

$ yayfedora() { echo "Fedora is awesome."; }

and then you have a new command. Keep in mind that the “echo” here isn’t actually run yet; it’s just saved as what will happen when we run our new command. This will be important in a minute!

$ yayfedora Fedora is awesome.

Useful! But, let’s say, for some reason, I need to execute a new instance of bash , as a subprocess, and want to run my awesome new command under that. The statement bash -c somecommand does exactly this: runs the given command in a new shell:

$ bash -c yayfedora bash: yayfedora: command not found

Ooh. Sad. The child didn’t inherit the function definition. But, it does inherit the environment — a collection of key-value pairs that have been exported from the shell. (This is a whole ‘nuther concept; if you’re not familiar with this, trust me for now.) And, it turns out, bash can export functions as well. So:

$ export -f yayfedora $ bash -c yayfedora Fedora is awesome.

Which is all well and good — except that the mechanism by which this is accomplished is sorta dodgy. Basically, since there is no Linux/Unix magic for doing functions in environment variables, the export function actually just creates a regular environment variable containing the function definition. Then, when the second shell reads the “incoming” environment and encounters a variable with contents that look like a function, it evaluates it.

In theory, this is perfectly safe, because, remember, defining a function doesn’t actually execute it. Except — and this is why we’re here — there was a bug in the code where the evaluation didn’t stop when the end of the function definition was reached. It just kepts going.

That would never happen when the function stored in an environment variable is made legitimately, with export -f. But, why be legit? An attacker can just make up any old environment variable, and if it looks like a function, new bash shells will think it is!

So, in our first example:

env x='() { :;}; echo OOPS' bash -c :

The “env” command runs a command with a given variable set. In this case, we’re setting “x” to something that looks like a function. The function is just a single “:”, which is actually a simple command which is defined as doing nothing. But then, after the semi-colon which signals the end of the function definition, there’s an echo command. That’s not supposed to be there, but there’s nothing stopping us from doing it.

Then, the command given to run with this new environment is a new bash shell, again with a “do nothing :” command, after which it will exit, completely harmlessly.

But — oops! When that new shell starts up and reads the environment, it gets to the “x” variable, and since it looks like a function, it evaluates it. The function definition is harmlessly loaded — and then our malicious payload is triggered too. So, if you run the above on a vulnerable system, you’ll get “ OOPS ” printed back at you. Or, an attacker could do a lot worse than just print things.

Our update does several things.

First, it fixes the flaw where the interpretation doesn’t stop at the end of the function definition (including some more sanity checks).

Second, it always prefixes the magical environment variables which hold exported functions with the string BASH_FUNC_ and suffixes them with () . That means you can’t just set any arbitrary environment variable — like those passed to a web server as part of the CGI interface! — to look like a function and attack via any bash subshell that comes up later.

And there’s quite a lot of other little cleanups in there too — security people at Fedora, at Red Hat, and around the world sure have been busy for the couple of days. Thanks to all of you for your hard work, and to Fedora’s awesome QA and Release Engineering teams, who sprung into action to make sure that these updates got to you quickly and safely.