Hi.

The bash reference mentions that /dev/fd/* files are treated specially,
as filedescriptors, when used in conditionals.

However, they're not if they're passed as script arguments to the bash
binary (e.g., `bash /dev/fd/3`).

If they were, it would be possible to have a small setuid-root
indirection binary start bash scripts via filedescriptors, thereby
eliminating the race condition setuid-script vulnerability mentioned in
Gilles's answer at
http://unix.stackexchange.com/questions/364/allow-setuid-on-shell-scripts#2910.

(Whether the rest of the execution would be secure with setuid is
another question)

Running readlink on the actual /dev/fd/$something could then be used to
set $0.

I think it would make sense. What do you think?

Attached is a tentative patch that implements the above described
functionality.

Best regards,
Petr Skocik
>From a4d348abfcb88bc5d6c26f6f92458a9043829b64 Mon Sep 17 00:00:00 2001
From: Petr Skocik <psko...@gmail.com>
Date: Sun, 31 Jul 2016 22:07:10 +0200
Subject: [PATCH] passing scripts via fds

---
 shell.c | 101 +++++++++++++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 78 insertions(+), 23 deletions(-)

diff --git a/shell.c b/shell.c
index 2fd8179..62e20a3 100644
--- a/shell.c
+++ b/shell.c
@@ -108,6 +108,9 @@ extern int gnu_error_format;
 extern char *primary_prompt, *secondary_prompt;
 extern char *this_command_name;
 
+//readlink into mallocated memory
+char* readlink_dyn(const char* path);
+
 /* Non-zero means that this shell has already been run; i.e. you should
    call shell_reinitialize () if you need to start afresh. */
 int shell_initialized = 0;
@@ -1416,6 +1419,7 @@ open_shell_script (script_name)
   char sample[80];
   int sample_len;
   struct stat sb;
+  int rawfd = 0;
 #if defined (ARRAY_VARS)
   SHELL_VAR *funcname_v, *bash_source_v, *bash_lineno_v;
   ARRAY *funcname_a, *bash_source_a, *bash_lineno_a;
@@ -1423,29 +1427,59 @@ open_shell_script (script_name)
 
   filename = savestring (script_name);
 
-  fd = open (filename, O_RDONLY);
-  if ((fd < 0) && (errno == ENOENT) && (absolute_program (filename) == 0))
-    {
-      e = errno;
-      /* If it's not in the current directory, try looking through PATH
-	 for it. */
-      path_filename = find_path_file (script_name);
-      if (path_filename)
-	{
-	  free (filename);
-	  filename = path_filename;
-	  fd = open (filename, O_RDONLY);
-	}
-      else
-	errno = e;
-    }
+#define DEV_FD "/dev/fd/"
+#define PROC_FD "/proc/self/fd/"
+  if (0 == strncmp(filename, DEV_FD, sizeof(DEV_FD)-1)){
+      const char* ptr = filename + sizeof(DEV_FD)-1;
+      char* endptr;
+      rawfd = 1;
+      fd = strtoul(ptr, &endptr, 10);
+      if (*endptr){
+          errno = ENOENT; 
+          file_error (filename);
+      }
+  }else if (0 == strncmp(filename, PROC_FD, sizeof(PROC_FD)-1)){
+      const char* ptr = filename + sizeof(PROC_FD)-1;
+      char* endptr;
+      rawfd = 1;
+      fd = strtoul(ptr, &endptr, 10);
+      if (*endptr){
+          errno = ENOENT; 
+          file_error (filename);
+      }
+#undef DEV_FD
+#undef PROC_FD
+  }else {
+      fd = open (filename, O_RDONLY);
+      if ((fd < 0) && (errno == ENOENT) && (absolute_program (filename) == 0))
+        {
+          e = errno;
+          /* If it's not in the current directory, try looking through PATH
+         for it. */
+          path_filename = find_path_file (script_name);
+          if (path_filename)
+        {
+          free (filename);
+          filename = path_filename;
+          fd = open (filename, O_RDONLY);
+        }
+          else
+        errno = e;
+        }
+
+      if (fd < 0)
+        {
+          e = errno;
+          file_error (filename);
+          exit ((e == ENOENT) ? EX_NOTFOUND : EX_NOINPUT);
+        }
+  }
+
+  if (rawfd){
+      free(exec_argv0);
+      exec_argv0 = readlink_dyn(script_name);
+  }
 
-  if (fd < 0)
-    {
-      e = errno;
-      file_error (filename);
-      exit ((e == ENOENT) ? EX_NOTFOUND : EX_NOINPUT);
-    }
 
   free (dollar_vars[0]);
   dollar_vars[0] = exec_argv0 ? savestring (exec_argv0) : savestring (script_name);
@@ -1477,7 +1511,7 @@ open_shell_script (script_name)
 #endif
 
   /* Only do this with non-tty file descriptors we can seek on. */
-  if (fd_is_tty == 0 && (lseek (fd, 0L, 1) != -1))
+  if (fd_is_tty == 0 && !rawfd && (lseek (fd, 0L, 1) != -1))
     {
       /* Check to see if the `file' in `bash file' is a binary file
 	 according to the same tests done by execute_simple_command (),
@@ -1895,3 +1929,24 @@ run_shopt_alist ()
   shopt_alist = 0;
   shopt_ind = shopt_len = 0;
 }
+
+char* readlink_dyn(const char* path){
+    struct stat sb;
+    char *data;
+    ssize_t r;
+
+    if ( 0 > lstat(path, &sb) )
+        return NULL; 
+    if (NULL == (data = malloc(sb.st_size + 1)))
+        return NULL;
+    if (0>(r = readlink(path, data, sb.st_size + 1)))
+        goto err_out;
+    if (r > sb.st_size) 
+        goto err_out;
+    data[r] = '\0';
+    return data;
+err_out:
+    free(data);
+    return NULL;
+}
+
-- 
2.9.2

Reply via email to