How do I explicitly and safely force the use of a built-in command in bash

Olivier D is almost correct, but you must set POSIXLY_CORRECT=1 before running unset. POSIX has a notion of Special Built-ins, and bash supports this. unset is one such builtin. Search for SPECIAL_BUILTIN in builtins/*.c in the bash source for a list, it includes set, unset, export, eval and source.

$ unset() { echo muahaha-unset; }
$ unset unset
muahaha-unset
$ POSIXLY_CORRECT=1
$ unset unset

The rogue unset has now been removed from the environment, if you unset command, type, builtin then you should be able to proceed, but unset POSIXLY_CORRECT if you are relying on non-POSIX behaviour or advanced bash features.

This does not address aliases though, so you must use \unset to be sure it works in interactive shell (or always, in case expand_aliases is in effect).

For the paranoid, this should fix everything, I think:

POSIXLY_CORRECT=1
\unset -f help read unset
\unset POSIXLY_CORRECT
re='^([a-z:.\[]+):' # =~ is troublesome to escape
while \read cmd; do 
    [[ "$cmd" =~ $re ]] && \unset -f ${BASH_REMATCH[1]}; 
done < <( \help -s "*" )

(while, do, done and [[ are reserved words and don't need precautions.) Note we are using unset -f to be sure to unset functions, although variables and functions share the same namespace it's possible for both to exist simultaneously (thanks to Etan Reisner) in which case unset-ing twice would also do the trick. You can mark a function readonly, bash does not prevent you unsetting a readonly function up to and including bash-4.2, bash-4.3 does prevent you but it still honours the special builtins when POSIXLY_CORRECT is set.

A readonly POSIXLY_CORRECT is not a real problem, this is not a boolean or flag its presence enables POSIX mode, so if it exists as a readonly you can rely on POSIX features, even if the value is empty or 0. You'll simply need to unset problematic functions a different way than above, perhaps with some cut-and-paste:

\help -s "*" | while IFS=": " read cmd junk; do echo \\unset -f $cmd; done

(and ignore any errors) or engage in some other scriptobatics.


Other notes:

  • function is a reserved word, it can be aliased but not overridden with a function. (Aliasing function is mildly troublesome because \function is not acceptable as a way of bypassing it)
  • [[, ]] are reserved words, they can be aliased (which will be ignored) but not overridden with a function (though functions can be so named)
  • (( is not a valid name for a function, nor an alias

I realize if someone controls your environment you're probably screwed anyway

Yes, that. If you run a script in an unknown environment, all manner of things can go wrong, starting with LD_PRELOAD causing the shell process to execute arbitrary code before it even reads your script. Attempting to protect against a hostile environment from inside the script is futile.

Sudo has been sanitizing the environment by removing anything that looks like a bash function definition for over a decade. Since Shellshock, other environments that run shell script in a not-fully-trusted environment have followed suit.

You cannot safely run a script in an environment that has been set by an untrusted entity. So worrying about function definitions is not productive. Sanitize your environment, and in doing so variables that bash would interpret as function definitions.

Tags:

Security

Bash