How to bypass bash functions called `command`, `builtin` and `unset`?

can you still recover if all three functions are marked readonly?

Yes, usually you can, though that does not mean you should.

Just as you can unset readonly variables by attaching a debugger and calling unbind_variable as shown in anishsane's answer to that question, you can also unset readonly functions passing their names to unbind_func using a debugger.

This is not a reasonable approach when they aren't readonly (if it indeed ever is). In that situation you should use cuonglm's solution, which takes advantage of how unset is treated in POSIX mode. That solution is something you might actually use in real life.

Since there's no actual guarantee that your shell will behave reasonably after you circumvent readonly with a debugger, I suggest avoiding it whenever a more reasonable alternative, like quitting and restarting your shell or replacing your shell with a new one using exec, is available.

With that said, here's anishsane's method adapted to unset functions instead of a variable:

cat <<EOF | sudo gdb
attach $$
call unbind_func("unset")
call unbind_func("builtin")
call unbind_func("command")
detach
EOF

Note that $$ is expanded into the shell's process ID, because no part of EOF in <<EOF is quoted.

I tested this on Bash 4.3.48(1)-release on Ubuntu 16.04 LTS, and it worked. You need gdb for this, though it could be adapted to other debuggers. As anishsane commented, piping from cat is intended to avoid a deadlock where the process that gives input to gdb is the one that gdb has stopped. I believe it achieves that goal, because in a pipeline of two or more commands, Bash runs each command in a subshell. But I am unsure if it is the most robust way. Ultimately, however, there's no actual guarantee that this works anyway, since it's entirely reasonable for Bash to assume readonly variables and functions won't change. In practice, my guess is that this does virtually always work.

To use this technique as written, you need sudo installed and you need to be able to sudo to root. You can, of course, replace it with another privilege-elevation method. Depending on what OS you are running and how it is configured, you might be able to omit sudo altogether and run gdb as yourself instead of root. For example, the Linux kernel will consult the value of /proc/sys/kernel/yama/ptrace_scope, which you can set through sysctl and may read or (as root) write, to determine what processes may debug other processes. If the value is 1, then only a process's direct parent--or any process running as root--may debug it. Most recent GNU/Linux systems have it set to 1, which is why I included sudo.

That description of Linux kernel behavior is somewhat oversimplified, in that other ptrace_scope values are allowed and in that the relationship required by 1 can be adjusted. See the relevant documentation for full details.


When bash is in posix mode, some builtins are considered special, which is compliant with POSIX standard.

One special thing about those special builtins, they are found before function in command lookup process. Taking this advantage, you can try:

$ unset builtin
Haha, nice try!
builtin
$ set -o posix
$ unset builtin
$ builtin command -v echo
echo

though it does not work if set is overridden by a function named set:

$ set() { printf 'Haha, nice try!\n%s\n' "$*";}
$ set -o posix
Haha, nice try!

In this case, you just have to set POSIXLY_CORRECT to make bash enter posix mode, then you have all special builtins:

$ POSIXLY_CORRECT=1