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