]> git.eshelyaron.com Git - emacs.git/commitdiff
emacsclient: fix symlink/socket race
authorPaul Eggert <eggert@cs.ucla.edu>
Mon, 3 Dec 2018 06:32:28 +0000 (22:32 -0800)
committerPaul Eggert <eggert@cs.ucla.edu>
Mon, 3 Dec 2018 07:55:00 +0000 (23:55 -0800)
* lib-src/emacsclient.c (socket_status): New arg UID.
All uses changed.
(set_local_socket): Don’t create the unbound socket unless the
initial sanity checks on the socket file succeed; this
simplifies cleaning it up.  Check socket ownership again
after connecting, to fix a race (Bug#33366).

lib-src/emacsclient.c

index ba72651343fe562e05287a8c47c1a5f93aaf9a98..df44bc4087cc91ca8853d0e13c4d82c3b3a5a18a 100644 (file)
@@ -1079,20 +1079,21 @@ find_tty (const char **tty_type, const char **tty_name, bool noabort)
 
 #ifdef SOCKETS_IN_FILE_SYSTEM
 
-/* Three possibilities:
+/* Return the file status of NAME, ordinarily a socket.
+   It should be owned by UID.  Return one of the following:
   >0 - 'stat' failed with this errno value
   -1 - isn't owned by us
    0 - success: none of the above */
 
 static int
-socket_status (const char *name)
+socket_status (const char *name, uid_t uid)
 {
   struct stat statbfr;
 
   if (stat (name, &statbfr) != 0)
     return errno;
 
-  if (statbfr.st_uid != geteuid ())
+  if (statbfr.st_uid != uid)
     return -1;
 
   return 0;
@@ -1316,18 +1317,11 @@ set_local_socket (char const *server_name)
     struct sockaddr_un un;
     struct sockaddr sa;
   } server = {{ .sun_family = AF_UNIX }};
-
-  HSOCKET s = socket (AF_UNIX, SOCK_STREAM, 0);
-  if (s < 0)
-    {
-      message (true, "%s: socket: %s\n", progname, strerror (errno));
-      return INVALID_SOCKET;
-    }
-
   char *sockname = server.un.sun_path;
   enum { socknamesize = sizeof server.un.sun_path };
   int tmpdirlen = -1;
   int socknamelen = -1;
+  uid_t uid = geteuid ();
 
   if (strchr (server_name, '/')
       || (ISSLASH ('\\') && strchr (server_name, '\\')))
@@ -1359,7 +1353,7 @@ set_local_socket (char const *server_name)
                tmpdirlen = snprintf (sockname, socknamesize, "/tmp");
            }
          socknamelen = local_sockname (sockname, socknamesize, tmpdirlen,
-                                       geteuid (), server_name);
+                                       uid, server_name);
        }
     }
 
@@ -1370,7 +1364,7 @@ set_local_socket (char const *server_name)
     }
 
   /* See if the socket exists, and if it's owned by us. */
-  int sock_status = socket_status (sockname);
+  int sock_status = socket_status (sockname, uid);
   if (sock_status)
     {
       /* Failing that, see if LOGNAME or USER exist and differ from
@@ -1387,7 +1381,7 @@ set_local_socket (char const *server_name)
        {
          struct passwd *pw = getpwnam (user_name);
 
-         if (pw && (pw->pw_uid != geteuid ()))
+         if (pw && pw->pw_uid != uid)
            {
              /* We're running under su, apparently. */
              socknamelen = local_sockname (sockname, socknamesize, tmpdirlen,
@@ -1399,39 +1393,49 @@ set_local_socket (char const *server_name)
                  exit (EXIT_FAILURE);
                }
 
-             sock_status = socket_status (sockname);
+             sock_status = socket_status (sockname, uid);
            }
        }
     }
 
-  switch (sock_status)
+  if (sock_status == 0)
     {
-    case -1:
-      /* There's a socket, but it isn't owned by us.  */
-      message (true, "%s: Invalid socket owner\n", progname);
-      break;
+      HSOCKET s = socket (AF_UNIX, SOCK_STREAM, 0);
+      if (s < 0)
+       {
+         message (true, "%s: socket: %s\n", progname, strerror (errno));
+         return INVALID_SOCKET;
+       }
+      if (connect (s, &server.sa, sizeof server.un) != 0)
+       {
+         message (true, "%s: connect: %s\n", progname, strerror (errno));
+         CLOSE_SOCKET (s);
+         return INVALID_SOCKET;
+       }
 
-    case 0:
-      if (connect (s, &server.sa, sizeof server.un) == 0)
+      struct stat connect_stat;
+      if (fstat (s, &connect_stat) != 0)
+       sock_status = errno;
+      else if (connect_stat.st_uid == uid)
        return s;
-      message (true, "%s: connect: %s\n", progname, strerror (errno));
-      break;
-
-    default:
-      /* 'stat' failed.  */
-      if (sock_status == ENOENT)
-       message (true,
-                ("%s: can't find socket; have you started the server?\n"
-                 "%s: To start the server in Emacs,"
-                 " type \"M-x server-start\".\n"),
-                progname, progname);
       else
-       message (true, "%s: can't stat %s: %s\n",
-                progname, sockname, strerror (sock_status));
-      break;
+       sock_status = -1;
+
+      CLOSE_SOCKET (s);
     }
 
-  CLOSE_SOCKET (s);
+  if (sock_status < 0)
+    message (true, "%s: Invalid socket owner\n", progname);
+  else if (sock_status == ENOENT)
+    message (true,
+            ("%s: can't find socket; have you started the server?\n"
+             "%s: To start the server in Emacs,"
+             " type \"M-x server-start\".\n"),
+            progname, progname);
+  else
+    message (true, "%s: can't stat %s: %s\n",
+            progname, sockname, strerror (sock_status));
+
   return INVALID_SOCKET;
 }
 #endif /* SOCKETS_IN_FILE_SYSTEM */