From: Fergus James HENDERSON (fjh@munta.cs.mu.OZ.AU)
Date: 07/08/93


From: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Subject: Re: no suid shell-scripts ?
Date: Thu, 8 Jul 1993 07:02:26 GMT

jem@sunSITE.unc.edu (Jonathan Magid) writes:

>fn0601@cd4680fs.rrze.uni-erlangen.de (Kurs-01) writes:
>>I want to write a shell script with suid root, but that doesn't
>>work (tested with 'id' in the script).
>>
>>Isn't it possible to do this with bash?
>
>There are a couple of well known mis-features in the design of
>Unix-like operating systems, which makes set-uid an intolerable
>security risk; Linux takes a safety inspired view of this and
>does not allow them.
>
>The first one is fairly obvious if you read the man page for sh or bash-
>the IFS variable may be set to change the default word seperator- if
>changed to a '/'; new commands can be inserted into the script's path..
>obviously, one does not arbitrary program run with root authority.

This is a problem for sh but not for bash. bash ignores the value of IFS
when reading scripts.

>The second is fairly obvious if you know how scripts are run.. the magic
>cookie, '#!' is detected, and a second process is spawned to run the
>text of the script, using the named interpreter- in the period of time
>between the first reading and the second, a different program could
>be substituted.. by renaming the link,for example.
>
>There is simple way of fixing this second problem in Linux- there once
>was a patch floating around it- that is to use the proc file system's
>/proc/self- feature to ensure that the proper file descriptor is handed
>the second time around.. but for some reason it never made it into the
>mainline kernel.

The reason is that that patch on it's own is not enough to ensure that
shell scripts are secure: there is one more security hole in bash that
needs to be fixed first. This is bash's $ENV variable processing (see
the bash manpage). So we're going to wait until that patch to bash
makes it's way into SLS and until most people are running a fixed version
of bash before allowing setuid scripts in the kernel.

But if you feel like applying them yourself, then go ahead.
Here's the patch to bash:

*** /src/mulga/gnu/bash-1.12//shell.c Tue Jan 21 16:52:37 1992
--- shell.c Thu Apr 22 11:06:10 1993
*************** main (argc, argv, env)
*** 579,585 ****
  
        /* Try a TMB suggestion. If running a script, then execute the
         file mentioned in the ENV variable. */
! if (!interactive_shell)
        {
          char *env_file = (char *)getenv ("ENV");
          if (env_file && *env_file)
--- 579,585 ----
  
        /* Try a TMB suggestion. If running a script, then execute the
         file mentioned in the ENV variable. */
! if (!interactive_shell && getuid() == geteuid() && getgid() == getegid())
        {
          char *env_file = (char *)getenv ("ENV");
          if (env_file && *env_file)

And here's the patch to the Linux kernel.
This patch is the original one against 0.99.6 but it applies cleanly to
0.99.9.

--- exec.c.orig Thu Apr 1 01:34:52 1993
+++ exec.c Thu Apr 1 05:57:41 1993
@@ -17,6 +17,7 @@
  * was less than 2 hours work to get demand-loading completely implemented.
  */
 
+#include <linux/config.h>
 #include <linux/fs.h>
 #include <linux/sched.h>
 #include <linux/kernel.h>
@@ -32,7 +33,7 @@
 
 #include <asm/segment.h>
 
-extern int sys_exit(int exit_code);
+extern int sys_open(const char * filename, int flag, int mode);
 extern int sys_close(int fd);
 
 /*
@@ -493,7 +494,11 @@
                 */
 
                char *cp, *interp, *i_name, *i_arg;
+ dev_t script_dev;
+ unsigned long script_ino;
 
+ script_dev = inode->i_dev;
+ script_ino = inode->i_ino;
                iput(inode);
                buf[127] = '\0';
                if ((cp = strchr(buf, '\n')) == NULL)
@@ -537,7 +542,46 @@
                 * This is done in reverse order, because of how the
                 * user environment and arguments are stored.
                 */
+
+ /*
+ * If we have the /proc filesystem, then we can allow
+ * setuid scripts. We avoid the race condition by
+ * opening the script before executing the interpreter
+ * and passing /proc/self/fd/%d as the script filename.
+ * Bug: this assumes you have a 'proc' filesystem
+ * mounted as /proc.
+ */
+
+#ifdef CONFIG_PROC_FS
+ if (e_uid != current->euid || e_gid != current->egid) {
+ char script_filename[20];
+ char *script_filename_ptr = script_filename;
+ int fd;
+
+ fd = sys_open(filename, O_RDONLY, 0);
+ if (fd < 0) {
+ retval = fd;
+ goto exec_error2;
+ }
+ if (current->filp[fd]->f_inode->i_dev != script_dev ||
+ current->filp[fd]->f_inode->i_ino != script_ino) {
+ printk("user %d attempted to exploit setuid script race "
+ "condition\n", current->uid);
+ printk("device=%d,%d inode=%d\n",
+ MAJOR(script_dev), MINOR(script_dev), script_ino);
+ sys_close(fd);
+ retval = -EPERM;
+ goto exec_error2;
+ }
+ sprintf(script_filename, "/proc/self/fd/%d", fd);
+ p = copy_strings(1, &script_filename_ptr, page, p, 2);
+ } else {
+ p = copy_strings(1, &filename, page, p, 1);
+ }
+#else
                p = copy_strings(1, &filename, page, p, 1);
+#endif
+
                argc++;
                if (i_arg) {
                        p = copy_strings(1, &i_arg, page, p, 2);
@@ -558,6 +602,10 @@
                set_fs(old_fs);
                if (retval)
                        goto exec_error1;
+#ifdef CONFIG_PROC_FS
+ current->suid = current->euid = e_uid;
+ current->sgid = current->egid = e_gid;
+#endif
                goto restart_interp;
        }
        if ((N_MAGIC(ex) != ZMAGIC && N_MAGIC(ex) != OMAGIC) ||

-- 
Fergus Henderson                     This .signature virus might be
fjh@munta.cs.mu.OZ.AU                getting old, but you still can't
                                     consistently believe it unless you
Linux: Choice of a GNU Generation    copy it to your own .signature file!