next up previous
Next: Unix socket magic Up: New Solutions Previous: Helper Programs

The fork approach

Sometimes, it is not possible to create a helper because there's no way to make it reastrictive enough. Consider the case of manual pagers. If you want to cache formatted manual pages below /var/catman, you have to make man setuid or setgid to the uid owning the cache directories. You could also make the cache directories world writable, but then you have no control over what's really in the cache files.11.6

Formatting manpages is a complex task, and the code of both Linux man pagers has a history of security problems. What you would like to do therefore is split off the part that puts the formatted man pages into the cache, and let the rest of man run with user privilege. A setuid helper won't do in this case because it has no way of finding out where the byte stream it's getting on standard input should really go.

One solution in this case is to leave man setgid, but fork a ``cache slave'' early on that continues to run with increased privilege, and have the parent process drop all privilege before going on an formatting the page. Communication between the formatting front-end and the cache slave happens via a pipe:

        static void     to_cache;

        void
        cache_init(void)
        {
                pid_t   pid;
                int     pfd[2], frontend;

                if (pipe(pfd) < 0)
                        fatal("pipe failed");
                if ((pid = fork()) < 0)
                        fatal("fork failed");
                if (pid != 0) {
                        /* parent process: drop privs and continue */
                        if (setreuid(getuid(), getuid()) < 0)
                                fatal("setreuid failed");
                        to_cache = pfd[1];
                        close(pfd[0]);
                        return;
                }
                frontend = pfd[0];
                close(pfd[1]);

                /* read file name and formatted data from frontend
                 * and put into cache */
                exit(0);
        }

        FILE *
        cache_open(const char *name)
        {
                /* send NUL-terminated name of cache file to cache slave */
                write(to_cache, name, strlen(name)+1);
                return fdopen(to_cache);
        }

        int
        main(int argc, char **argv)
        {
                cache_init();

                /* Now we're running with user privilege */
                ...

                /* Open cache file and send data to it */
                cachefp = cache_open(filename);
                ...
        }

What have we gained? If there's a bug in the formatting code, it will not give the attacker group man privilege because by the time the process executes this code, it isn't running with group man privilege anymore. The only thing the attacker gains is the ability to put random junk into the catman cache. Still it's better than a setuid helper, because with the fork approach, an attacker has to find and exploit a security bug forst before being able to pollute the cache.

The fork approach also works well for the telnet daemon. The server forks one process that handles the network protocol etc, which is quite complex and prone to bugs. This part can run as user telnet in a chroot jail. It hands the parent process a list of environment variables, and eventually, the parent process starts /bin/login.

The same approach can be applied to setuid applications that use GUI libraries like X11, KDE or gnome. By forking a privileged ``worker'' process, you can make the GUI front-end run without increased privilege, and stop having headaches about whether you can really trust those GUI libraries. There's a small caveat though if you're using C++ based libraries; if they use global objects the constructors of these objects will be invoked before the flow of execution even enters your main() procedure, and before you're able to drop privilege. Note that this is a hypothetical problem; I haven't checked whether for instance KDE uses global objects that require a constructor.


next up previous
Next: Unix socket magic Up: New Solutions Previous: Helper Programs
Olaf Kirch 2002-01-16