aboutsummaryrefslogtreecommitdiffstats
path: root/lib-src
diff options
context:
space:
mode:
authorPaul Eggert2021-07-23 13:33:21 +0200
committerLars Ingebrigtsen2021-07-23 13:33:37 +0200
commit007744dd0404d6febca88b00c22981cc630fb8c0 (patch)
tree34e0756665429089a8098bc091eaf03a2032a8db /lib-src
parent55a19a1da26d35673c8eb2c52171ff3b31594dd9 (diff)
downloademacs-007744dd0404d6febca88b00c22981cc630fb8c0.tar.gz
emacs-007744dd0404d6febca88b00c22981cc630fb8c0.zip
Redo emacsclient socket symlink-attack checking
* admin/merge-gnulib (GNULIB_MODULES): Add file-has-acl. * lib/file-has-acl.c: New file, copied from Gnulib. * lib/gnulib.mk.in, m4/gnulib-comp.m4: Regenerate. * lib-src/emacsclient.c: Include acl.h, for file_has_acl. (O_PATH): Default to O_SEARCH, which is good enough here. (union local_sockaddr): New type. (socket_status): Remove, replacing with ... (connect_socket): New function. All callers changed. This function checks for ownership and permissions issues with the parent directory of the socket file, instead of checking the owner of the socket (which does not help security). (socknamesize): Move to file scope. (local_sockname): New arg S. No need to pass socknamesize. UID arg is now uid_t. All callers changed. Get file descriptor of parent directory of socket, to foil some symlink attacks. Do not follow symlinks to that directory. (set_local_socket): Create the socket here instead of on each attempt to connect it. Fall back from XDG_RUNTIME_DIR to /tmp only if the former fails due to ENOENT. Adjust permission-failure diagnostic to match changed behavior. This addresses Bug#33847, which complained about emacsclient in a safer XDG environment not connecting to an Emacs server running in a less-safe enviroment outside XDG. The patch fixes a longstanding issue with emacsclient permission checking. It’s ineffective to look at the permission of the socket file itself; on some platforms, these permissions are ignored anyway. What matters are the permissions on the parent directory of the socket file, as these are what make symlink attacks possible. Change the permissions check accordingly, and also refuse to follow symlinks to that parent directory. These changes make it OK for emacsclient to fall back from XDG_RUNTIME_DIR to the traditionally less-safe /tmp/emacsNNNN directories, since /tmp is universally sticky nowadays.
Diffstat (limited to 'lib-src')
-rw-r--r--lib-src/emacsclient.c226
1 files changed, 147 insertions, 79 deletions
diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c
index 12ced4aadbd..8346524a3eb 100644
--- a/lib-src/emacsclient.c
+++ b/lib-src/emacsclient.c
@@ -80,6 +80,9 @@ char *w32_getenv (const char *);
80#include <sys/stat.h> 80#include <sys/stat.h>
81#include <unistd.h> 81#include <unistd.h>
82 82
83#ifndef WINDOWSNT
84# include <acl.h>
85#endif
83#include <filename.h> 86#include <filename.h>
84#include <intprops.h> 87#include <intprops.h>
85#include <min-max.h> 88#include <min-max.h>
@@ -91,6 +94,10 @@ char *w32_getenv (const char *);
91# pragma GCC diagnostic ignored "-Wformat-truncation=2" 94# pragma GCC diagnostic ignored "-Wformat-truncation=2"
92#endif 95#endif
93 96
97#if !defined O_PATH && !defined WINDOWSNT
98# define O_PATH O_SEARCH
99#endif
100
94 101
95/* Name used to invoke this program. */ 102/* Name used to invoke this program. */
96static char const *progname; 103static char const *progname;
@@ -1128,24 +1135,74 @@ process_grouping (void)
1128 1135
1129#ifdef SOCKETS_IN_FILE_SYSTEM 1136#ifdef SOCKETS_IN_FILE_SYSTEM
1130 1137
1131/* Return the file status of NAME, ordinarily a socket. 1138/* A local socket address. The union avoids the need to cast. */
1132 It should be owned by UID. Return one of the following: 1139union local_sockaddr
1133 >0 - 'stat' failed with this errno value 1140{
1134 -1 - isn't owned by us 1141 struct sockaddr_un un;
1135 0 - success: none of the above */ 1142 struct sockaddr sa;
1143};
1144
1145/* Relative to the directory DIRFD, connect the socket file named ADDR
1146 to the socket S. Return 0 if successful, -1 if DIRFD is not
1147 AT_FDCWD and DIRFD's permissions would allow a symlink attack, an
1148 errno otherwise. */
1136 1149
1137static int 1150static int
1138socket_status (const char *name, uid_t uid) 1151connect_socket (int dirfd, char const *addr, int s, uid_t uid)
1139{ 1152{
1140 struct stat statbfr; 1153 int sock_status = 0;
1141 1154
1142 if (stat (name, &statbfr) != 0) 1155 union local_sockaddr server;
1143 return errno; 1156 if (sizeof server.un.sun_path <= strlen (addr))
1157 return ENAMETOOLONG;
1158 server.un.sun_family = AF_UNIX;
1159 strcpy (server.un.sun_path, addr);
1144 1160
1145 if (statbfr.st_uid != uid) 1161 /* If -1, WDFD is not set yet. If nonnegative, WDFD is a file
1146 return -1; 1162 descriptor for the initial working directory. Otherwise -1 - WDFD is
1163 the error number for the initial working directory. */
1164 static int wdfd = -1;
1147 1165
1148 return 0; 1166 if (dirfd != AT_FDCWD)
1167 {
1168 /* Fail if DIRFD's permissions are bogus. */
1169 struct stat st;
1170 if (fstat (dirfd, &st) != 0)
1171 return errno;
1172 if (st.st_uid != uid || (st.st_mode & (S_IWGRP | S_IWOTH)))
1173 return -1;
1174
1175 if (wdfd == -1)
1176 {
1177 /* Save the initial working directory. */
1178 wdfd = open (".", O_PATH | O_CLOEXEC);
1179 if (wdfd < 0)
1180 wdfd = -1 - errno;
1181 }
1182 if (wdfd < 0)
1183 return -1 - wdfd;
1184 if (fchdir (dirfd) != 0)
1185 return errno;
1186
1187 /* Fail if DIRFD has an ACL, which means its permissions are
1188 almost surely bogus. */
1189 int has_acl = file_has_acl (".", &st);
1190 if (has_acl)
1191 sock_status = has_acl < 0 ? errno : -1;
1192 }
1193
1194 if (!sock_status)
1195 sock_status = connect (s, &server.sa, sizeof server.un) == 0 ? 0 : errno;
1196
1197 /* Fail immediately if we cannot change back to the initial working
1198 directory, as that can mess up the rest of execution. */
1199 if (dirfd != AT_FDCWD && fchdir (wdfd) != 0)
1200 {
1201 message (true, "%s: .: %s\n", progname, strerror (errno));
1202 exit (EXIT_FAILURE);
1203 }
1204
1205 return sock_status;
1149} 1206}
1150 1207
1151 1208
@@ -1322,32 +1379,49 @@ act_on_signals (HSOCKET emacs_socket)
1322 } 1379 }
1323} 1380}
1324 1381
1325/* Create in SOCKNAME (of size SOCKNAMESIZE) a name for a local socket. 1382enum { socknamesize = sizeof ((struct sockaddr_un *) NULL)->sun_path };
1326 The first TMPDIRLEN bytes of SOCKNAME are already initialized to be 1383
1327 the name of a temporary directory. Use UID and SERVER_NAME to 1384/* Given a local socket S, create in *SOCKNAME a name for a local socket
1328 concoct the name. Return the total length of the name if successful, 1385 and connect to that socket. The first TMPDIRLEN bytes of *SOCKNAME are
1329 -1 if it does not fit (and store a truncated name in that case). 1386 already initialized to be the name of a temporary directory.
1330 Fail if TMPDIRLEN is out of range. */ 1387 Use UID and SERVER_NAME to concoct the name. Return 0 if
1388 successful, -1 if the socket's parent directory is not safe, and an
1389 errno if there is some other problem. */
1331 1390
1332static int 1391static int
1333local_sockname (char *sockname, int socknamesize, int tmpdirlen, 1392local_sockname (int s, char sockname[socknamesize], int tmpdirlen,
1334 uintmax_t uid, char const *server_name) 1393 uid_t uid, char const *server_name)
1335{ 1394{
1336 /* If ! (0 <= TMPDIRLEN && TMPDIRLEN < SOCKNAMESIZE) the truncated 1395 /* If ! (0 <= TMPDIRLEN && TMPDIRLEN < SOCKNAMESIZE) the truncated
1337 temporary directory name is already in SOCKNAME, so nothing more 1396 temporary directory name is already in SOCKNAME, so nothing more
1338 need be stored. */ 1397 need be stored. */
1339 if (0 <= tmpdirlen) 1398 if (! (0 <= tmpdirlen && tmpdirlen < socknamesize))
1340 { 1399 return ENAMETOOLONG;
1341 int remaining = socknamesize - tmpdirlen; 1400
1342 if (0 < remaining) 1401 /* Put the full address name into the buffer, since the caller might
1343 { 1402 need it for diagnostics. But don't overrun the buffer. */
1344 int suffixlen = snprintf (&sockname[tmpdirlen], remaining, 1403 uintmax_t uidmax = uid;
1345 "/emacs%"PRIuMAX"/%s", uid, server_name); 1404 int emacsdirlen;
1346 if (0 <= suffixlen && suffixlen < remaining) 1405 int suffixlen = snprintf (sockname + tmpdirlen, socknamesize - tmpdirlen,
1347 return tmpdirlen + suffixlen; 1406 "/emacs%"PRIuMAX"%n/%s", uidmax, &emacsdirlen,
1348 } 1407 server_name);
1349 } 1408 if (! (0 <= suffixlen && suffixlen < socknamesize - tmpdirlen))
1350 return -1; 1409 return ENAMETOOLONG;
1410
1411 /* Make sure the address's parent directory is not a symlink and is
1412 this user's directory and does not let others write to it; this
1413 fends off some symlink attacks. To avoid races, keep the parent
1414 directory open while checking. */
1415 char *emacsdirend = sockname + tmpdirlen + emacsdirlen;
1416 *emacsdirend = '\0';
1417 int dir = openat (AT_FDCWD, sockname,
1418 O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
1419 *emacsdirend = '/';
1420 if (dir < 0)
1421 return errno;
1422 int sock_status = connect_socket (dir, server_name, s, uid);
1423 close (dir);
1424 return sock_status;
1351} 1425}
1352 1426
1353/* Create a local socket for SERVER_NAME and connect it to Emacs. If 1427/* Create a local socket for SERVER_NAME and connect it to Emacs. If
@@ -1358,28 +1432,43 @@ local_sockname (char *sockname, int socknamesize, int tmpdirlen,
1358static HSOCKET 1432static HSOCKET
1359set_local_socket (char const *server_name) 1433set_local_socket (char const *server_name)
1360{ 1434{
1361 union { 1435 union local_sockaddr server;
1362 struct sockaddr_un un; 1436 int sock_status;
1363 struct sockaddr sa;
1364 } server = {{ .sun_family = AF_UNIX }};
1365 char *sockname = server.un.sun_path; 1437 char *sockname = server.un.sun_path;
1366 enum { socknamesize = sizeof server.un.sun_path };
1367 int tmpdirlen = -1; 1438 int tmpdirlen = -1;
1368 int socknamelen = -1; 1439 int socknamelen = -1;
1369 uid_t uid = geteuid (); 1440 uid_t uid = geteuid ();
1370 bool tmpdir_used = false; 1441 bool tmpdir_used = false;
1442 int s = cloexec_socket (AF_UNIX, SOCK_STREAM, 0);
1443 if (s < 0)
1444 {
1445 message (true, "%s: can't create socket: %s\n",
1446 progname, strerror (errno));
1447 fail ();
1448 }
1371 1449
1372 if (strchr (server_name, '/') 1450 if (strchr (server_name, '/')
1373 || (ISSLASH ('\\') && strchr (server_name, '\\'))) 1451 || (ISSLASH ('\\') && strchr (server_name, '\\')))
1374 socknamelen = snprintf (sockname, socknamesize, "%s", server_name); 1452 {
1453 socknamelen = snprintf (sockname, socknamesize, "%s", server_name);
1454 sock_status = (0 <= socknamelen && socknamelen < socknamesize
1455 ? connect_socket (AT_FDCWD, sockname, s, 0)
1456 : ENAMETOOLONG);
1457 }
1375 else 1458 else
1376 { 1459 {
1377 /* socket_name is a file name component. */ 1460 /* socket_name is a file name component. */
1461 sock_status = ENOENT;
1378 char const *xdg_runtime_dir = egetenv ("XDG_RUNTIME_DIR"); 1462 char const *xdg_runtime_dir = egetenv ("XDG_RUNTIME_DIR");
1379 if (xdg_runtime_dir) 1463 if (xdg_runtime_dir)
1380 socknamelen = snprintf (sockname, socknamesize, "%s/emacs/%s", 1464 {
1381 xdg_runtime_dir, server_name); 1465 socknamelen = snprintf (sockname, socknamesize, "%s/emacs/%s",
1382 else 1466 xdg_runtime_dir, server_name);
1467 sock_status = (0 <= socknamelen && socknamelen < socknamesize
1468 ? connect_socket (AT_FDCWD, sockname, s, 0)
1469 : ENAMETOOLONG);
1470 }
1471 if (sock_status == ENOENT)
1383 { 1472 {
1384 char const *tmpdir = egetenv ("TMPDIR"); 1473 char const *tmpdir = egetenv ("TMPDIR");
1385 if (tmpdir) 1474 if (tmpdir)
@@ -1398,23 +1487,24 @@ set_local_socket (char const *server_name)
1398 if (tmpdirlen < 0) 1487 if (tmpdirlen < 0)
1399 tmpdirlen = snprintf (sockname, socknamesize, "/tmp"); 1488 tmpdirlen = snprintf (sockname, socknamesize, "/tmp");
1400 } 1489 }
1401 socknamelen = local_sockname (sockname, socknamesize, tmpdirlen, 1490 sock_status = local_sockname (s, sockname, tmpdirlen,
1402 uid, server_name); 1491 uid, server_name);
1403 tmpdir_used = true; 1492 tmpdir_used = true;
1404 } 1493 }
1405 } 1494 }
1406 1495
1407 if (! (0 <= socknamelen && socknamelen < socknamesize)) 1496 if (sock_status == 0)
1497 return s;
1498
1499 if (sock_status == ENAMETOOLONG)
1408 { 1500 {
1409 message (true, "%s: socket-name %s... too long\n", progname, sockname); 1501 message (true, "%s: socket-name %s... too long\n", progname, sockname);
1410 fail (); 1502 fail ();
1411 } 1503 }
1412 1504
1413 /* See if the socket exists, and if it's owned by us. */ 1505 if (tmpdir_used)
1414 int sock_status = socket_status (sockname, uid);
1415 if (sock_status)
1416 { 1506 {
1417 /* Failing that, see if LOGNAME or USER exist and differ from 1507 /* See whether LOGNAME or USER exist and differ from
1418 our euid. If so, look for a socket based on the UID 1508 our euid. If so, look for a socket based on the UID
1419 associated with the name. This is reminiscent of the logic 1509 associated with the name. This is reminiscent of the logic
1420 that init_editfns uses to set the global Vuser_full_name. */ 1510 that init_editfns uses to set the global Vuser_full_name. */
@@ -1431,48 +1521,26 @@ set_local_socket (char const *server_name)
1431 if (pw && pw->pw_uid != uid) 1521 if (pw && pw->pw_uid != uid)
1432 { 1522 {
1433 /* We're running under su, apparently. */ 1523 /* We're running under su, apparently. */
1434 socknamelen = local_sockname (sockname, socknamesize, tmpdirlen, 1524 sock_status = local_sockname (s, sockname, tmpdirlen,
1435 pw->pw_uid, server_name); 1525 pw->pw_uid, server_name);
1436 if (socknamelen < 0) 1526 if (sock_status == 0)
1527 return s;
1528 if (sock_status == ENAMETOOLONG)
1437 { 1529 {
1438 message (true, "%s: socket-name %s... too long\n", 1530 message (true, "%s: socket-name %s... too long\n",
1439 progname, sockname); 1531 progname, sockname);
1440 exit (EXIT_FAILURE); 1532 exit (EXIT_FAILURE);
1441 } 1533 }
1442
1443 sock_status = socket_status (sockname, uid);
1444 } 1534 }
1445 } 1535 }
1446 } 1536 }
1447 1537
1448 if (sock_status == 0) 1538 close (s);
1449 {
1450 HSOCKET s = cloexec_socket (AF_UNIX, SOCK_STREAM, 0);
1451 if (s < 0)
1452 {
1453 message (true, "%s: socket: %s\n", progname, strerror (errno));
1454 return INVALID_SOCKET;
1455 }
1456 if (connect (s, &server.sa, sizeof server.un) != 0)
1457 {
1458 message (true, "%s: connect: %s\n", progname, strerror (errno));
1459 CLOSE_SOCKET (s);
1460 return INVALID_SOCKET;
1461 }
1462 1539
1463 struct stat connect_stat; 1540 if (sock_status == -1)
1464 if (fstat (s, &connect_stat) != 0) 1541 message (true,
1465 sock_status = errno; 1542 "%s: Invalid permissions on parent directory of socket: %s\n",
1466 else if (connect_stat.st_uid == uid) 1543 progname, sockname);
1467 return s;
1468 else
1469 sock_status = -1;
1470
1471 CLOSE_SOCKET (s);
1472 }
1473
1474 if (sock_status < 0)
1475 message (true, "%s: Invalid socket owner\n", progname);
1476 else if (sock_status == ENOENT) 1544 else if (sock_status == ENOENT)
1477 { 1545 {
1478 if (tmpdir_used) 1546 if (tmpdir_used)
@@ -1502,7 +1570,7 @@ set_local_socket (char const *server_name)
1502 } 1570 }
1503 } 1571 }
1504 else 1572 else
1505 message (true, "%s: can't stat %s: %s\n", 1573 message (true, "%s: can't connect to %s: %s\n",
1506 progname, sockname, strerror (sock_status)); 1574 progname, sockname, strerror (sock_status));
1507 1575
1508 return INVALID_SOCKET; 1576 return INVALID_SOCKET;