Tracing exec() calls with sudo
(Skip the lecture and give me the good stuff.)
Sudo is an incredibly useful Unix utility for giving users (potentially limited) root access. One of the great benefits of sudo, besides fine-grained control over what commands are allowed to be executed, is that all invocations are logged, leaving an audit trail. For this reason, it is generally considered good practice to disallow executing shells (e.g. sudo -i or sudo bash), even for users with otherwise unlimited access.
However, there are other ways of dropping into a shell and circumventing the log trail; for example, both VIM and Emacs allow users to invoke a shell. This can be disallowed through the noexec directive, which prevents processes launched by sudo from calling exec(). Thus, users are forced to execute each command via sudo directly, rather than from within emacs or vim.
This behavior may not be entirely desirable, however. It puts up an unnecessary barrier if the only goal is to require a log trail for otherwise trusted users. In light of this, I've exploited the fact that sudo's noexec_file directive allows you to specify a shared object containing alternate versions of the exec() family of functions (the default version always returns an error). In my implementation, rather than returning an error, I simply log the command being executed and then proceed with the function call.
The result is that trusted users can be allowed to execute any command, and the entire call tree is logged, rather than just the one invoked with sudo. Note the implication of the term "trusted users": a nefarious user can avoid the audit trail with minimal effort (or even more easily destroy the trail by editing /var/log/messages). For example, if a process wants to call execv() in "stealth mode," all it needs to do is use dlopen() to obtain the address of the "real" version of the function, and call it directly: this is true even of the standard noexec implementation. (In addition, I currently do not overload the infrequently-used execl() and fexecve() functions.)
The good stuff
With that caveat in mind, here's how to set it up. First, download sudo_exectrace-0.3.tar.gz, extract it, and run make install. Then, run visudo and add the following line:
Defaults noexec, noexec_file=/usr/local/lib/sudo_exectrace.so
You may also want to set up syslog to place messages from sudo and sudo+ into their own log file, as you will now get a lot more messages. For example, running the following sequence of commands:
$ sudo -i
# cd /
# perl -e 'system("ls")'
will now write the following to syslog:
sudo: afn : TTY=pts/37 ; PWD=/home/afn ; USER=root ; COMMAND=/bin/bash
sudo+: afn : TTY=pts/37 ; PWD=/ ; USER=root ; COMMAND=perl -e system("ls") ; SUDO_COMMAND=/bin/bash
sudo+: afn : TTY=pts/37 ; PWD=/ ; USER=root ; COMMAND=ls ; SUDO_COMMAND=/bin/bash
The lines logged with command name sudo+ would not have been generated before.
TODO
There are a few features that would make this even nicer; here are a few that I've thought of adding. If you're bored and decide to implement one of them, please let me know :-)
- Disable logging for specific commands. For example, if a user calls
make install, which then invokes cp 500 times, I may not be interested in seeing a log message for each invocation of cp.
- Disallow specific commands. It may be desirable for
sudo_exectrace to behave like the standard sudo_noexec for certain classes of commands. For example, if I want to ensure that users don't accidentally reboot my machine, I may want to have execv() return failure for the shutdown and reboot commands. Note that simply disallowing these commands in /etc/sudoers is insufficient, as these rules are only examined by sudo for the initial command (in fact, it would be nice to have sudo_exectrace examine /etc/sudoers to avoid needing two configuration files!). Note also that, as described above, a nefarious user can get around these restrictions; thus, this would only serve as a deterrent to prevent users from accidentally executing commands while running as root.
Changelog
0.3 / 2006.08.12
- IMPORTANT: Fixed buffer overflow. (As it turns out
snprintf() returns the number of characters that would be written — as opposed to the number that were actually written — to the buffer.)
0.2 / 2006.08.12
- Added functionality to also trace calls to
open(), link(), symlink(), unlink(), mkdir(), and rmdir().
0.1 / 2006.08.11
|